iOS Runtime能做什麼?

轉自Anselz的博客

 


之前的文章中我們介紹了Runtime是什麼,屬於理論性介紹,你看了上篇很迫切的想知道Runtime到底能幹什麼?不要着急,這一篇Blog將將講解Runtime怎麼應用到實戰中Runtime官方文檔在這裏,包括了接口名字以及使用說明。下文講到的接口都能在此文檔中找到。
 
1. KVC中setValue中使用
我們知道在KVC中如果直接setValue如果對象沒有這個屬性或者是變量就會直接Crash,如:
  1. RuntimeObj *obj = [[RuntimeObj alloc]init]; 
  2. [obj setValue:@"value4Name" forKey:@"objName"];//RuntimeObj 沒有objName這個屬性 
這段代碼會直接Crash
 
有沒有對這個感覺頭疼,要是能先用某種方式檢查下再set那就不會Crash? 沒錯,這件事情Runtime能做到
 
先看一下示例代碼吧:
  1. -(BOOL)hasAttribute:(NSString *)attName 
  2.     BOOL flag = NO; 
  3.     u_int               count; 
  4.     Ivar *ivars = class_copyIvarList([self class], &count); 
  5.     for (int i = 0; i < count ; i++) 
  6.     { 
  7.         const char* propertyName = ivar_getName(ivars[i]); 
  8.         NSString *strName = [NSString  stringWithCString:propertyName encoding:NSUTF8StringEncoding]; 
  9.         if ([attName isEqualToString:strName]) { 
  10.             flag = YES; 
  11.         } 
  12.         NSLog(@"===%@",strName); 
  13.     } 
  14.     return flag; 
沒錯,這個函數就是能幫你檢查是否有某個屬性或變量,下面講解下一個代碼:
Ivar 原型是typedef struct objc_ivar *Ivar; 
 
class_copyIvarList 返回的是某個類所有屬性或變量原型Ivar *class_copyIvarList(Class cls, unsigned int *outCount) ;
 
ivar_getName 返回的是沒有 Ivar 結構體的名字,即變量的名字 原型 const char *ivar_getName(Ivar v);
 
2. * 與這個對應的還有一個函數class_copyPropertyListclass_copyIvarList 不同點在前者只取屬性(@property申明的屬性) 後者所有的 包括在interface大括號中申明的。
 
class_copyPropertyList使用的示例代碼如下:
  1. objc_property_t*    properties= class_copyPropertyList([self class], &count); 
  2. for (int i = 0; i < count ; i++) 
  3.      const char* propertyName = property_getName(properties[i]); 
  4.      NSString *strName = [NSString  stringWithCString:propertyName encoding:NSUTF8StringEncoding]; 
  5.       NSLog(@"===%@",strName); 
  
兩個不同可以用代碼來演示的,具體代碼自己動手寫 我就不貼出來,看看兩者到底有什麼區別?
 
有了這一步 你還擔心濫用KVC時崩潰了麼?
 
3. 動態創建函數
有時候會根據項目需求動態創建某個函數,沒錯Runtime完全能做到
 
先看代碼:
  1. void dynamicMethod(id self, SEL _cmd) 
  2.     printf("SEL %s did not exist\n",sel_getName(_cmd)); 
  3.   
  4. + (BOOL) resolveInstanceMethod:(SEL)aSEL 
  5.     
  6.     class_addMethod([self class], aSEL, (IMP)dynamicMethod, "v@:"); 
  7.     return YES; 
  8. void dynamicMethod(id self, SEL _cmd) 
  9.     printf("SEL %s did not exist\n",sel_getName(_cmd)); 
  10.   
  11. + (BOOL) resolveInstanceMethod:(SEL)aSEL 
  12.     class_addMethod([self class], aSEL, (IMP)dynamicMethod, "v@:"); 
  13.     return YES; 
 
測試代碼:
  1. RuntimeObj *obj = [[RuntimeObj alloc]init]; 
  2. [obj performSelector:@selector(dynamicMethod:)]; 
 
看看代碼運行效果
 
講解: 
* + (BOOL) resolveInstanceMethod:(SEL)aSEL 是在調用此類方法時,如果沒有這個方法就會掉這個函數。
 
* class_addMethod 就是動態給類添加方法 原型 BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)  
 
 注:IMP 是函數指針
 
