Cycript(五):安装与使用

2024-02-28 20:10
文章标签 安装 使用 cycript

本文主要是介绍Cycript(五):安装与使用,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

    • Cycript 的安装
    • 在非越狱环境中使用 Cycript(通过手动配置)
    • 在非越狱环境中使用 Cycript(通过 MonkeyDev 自动配置)
    • 在越狱环境中使用 Cycript
    • 导入外部的 Cycript 脚本
    • 补充:Cycript 和 LLDB 中经常用到的 Objective-C 私有方法
    • 补充:其他细节

Cycript 的安装

  • ① 下载与安装

    从 Cycript 官网 下载 Cycript SDK,并将其放到 /opt 目录下。Cycript SDK 的目录结构如下所示:

    /opt/cycript_0.9.594 > tree
    .
    ├── Cycript.ios
    │   └── Cycript.framework
    │       ├── Cycript
    │       └── Headers
    │           └── Cycript.h
    ├── Cycript.lib
    │   ├── cycript-a32
    │   ├── cycript-apl
    │   ├── cycript-pie
    │   ├── cycript0.9
    │   │   ├── com
    │   │   │   └── saurik
    │   │   │       └── substrate
    │   │   │           └── MS.cy
    │   │   └── org
    │   │       └── cycript
    │   │           └── NSLog.cy
    │   ├── cynject
    │   ├── l
    │   │   └── linux
    │   ├── libJavaScriptCore.so
    │   ├── libcycript-sim.dylib
    │   ├── libcycript-sys.dylib -> libcycript.dylib
    │   ├── libcycript.cy
    │   ├── libcycript.db
    │   ├── libcycript.dylib
    │   ├── libcycript.jar
    │   ├── libcycript.so
    │   ├── libsubstrate.dylib
    │   └── u
    │       └── unknown
    ├── Cycript.osx
    │   └── Cycript.framework
    │       ├── Cycript
    │       └── Headers
    │           └── Cycript.h
    └── cycript15 directories, 22 files
    
  • ② 解决 Ruby 版本不匹配而导致的报错

    打开终端,进入到 Cycript 的存放目录 /opt/cycript_0.9.594,运行 ./cycript 命令。不出意外的话,这时候会有如下报错:

    /opt/cycript_0.9.594 > ./cycript
    dyld: Library not loaded: /System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/lib/libruby.2.0.0.dylibReferenced from: /opt/cycript_0.9.594/./Cycript.lib/cycript-aplReason: image not found
    [1]    46992 abort      ./cycript
    

    这是因为本地的 Ruby 版本与 Cycript 要求的 Ruby 版本不一样导致的。先查看一下本地的 Ruby 版本:

    ~ > ruby -v
    ruby 2.6.3p62 (2019-04-16 revision 67580) [universal.x86_64-darwin19]
    ~ > cd /System/Library/Frameworks/Ruby.framework/Versions/System/Library/Frameworks/Ruby.framework/Versions > ls
    2.6     Current
    

    因为本地的 Ruby 的版本(Ruby 2.6)与 Cycript 要求的 Ruby 的版本(Ruby 2.0),同属于 Ruby 2.x 这个大版本内,并且一般情况下库的升级都会考虑向后兼容性。所以这两个版本的 Ruby 所提供的接口与支持,应该是差不多的。没必要为了配置 Cycript,降级本地的 Ruby 版本,或者再单独下载一个 Ruby 2.0。这里为了省事,直接为 Ruby 2.6 创建一个符号链接,以后对 Ruby 2.0 的调用,都会被重定向到 Ruby 2.6

    1. 禁用系统完整性保护(SIP,System Integrity Protection)

      # 重启 macOS 并按住 option 键,直到出现磁盘图标后松开
      # 然后按住 command + R,直到出现苹果图标后松开
      # 之后等待片刻,进入 macOS 恢复模式
      # 在进入恢复模式后,在顶部菜单栏中选择: 实用工具-终端,在终端输入命令
      csrutil disable
      # 如果返回以下提示,则说明 SIP 禁用成功
      Successfully disabled System Integrity Protection.Please restart the machine for the changes to take effect.
      # 重启 macOS
      reboot
      
    2. 创建符号链接,将对 Ruby 2.0 的调用重定向到对 Ruby 2.6 的调用

      # 重新以可读可写的方式挂载文件系统
      ~ >  sudo mount -uw /
      # 创建符号链接,将对 Ruby 2.0 的调用重定向到对 Ruby 2.6 的调用
      ~ > sudo mkdir -p /System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/lib/
      ~ > sudo ln -s /System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/lib/libruby.2.6.dylib /System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/lib/libruby.2.0.0.dylib
      
    3. 启用系统完整性保护(SIP,System Integrity Protection)

      # 重启 macOS 并按住 option 键,直到出现磁盘图标后松开
      # 然后按住 command + R,直到出现苹果图标后松开
      # 之后等待片刻,进入 macOS 恢复模式
      # 在进入恢复模式后,在顶部菜单栏中选择: 实用工具-终端,在终端输入命令
      csrutil enable
      # 如果返回以下提示,则说明 SIP 启用成功
      Successfully enabled System Integrity Protection.Please restart the machine for the changes to take effect.
      # 重启 macOS
      reboot
      
  • ③ 配置环境变量 PATH,以方便地启动 Cycript 控制台

    虽然安装进行到这里,Cycript 就可以正常运行了。但是实际使用起来不是很方便:因为每次都需要进入到 /opt/cycript_0.9.594 目录,然后运行 ./cycript 命令,才能调起 Cycript 的控制台

    为了能够在任意目录下方便地启动 Cycript 控制台,我们需要将存放 Cycript 的目录 /opt/cycript_0.9.594/ 添加到环境变量 PATH 中。在 ~/.zshrc 的底部添加如下配置:

    # 将 Cycript 的存放目录添加到环境变量 PATH 中
    export PATH=/opt/cycript_0.9.594/:$PATH
    

    之后,就可以在任意目录下启动 Cycript 的控制台了:

    ~ > cycript
    cy# a = 99 + 1
    100
    

