自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

iOS面試題·項(xiàng)目中用過(guò) Runtime 嗎?

移動(dòng)開(kāi)發(fā)
我們知道靜態(tài)語(yǔ)言在編譯時(shí)期,就已經(jīng)確定了函數(shù)的具體調(diào)用,而動(dòng)態(tài)語(yǔ)言要等到運(yùn)行時(shí)期才能真正確定調(diào)用哪個(gè)函數(shù); Objective-C 是一門(mén)動(dòng)態(tài)語(yǔ)言,它是通過(guò) Runtime 這個(gè)運(yùn)行時(shí)機(jī)制來(lái)實(shí)現(xiàn)的。

前言

我們知道靜態(tài)語(yǔ)言在編譯時(shí)期,就已經(jīng)確定了函數(shù)的具體調(diào)用,而動(dòng)態(tài)語(yǔ)言要等到運(yùn)行時(shí)期才能真正確定調(diào)用哪個(gè)函數(shù); Objective-C 是一門(mén)動(dòng)態(tài)語(yǔ)言,它是通過(guò) Runtime 這個(gè)運(yùn)行時(shí)機(jī)制來(lái)實(shí)現(xiàn)的。

[[248751]]

雖然說(shuō) Runtime 是相對(duì)于底層的機(jī)制,但是在項(xiàng)目過(guò)程中也經(jīng)常用來(lái)解決一些問(wèn)題。下面我們就來(lái)看看利用 Runtime 可以解決項(xiàng)目中什么問(wèn)題。

項(xiàng)目中用 Runtime 實(shí)現(xiàn)的功能

利用關(guān)聯(lián)對(duì)象為分類增加偽屬性

在項(xiàng)目的開(kāi)發(fā)中,經(jīng)常會(huì)遇到要為已經(jīng)存在的類添加屬性。面對(duì)這種情況,我們一般都是創(chuàng)建一個(gè)分類,來(lái)實(shí)現(xiàn)為已有的類增加屬性,但是由于分類結(jié)構(gòu)的特殊性,在分類添加屬性,并不會(huì)為我們自動(dòng)創(chuàng)建實(shí)例變量和存儲(chǔ)方法。

首先我們要知道,常規(guī)定義一個(gè) @property,其實(shí)編譯器會(huì)為我們做三件事情:

  • 生成實(shí)例變量 _property
  • 生成 getter 方法
  • 生成 setter 方法

