runtime详解内容补充

2018-09-05  本文已影响13人  心亦逸风

最近研究了一下oc底层的runtime机制,在网上找到了一篇不错的文章对于runtime讲的也比较详细(iOS Runtime详解)。
对于runtime不太了解的同学可以先看下这篇文章,下面是一些我的理解和内容的补充。

关于元类

根据基类NSObject我们知道在每个类中都有一个isa,实际上是一个objc_class的结构体(runtime.h中有显示)。
其中有两个字段是指向其元类的isa和指向父类的supperclass(两者都是Class也就是objc_class)。


结构关系

在objc_class中有一个methodLists字段存放的是类的方法,通过实验(class_copyMethodList获取这个方法列表),我发现类中的methodLists都是实例方法,而元类都是类方法。
所以元类是存储了这个类的类方法,并且保证了子类也能调用到父类的类方法(因为类的元类的父类指向的是类的父类的元类)。

关于消息转发机制的应用(交换两个方法)

这边大多数主要是为了全局修改某个方法实现。
第一种方法,比如给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";
    }
    //因为交换了方法的关系,系统的setObject:forKey:
    [self handleSetObject:anobject forKey:akey]; 
}

还有通过block的方式,比如给UIViewController要在执行ViewDidLoad前执行一些操作:

+ (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的方法调用本质上就是消息转发
在一个方法Method中有一个SEL和IMP,SEL是方法的编号实际上就是一个char类型数组字符串,比如viewDidLoad方法的SEL打印出来就是"viewDidLoad"这个在没运行程序时就能确定,而动态变的是IMP是方法的具体实现,还有一个SEL和IMP的对应表。方法交换本质上就是交换SEL和IMP的对应关系。

- (void)methodIMP{
//相当于[self testMethod:@"123"];
    ((void (*)(id ,SEL, NSString*))objc_msgSend)(self, @selector(testMethod:), @"123");
}

- (void)testMethod:(NSString*)agr{  
    NSLog(@"%@", agr);
}

关于分类

对于分类为什么能添加方法或者说覆盖原来类中方法名相同的方法呢?
我做了一个实验,新建一个方法,写了一个方法hello,并建了一个它的分类也实现了这个,方法然后实例话类调用方法,这时候调用的分类的方法。
然后我查看了这个类的方法列表methodLists,发现有两个名字都是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中的前面,在调用方法转发消息的时候,类通过methodLists依次寻找方法,结果先找到了分类的方法。

关于类的动态创建

由于runtime机制的动态性,我们可以通过在代码运行时创建一个类(虽然具体使用场景我没有想到..),具体如下:

+ (void)createClass{
    //TestClass判断这个类是否存在
    if (NSClassFromString(@"TestClass")) {
        return;
    }
    
    // 1 创建
    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"]);    
}

就先写这些,之后有新的体会在补充 :)

上一篇下一篇

猜你喜欢

热点阅读