運(yùn)行時(shí)的文章一直被同學(xué)們熱炒枕扫,當(dāng)然現(xiàn)在面試中也都喜歡問道姨涡,當(dāng)大伙說的頭頭是道時(shí)候福澡,可到真正的項(xiàng)目中幾乎局限只會(huì)關(guān)聯(lián)對(duì)象或者M(jìn)ethodSwizzling奉為神劍到處揮砍碉钠,開發(fā)畢竟不能紙上談兵吱肌,實(shí)踐出真知痘拆,介紹目前在項(xiàng)目中runtime的具體使用,真切希望和各位同學(xué)探討氮墨。
(個(gè)人的使用)
在這里我還是要推薦下我自己建的iOS開發(fā)學(xué)習(xí)群:680565220纺蛆,群里都是學(xué)ios開發(fā)的,如果你正在學(xué)習(xí)ios 勇边,小編歡迎你加入犹撒,今天分享的這個(gè)案例已經(jīng)上傳到群文件,大家都是軟件開發(fā)黨粒褒,不定期分享干貨(只有iOS軟件開發(fā)相關(guān)的)识颊,包括我自己整理的一份2018最新的iOS進(jìn)階資料和高級(jí)開發(fā)教程
使用 runtime 為 UIButton 分類修改響應(yīng)位置 使得點(diǎn)擊范圍擴(kuò)大https://github.com/stevendinggang/UIButton-HitRect
使用 runtime 為 UIView 添加一個(gè)點(diǎn)擊手勢(shì),使得所有的繼承 UIView 的控件輕松添加點(diǎn)擊效果
https://github.com/stevendinggang/UIView-tap-runtime
1 關(guān)聯(lián)對(duì)象(AssociatedObject )
Catagory主要為已經(jīng)存在的類(主要是系統(tǒng)類)擴(kuò)展新的方法,關(guān)聯(lián)對(duì)象是runtime在開發(fā)中應(yīng)用的最廣泛,其主要用于為Catagory的對(duì)象增加屬性奕坟。
AFNetworking的關(guān)聯(lián)對(duì)象的
Masony的關(guān)聯(lián)的對(duì)象
關(guān)于分類的介紹可以查看美團(tuán)技術(shù)團(tuán)隊(duì)寫的?深入理解Objective-C:Category
1.1 為什么catagory 無法設(shè)置屬性
structobjc_category{char*category_name ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?OBJC2_UNAVAILABLE;char*class_name ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;structobjc_method_list*instance_methods ? ? ? ? ? ? ? ?OBJC2_UNAVAILABLE;structobjc_method_list*class_methods ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;structobjc_protocol_list*protocols ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;} ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?OBJC2_UNAVAILABLE;
分類中可以添加實(shí)例方法祥款,類方法,甚至可以實(shí)現(xiàn)協(xié)議月杉,添加屬性刃跛,不可以添加成員變量。主要因?yàn)榉椒ǘx都在objc_class中管理的苛萎,不管如何增刪方法桨昙,都不影響類實(shí)例的內(nèi)存布局检号,創(chuàng)建一個(gè)對(duì)象必然會(huì)分配一塊內(nèi)存區(qū)域,包含了isa指針和所有的成員變量蛙酪。假如允許動(dòng)態(tài)修改類成員變量布局齐苛,已經(jīng)創(chuàng)建出的類實(shí)例就不符合類定義了,變成了無效對(duì)象桂塞。
1.2 相關(guān)函數(shù)
//為一個(gè)實(shí)例對(duì)象添加一個(gè)關(guān)聯(lián)對(duì)象凹蜂,由于是C函數(shù)只能使用C字符串,這個(gè)key就是關(guān)聯(lián)對(duì)象的名稱阁危,value為具體的關(guān)聯(lián)對(duì)象的值玛痊,policy為關(guān)聯(lián)對(duì)象策略,與我們自定義屬性時(shí)設(shè)置的修飾符類似voidobjc_setAssociatedObject(idobject,constvoid*key, idvalue, objc_AssociationPolicy policy);//通過key和實(shí)例對(duì)象獲取關(guān)聯(lián)對(duì)象的值idobjc_getAssociatedObject(idobject,constvoid*key);//刪除實(shí)例對(duì)象的關(guān)聯(lián)對(duì)象voidobjc_removeAssociatedObjects(idobject);
(1)key值
關(guān)于前兩個(gè)函數(shù)中的 key 值是我們需要重點(diǎn)關(guān)注的一個(gè)點(diǎn)狂打,這個(gè) key 值必須保證是一個(gè)對(duì)象級(jí)別(為什么是對(duì)象級(jí)別擂煞?看完下面的章節(jié)你就會(huì)明白了)的唯一常量。一般來說趴乡,有以下三種推薦的 key 值:
聲明 static char kAssociatedObjectKey; 颈娜,使用 &kAssociatedObjectKey 作為 key 值;
聲明 static void *kAssociatedObjectKey = &kAssociatedObjectKey; ,使用 kAssociatedObjectKey 作為 key 值浙宜;
用 selector 官辽,使用 getter 方法的名稱作為 key 值。
static char kAssociatedObjectKey;
objc_getAssociatedObject(self,&kAssociatedObjectKey);
但是還有更簡(jiǎn)單的方法, 可以使用selector:
objc_getAssociatedObject(self,@selector(associatedObject));
或者直接使用_cmd: _cmd在Objective-C的方法中表示當(dāng)前方法的selector, 正如同self表示調(diào)用當(dāng)前方法的對(duì)象(類)一樣.
objc_getAssociatedObject(self, _cmd);
(2) 關(guān)聯(lián)規(guī)則 objc_AssociationPolicy policy和property使用的修飾符神似粟瞬,具體含義也與property修飾符相同同仆。
image.png
objc_AssociationPolicymodifier
OBJC_ASSOCIATION_ASSIGNassign
OBJC_ASSOCIATION_RETAIN_NONATOMICnonatomic, strong
OBJC_ASSOCIATION_COPY_NONATOMICnonatomic, copy
OBJC_ASSOCIATION_RETAINatomic, strong
OBJC_ASSOCIATION_COPYatomic, copy
(3)objc_removeAssociatedObjects函數(shù)實(shí)際運(yùn)用很少,它會(huì)移除一個(gè)對(duì)象的所有關(guān)聯(lián)對(duì)象裙品,將該對(duì)象恢復(fù)成“原始”狀態(tài)俗批。這樣做就很有可能把別人添加的關(guān)聯(lián)對(duì)象也一并移除,這并不是我們所希望的市怎。所以一般的做法是通過給 objc_setAssociatedObject 函數(shù)傳入 nil 來移除某個(gè)已有的關(guān)聯(lián)對(duì)象岁忘。
1.4 category關(guān)聯(lián)對(duì)象的大體原理
isa 結(jié)構(gòu)體中的標(biāo)記位 has_assoc 標(biāo)記為 true,表示當(dāng)前對(duì)象有關(guān)聯(lián)對(duì)象区匠,關(guān)聯(lián)對(duì)象并不是成員變量干像,關(guān)聯(lián)對(duì)象是由一個(gè)全局哈希表存儲(chǔ)的鍵值對(duì)中的值。
2 對(duì)象關(guān)系映射(ORM)
通過逆向APP會(huì)發(fā)現(xiàn)目前對(duì)象轉(zhuǎn)模型這塊目前主要用的是MJExtension和YYModel,老項(xiàng)目一般是MJExtension驰弄,新崛起的項(xiàng)目轉(zhuǎn)到了YYModel上麻汰。利用runtime 我們可以實(shí)現(xiàn)json數(shù)據(jù)的直接轉(zhuǎn)換成對(duì)象模型,或者把模型通過映射拼接成晦澀的sql語句戚篙,間接實(shí)現(xiàn)了對(duì)象存儲(chǔ)到sqlite數(shù)據(jù)庫(kù)
MJExtension
YYModel中的YYClassInfo
其中ORM主要涉及到一下方法:
獲取屬性列表
objc_property_t *propertyList = class_copyPropertyList([selfclass], &count);for(unsignedinti=0; i%@", [NSStringstringWithUTF8String:propertyName]);}
獲取成員變量列表
Ivar *ivarList = class_copyIvarList([selfclass], &count);for(unsignedinti; i%@", [NSStringstringWithUTF8String:ivarName]);}
獲取方法列表
unsigned int count;Method *methodList = class_copyMethodList([self class],&count);for (inti =0; i < count; i++) {Method method = methodList[i];DebugLog(@"------getRunTimeMethodList: %@",NSStringFromSelector(method_getName(method)));}
3 熱修復(fù)(HotfixPatch)
蘋果審核一直被開發(fā)者吐槽的五鲫,一是蘋果審核的嚴(yán)格,各種理由反反復(fù)復(fù)被打回去欲哭無淚岔擂,二是審核周期長(zhǎng)位喂,在2017年之前蘋果審核的周期一般都在三天浪耘,如果是新應(yīng)用甚至需要一周以上,如果碰上圣誕節(jié)蘋果放假我們這邊是一般都不會(huì)提交審核塑崖,于是JSPatch 為代表的熱修復(fù)技術(shù)被開發(fā)者推崇点待,通過逆向中國(guó)市面上有頭有臉的iOS應(yīng)用,我發(fā)現(xiàn)幾乎都使用JSPath或者JSPath的變種弃舒。以至于蘋果發(fā)郵件禁止使用熱修復(fù)時(shí) 整個(gè)JSPath的Issues被炸鍋了。熱修復(fù)主要做的是替換現(xiàn)有的方法状原,或者增加新方法聋呢,需要對(duì)消息發(fā)送和轉(zhuǎn)發(fā)有一定的理解。
3.1 消息轉(zhuǎn)發(fā)_objc_msgForward
-[*** ***]:unrecognized selector sent to instance 0x*****
這個(gè)是ios開發(fā)中最常見的crash,當(dāng)前對(duì)象找不到這個(gè)方法颠区,實(shí)際上蘋果 調(diào)用doesNotRecognizeSelector方法的時(shí)候削锰,是給了我們?nèi)螜C(jī)會(huì)的。就是我們常說的消息轉(zhuǎn)發(fā)毕莱,
舉一個(gè)栗子器贩,我在工作中項(xiàng)目出現(xiàn)了差錯(cuò),本著挽救同志的目的朋截,領(lǐng)導(dǎo)讓我立即馬上提供一次挽回的方法蛹稍,如果我給力這個(gè)危機(jī)到此沒了,但是我跪了搞不定,領(lǐng)導(dǎo)就問誰可以解決部服,這是老王站了出來唆姐,如果老王接盤搞定了這個(gè)危機(jī)那也沒事了,但是老王也沒有解決 領(lǐng)導(dǎo)就會(huì)找小李啊或者小張?zhí)幚砝耍绻蠹叶汲聊瑹o法解決奉芦, 那就項(xiàng)目徹底破產(chǎn)啦。oc中消息轉(zhuǎn)發(fā)差不多就是這樣的剧蹂。
+(BOOL)resolveInstanceMethod:(SEL)sel// 實(shí)例方法+(BOOL)resolveClassMethod:(SEL)sel// 類方法
第一次機(jī)會(huì)允許用戶在此時(shí)為該Class動(dòng)態(tài)添加實(shí)現(xiàn)声功。如果有實(shí)現(xiàn)了,則調(diào)用并返回宠叼。
BOOL class_addMethod(Classcls, SELname, IMP imp,constchar *types);
如果仍沒實(shí)現(xiàn)先巴,繼續(xù)下面的動(dòng)作。
-(id)forwardingTargetForSelector:(SEL)aSelector
調(diào)用forwardingTargetForSelector:方法冒冬,嘗試找到一個(gè)能響應(yīng)該消息的對(duì)象筹裕。如果獲取到,則直接轉(zhuǎn)發(fā)給它窄驹。如果返回了nil朝卒,繼續(xù)下面的動(dòng)作。
-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector-(void)forwardInvocation:(NSInvocation*)anInvocation
通過 -methodSignatureForSelector: 消息獲得函數(shù)的參數(shù)和返回值類型乐埠。如果 -methodSignatureForSelector: 返回 nil 抗斤,Runtime 則會(huì)發(fā)出 -doesNotRecognizeSelector: 消息囚企,程序這時(shí)也就掛掉了。如果返回了一個(gè)函數(shù)簽名瑞眼,Runtime 就會(huì)創(chuàng)建一個(gè) NSInvocation 對(duì)象并發(fā)送 -forwardInvocation: 消息給目標(biāo)對(duì)象龙宏。
NSInvocation 是一個(gè)消息體的封裝,包括selector 以及參數(shù)等信息伤疙。因此JSPatch通過NSInvocation來創(chuàng)建消息
JSPatch
NSInvocation可以實(shí)現(xiàn)傳遞多個(gè)參數(shù)银酗。
- (void)viewDidLoad { ? ?[superviewDidLoad];//創(chuàng)建一個(gè)函數(shù)簽名,這個(gè)簽名可以是任意的,但需要注意徒像,簽名函數(shù)的參數(shù)數(shù)量要和調(diào)用的一致黍特。NSMethodSignature* signature = [[selfclass] instanceMethodSignatureForSelector:@selector(testFun:argb:)];NSLog(@"參數(shù)個(gè)數(shù)%lu---返回參數(shù)類型%s",signature.numberOfArguments,signature.methodReturnType);//通過簽名初始化NSInvocation* invocatin = [NSInvocationinvocationWithMethodSignature:signature];NSString*argumentOne =@"First";NSString*argumentTwo =@"Two";//atIndex的下標(biāo)必須從2開始。原因?yàn)椋? 1 兩個(gè)參數(shù)已經(jīng)被target和selector占用[invocatin setArgument:&argumentOne atIndex:2]; ? ?[invocatin setArgument:&argumentTwo atIndex:3]; ? ?[invocatin setTarget:self];//設(shè)置target[invocatin setSelector:@selector(testFun:argb:)];//設(shè)置selecteor[invocatin invoke];//消息調(diào)用}-(NSString*)testFun:(NSString*)argc argb:(NSString*)argb{//實(shí)現(xiàn) [argc stringByAppendingString:argb];NSString* string = argc;NSString* aString;NSString* stringToAppend = argb;NSInvocation* inv = [NSInvocationinvocationWithMethodSignature:[NSStringinstanceMethodSignatureForSelector:@selector(stringByAppendingString:)]]; ? ?[inv setTarget: string]; ? ?[inv setSelector:@selector(stringByAppendingString:)]; ? ?[inv setArgument:&stringToAppend atIndex:2]; ? ?[inv retainArguments]; ? ?[inv invoke];// 獲取返回值[inv getReturnValue:&aString];returnaString;}
4 私有變量的修改
主要是利用class_copyIvarList獲取當(dāng)前類的所有屬性锯蛀,主要為了獲取私有變量然后利用KVC修改對(duì)象的屬性灭衷。
通過打印UITextField的屬性,獲取到變量名稱為_placeholderLabel旁涤,可以修改placeholder字體顏色翔曲。
unsignedintoutCount =0; ? Ivar *ivars ?= class_copyIvarList([UITextFieldclass], &outCount);for(NSIntegeri =0; i < outCount; ++i) {// 遍歷取出該類成員變量Ivar ivar = *(ivars + i);NSLog(@"\n name = %s ?\n type = %s", ivar_getName(ivar),ivar_getTypeEncoding(ivar)); ? ?}
KVC 修改屬性值
[_textView setValue: [UIColor redColor]forKeyPath:@"_placeholderLabel.textColor"];
一般上面寫法用的很少,盡快替換了方法還是有很多坑等著
一般我們的用法直接KVC 替換系統(tǒng)原有的變量
UITextField*textFiled = [[UITextFieldalloc] initWithFrame:CGRectMake(20,100,100,50)]; ? ?[self.view addSubview:textFiled];UILabel*placeholderLabel = [UILabelnew]; ? ?placeholderLabel.textColor = [UIColorredColor]; ? ?[placeholderLabel sizeToFit]; ? ?placeholderLabel.text =@"請(qǐng)輸入密碼"; ? ?[textFiled addSubview:placeholderLabel]; ? ?[textFiled setValue:placeholderLabel forKey:@"_placeholderLabel"];
5 面向切面編程(AOP)
主要利用Method Swizzling 在不破話原有的代碼劈愚,將獨(dú)立的功能模塊剝離出來瞳遍,實(shí)現(xiàn)代碼注入。
5.1 Method Swizzling
+ (BOOL)swizzleInstanceMethod:(SEL)originalSel with:(SEL)newSel { ? ?Method originalMethod = class_getInstanceMethod(self, originalSel); ? ?Method newMethod = class_getInstanceMethod(self, newSel);if(!originalMethod || !newMethod)returnNO; ? ?class_addMethod(self, ? ? ? ? ? ? ? ? ? ?originalSel, ? ? ? ? ? ? ? ? ? ?class_getMethodImplementation(self, originalSel), ? ? ? ? ? ? ? ? ? ?method_getTypeEncoding(originalMethod)); ? ?class_addMethod(self, ? ? ? ? ? ? ? ? ? ?newSel, ? ? ? ? ? ? ? ? ? ?class_getMethodImplementation(self, newSel), ? ? ? ? ? ? ? ? ? ?method_getTypeEncoding(newMethod)); ? ?method_exchangeImplementations(class_getInstanceMethod(self, originalSel), ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? class_getInstanceMethod(self, newSel));returnYES;}+ (BOOL)swizzleClassMethod:(SEL)originalSel with:(SEL)newSel { ? ?Classclass= object_getClass(self); ? ?Method originalMethod = class_getInstanceMethod(class, originalSel); ? ?Method newMethod = class_getInstanceMethod(class, newSel);if(!originalMethod || !newMethod)returnNO; ? ?method_exchangeImplementations(originalMethod, newMethod);returnYES;}
最重要的是需要理解selector, method, implementation 三者之間關(guān)系:在運(yùn)行時(shí)菌羽,類(Class)維護(hù)了一個(gè)消息分發(fā)列表來解決消息的正確發(fā)送傅蹂。每一個(gè)消息列表的入口是一個(gè)方法(Method),這個(gè)方法映射了一對(duì)鍵值對(duì)算凿,其中鍵值是這個(gè)方法的名字 selector(SEL)份蝴,值是指向這個(gè)方法實(shí)現(xiàn)的函數(shù)指針 implementation(IMP)。 Method swizzling 修改了類的消息分發(fā)列表使得已經(jīng)存在的 selector 映射了另一個(gè)實(shí)現(xiàn) implementation氓轰,同時(shí)重命名了原生方法的實(shí)現(xiàn)為一個(gè)新的 selector婚夫。
NSPipster的Method Swizzling
Method Swizzling需要注意的是:
(1)應(yīng)該總在+load中執(zhí)行,+load會(huì)在類初始加載時(shí)調(diào)用,和+initialize比較+load能保證在類的初始化過程中被加載署鸡。
(2) dispatch_once中執(zhí)行:swizzling會(huì)改變?nèi)譅顟B(tài)案糙,所以在運(yùn)行時(shí)采取一些預(yù)防措施,使用dispatch_once就能夠確保代碼不管有多少線程都只被執(zhí)行一次靴庆。這將成為method swizzling的最佳實(shí)踐时捌。
5.2日志打印 快速熟悉項(xiàng)目。
程序猿是跳槽率偏高的職業(yè)炉抒,如果去新公司做新項(xiàng)目還好說奢讨,一旦需要接手老項(xiàng)目的維護(hù),商業(yè)項(xiàng)目可不是我們平常寫的Demo的代碼量,那代碼中的邏輯結(jié)構(gòu)瞬間會(huì)讓新入職的小伙伴們懵逼焰薄,通過通過攔截點(diǎn)擊事件拿诸,可以快速的熟悉代碼的邏輯扒袖。
image.png
5.3處理通用邏輯
比如在一些界面我們需要用戶登錄才能查看,最笨的辦法實(shí)在實(shí)在需要的ViewController 添加判斷登錄的邏輯亩码。下面這張截圖是從Github的找到的利用AOP處理用戶登錄的代碼季率,當(dāng)然這個(gè)用繼承基礎(chǔ)類去寫也是不錯(cuò)的,暫且不要在意寫法的好壞 最起碼我們程序開發(fā)提供了新的思路描沟。
處理用戶登錄
5.4Crash的防范
OC中容器類在空值nil 和數(shù)組越界都會(huì)直接導(dǎo)致我們app 的crash 我們一種處理方式是利用Category增加新方法中判斷值是否為空或者越界飒泻,對(duì)于新工程我們使用大家約定使用容器的category還好說,
- (id)objectOrNilAtIndex:(NSUInteger)index {returnindex
但是對(duì)于老項(xiàng)目 我們難道需要修改所有的容器類方法吏廉?我們可以使用切面來修改泞遗。
+(void)load{staticdispatch_once_t onceToken; ? ?dispatch_once(&onceToken, ^{ ? ? ? ?Class __NSPlaceholderArray = NSClassFromString(@"__NSPlaceholderArray"); ? ? ? ?[NSArrayswizzleInstance:__NSPlaceholderArrayorigMethod:@selector(initWithObjects:count:)withMethod:@selector(RBSafe_initWithObjects:count:)]; ? ? ? ?Class __NSArray = NSClassFromString(@"NSArray"); ? ? ? ?Class __NSArrayI = NSClassFromString(@"__NSArrayI");//數(shù)組有內(nèi)容obj類型才是__NSArrayIClass __NSSingleObjectArrayI = NSClassFromString(@"__NSSingleObjectArrayI");//iOS10 以上,單個(gè)內(nèi)容類型是__NSArraySingleObjectIClass __NSArray0 = NSClassFromString(@"__NSArray0");//iOS9 以上迟蜜,沒內(nèi)容類型是__NSArray0[NSArrayswizzleInstance:__NSArrayorigMethod:@selector(subarrayWithRange:)withMethod:@selector(RBSafe_subarrayWithRange:)]; ? ? ? ?[NSArrayswizzleInstance:__NSArrayorigMethod:@selector(objectsAtIndexes:)withMethod:@selector(RBSafe_objectsAtIndexes:)]; ? ? ? ?[NSArrayswizzleInstance:__NSArrayIorigMethod:@selector(objectAtIndex:)withMethod:@selector(RBSafe_NSArrayIobjectAtIndex:)]; ? ? ? ?[NSArrayswizzleInstance:__NSSingleObjectArrayIorigMethod:@selector(objectAtIndex:)withMethod:@selector(RBSafe_NSSingleObjectArrayIobjectAtIndex:)]; ? ? ? ?[NSArrayswizzleInstance:__NSArray0origMethod:@selector(objectAtIndex:)withMethod:@selector(RBSafe_NSArray0ObjectAtIndex:)]; ? ?});}
當(dāng)然這種用法 我個(gè)人是持中立態(tài)度的,因?yàn)榭梢运查g把我們代碼所犯的錯(cuò)誤處理的風(fēng)平浪靜啡省,但是讓我有一種掩耳盜鈴的感覺娜睛,我們的問題和錯(cuò)誤根源還在的,不斷的錯(cuò)誤疊加只會(huì)讓我們代碼變得危機(jī)重重卦睹,同時(shí)AOP的crash處理是無痛無感知的畦戒,一旦我們運(yùn)用在第三方的靜態(tài)庫(kù)實(shí)際上我們就會(huì)侵入被人工程的代碼批钠,被人的代碼被篡改都不知情的棕兼,這個(gè)需要謹(jǐn)慎使用。
6 逆向開發(fā)
逆向開發(fā)主要集中在iOS越獄方面泪掀,逆向開發(fā)可以讓我們?cè)趇OS開發(fā)中打開另一扇門徐鹤,對(duì)于大部門開發(fā)者來說很少接觸這個(gè)領(lǐng)域垃环,我也是在工作中才接觸到iOS的越獄,逆向開發(fā)的基礎(chǔ)就是利用Method Swizzling返敬,不管是現(xiàn)在熱門的THEOS還是iOSOpenDev都是Method Swizzling的封裝遂庄,點(diǎn)擊iOSOpenDev使用的CaptainHook就可以看到都是Method Swizzling 各種方法。
#import#import#define CYCRIPT_PORT 8888CHDeclareClass(AppDelegate);CHDeclareClass(UIApplication);CHOptimizedMethod2(self,void, AppDelegate, application,UIApplication*, application, didFinishLaunchingWithOptions,NSDictionary*, options){ ? ?CHSuper2(AppDelegate, application, application, didFinishLaunchingWithOptions, options);NSLog(@"## Start Cycript ##"); ? ?CYListenServer(CYCRIPT_PORT);}__attribute__((constructor))staticvoidentry() { ? ?CHLoadLateClass(AppDelegate); ? ?CHHook2(AppDelegate, application, didFinishLaunchingWithOptions);}
使用 runtime 為 UIButton 分類修改響應(yīng)位置 使得點(diǎn)擊范圍擴(kuò)大https://github.com/stevendinggang/UIButton-HitRect
使用 runtime 為 UIView 添加一個(gè)點(diǎn)擊手勢(shì),使得所有的繼承 UIView 的控件輕松添加點(diǎn)擊效果
https://github.com/stevendinggang/UIView-tap-runtime