objective-c runtime

來源:互聯網
上載者:User
原文地址:http://www.cnblogs.com/gugupluto/p/3159733.htmlObjective C運行時(runtime)技術的幾個要點總結

前言:

         Objective C的runtime技術功能非常強大,能夠在運行時擷取並修改類的各種資訊,包括擷取方法列表、屬性列表、變數列表、修改方法、修改屬性、增加方法、增加屬性等等,本文對相關的幾個要點做了一個小結。

目錄:

(1)使用class_replaceMethod/class_addMethod函數在運行時對函數進行動態替換或增加新函數

(2)重載forwardingTargetForSelector,將無法處理的selector轉寄給其他對象

(3)重載resolveInstanceMethod,從而在無法處理某個selector時,動態添加一個selector

(4)使用class_copyPropertyList及property_getName擷取類的屬性列表及每個屬性的名稱

  (5) 使用class_copyMethodList擷取類的所有方法列表

  (6) 總結

 

(1)在運行時對函數進行動態替換 : class_replaceMethod

      使用該函數可以在運行時動態替換某個類的函數實現,這樣做有什麼用呢?最起碼,可以實作類別似windows上hook效果,即截獲系統類別的某個執行個體函數,然後塞一些自己的東西進去,比如打個log什麼的。

範例程式碼:

IMP orginIMP;NSString * MyUppercaseString(id SELF, SEL _cmd){    NSLog(@"begin uppercaseString");    NSString *str = orginIMP (SELF, _cmd);(3)    NSLog(@"end uppercaseString");    return str;}-(void)testReplaceMethod{      Class strcls = [NSStringclass];      SEL  oriUppercaseString = @selector(uppercaseString);      orginIMP = [NSStringinstanceMethodForSelector:oriUppercaseString];  (1)        IMP imp2 = class_replaceMethod(strcls,oriUppercaseString,(IMP)MyUppercaseString,NULL);(2)      NSString *s = "hello world";      NSLog(@"%@",s];}

執行結果為:

begin uppercaseString

end uppercaseString

HELLO WORLD

這段代碼的作用就是

(1)得到uppercaseString這個函數的函數指標存到變數orginIMP中

(2)將NSString類中的uppercaseString函數的實現替換為自己定義的MyUppercaseString

(3)在MyUppercaseString中,先執行了自己的log代碼,然後再調用之前儲存的uppercaseString的系統實現,這樣就在系統函數執行之前加入自己的東西,後面每次對NSString調用uppercaseString的時候,都會列印出log來

 與class_replaceMethod相仿,class_addMethod可以在運行時為類增加一個函數。

(2)當某個對象不能接受某個selector時,將對該selector的調用轉寄給另一個對象:-
(id)forwardingTargetForSelector:(SEL)aSelector

     forwardingTargetForSelector是NSObject的函數,使用者可以在衍生類別中對其重載,從而將無法處理的selector轉寄給另一個對象。還是以上面的uppercaseString為例,如果使用者自己定義的CA類的對象a,沒有uppercaseString這樣一個執行個體函數,那麼在不調用respondSelector的情況下,直接執行[a performSelector:@selector"uppercaseString"],那麼執行時一定會crash,此時,如果CA實現了forwardingTargetForSelector函數,並返回一個NSString對象,那麼就相對於對該NSString對象執行了uppercaseString函數,此時就不會crash了。當然實現這個函數的目的並不僅僅是為了程式不crash那麼簡單,在實現裝飾者模式時,也可以使用該函數進行訊息轉寄。

範例程式碼:

 1 @interface CA : NSObject 3 -(void)f; 4  5 @end 6  7 @implementation CA 8  9 - (id)forwardingTargetForSelector:(SEL)aSelector11 {13     if (aSelector == @selector(uppercaseString))15     {17         return@"hello world";19     }21 }

 

測試代碼:

CA *a = [CA new]; NSString * s = [a performSelector:@selector(uppercaseString)];NSLog(@"%@",s);

 

測試代碼的輸出為:HELLO WORLD 

 ps:這裡有個問題,CA類的對象不能接收@selector(uppercaseString),那麼如果我在forwardingTargetForSelector函數中用class_addMethod給CA類增加一個uppercaseString函數,然後返回self,可行嗎?經過實驗,這樣會crash,此時CA類其實已經有了uppercaseString函數,但是不知道為什麼不能調用,如果此時new一個CA類的對象,並返回,是可以成功的。

(3)當某個對象不能接受某個selector時,向對象所屬的類動態添加所需的selector

+ (BOOL) resolveInstanceMethod:(SEL)aSEL 

     這個函數與forwardingTargetForSelector類似,都會在對象不能接受某個selector時觸發,執行起來略有差別。前者的目的主要在於給客戶一個機會來向該對象添加所需的selector,後者的目的在於允許使用者將selector轉寄給另一個對象。另外觸發時機也不完全一樣,該函數是個類函數,在程式剛啟動,介面尚未顯示出時,就會被調用。

     在類不能處理某個selector的情況下,如果類重載了該函數,並使用class_addMethod添加了相應的selector,並返回YES,那麼後面forwardingTargetForSelector就不會被調用,如果在該函數中沒有添加相應的selector,那麼不管返回什麼,後面都會繼續調用forwardingTargetForSelector,如果在forwardingTargetForSelector並未返回能接受該selector的對象,那麼resolveInstanceMethod會再次被觸發,這一次,如果仍然不添加selector,程式就會報異常