在非越狱环境中使用 Cycript(通过手动配置)

  • ① 新建一个名为 CycriptDemo 的 iOS - App 工程
    并将 Project - Info - Deployment Target - iOS Deployment Target 设置为 10.0
    1

  • ② 将 /opt/cycript_0.9.594/Cycript.ios/Cycript.framework 集成到 CycriptDemo 工程中
    2.1
    因为 Cycript.framework 不包含 Bitcode
    所以需要将 Target - Build Settings - Build Options - Enable Bitcode 设置为 NO
    2.2

  • ③ 因为 Cycript.framework 依赖:JavaScriptCore.frameworklibsqlite3.tbdlibc++.tbd
    所以需要在 Target - Build Phases - Link Binary With Libraries 中添加对这三个库的引用
    3

  • ④ 因为 Cycript.framework 在启动时,会去主 Bundle 中读取它自己的数据库 libcycript.db
    所以需要在 Target - Build Phases - Copy Bundle Resources 中添加对 /opt/cycript_0.9.594/Cycript.lib/libcycript.db 的引用
    4

  • ⑤ 在 ViewController.m 中编写如下代码
    5

  • ⑥ 将 MacBook 和 iPhone 连接到同一 WiFi 下,并查看 iPhone 的 IP 地址
    6

  • ⑦ 将工程 CycriptDemo 运行到真机
    7

  • ⑧ 在 MacBook 端启动 Cycript 控制台,然后连接到 iPhone 上 Cycript 的监听端口,并将 App 的背景颜色修改为橙色

    ~ > cycript -r 192.168.1.229:8899
    cy# UIApp.keyWindow.rootViewController.view.backgroundColor = UIColor.orangeColor
    #"UIExtendedSRGBColorSpace 1 0.5 0 1"
    

    8

  • 如果终端界面一直停留在执行连接命令 cycript -r 192.168.1.229:8899 这一步,没有进入到 Cycript 的 REPL 环境,则有以下三种可能:

    1. MacBook 和 iPhone 没有连接到同一 WiFi 下(请将 MacBook 和 iPhone 连接到同一 WiFi 下)
    2. MacBook 和 iPhone 连接的 WiFi 有问题(换一个 WiFi 试试;或直接使用 iPhone 开个热点,然后 MacBook 连接到 iPhone 发出的热点)
    3. Cycript 在 iPhone 端监听的端口号被占用(修改 iPhone 监听的端口号,重新将工程运行到真机,并使用新的端口号进行连接)

在非越狱环境中使用 Cycript(通过 MonkeyDev 自动配置)

虽然在非越狱环境中,我们可以通过手动配置的方式使用 Cycript
但是当我们每次要使用 Cycript 时,都需要:手动添加 Cycript.framework 及其依赖库、手动禁用 Bitcode、手动开启 Cycript 的监听端口,这是个无聊且没有技术含量的过程

而在 MonkeyDev 中,已经为我们:自动集成了 Cycript.framework 及其依赖库、默认禁用了 Bitcode、默认开启了 Cycript 的监听端口 6666。只需要简单的几步,就可以使用 Cycript:

  • ① 新建一个名为 CycriptDemo 的 MonkeyApp 工程
    并将 Project - Info - Deployment Target - iOS Deployment Target 设置为 10.0
    1

  • ② 将 MacBook 和 iPhone 连接到同一 WiFi 下,并查看 iPhone 的 IP 地址
    2

  • ③ 将工程运行到真机
    3

  • ④ 在 MacBook 端启动 Cycript 控制台,然后连接到 iPhone 上 Cycript 的监听端口,并将 App 的背景颜色修改为橙色

    ~ > cycript -r 192.168.1.229:6666
    cy# UIApp.keyWindow.rootViewController.view.backgroundColor = UIColor.orangeColor
    #"UIExtendedSRGBColorSpace 1 0.5 0 1"
    

    4

  • 如果终端界面一直停留在执行连接命令 cycript -r 192.168.1.229:6666 这一步,没有进入到 Cycript 的 REPL 环境,则有以下三种可能:

    1. MacBook 和 iPhone 没有连接到同一 WiFi 下(请将 MacBook 和 iPhone 连接到同一 WiFi 下)
    2. MacBook 和 iPhone 连接的 WiFi 有问题(换一个 WiFi 试试;或直接使用 iPhone 开个热点,然后 MacBook 连接到 iPhone 发出的热点)
    3. Cycript 在 iPhone 端监听的端口号被占用(修改 iPhone 监听的端口号,重新将工程运行到真机,并使用新的端口号进行连接)