* “v@:” 是參數的一種寫法 以後會做詳細講解
  
4. 替換已有函數
在混合編碼的時候不能按照已有思路執行原來的函數,那我們把它替換掉不就好了嘛,看Runtime是怎麼做到的?
 
先上代碼:(注講下面的代碼是爲了講targetReplaceMethod 替換成 demoReplaceMethod)
  1. void demoReplaceMethod(id SELF, SEL _cmd) 
  2.     NSLog(@"demoReplaceMethod"); 
  3.   
  4. -(void)replaceMethod 
  5.     Class strcls = [self class]; 
  6.     SEL  targetSelector = @selector(targetRelplacMethod); 
  7.     class_replaceMethod(strcls,targetSelector,(IMP)demoReplaceMethod,NULL); 
  8.   
  9. -(void)targetRelplacMethod 
  10.     NSLog(@"targetRelplacMethod"); 
 
測試代碼:
  1. RuntimeObj *obj = [[RuntimeObj alloc]init]; 
  2. [obj replaceMethod]; 
  3. [obj targetRelplacMethod]; 
 
運行結果:
  1. 2014-05-12 19:38:37.490 Runtime[1497:303] demoReplaceMethod 
 
是不是原來的  NSLog(@”targetRelplacMethod”); 這句話就沒有執行 被替換掉了!
 
注:
1. class_replaceMethod 方法就是動態替換Method的函數,原型 IMP 。
2. class_replaceMethod(Class cls, SEL name,IMP imp, const char *types) 返回值就是一個新函數的地址(IMP指針)。
3. 在實際項目中會經常用到這種方式, 比如:iOS 7以及7以下繪製NavigationBar, 自己慢慢體會吧。
 
5. 動態掛載對象
掛載這個詞語大家應該並不陌生吧,但是在這裏有一點點微妙的不同,在這裏博主也不是很好解釋這個詞語到底什麼含義,那我來舉個例子吧
 
如:如果你在對象傳遞(傳參)的時候需要用到某個屬性,按照以往的思路:我繼承這個類重新一個新類就完事了,OK,這個思路沒有問題,但是你不覺得要新建一個.h和一個.m文件有點麻煩?程序員都是懶惰的,要是有一個方法能直接講我想要的屬性掛載上前去豈不是更好?代碼簡單、易懂。看了標題你就應該知道Runtime能幫你實現你的願望。
 
下面就來講解下如何使用Runtime來 在已有對象上動態掛載另外一個對象。
 
先不說 直接放代碼(這裏以UIAlertView爲例子):
  1. //掛載對象所需要的參數(UIAlertView掛載對象) 
  2. static const char kRepresentedObject; 
  3. -(void)showAlert:(id)sender 
  4.     UIAlertView *alert = [[UIAlertView alloc]initWithTitle:@"提示" message:message delegate:self cancelButtonTitle:@"取消" otherButtonTitles:@"去看看", nil]; 
  5.     alert.tag = ALERT_GOTO_TAG; 
  6.     objc_setAssociatedObject(alert, &kRepresentedObject, 
  7.     @"我是被掛載的"
  8.     OBJC_ASSOCIATION_RETAIN_NONATOMIC); 
  9.     [alert show]; 
 
這個只是掛載看看如何去獲取我們掛載的對象(NSString @“我是被掛載的”)
  1. -(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex 
  2.     if (buttonIndex == 1) { 
  3.         NSString *str = objc_getAssociatedObject(alertView, 
  4.                                                     &kRepresentedObject); 
  5.         NSLog(@"%@",str) 
  6.     }  
 
自己動手編寫代碼看看效果 是不是和你想的一樣?
 
下面講解下:
1. static const char kRepresentedObject; 這個只是一個標記,但是必不可少 具體什麼作用沒做過調研,我覺得應該就是你掛載的一個標記 Runtime 應該會根據這個標記來區別被掛載對象是掛載在哪個實例上。
2. objc_setAssociatedObject 動態設置關聯對象(也就是掛載)。
3. objc_getAssociatedObject 動態獲取關聯對象 看到沒有這裏也要傳 kRepresentedObject 這個標記,好像有點證明我前面的猜想了。
 
更多接口見官方文檔
 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章