程式碼範例一: 1 @implementation CA 3 void dynamicMethodIMP(id self, SEL _cmd) 5 {     7      printf("SEL %s did not exist\n",sel_getName(_cmd)); 9 }10 11 + (BOOL) resolveInstanceMethod:(SEL)aSEL13 {15     if (aSEL == @selector(t))17     {19         class_addMethod([selfclass], aSEL, (IMP) dynamicMethodIMP, "v@:");21         return YES;23     }25     return [superresolveInstanceMethod:aSEL];27 }28 29 @end 測試代碼:  CA * ca = [CA new]  [ca performSelector:@selector(t)];  

  

 

執行結果

   SEL t did not exist

 

範例程式碼二:@implementation CAvoid dynamicMethodIMP(id self, SEL _cmd){    printf("SEL %s did not exist\n",sel_getName(_cmd));}+ (BOOL) resolveInstanceMethod:(SEL)aSEL{    return  YES;}- (id)forwardingTargetForSelector:(SEL)aSelector{    if (aSelector == @selector(uppercaseString))    {        return @"hello world";    }}測試代碼 : a = [[CA alloc]init]; NSLog(@"%@",[a performSelector:@selector(uppercaseString)];

  

該測試代碼的輸出為:HELLO WORLD 

對於該測試代碼,由於a沒有uppercaseString函數,因此會觸發resolveInstanceMethod,但是由於該函數並沒有添加selector,因此運行時發現找不到該函數,會觸發

forwardingTargetForSelector函數,在forwardingTargetForSelector函數中,返回了一個NSString "hello world",因此會由該string來執行uppercaseString函數,最終返回大寫的hello world。

 

範例程式碼三:@implementation CA+ (BOOL) resolveInstanceMethod:(SEL)aSEL{    return  YES;}- (id)forwardingTargetForSelector:(SEL)aSelector{    return nil;  } 測試代碼:1  a = [[CA alloc]init];2  NSLog(@"%@",[a performSelector:@selector(uppercaseString)];

  

  

這段代碼的執行順序為:

 (1):首先在程式剛執行,AppDelegate都還沒有出來時,resolveInstanceMethod就被觸發,

 

(2)等測試代碼執行時,forwardingTargetForSelector被調用

(3)由於forwardingTargetForSelector返回了nil,因此運行時還是找不到uppercaseString selector,這時又會觸發resolveInstanceMethod,由於還是沒有加入selector,於是會crash。

 

(4) 使用class_copyPropertyList及property_getName擷取類的屬性列表及每個屬性的名稱

  

    u_int               count;    objc_property_t*    properties= class_copyPropertyList([UIView class], &count);    for (int i = 0; i < count ; i++)    {        const char* propertyName = property_getName(properties[i]);        NSString *strName = [NSString  stringWithCString:propertyName encoding:NSUTF8StringEncoding];        NSLog(@"%@",strName);    }

 以上代碼擷取了UIView的所有屬性並列印屬性名稱, 輸出結果為:

 skipsSubviewEnumeration viewTraversalMark viewDelegate monitorsSubtree backgroundColorSystemColorName gesturesEnabled deliversTouchesForGesturesToSuperview userInteractionEnabled tag layer _boundsWidthVariable _boundsHeightVariable _minXVariable _minYVariable _internalConstraints _dependentConstraints _constraintsExceptingSubviewAutoresizingConstraints _shouldArchiveUIAppearanceTags

 

 

(5) 使用class_copyMethodList擷取類的所有方法列表

    擷取到的資料是一個Method數組,Method資料結構中包含了函數的名稱、參數、傳回值等資訊,以下代碼以擷取名稱為例:

    u_int               count;    Method*    methods= class_copyMethodList([UIView class], &count);    for (int i = 0; i < count ; i++)    {        SEL name = method_getName(methods[i]);        NSString *strName = [NSString  stringWithCString:sel_getName(name) encoding:NSUTF8StringEncoding];        NSLog(@"%@",strName);    }

  代碼執行後將輸出UIView所有函數的名稱,具體結果略。

     其他一些相關函數:

    

1.SEL method_getName(Method m) 由Method得到SEL2.IMP method_getImplementation(Method m)  由Method得到IMP函數指標3.const char *method_getTypeEncoding(Method m)  由Method得到類型編碼資訊4.unsigned int method_getNumberOfArguments(Method m)擷取參數個數5.char *method_copyReturnType(Method m)  得到傳回值類型名稱6.IMP method_setImplementation(Method m, IMP imp)  為該方法設定一個新的實現

 

(6)總結

       總而言之,使用runtime技術能做些什麼事情呢?

       可以在運行時,在不繼承也不category的情況下,為各種類(包括系統的類)做很多操作,具體包括:

  •    增加
增加函數:class_addMethod增加執行個體變數:class_addIvar增加屬性:@dynamic標籤,或者class_addMethod,因為屬性其實就是由getter和setter函數組成增加Protocol:class_addProtocol (說實話我真不知道動態增加一個protocol有什麼用,-_-!!)
  •    擷取  
擷取函數列表及每個函數的資訊(函數指標、函數名等等):class_getClassMethod method_getName ...擷取屬性列表及每個屬性的資訊:class_copyPropertyList property_getName擷取類本身的資訊,如類名等:class_getName class_getInstanceSize擷取變數列表及變數資訊:class_copyIvarList擷取變數的值
  •    替換
將執行個體替換成另一個類:object_setClass將函數替換成一個函數實現:class_replaceMethod直接通過char *格式的名稱來修改變數的值,而不是通過變數
相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.