在越狱环境中使用 Cycript

  • ① 在已越狱的 iOS 设备上安装 Cycript

    从 Cydia 自带的源 Cydia/Telesphoreo 下载:直接打开已越狱的 iOS 设备上的 Cydia,然后搜索 Cycript 后安装即可

  • ② 使用 SSH 连接到已越狱的 iOS 设备

    ssh root@192.168.1.229
    
  • ③ 使用 ps 命令查看要修改的应用的进程 ID 或进程名称

    ps ax | grep WeChat
    
  • ④ 将 Cycript 附加到指定 ID 或指定名称所标识的进程

    cycript -p 1354
    
  • ⑤ 补充

    因为已越狱的 iOS 设备上可以获取 root 权限
    所以可以直接抓取要附加的进程,并在该进程上创建一个挂起的线程,然后在这个挂起的线程里申请一片用于加载 Cycript.framework 动态库的内存,最后恢复线程,Cycript.framework 动态库就被注入了

导入外部的 Cycript 脚本

  • 在非越狱环境中导入外部的 Cycript 脚本(在没有使用 MonkeyDev 的情况下)

    ① 将外部 Cycript 脚本添加到主工程中,并在主工程的 Target - Build Phases - Copy Bundle Resources 中创建对这些外部脚本的引用:
    1
    ② 当需要使用外部 Cycript 脚本所提供的功能时,请在 Cycript 的 REPL 中使用 @import 命令导入这些外部脚本(其中,模块名称即文件名称):

    ~ > cycript -r 192.168.1.229:8899
    cy# @import libcycript
    cy# @import md
    cy# @import mjcript
    cy# @import MS
    cy# @import utils
    
  • 在非越狱环境中导入外部的 Cycript 脚本(在使用 MonkeyDev 的情况下)

    ① 在 MonkeyDev 中,通过配置 MDConfig.plist 可以导入从网络下载的外部 Cycript 脚本,比如通过如下的配置:
    1
    LoadAtLaunch:表示是否在启动的时候默认加载脚本,默认加载的脚本就不用再 @import xxx 导入,可以直接使用。如果不是默认加载的脚本就需要 @import xxx 导入,xxx 就是图中的 key,比如 @import nslog@import ms@import hook@import md

    priority:表示加载的优先级,数字越小优先级越高,比如某些脚本需要依赖其它脚本就需要调整优先级,让被依赖的脚本先加载

    contenturl:脚本可以直接写到 content 里面,也可以是网络的 url,会自动下载下来:

    ➜  cycript_0.9.594 ./cycript -r 192.168.1.229:6666
    cy# APPID
    @"com.alonemonkey.TestCycript"
    cy# pviews()
    <UIWindow: 0x105313b60; frame = (0 0; 375 667); autoresize = W+H; gestureRecognizers = <NSArray: 0x1c425bae0>; layer = <UIWindowLayer: 0x1c40394e0>>| <UIView: 0x1053205d0; frame = (0 0; 375 667); autoresize = W+H; layer = <CALayer: 0x1c403bd80>>|    | <UILabel: 0x10531e7d0; frame = (102.5 45; 170 40); text = 'AloneMonkey'; opaque = NO; autoresize = RM+BM; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x1c408c0d0>>|    | <UILabel: 0x1053207b0; frame = (97.5 110; 180 40); text = 'You are the best!!!'; opaque = NO; autoresize = RM+BM; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x1c408e150>>|    | <UITextView: 0x105836000; frame = (26 230; 343 427); text = '/opt/MonkeyDev/bin/md ....'; clipsToBounds = YES; autoresize = RM+BM; gestureRecognizers = <NSArray: 0x1c0058fc0>; layer = <CALayer: 0x1c002f1a0>; contentOffset: {0, 0}; contentSize: {343, 317}; adjustedContentInset: {0, 0, 0, 0}>|    |    | <<_UITextContainerView: 0x105318060; frame = (0 0; 343 317); layer = <__UITextTiledLayer: 0x1c40c42f0>> minSize = {0, 0}, maxSize = {1.7976931348623157e+308, 1.7976931348623157e+308}, textContainer = <NSTextContainer: 0x1c0105fa0 size = (343.000000,inf); widthTracksTextView = YES; heightTracksTextView = NO>; exclusionPaths = 0x1c40025e0; lineBreakMode = 0>|    |    |    | <__UITileLayer: 0x1c0240780> (layer)|    |    |    | <__UITileLayer: 0x1c0240720> (layer)|    |    |    | <__UITileLayer: 0x1c0240a20> (layer)|    |    | <UIImageView: 0x105322260; frame = (3 421.5; 337 2.5); alpha = 0; opaque = NO; autoresize = TM; userInteractionEnabled = NO; layer = <CALayer: 0x1c403c620>>|    |    | <UIImageView: 0x105322490; frame = (337.5 380; 2.5 44); alpha = 0; opaque = NO; autoresize = LM; userInteractionEnabled = NO; layer = <CALayer: 0x1c403c6e0>>|    | <UIButton: 0x1053163b0; frame = (127.5 175; 120 30); opaque = NO; autoresize = RM+BM; layer = <CALayer: 0x1c4039aa0>>|    |    | <UIButtonLabel: 0x10540fcf0; frame = (2 6; 116.5 18); text = 'ShowChangeLog'; opaque = NO; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x1c0087b70>>|    | <_UILayoutGuide: 0x105320fa0; frame = (0 0; 0 20); hidden = YES; layer = <CALayer: 0x1c403bfe0>>|    | <_UILayoutGuide: 0x1053213a0; frame = (0 667; 0 0); hidden = YES; layer = <CALayer: 0x1c403bee0>>
    cy# pvcs()
    "<CustomViewController 0x1053133f0>, state: appeared, view: <UIView 0x1053205d0>"
    cy# pactions(#0x1053163b0)
    "<CustomViewController: 0x1053133f0> showChangeLog:"
    cy# rp(#0x1053163b0)
    <UIButton: 0x1053163b0; frame = (127.5 175; 120 30); opaque = NO; autoresize = RM+BM; layer = <CALayer: 0x1c4039aa0>>
    <UIView: 0x1053205d0; frame = (0 0; 375 667); autoresize = W+H; layer = <CALayer: 0x1c403bd80>>
    <CustomViewController: 0x1053133f0>
    <UIWindow: 0x105313b60; frame = (0 0; 375 667); autoresize = W+H; gestureRecognizers = <NSArray: 0x1c425bae0>; layer = <UIWindowLayer: 0x1c40394e0>>
    <UIApplication: 0x105406120>
    <AppDelegate: 0x1c002b1a0>
    cy# ?exit
    

    虽然理想很丰满,但是现实很骨感。因为 GitHub 的服务器位于国外,所以在通过网络下载 Cycript 脚本的时候,通常会失败。此时,MonkeyDev 工程的控制台会输出如下报错信息:

    ......
    Download cycript(https://cydia.saurik.com/api/latest/3) then run: ./cycript -r 192.168.1.229:6666
    ......
    # 提示下载 https://raw.githubusercontent.com/AloneMonkey/MDCycript/master/MS.cy 失败
    2022-02-20 23:35:01.773264+0800 TargetApp[24710:5320995] [] tcp_input [C1.1:3] flags=[R] seq=228439538, ack=2355836980, win=1031 state=ESTABLISHED rcv_nxt=228439538, snd_una=2355836463
    2022-02-20 23:35:01.774120+0800 TargetApp[24710:5320995] Connection 1: received failure notification
    2022-02-20 23:35:01.774150+0800 TargetApp[24710:5320995] Connection 1: received ECONNRESET with incomplete TLS handshake - generating errSSLClosedNoNotify
    2022-02-20 23:35:01.774176+0800 TargetApp[24710:5320995] Connection 1: failed to connect 3:-9816, reason -1
    2022-02-20 23:35:01.774188+0800 TargetApp[24710:5320995] Connection 1: encountered error(3:-9816)
    2022-02-20 23:35:01.775097+0800 TargetApp[24710:5320995] Task <FE90F26F-FBA4-436F-A767-4A2DC298A95D>.<1> HTTP load failed, 0/0 bytes (error code: -1200 [3:-9816])
    2022-02-20 23:35:01.782158+0800 TargetApp[24710:5320994] Task <FE90F26F-FBA4-436F-A767-4A2DC298A95D>.<1> finished with error [-1200] Error Domain=NSURLErrorDomain Code=-1200 "An SSL error has occurred and a secure connection to the server cannot be made." UserInfo={_kCFStreamErrorCodeKey=-9816, NSLocalizedRecoverySuggestion=Would you like to connect to the server anyway?, NSUnderlyingError=0x2838648a0 {Error Domain=kCFErrorDomainCFNetwork Code=-1200 "(null)" UserInfo={_kCFStreamPropertySSLClientCertificateState=0, _kCFNetworkCFStreamSSLErrorOriginalValue=-9816, _kCFStreamErrorDomainKey=3, _kCFStreamErrorCodeKey=-9816}}, NSLocalizedDescription=An SSL error has occurred and a secure connection to the server cannot be made., NSErrorFailingURLKey=https://raw.githubusercontent.com/AloneMonkey/MDCycript/master/MS.cy, NSErrorFailingURLStringKey=https://raw.githubusercontent.com/AloneMonkey/MDCycript/master/MS.cy, _kCFStreamErrorDomainKey=3}
    2022-02-20 23:35:01.782232+0800 TargetApp[24710:5320994] [Cycript] Failed download script [ms]: An SSL error has occurred and a secure connection to the server cannot be made.
    # 提示下载 https://raw.githubusercontent.com/AloneMonkey/MDCycript/master/md.cy 失败
    2022-02-20 23:35:01.953544+0800 TargetApp[24710:5320996] [] tcp_input [C2.1:3] flags=[R] seq=1015217985, ack=2664387600, win=1031 state=ESTABLISHED rcv_nxt=1015217985, snd_una=2664387083
    2022-02-20 23:35:01.954294+0800 TargetApp[24710:5320996] Connection 2: received failure notification
    2022-02-20 23:35:01.954342+0800 TargetApp[24710:5320996] Connection 2: received ECONNRESET with incomplete TLS handshake - generating errSSLClosedNoNotify
    2022-02-20 23:35:01.954358+0800 TargetApp[24710:5320996] Connection 2: failed to connect 3:-9816, reason -1
    2022-02-20 23:35:01.954372+0800 TargetApp[24710:5320996] Connection 2: encountered error(3:-9816)
    2022-02-20 23:35:01.955309+0800 TargetApp[24710:5320996] Task <DCA9F824-77FA-42E2-9CE5-30930D8F652B>.<2> HTTP load failed, 0/0 bytes (error code: -1200 [3:-9816])
    2022-02-20 23:35:01.956396+0800 TargetApp[24710:5320997] Task <DCA9F824-77FA-42E2-9CE5-30930D8F652B>.<2> finished with error [-1200] Error Domain=NSURLErrorDomain Code=-1200 "An SSL error has occurred and a secure connection to the server cannot be made." UserInfo={_kCFStreamErrorCodeKey=-9816, NSLocalizedRecoverySuggestion=Would you like to connect to the server anyway?, NSUnderlyingError=0x283884b40 {Error Domain=kCFErrorDomainCFNetwork Code=-1200 "(null)" UserInfo={_kCFStreamPropertySSLClientCertificateState=0, _kCFNetworkCFStreamSSLErrorOriginalValue=-9816, _kCFStreamErrorDomainKey=3, _kCFStreamErrorCodeKey=-9816}}, NSLocalizedDescription=An SSL error has occurred and a secure connection to the server cannot be made., NSErrorFailingURLKey=https://raw.githubusercontent.com/AloneMonkey/MDCycript/master/md.cy, NSErrorFailingURLStringKey=https://raw.githubusercontent.com/AloneMonkey/MDCycript/master/md.cy, _kCFStreamErrorDomainKey=3}
    2022-02-20 23:35:01.956501+0800 TargetApp[24710:5320997] [Cycript] Failed download script [md]: An SSL error has occurred and a secure connection to the server cannot be made.
    # 不管通过网络下载 Cycript 脚本是成功还是失败,都会调用 -[MDCycriptManager finishDownload] 方法,输出如下信息:
    2022-02-20 23:35:01.956551+0800 TargetApp[24710:5320997] [Cycript] Finish download all script!
    ......
    

    ② 幸运的是,在 MonkeyDev 中也可以手动导入外部 Cycript 脚本。不过,导入的外部 Cycript 脚本需要放置在主工程的 Target - Build Phases - Copy Files - Frameworks 中:
    2
    当需要使用外部 Cycript 脚本所提供的功能时,请在 Cycript 的 REPL 中使用 @import 命令导入这些外部脚本(其中,模块名称即文件名称):

    ~ > cycript -r 192.168.1.229:6666
    cy# @import libcycript
    cy# @import md
    cy# @import mjcript
    cy# @import MS
    cy# @import utils
    

    注意:
    在将外部 Cycript 脚本添加到 Xcode 工程中时,Xcode 默认会在主工程的 Target - Build Phases - Copy Bundle Resources 中创建对这些外部 Cycript 脚本的引用。但是这与 MonkeyDev 所约定的保存外部 Cycript 脚本的位置(Target - Build Phases - Copy Files - Frameworks)不一样。为了避免将外部 Cycript 脚本重复添加到主工程的 Bundle 中,最好将 Xcode 默认创建的这些对外部 Cycript 脚本的引用删除
    3

  • 在越狱环境中导入外部的 Cycript 脚本

    在已越狱的 iOS 设备中,提供了一个名为 /usr/lib/cycript0.9/ 的目录,用于存放外部 Cycript 脚本,此目录相当于在越狱环境中 Cycript @import 命令的根目录。例如:

    saurik 将其编写的 Cycript 脚本 MS.cy 存放在了已越狱 iOS 设备中的 /usr/lib/cycript0.9/com/saurik/substrate 目录下,则在 Cycript 的 REPL 中可以通过以下命令导入该脚本:

    cy# @import com.saurik.substrate.MS
    

    Tyilo 将其编写的 Cycript 脚本 utils.cy 存放在了已越狱 iOS 设备中的 /usr/lib/cycript0.9/com/tyilo 目录下,则在 Cycript 的 REPL 中可以通过以下命令导入该脚本:

    cy# @import com.tyilo.utils;
    

补充:Cycript 和 LLDB 中经常用到的 Objective-C 私有方法

  • ① -[NSObject _ivarDescription]

    用于列出指定实例对象的所有成员变量的类型和值,包括继承自(自定义超类和系统超类)的成员变量

    cy# [#0x00000002837e1800 _ivarDescription].toString()
    `<HcgStudent: 0x2837e1800>:
    in HcgStudent:
    \t_chineseScore (float): 80
    \t_englishScore (float): 90
    \t_mathematicalScore (float): 100
    in HcgPerson:
    \t_age (int): 20
    \t_height (float): 170
    \t_name (NSString*): @"hcg"
    \t_addr (NSString*): @"XiaMen"
    in NSObject:
    \tisa (Class): HcgStudent (isa, 0x21a104ba2035)`
    
  • ② -[NSObject _shortMethodDescription]

    用于列出指定实例对象的所有对象方法和类方法,包括继承自(自定义超类)的方法

    cy# [#0x00000002837e1800 _shortMethodDescription].toString()
    `<HcgStudent: 0x2837e1800>:
    in HcgStudent:
    \tClass Methods:
    \t\t+ (id) studentWithChineseScore:(float)arg1 englishScore:(float)arg2 mathematicalScore:(float)arg3; (0x104a94080)
    \tProperties:
    \t\t@property (nonatomic) float chineseScore;  (@synthesize chineseScore = _chineseScore;)
    \t\t@property (nonatomic) float englishScore;  (@synthesize englishScore = _englishScore;)
    \t\t@property (nonatomic) float mathematicalScore;  (@synthesize mathematicalScore = _mathematicalScore;)
    \tInstance Methods:
    \t\t- (void) setChineseScore:(float)arg1; (0x104a9416c)
    \t\t- (void) setEnglishScore:(float)arg1; (0x104a941c4)
    \t\t- (void) setMathematicalScore:(float)arg1; (0x104a9421c)
    \t\t- (id) initWithChineseScore:(float)arg1 englishScore:(float)arg2 mathematicalScore:(float)arg3; (0x104a94000)
    \t\t- (float) chineseScore; (0x104a94144)
    \t\t- (float) englishScore; (0x104a9419c)
    \t\t- (float) mathematicalScore; (0x104a941f4)
    in HcgPerson:
    \tClass Methods:
    \t\t+ (id) personWithName:(id)arg1 age:(int)arg2 height:(float)arg3 addr:(id)arg4; (0x104a94338)
    \t\t+ (id) breathe; (0x104a94574)
    \tProperties:
    \t\t@property (retain, nonatomic) NSString* name;  (@synthesize name = _name;)
    \t\t@property (nonatomic) int age;  (@synthesize age = _age;)
    \t\t@property (nonatomic) float height;  (@synthesize height = _height;)
    \t\t@property (retain, nonatomic) NSString* addr;  (@synthesize addr = _addr;)
    \tInstance Methods:
    \t\t- (void) setAddr:(id)arg1; (0x104a9467c)
    \t\t- (id) addr; (0x104a94660)
    \t\t- (id) initWithName:(id)arg1 age:(int)arg2 height:(float)arg3 addr:(id)arg4; (0x104a9424c)
    \t\t- (id) sayHello; (0x104a9447c)
    \t\t- (id) name; (0x104a94590)
    \t\t- (void) .cxx_destruct; (0x104a946b0)
    \t\t- (void) setName:(id)arg1; (0x104a945ac)
    \t\t- (float) height; (0x104a94620)
    \t\t- (void) setHeight:(float)arg1; (0x104a9463c)
    \t\t- (int) age; (0x104a945e0)
    \t\t- (void) setAge:(int)arg1; (0x104a945fc)
    (NSObject ...)`
    
  • ③ -[NSObject _methodDescription]

    用于列出指定实例对象的所有对象方法和类方法,包括继承自(自定义超类和系统超类)的方法

    cy# [#0x00000002837e1800 _methodDescription].toString()
    `<HcgStudent: 0x2837e1800>:
    in HcgStudent:
    \tClass Methods:
    \t\t+ (id) studentWithChineseScore:(float)arg1 englishScore:(float)arg2 mathematicalScore:(float)arg3; (0x104a94080)
    \tProperties:
    \t\t@property (nonatomic) float chineseScore;  (@synthesize chineseScore = _chineseScore;)
    \t\t@property (nonatomic) float englishScore;  (@synthesize englishScore = _englishScore;)
    \t\t@property (nonatomic) float mathematicalScore;  (@synthesize mathematicalScore = _mathematicalScore;)
    \tInstance Methods:
    \t\t- (void) setChineseScore:(float)arg1; (0x104a9416c)
    \t\t- (void) setEnglishScore:(float)arg1; (0x104a941c4)
    \t\t- (void) setMathematicalScore:(float)arg1; (0x104a9421c)
    \t\t- (id) initWithChineseScore:(float)arg1 englishScore:(float)arg2 mathematicalScore:(float)arg3; (0x104a94000)
    \t\t- (float) chineseScore; (0x104a94144)
    \t\t- (float) englishScore; (0x104a9419c)
    \t\t- (float) mathematicalScore; (0x104a941f4)
    in HcgPerson:
    \tClass Methods:
    \t\t+ (id) personWithName:(id)arg1 age:(int)arg2 height:(float)arg3 addr:(id)arg4; (0x104a94338)
    \t\t+ (id) breathe; (0x104a94574)
    \tProperties:
    \t\t@property (retain, nonatomic) NSString* name;  (@synthesize name = _name;)
    \t\t@property (nonatomic) int age;  (@synthesize age = _age;)
    \t\t@property (nonatomic) float height;  (@synthesize height = _height;)
    \t\t@property (retain, nonatomic) NSString* addr;  (@synthesize addr = _addr;)
    \tInstance Methods:
    \t\t- (void) setAddr:(id)arg1; (0x104a9467c)
    \t\t- (id) addr; (0x104a94660)
    \t\t- (id) initWithName:(id)arg1 age:(int)arg2 height:(float)arg3 addr:(id)arg4; (0x104a9424c)
    \t\t- (id) sayHello; (0x104a9447c)
    \t\t- (id) name; (0x104a94590)
    \t\t- (void) .cxx_destruct; (0x104a946b0)
    \t\t- (void) setName:(id)arg1; (0x104a945ac)
    \t\t- (float) height; (0x104a94620)
    \t\t- (void) setHeight:(float)arg1; (0x104a9463c)
    \t\t- (int) age; (0x104a945e0)
    \t\t- (void) setAge:(int)arg1; (0x104a945fc)
    in NSObject:
    \tClass Methods:
    ...
    \tProperties:
    ...
    \tInstance Methods:
    ...`
    
  • ④ -[UIView _autolayoutTrace]

    用于打印包含指定视图的窗口中的整个视图的层级结构(简要)

    cy# [#0x137f26d40 _autolayoutTrace].toString()
    `
    \u2022UIWindow:0x137d06b30
    |   \u2022UIView:0x137f26d40
    |   |   *<UILayoutGuide: 0x283d315e0 - "UIViewLayoutMarginsGuide", layoutFrame = {{16, 44}, {343, 734}}, owningView = <UIView: 0x137f26d40; frame = (0 0; 375 812); autoresize = W+H; layer = <CALayer: 0x2804097a0>>>
    |   |   *UILabel:0x137f264d0'Airths LionHeart'
    |   |   *UILabel:0x137f26eb0'You are the best!!!'
    |   |   *UITextView:0x138868800'Current Version:/opt/Mon...'
    |   |   |   _UITextLayoutView:0x137f20e10
    |   |   |   _UITextContainerView:0x137f1fea0
    |   |   |   |   _UITextViewCanvasView:0x137f202c0
    |   |   |   _UIScrollViewScrollIndicator:0x139d0bd50
    |   |   |   |   UIView:0x139d0bef0
    |   |   |   _UIScrollViewScrollIndicator:0x139d0c060
    |   |   |   |   UIView:0x139d0c200
    |   |   *UIButton:0x137f1ca00'ShowChangeLog'
    |   |   |   UIButtonLabel:0x139d0c870'ShowChangeLog'
    |   |   *_UILayoutGuide:0x137f27130
    |   |   *_UILayoutGuide:0x137f274c0Legend:
    \t* - is laid out with auto layout
    \t+ - is laid out manually, but is represented in the layout engine because translatesAutoresizingMaskIntoConstraints = YES
    \t\u2022 - layout engine host`
    
  • ⑤ -[UIView recursiveDescription]

    用于打印包含指定视图的窗口中的整个视图的层级结构(详细)

    cy# [#0x137f26d40 recursiveDescription].toString()
    `<UIView: 0x137f26d40; frame = (0 0; 375 812); autoresize = W+H; layer = <CALayer: 0x2804097a0>>| <UILabel: 0x137f264d0; frame = (102.5 69; 170 40); text = 'Airths LionHeart'; opaque = NO; autoresize = RM+BM; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x28271f4d0>>| <UILabel: 0x137f26eb0; frame = (97.5 134; 180 40); text = 'You are the best!!!'; opaque = NO; autoresize = RM+BM; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x28271ce10>>| <UITextView: 0x138868800; frame = (26 254; 343 514); text = 'Current Version:/opt/Mon...'; clipsToBounds = YES; autoresize = RM+BM; gestureRecognizers = <NSArray: 0x280a592f0>; layer = <CALayer: 0x28040d6a0>; contentOffset: {0, 0}; contentSize: {343, 50}; adjustedContentInset: {0, 0, 0, 0}>|    | <_UITextLayoutView: 0x137f20e10; frame = (0 0; 0 0); layer = <CALayer: 0x28040f440>>|    | <<_UITextContainerView: 0x137f1fea0; frame = (0 0; 343 50); layer = <CALayer: 0x28040f2e0>> minSize = {0, 0}, maxSize = {1.7976931348623157e+308, 1.7976931348623157e+308}, textContainer = <NSTextContainer: 0x283530dc0 size = (343.000000,inf); widthTracksTextView = YES; heightTracksTextView = NO>; exclusionPaths = 0x1cdc13c98; lineBreakMode = 0>|    |    | <_UITextViewCanvasView: 0x137f202c0; frame = (0 0; 343 50); userInteractionEnabled = NO; layer = <_UITextTiledLayer: 0x2836387e0>>|    | <_UIScrollViewScrollIndicator: 0x139d0bd50; frame = (3 508; 337 3); alpha = 0; autoresize = TM; layer = <CALayer: 0x2804653a0>>|    |    | <UIView: 0x139d0bef0; frame = (0 0; 337 3); layer = <CALayer: 0x2804653e0>>|    | <_UIScrollViewScrollIndicator: 0x139d0c060; frame = (337 383; 3 41); alpha = 0; autoresize = LM; layer = <CALayer: 0x280465420>>|    |    | <UIView: 0x139d0c200; frame = (0 0; 3 41); layer = <CALayer: 0x280465440>>| <UIButton: 0x137f1ca00; frame = (127.5 199; 120 30); opaque = NO; autoresize = RM+BM; layer = <CALayer: 0x28040d5a0>>|    | <UIButtonLabel: 0x139d0c870; frame = (1.5 6; 117 18); text = 'ShowChangeLog'; opaque = NO; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x2827050e0>>| <_UILayoutGuide: 0x137f27130; frame = (0 0; 0 44); hidden = YES; layer = <CALayer: 0x280409860>>| <_UILayoutGuide: 0x137f274c0; frame = (0 778; 0 34); hidden = YES; layer = <CALayer: 0x280409940>>`
    
  • ⑥ -[UIViewController _printHierarchy]

    用于打印包含指定控制器的窗口中的整个控制器的层级结构

    cy# [#0x10510fcf0 _printHierarchy].toString()
    "<CustomViewController 0x10510fcf0>, state: appeared, view: <UIView 0x10521e860>"
    

补充:其他细节

  • Cycript 在打印控制器或者视图时,会将汉字转换为 Unicode 编码

    如下所示的界面:
    1
    通过 Cycript 打印视图的层级结构:

    ~ > cycript -r 192.168.1.229:8899
    cy# UIApp.keyWindow.recursiveDescription().toString()
    `<UIWindow: 0x102e0c270; frame = (0 0; 414 896); gestureRecognizers = <NSArray: 0x281ba4b10>; layer = <UIWindowLayer: 0x2815fde60>>| <UITransitionView: 0x102e12ab0; frame = (0 0; 414 896); autoresize = W+H; layer = <CALayer: 0x2815e5de0>>|    | <UIDropShadowView: 0x102f08600; frame = (0 0; 414 896); clipsToBounds = YES; autoresize = W+H; layer = <CALayer: 0x2815e0d20>>|    |    | <UIView: 0x102e10d10; frame = (0 0; 414 896); autoresize = W+H; layer = <CALayer: 0x2815e6900>>|    |    |    | <UIButton: 0x102e11080; frame = (115 106; 184 30); opaque = NO; autoresize = RM+BM; layer = <CALayer: 0x2815e6a40>>|    |    |    |    | <UIButtonLabel: 0x102f1e6f0; frame = (0 6; 184 18); text = '\u8fd9\u662f\u4e00\u4e2a\u53ea\u5305\u542b\u4e2d\u6587\u7684\u6309\u94ae'; opaque = NO; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x2836eb2a0>>|    |    |    |    |    | <_UILabelContentLayer: 0x2815fbf00> (layer)|    |    |    | <_UILayoutGuide: 0x102e11b30; frame = (0 0; 0 44); hidden = YES; layer = <CALayer: 0x2815e67a0>>|    |    |    | <_UILayoutGuide: 0x102e120c0; frame = (0 862; 0 34); hidden = YES; layer = <CALayer: 0x2815e66a0>>`
    

    Cycript 在打印视图的层级结构时,会将遇到的所有中文转换成对应的 Unicode 编码
    如果需要通过界面上控件中的文本快速地定位指定的控件
    则需要将文本中的中文转换成对应的 Unicode 编码后,再在输出结果中进行搜索,例如

    中文(这是一个只包含中文的按钮): Unicode 编码 (\u8fd9\u662f\u4e00\u4e2a\u53ea\u5305\u542b\u4e2d\u6587\u7684\u6309\u94ae

  • Cycript 的两种使用方式

    1. 在非越狱环境中,通过将 Cycript.framework 集成到特定的 App,以在该 App 中使用 Cycript
    2. 在越狱环境中,通过 MobileSubstrate 加载并注入所有的 App 中,以在所有 App 中使用 Cycript

这篇关于Cycript(五):安装与使用的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/756494

相关文章

Go语言使用sync.Mutex实现资源加锁

《Go语言使用sync.Mutex实现资源加锁》数据共享是一把双刃剑,Go语言为我们提供了sync.Mutex,一种最基础也是最常用的加锁方式,用于保证在任意时刻只有一个goroutine能访问共享... 目录一、什么是 Mutex二、为什么需要加锁三、实战案例:并发安全的计数器1. 未加锁示例(存在竞态)

setsid 命令工作原理和使用案例介绍

《setsid命令工作原理和使用案例介绍》setsid命令在Linux中创建独立会话,使进程脱离终端运行,适用于守护进程和后台任务,通过重定向输出和确保权限,可有效管理长时间运行的进程,本文给大家介... 目录setsid 命令介绍和使用案例基本介绍基本语法主要特点命令参数使用案例1. 在后台运行命令2.

使用Redis快速实现共享Session登录的详细步骤

《使用Redis快速实现共享Session登录的详细步骤》在Web开发中,Session通常用于存储用户的会话信息,允许用户在多个页面之间保持登录状态,Redis是一个开源的高性能键值数据库,广泛用于... 目录前言实现原理:步骤:使用Redis实现共享Session登录1. 引入Redis依赖2. 配置R

使用Python的requests库调用API接口的详细步骤

《使用Python的requests库调用API接口的详细步骤》使用Python的requests库调用API接口是开发中最常用的方式之一,它简化了HTTP请求的处理流程,以下是详细步骤和实战示例,涵... 目录一、准备工作:安装 requests 库二、基本调用流程(以 RESTful API 为例)1.

使用Python开发一个Ditto剪贴板数据导出工具

《使用Python开发一个Ditto剪贴板数据导出工具》在日常工作中,我们经常需要处理大量的剪贴板数据,下面将介绍如何使用Python的wxPython库开发一个图形化工具,实现从Ditto数据库中读... 目录前言运行结果项目需求分析技术选型核心功能实现1. Ditto数据库结构分析2. 数据库自动定位3

Python yield与yield from的简单使用方式

《Pythonyield与yieldfrom的简单使用方式》生成器通过yield定义,可在处理I/O时暂停执行并返回部分结果,待其他任务完成后继续,yieldfrom用于将一个生成器的值传递给另一... 目录python yield与yield from的使用代码结构总结Python yield与yield

Go语言使用select监听多个channel的示例详解

《Go语言使用select监听多个channel的示例详解》本文将聚焦Go并发中的一个强力工具,select,这篇文章将通过实际案例学习如何优雅地监听多个Channel,实现多任务处理、超时控制和非阻... 目录一、前言:为什么要使用select二、实战目标三、案例代码:监听两个任务结果和超时四、运行示例五

python使用Akshare与Streamlit实现股票估值分析教程(图文代码)

《python使用Akshare与Streamlit实现股票估值分析教程(图文代码)》入职测试中的一道题,要求:从Akshare下载某一个股票近十年的财务报表包括,资产负债表,利润表,现金流量表,保存... 目录一、前言二、核心知识点梳理1、Akshare数据获取2、Pandas数据处理3、Matplotl

Java使用Thumbnailator库实现图片处理与压缩功能

《Java使用Thumbnailator库实现图片处理与压缩功能》Thumbnailator是高性能Java图像处理库,支持缩放、旋转、水印添加、裁剪及格式转换,提供易用API和性能优化,适合Web应... 目录1. 图片处理库Thumbnailator介绍2. 基本和指定大小图片缩放功能2.1 图片缩放的

Win10安装Maven与环境变量配置过程

《Win10安装Maven与环境变量配置过程》本文介绍Maven的安装与配置方法,涵盖下载、环境变量设置、本地仓库及镜像配置,指导如何在IDEA中正确配置Maven,适用于Java及其他语言项目的构建... 目录Maven 是什么?一、下载二、安装三、配置环境四、验证测试五、配置本地仓库六、配置国内镜像地址