Cycript 是 Objective-C++、ES6(JavaScript)、Java 等语法的混合物,也就是语法格式支持这些语言。可以用来探索、修改、调试正在运行的 iOS/Mac APP。
官网:http://www.cycript.org/
文档:http://www.cycript.org/manual/
在使用 Cycript 之前,需要通过 Cydia 安装 Cycript,然后 Mac 通过 SSH 登录 iPhone。
提示:在编写 Cycript 过程中,按 tab 键会有语法填充和提示。
开启Cycript
开启 Cycript,通过 SSH 登录 iPhone 后,可以直接使用下面命令:
cycript
开启成功,终端则会显示:
cy#
常用快捷键(推荐使用 iTerm 终端,默认终端快捷键会冲突):
Control + D #关闭Cycript
Command + R #清屏
Control + C #取消输入
使用 Cycript 调试程序:
cycript -p 进程ID
或
cycript -p 进程名称
ps
命令是 process ststus 的缩写,使用 ps 命令可以列出系统当前的进程(需要在 Cydia 安装 adv-cmds
插件)。
列出所有进程:
ps -A
列出结果,下面这个进程是喜马拉雅 APP:
15462 ?? 3:30.87 /var/containers/Bundle/Application/2991BBBF-1444-4A60-89FE-4991586C20BF/ting.app/ting
搜索关键词:
ps -A | grep 关键词
所以如果我们要调试喜马拉雅 APP,则通过 SSH 登录 iPhone 后,使用下面命令(进程ID可能会变,建议使用进程名称):
cycript -p ting
常用语法
开启 expand
功能,Cycript 会把格式符号翻译成相应的格式,如 \n
会被翻译成一个换行,让输出的可读性更高:
cy# ?expand
expand == true
获取 UIApplication
对象:
cy# [UIApplication sharedApplication]
#"<XMUIApplication: 0x117e0f9e0>"
cy# UIApp
#"<XMUIApplication: 0x117e0f9e0>"
使用 var 变量名 = 值
来定义变量,定义的变量在 APP 进程结束前一直有效:
cy# var keyWindow = UIApp.keyWindow
#"<UIWindow: 0x118a145b0; frame = (0 0; 375 812); autoresize = W+H; gestureRecognizers = <NSArray: 0x2824d6220>; layer = <UIWindowLayer: 0x282ac0600>>"
cy# keyWindow
#"<UIWindow: 0x118a145b0; frame = (0 0; 375 812); autoresize = W+H; gestureRecognizers = <NSArray: 0x2824d6220>; layer = <UIWindowLayer: 0x282ac0600>>"
使用 #内存地址
来获取对象:
cy# UIApp.keyWindow
#"<UIWindow: 0x10931dbd0; frame = (0 0; 375 812); autoresize = W+H; gestureRecognizers = <NSArray: 0x2814c5c50>; layer = <UIWindowLayer: 0x281ace6a0>>"
cy# #0x10931dbd0
#"<UIWindow: 0x10931dbd0; frame = (0 0; 375 812); autoresize = W+H; gestureRecognizers = <NSArray: 0x2814c5c50>; layer = <UIWindowLayer: 0x281ace6a0>>"
ObjectiveC.classes
打印出所有已加载的 OC 类:
cy# ObjectiveC.classes
{NSLeafProxy:NSLeafProxy,JSExport:JSExport,PFEmbeddedMulticasterImplementation:PFEmbeddedMulticasterImplementation,PFMulticasterDistributionMethods:PFMulticasterDistributionMethods,__NSGenericDeallocHandler:__NSGenericDeallocHandler,__NSAtom:__NSAtom,_NSZombie_:_NSZombie_,__NSMessageBuilder:__NSMessageBuilder,Object:Object,NSProxy:NSProxy,EXTNil:EXTNil,_YYWebImageWeakProxy:_YYWebImageWeakProxy,YYTextWeakProxy:YYTextWeakProxy,_YYImageWeakProxy:_YYImageWeakProxy,WVExtContext:WVExtContext,WVWeakObjectWrapper:WVWeakObjectWrapper,WVExtHandler:WVExtHandler,SDWeakProxy:SDWeakProxy,PHFDelegateChain:PHFDelegateChain,_NMLWebImageWeakProxy:_NMLWebImageWeakProxy,_NMCADisplayLinkWeakProxy:_NMCADisplayLinkWeakProxy,NELayoutComponentDelegateProxy:NELayoutComponentDelegateProxy,MCDelegateIntercept:MCDelegateIntercept,
使用 *对象
语法,来获取对象的所有成员变量,打印出来的键值对是成员变量名和成员变量值:
cy# *UIApp
{isa:NSKVONotifying_XMUIApplication,_responderFlags:@error,_delegate:#"<XMAppDelegate: 0x282bf9540>",_remoteControlEventObservers:1,_topLevelNibObjects:null,_networkResourcesCurrentlyLoadingCount:0,_hideNetworkActivityIndicatorTimer:null,_editAlertController:null,_statusBar:null,_statusBarRequestedStyle:0,...
使用 recursiveDescription()
函数,递归打印 view 所有子控件(跟 LLDB 一样的函数):
cy# UIApp.keyWindow.recursiveDescription().toString()
`<UIWindow: 0x10931dbd0; frame = (0 0; 375 812); autoresize = W+H; gestureRecognizers = <NSArray: 0x2814c5c50>; layer = <UIWindowLayer: 0x281ace6a0>>
| <UITransitionView: 0x109362430; frame = (0 0; 375 812); autoresize = W+H; layer = <CALayer: 0x281c7b180>>
| | <UIDropShadowView: 0x1093bad40; frame = (0 0; 375 812); autoresize = W+H; layer = <CALayer: 0x281c7b2e0>>
| | | <UILayoutContainerView: 0x10974a430; frame = (0 0; 375 812); autoresize = W+H; layer = <CALayer: 0x281b18980>>
| | | | <UIView: 0x10935ec80; frame = (0 0; 375 812); layer = <CALayer: 0x281a2fca0>>
| | | | <UITransitionView: 0x109364100; frame = (0 0; 375 812); clipsToBounds = YES; autoresize = W+H; layer = <CALayer: 0x281a2e240>>
| | | | | <UIViewControllerWrapperView: 0x1093bbc40; frame = (0 0; 375 812); autoresize = W+H; layer = <CALayer: 0x281c79ca0>>
使用 choose()
函数筛选出某种类型的对象:
cy# choose(UIViewController)
[#"<UIViewController: 0x109380ee0>",#"<UIViewController: 0x1093b25c0>",#"<XMPageViewController: 0x109792ee0>",#"<XMMainTabBarController: 0x10a814000>",#"<XMFindTopViewController: 0x10a846a00>",#"<XMSegmentTabViewController: 0x10a848a00>",#"<XMNavigationController: 0x10a849a00>",#"<XMFindFlowViewController: 0x10a91c400>",#"<XMNavigationController: 0x10a93f600>",#"<XMSoundCustomViewController: 0x10a98d000>",#"<UINavigationController: 0x1098a0e00>",#"<XMNewDiscoveryViewController: 0x109961000>",#"<XMNavigationController: 0x109961600>",#"<XMPersonalCenterViewController: 0x109963200>",#"<XMNavigationController: 0x109963a00>"]
工具库使用
类似 iOS 开发里的各种工具类,我们也可以把自己写的一些 Cycript 代码封装起来,方便快速开发。
MJ 大神封装的工具库:https://github.com/CoderMJLee/mjcript 。
从 GitHub 下载后,将 mjcript.cy
文件拷贝到 /usr/lib/cycript0.9
目录下,可以重命名为 MJTool.cy
。
每次都需要 @import
导入才能使用:
cy# @import MJTool
{}
cy# MJAppId
@"com.xr.chuangyin"
cy# MJAppPath
@"/private/var/containers/Bundle/Application/6241E28B-FE27-4F31-8633-9FDEF5D3C30A/Dagou.app"
cy# MJFrontVc()
#"<JFLoginVC: 0x10bb46300>"
cy# MJInstanceMethodNames(JFLoginVC)
[&"toWebViewUrl:",&"requestTicketInfo:qqInfo:wxInfo:",&"handleAppleLoginReuslt:",&"loginAppleHandleAppleId:identityToken:authorizationCode:",&"onClickPhoneLogin:",&"onClickIdLogin:",&"onClickOneKeyLogin:",&"onClickAppleLogin:",&"loginWithWxInfo:qqInfo:type:",&"appleLoginBtn",&"setAppleLoginBtn:",&"thirdPartView",&"setThirdPartView:",&"viewDidLoad",&"setupUI",&"dealloc",&".cxx_destruct",&"viewWillAppear:"]
cy# MJInstanceMethodNames(#0x10bb46300)
[&"toWebViewUrl:",&"requestTicketInfo:qqInfo:wxInfo:",&"handleAppleLoginReuslt:",&"loginAppleHandleAppleId:identityToken:authorizationCode:",&"onClickPhoneLogin:",&"onClickIdLogin:",&"onClickOneKeyLogin:",&"onClickAppleLogin:",&"loginWithWxInfo:qqInfo:type:",&"appleLoginBtn",&"setAppleLoginBtn:",&"thirdPartView",&"setThirdPartView:",&"viewDidLoad",&"setupUI",&"dealloc",&".cxx_destruct",&"viewWillAppear:"]
封装Cycript
除了直接使用别人封装好的 Cycript 代码,我们自己也可以封装。类似 mjcript.py 那样,将常用代码封装在一个 .cy 文件中。
exports
参数名固定,用于向外提供接口:
(function(exports) {
exports.变量名 = 变量值;
exports.方法名 = function(参数) {
return 返回值
};
})(exports);
调用方式:
文件名.方法名(参数)
文件名.变量名
例如编写以下代码,保存为 test.cy
,并将此文件放在 /usr/lib/cycript0.9
目录下。
(function(exports) {
exports.age = 100;
exports.sum = function(a, b) {
return a + b
};
})(exports);
测试:
cy# @import test
{sum:function (t,e){return t+e}}
cy# test.sum(10, 20)
30
cy# test.age
100
如果在定义变量和函数的时候,不使用 exports,则相当于是定义全局的变量和函数。全局则需要注意标识符命名唯一,防止多个 .cy 文件中的标识符名称冲突,我这里使用了 test_ 前缀。
(function(exports) {
test_age = 100;
test_sum = function(a, b) {
return a + b
};
})(exports);
测试:
cy# @import test
{}
cy# test_sum(10, 20)
30
cy# test_age
100