但是,在分類中并不會(huì)幫我們?nèi)ド蓪?shí)例變量和存取方法,所以我們需要自己去實(shí)現(xiàn)存取方法,這里我們會(huì)通過(guò)關(guān)聯(lián)對(duì)象去將鍵值關(guān)聯(lián)到對(duì)象上面去,以下是代碼示例: 

  1. @property (nonatomic, strong) NSString *title; 
  2.  
  3. - (NSString *)title { 
  4.     return objc_getAssociatedObject(self, _cmd); 
  5.  
  6. - (void)setTitle:(NSString *)title { 
  7.     objc_setAssociatedObject(self, @selector(title), title, OBJC_ASSOCIATION_RETAIN); 

這個(gè)我們暫時(shí)只講如何通過(guò)關(guān)聯(lián)對(duì)象為分類增加偽屬性,至于分類為什么不會(huì)為我們自動(dòng)添加實(shí)例變量和存取方法,以及關(guān)聯(lián)對(duì)象的實(shí)現(xiàn)原理等,我們會(huì)在后面的面試題繼續(xù)涉及到這一話題。

利用 Method Swizzling 交換方法

我們可以用 Method Swizzling 來(lái)交換兩個(gè)方法的實(shí)現(xiàn),以便達(dá)到 Hook 的效果;例如交換 ViewController 生命周期方法來(lái)實(shí)現(xiàn)頁(yè)面埋點(diǎn),或者在不影響原有的功能增加一些特殊的功能。

交換方法主要是利用到 Runtime 中的class_addMethod 、class_replaceMethod、method_exchangeImplementations 方法來(lái)實(shí)現(xiàn)的,以下是 Method Swizzling 代碼示例: 

  1. /** 
  2.  交換方法 
  3.  */ 
  4. + (void)pxy_swizzleMethodWithOriginalSelector:(SEL)originalSelector swizzledSelector:(SEL)swizzledSelector { 
  5.     Class class = [self class]; 
  6.  
  7.     SEL originalSeletor = originalSelector; 
  8.     SEL swizzledSeletor = swizzledSelector; 
  9.  
  10.     Method originMethod = class_getInstanceMethod(class, originalSeletor); 
  11.     Method swizzledMethod = class_getInstanceMethod(class, swizzledSeletor); 
  12.  
  13.     //先嘗試給源SEL添加IMP,這里是為了避免源SEL沒(méi)有實(shí)現(xiàn)IMP的情況 
  14.     BOOL didAddMethod = class_addMethod(class, originalSeletor, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod)); 
  15.     if (didAddMethod) { 
  16.         //添加成功:說(shuō)明源SEL沒(méi)有實(shí)現(xiàn)IMP,將源SEL的IMP替換到交換SEL的IMP 
  17.         class_replaceMethod(class, swizzledSeletor, method_getImplementation(originMethod), method_getTypeEncoding(originMethod)); 
  18.     } else { 
  19.         //添加失?。赫f(shuō)明源SEL已經(jīng)有IMP,直接將兩個(gè)SEL的IMP交換即可 
  20.         method_exchangeImplementations(originMethod, swizzledMethod); 
  21.     } 

利用 class_copyIvarList 實(shí)現(xiàn) NSCoding 的自動(dòng)歸檔解檔

在利用 NSKeyedArchiver 歸檔解檔對(duì)象的時(shí)候,對(duì)象 Model 需要實(shí)現(xiàn) NSCoding 協(xié)議,并且要實(shí)現(xiàn) encodeWithCoder、initWithCoder 兩個(gè)方法,在這兩個(gè)方法中要為每個(gè)屬性進(jìn)行 code 和 encode,不然就會(huì) crash。

在項(xiàng)目開(kāi)發(fā)過(guò)程中,經(jīng)常會(huì)出現(xiàn) Model 中的屬性會(huì)變更,這個(gè)時(shí)候總是會(huì)忘記去修改對(duì)應(yīng)的屬性 code 和 encode,這里就會(huì)導(dǎo)致 crash;為了避免這個(gè)現(xiàn)象和讓 Model 中的方法更加簡(jiǎn)潔可控,這里我們會(huì)利用 class_copyIvarList 來(lái)獲取對(duì)象中的成員變量列表,然后利用 KVC 來(lái) code 和 encode。實(shí)例代碼如下:(這里我們將這個(gè)通用的代碼抽象成宏,這樣子在需要的 Model 中直接調(diào)用就可以了) 

  1. #define PXYNSCodingRuntime_EncodeWithCoder(Class) \ 
  2. unsigned int outCount = 0;\ 
  3. Ivar *ivars = class_copyIvarList([Class class], &outCount);\ 
  4. for (int i = 0; i < outCount; i++) {\ 
  5.     Ivar ivar = ivars[i];\ 
  6.     NSString *key = [NSString stringWithUTF8String:ivar_getName(ivar)];\ 
  7.     [aCoder encodeObject:[self valueForKey:key] forKey:key];\ 
  8. }\ 
  9. free(ivars);\ 
  10.  
  11. #define PXYNSCodingRuntime_InitWithCoder(Class)\ 
  12. if (self = [super init]) {\ 
  13.     unsigned int outCount = 0;\ 
  14.     Ivar *ivars = class_copyIvarList([Class class], &outCount);\ 
  15.     for (int i = 0; i < outCount; i++) {\ 
  16.         Ivar ivar = ivars[i];\ 
  17.         NSString *key = [NSString stringWithUTF8String:ivar_getName(ivar)];\ 
  18.         id value = [aDecoder decodeObjectForKey:key];\ 
  19.         if (value) {\ 
  20.             [self setValue:value forKey:key];\ 
  21.         }\ 
  22.     }\ 
  23.     free(ivars);\ 
  24. }\ 
  25. return self;\ 
  26.  
  27. // 對(duì)應(yīng)調(diào)用 
  28. - (void)encodeWithCoder:(NSCoder *)aCoder { 
  29.     PXYNSCodingRuntime_EncodeWithCoder(Father) 
  30. - (nullable instancetype)initWithCoder:(NSCoder *)aDecoder { 
  31.     PXYNSCodingRuntime_InitWithCoder(Father) 

利用 objc_allocateClassPair、object_setClass 等 API 來(lái)實(shí)現(xiàn) KVO Block

在項(xiàng)目中,會(huì)經(jīng)常使用 KVO 來(lái)監(jiān)聽(tīng)某個(gè)屬性的變化。先給出系統(tǒng)調(diào)用的方式,添加監(jiān)聽(tīng)后,在 observeValueForKeyPath 方法中處理變化: 

  1. - (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context; 
  2.  
  3.  - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context { 
  4.  NSLog(@"%@ 對(duì)象的 %@ 屬性改變了:%@",object,keyPath,change); 
  5.  } 

但是在開(kāi)發(fā)過(guò)程中,有時(shí)候想將代碼增加內(nèi)聚性和在 observeValueForKeyPath 減少判斷,我們可以通過(guò) Runtime 來(lái)實(shí)現(xiàn)一個(gè) KVO Block,這樣調(diào)用地方即處理消息的地方,代碼上比較直觀,簡(jiǎn)單 API 如下: 

  1. typedef void(^PXYKVOCompleteBlock)(id observer, NSString *keyPath, id oldValue, id newValue); 
  2.  
  3. /** 
  4.  添加 KVO Block 
  5.  */ 
  6. - (void)pxy_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath completeBlock:(PXYKVOCompleteBlock)completeBlock; 
  7.  
  8. /** 
  9.  移除 KVO Block 
  10.  */ 
  11. - (void)pxy_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath; 

KVO 主要是動(dòng)態(tài)派生出一個(gè)中間類,然后在這個(gè)中間類處理相關(guān)通知邏輯,具體代碼可以 Demo 中的 NSObject+PXYKVO 具體實(shí)現(xiàn);

利用消息轉(zhuǎn)發(fā)機(jī)制實(shí)現(xiàn)多播委托(蹦床模式)

首先,在對(duì)象收到無(wú)法處理的消息之后,會(huì)執(zhí)行消息轉(zhuǎn)發(fā),消息轉(zhuǎn)發(fā)有三個(gè)步驟:

  • 調(diào)用 resolveInstanceMethod 方法。動(dòng)態(tài)方法解析,這里會(huì)給類使用 class_addMethod 來(lái)增加方法的機(jī)會(huì)。
  • 調(diào)用 forwardingTargetForSelector 方法,看是否有備用接收者,將消息轉(zhuǎn)發(fā)給備用接收者處理。
  • 調(diào)用 methodSignatureForSelector 和 forwardInvocation 方法,進(jìn)行完成的消息轉(zhuǎn)發(fā)。

如果經(jīng)過(guò)上面三個(gè)步驟,還不能正確處理消息,程序就會(huì)走 doesNotRecognizeSelector 方法,crash 掉。

蹦床模式:就是把一條消息 “反彈” 到另外一個(gè)對(duì)象,蹦床一般使用 forwardInvocation 來(lái)實(shí)現(xiàn)。

在項(xiàng)目開(kāi)發(fā)中,事件回調(diào)一般使用:Block、Delegate、NSNotificationCenter;但是在多個(gè)模塊需要監(jiān)聽(tīng)一個(gè)事件的場(chǎng)景:使用通知會(huì)將項(xiàng)目變得不可控,因?yàn)槿魏我粋€(gè)地方都可以監(jiān)聽(tīng)這個(gè)通知,在排查問(wèn)題的時(shí)候就會(huì)變得異常困難,這個(gè)時(shí)候我們可以使用多播委托,實(shí)現(xiàn)一對(duì)多回調(diào)。

大致原理:實(shí)現(xiàn)一個(gè)管理類,將需要回調(diào)的對(duì)象注冊(cè)進(jìn)來(lái),然后將事件消息發(fā)送給這個(gè)管理類,由于這個(gè)管理類是沒(méi)有實(shí)現(xiàn)委托方法的,就不能正常處理這個(gè)消息,這個(gè)時(shí)候就會(huì)走消息轉(zhuǎn)發(fā)流程;然后我們通過(guò)消息轉(zhuǎn)發(fā)流程,將消息轉(zhuǎn)發(fā)到注冊(cè)進(jìn)來(lái)的對(duì)象中去,這樣子就要可以實(shí)現(xiàn)我們的多播委托了。

具體代碼可以看 Demo 中的 PXYMulticastDelegate 多播委托實(shí)現(xiàn)類。

總結(jié)

Objective-C 利用 Runtime 運(yùn)行時(shí)變成一門(mén)動(dòng)態(tài)語(yǔ)言,在開(kāi)發(fā)過(guò)程中,使用 Runtime 相關(guān) API 可以實(shí)現(xiàn)一些很強(qiáng)大的功能,這里我們簡(jiǎn)單講到使用 Runtime 完成為分類增加偽屬性、利用 Method SWizzling 來(lái) Hook 方法、實(shí)現(xiàn) NSCoding 自動(dòng)歸檔解檔、實(shí)現(xiàn) KVO Block、多播委托。

當(dāng)然還可以實(shí)現(xiàn)更多的功能,比如字典模型之間的轉(zhuǎn)換、頁(yè)面無(wú)侵入埋點(diǎn)、監(jiān)聽(tīng) App 網(wǎng)絡(luò)流量等等。

還有可以實(shí)現(xiàn)什么好玩的功能,歡迎留言,感激不盡。

責(zé)任編輯:未麗燕 來(lái)源: 簡(jiǎn)書(shū)
相關(guān)推薦

2022-07-12 12:05:22

JavaSemaphore

2016-03-03 10:07:39

ios內(nèi)存管理面試總結(jié)

2020-06-04 14:40:40

面試題Vue前端

2023-11-13 07:37:36

JS面試題線程

2011-03-24 13:27:37

SQL

2021-08-05 05:04:50

熱部署模型字節(jié)

2024-04-01 00:00:00

Redis緩存服務(wù)消息隊(duì)列

2009-06-06 18:34:05

java面試題

2009-06-06 18:36:02

java面試題

2015-09-02 09:32:56

java線程面試

2014-09-19 11:17:48

面試題

2019-11-26 10:30:11

CSS前端面試題

2023-07-28 08:04:56

StringHeaatomic線程

2025-02-26 07:58:41

2018-03-08 18:40:47

Java百度面試題

2013-07-10 10:02:05

iOS面試題Objective-CiOS開(kāi)發(fā)

2023-07-14 08:12:21

計(jì)時(shí)器unsafecontext

2013-01-05 14:51:34

JavaScriptjQuery面試

2024-06-04 14:52:28

2014-07-28 14:00:40

linux面試題
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)