前言:html
Objective C的runtime技術功能很是強大,可以在運行時獲取並修改類的各類信息,包括獲取方法列表、屬性列表、變量列表,修改方法、屬性,增長方法,屬性等等,本文對相關的幾個要點作了一個小結。windows
目錄:數組
(1)使用class_replaceMethod/class_addMethod函數在運行時對函數進行動態替換或增長新函數數據結構
(2)重載forwardingTargetForSelector,將沒法處理的selector轉發給其餘對象函數
(3)重載resolveInstanceMethod,從而在沒法處理某個selector時,動態添加一個selector測試
(4)使用class_copyPropertyList及property_getName獲取類的屬性列表及每一個屬性的名稱編碼
(5) 使用class_copyMethodList獲取類的全部方法列表指針
(6) 總結code
(1)在運行時對函數進行動態替換 : class_replaceMethodorm
使用該函數能夠在運行時動態替換某個類的函數實現,這樣作有什麼用呢?最起碼,能夠實現相似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 = [NSString class]; SEL oriUppercaseString = @selector(uppercaseString); orginIMP = [NSStringinstanceMethodForSelector:oriUppercaseString]; (1) IMP imp2 = class_replaceMethod(strcls,oriUppercaseString,(IMP)MyUppercaseString,NULL);(2) NSString *s = "hello world"; NSLog(@"%@",[s uppercaseString]]; }
執行結果爲:
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那麼簡單,在實現裝飾者模式時,也 可使用該函數進行消息轉發。
示例代碼:
@interface CA : NSObject -(void)f; @end @implementation CA -(id)forwardingTargetForSelector:(SEL)aSelector { if (aSelector == @selector(uppercaseString)) { return@"hello world"; } }
測試代碼:
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,程序就會報異常
代碼示例一:
@implementation CA void dynamicMethodIMP(id self, SEL _cmd) { printf("SEL %s did not exist\n",sel_getName(_cmd)); } +(BOOL) resolveInstanceMethod:(SEL)aSEL { if (aSEL == @selector(t)) { class_addMethod([selfclass], aSEL, (IMP) dynamicMethodIMP, "v@:"); return YES; } return [superresolveInstanceMethod:aSEL]; } @end
測試代碼:
CA * ca = [CA new] [ca performSelector:@selector(t)];
執行結果
SEL t did not exist
示例代碼二:
@implementation CA void 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; }
測試代碼:
a = [[CA alloc]init]; 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獲得SEL
2.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 *格式的名稱來修改變量的值,而不是經過變量