BigBoss有一个分类,称之为Tweaks。网上所说得越狱插件,大多可归类与此。至于tweak这个名称的由来以及含义,由于接触得晚,完全不知其所以然。若有朋友对此熟悉,还请不吝赐教。
Tweak开发,离不开的是MobielSubstrate。按百科上所讲的 ,MobieSubstrate是一个允许第三方开发者在运行时(run-time)替换扩展iOS 上系统方法的框架,主要包含了三个组件:MobileHooker,MobileLoader以及safe mode。MobielSubstrate由Saurik开发。假如不知道Saurik是哪位大神,开句外国式的玩笑,stop reading this!
1、MobileHooker:“钩子”这个名称可谓形象。它的作用就是,在运行时替代Objective-C消息(由于动态特性,Objective-C一般都用“消息”来指代“方法”)的实现。简单来讲,就是系统向某个类发送消息(objc_msgSend)时,会被“钩住”,并用自己的实现方法代替(是否执行系统实现方法以及执行时机可由自己决定)。
代码如下:
|
static IMP original_UIView_setFrame_; |
|
void replaced_UIView_setFrame_(UIView* self, SEL _cmd, CGRect frame) { |
|
CGRect originalFrame = self.frame; |
|
NSLog( "Changing frame of %p from %@ to %@" , self, NSStringFromCGRect(originalFrame), NSStringFromCGRect(frame)); |
|
original_UIView_setFrame_(self, _cmd, frame); |
|
MSHookMessageEx([UIView class ], @selector( setFrame: ), (IMP)replaced_UIView_setFrame_, (IMP*)&original_UIView_setFrame_); |
|
myView.frame = CGRectMake(0, 0, 100, 100); |
当然,最后一句也可以写成
|
original_UIView_setFrame_(self, _cmd, CGRectMake(0, 0, 0, 0)); |
,或者直接注销掉。不过强烈建议不要尝试。只想说的是,替代原有方法绝不是输出一句log那么简单,可以做的事情远超乎想象。
类方法的“钩子”的声明,需要修改如下:
|
MSHookMessageEx(objc_getMetaClass( "UIView" ), @selector(commitAnimations), replaced_UIView_commitAnimations, (IMP*)&original_UIView_commitAnimations); |
2、MobileLoader:“钩子”需要在运行时被加载,靠的就是MobileLoader的功劳。MobileLoader会在适当的时机加载/Library/MobileSubstrate/DynamicLibraries/目录下的动态库(.dylib,这是tweak的最终产品)。当.dylib被加载时,下面的语句会被最先调用:
|
__attribute__((constructor))staticvoid initialize() { } |
打开/Library/MobileSubstrate/DynamicLibraries/,可以看到每一个.dylib都有会一个同名的.plist。.plist的作用就是用来限制.dylib的加载范围的,包括iOS版本和被加载对象的bundleID。
3、safe mode:看了MobileHooker的大概原理,可以猜出这东西是多么不安全。safe mode算是个保险,当SpirngBoard崩溃时会自动进入安全模式,同时仅用所有.dylib的加载。
至于MobilSubstrate的原理,个人觉得和Objective-C的动态特性关系密切。关于这方面,可以参考这里。
假如按照上面的语法来开发Tweak,语句实在发杂繁琐。网上由各种各样的模板,像iOSOpenDev,就内置了两种Tweak模板:CaptainHook Tweak和Logos Tweak。CaptainHook Tweak模板使用了大量宏定义来简化格式,而Logos Tweak则走得更远。初期建议用原生的语法或者CaptainHook Tweak之类的模板。在熟悉了相关的原理和语法之后,强烈建议在实际开发中使用Logos Tweak模板。
使用iOSOpenDev新建一个Logos Tweak工程,目录结构如下:
Package目录是deb包的相关东西,稍微了解deb包结构的人应该很熟悉。打开control,有这么一句:Depends:mobilesubstrate。说明Tweak开发是必须依赖mobilesubstrate这个框架的。
Products目录里面的.dylib就是最终产品了,是一个动态库。
.xm文件是我们敲代码的地方(不是.mm)。按照.xm的提示,先链接libsubstrate.dylib,然后按照Logos Tweak模板格式编写tweak,如下:
|
#import < Foundation/Foundation.h > |
|
#import < UIKit/UIKit.h > |
|
-(void)applicationDidFinishLaunching: (id)application |
|
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@ "Welcome" message:@ "Welcome to your iPhone Brandon!" delegate:self cancelButtonTitle:@ "Thanks" otherButtonTitles:nil]; |
|
- (void)alertView: (UIAlertView *)alertView clickedButtonAtIndex: (NSInteger)buttonIndex |
编译一下,然后打开.mm文件,可以看到Logos Tweak的脚本已经自动将.xm文件中Logos格式的语句转化为我们熟悉的符合mobilesubstrate语法的语句了,使用了类似于CaptainHook的宏定义。每次编译.xm文件都会自动覆盖.mm文件,所以,除了了解一些实现原理外,可基本忽略.mm文件。
而Logos Tweak的这种类似于类定义的语法,看着确实让人舒服,同时也可实实在在地提高开发速度。
%hook Classname:需要“hook”某个类的起始标志
%new:插入新方法到类中
%end:“hook”某个类的结束标志
%orig或者%orig(args):执行系统方法,若有参数可带参数
%c(Class):其实就是objc_getClass()
%log:快速打印类、消息名以及参数到系统log中
%ctor{…}:其实就是
1 |
__attribute__((constructor))staticvoid initialize(){} |
这个入口函数。
还有很多,可以查看这里
前面那段代码其实算是Tweak的HelloWorld吧,其实就是在SpringBoard启动时弹出一个对话框(被注销掉的代码是临时加的,可以尝试去掉看看效果)。将.dylib和.plist拷贝出来复制到/Library/MobileSubstrate/DynamicLibraries/目录下(由于没有打deb包,请确认手机已经安装了MobilSubstrate),重启SpringBoard,假如启动后弹出了一个对话框,说明tweak成功了。Hello world!