最近研究了一下oc底層的runtime機制秤标,在網(wǎng)上找到了一篇不錯的文章對于runtime講的也比較詳細(iOS Runtime詳解)。
對于runtime不太了解的同學(xué)可以先看下這篇文章睛挚,下面是一些我的理解和內(nèi)容的補充。
關(guān)于元類
根據(jù)基類NSObject我們知道在每個類中都有一個isa偎捎,實際上是一個objc_class的結(jié)構(gòu)體(runtime.h中有顯示)婴程。
其中有兩個字段是指向其元類的isa和指向父類的supperclass(兩者都是Class也就是objc_class)。
在objc_class中有一個methodLists字段存放的是類的方法渠脉,通過實驗(class_copyMethodList獲取這個方法列表)宇整,我發(fā)現(xiàn)類中的methodLists都是實例方法,而元類都是類方法芋膘。
所以元類是存儲了這個類的類方法鳞青,并且保證了子類也能調(diào)用到父類的類方法(因為類的元類的父類指向的是類的父類的元類)。
關(guān)于消息轉(zhuǎn)發(fā)機制的應(yīng)用(交換兩個方法)
這邊大多數(shù)主要是為了全局修改某個方法實現(xiàn)为朋。
第一種方法臂拓,比如給NSMutableDictionary的setObject:forKey:方法添加nil判斷,建一個NSMutableDictionary的分類添加下面方法:
+ (void)load {
[self changeMehtod];
}
+ (void)changeMehtod {
//這邊注意不用[NSMutableDictionary class]是因為NSMutableDictionary屬于類簇
NSMutableDictionary *dict = [NSMutableDictionary new];
Method originMethod = class_getInstanceMethod(object_getClass(dict), @selector(setObject:forKey:));
Method targetMethod = class_getInstanceMethod(object_getClass(dict), @selector(handleSetObject:forKey:));
method_exchangeImplementations(originMethod, targetMethod);
}
- (void)handleSetObject:(id)anobject forKey:(id)akey {
if (anobject == nil) {
anobject = @"error";
}
//因為交換了方法的關(guān)系习寸,系統(tǒng)的setObject:forKey:
[self handleSetObject:anobject forKey:akey];
}
還有通過block的方式胶惰,比如給UIViewController要在執(zhí)行ViewDidLoad前執(zhí)行一些操作:
+ (void)load {
[self changeMehtod];
}
+ (void)changeMehtod{
Method method = class_getInstanceMethod([UIViewController class], @selector(viewDidLoad));
void (*originIMP)(id self, SEL _cmd) = nil;
originIMP = method_getImplementation(method);
id newViewDidLoad = ^(id self, SEL _cmd){
NSLog(@"operation");
originIMP(self, _cmd);
};
IMP newIMP = imp_implementationWithBlock(newViewDidLoad);
method_setImplementation(method, newIMP);
}
我們知道oc的方法調(diào)用本質(zhì)上就是消息轉(zhuǎn)發(fā)
在一個方法Method中有一個SEL和IMP,SEL是方法的編號實際上就是一個char類型數(shù)組字符串霞溪,比如viewDidLoad方法的SEL打印出來就是"viewDidLoad"
這個在沒運行程序時就能確定孵滞,而動態(tài)變的是IMP是方法的具體實現(xiàn),還有一個SEL和IMP的對應(yīng)表鸯匹。方法交換本質(zhì)上就是交換SEL和IMP的對應(yīng)關(guān)系坊饶。
- (void)methodIMP{
//相當(dāng)于[self testMethod:@"123"];
((void (*)(id ,SEL, NSString*))objc_msgSend)(self, @selector(testMethod:), @"123");
}
- (void)testMethod:(NSString*)agr{
NSLog(@"%@", agr);
}
關(guān)于分類
對于分類為什么能添加方法或者說覆蓋原來類中方法名相同的方法呢?
我做了一個實驗忽你,新建一個方法,寫了一個方法hello臂容,并建了一個它的分類也實現(xiàn)了這個科雳,方法然后實例話類調(diào)用方法根蟹,這時候調(diào)用的分類的方法。
然后我查看了這個類的方法列表methodLists糟秘,發(fā)現(xiàn)有兩個名字都是hello方法简逮。
[[TestFunctionOne new] hello];
int outCount;
Method *methods = class_copyMethodList([TestFunctionOne class], &outCount);
for (int i = 0; i < outCount; i++) {
Method method = methods[i];
NSLog(@"%s",sel_getName(method_getName(method)));
}
從這里可以看出category實際上就是把方法插到了類中的methodLists中的前面,在調(diào)用方法轉(zhuǎn)發(fā)消息的時候尿赚,類通過methodLists依次尋找方法散庶,結(jié)果先找到了分類的方法。
關(guān)于類的動態(tài)創(chuàng)建
由于runtime機制的動態(tài)性凌净,我們可以通過在代碼運行時創(chuàng)建一個類(雖然具體使用場景我沒有想到..)悲龟,具體如下:
+ (void)createClass{
//TestClass判斷這個類是否存在
if (NSClassFromString(@"TestClass")) {
return;
}
// 1 創(chuàng)建
Class EOCTestClass = objc_allocateClassPair([NSObject class], "TestClass", 0);
// 2 添加變量
if(class_addIvar(TestClass, "str", sizeof(NSString*), log2(sizeof(NSString*)), @encode(NSString*))){
NSLog(@"添加成功");
}
if(class_addIvar(EOCTestClass, "intNum", sizeof(int), log2(sizeof(int)), @encode(int))){
NSLog(@"添加成功");
}
// 3 提交 (提交完之后, 就不能在添加變量)
objc_registerClassPair(TestClass);
// 4 測試
id testObject = [TestClass new];
[testObject setValue:@"string" forKey:@"str"];
NSLog(@"%@", [testObject valueForKey:@"str"]);
}
就先寫這些冰寻,之后有新的體會在補充 :)