美文网首页iOS锦囊
Runtime-基础与应用

Runtime-基础与应用

作者: mtry | 来源:发表于2018-02-06 20:34 被阅读10次

    主要内容

    • Runtime 基础
    • Runtime 应用

    Runtime 基础

    Objective-C 语言将决定尽可能的从编译和链接推迟到运行时。只要有可能 Objective-C 总是使用动态的方式来解决问题。这就意味着 Objective-C 语言不仅需要一个编译器,同时需要一个运行时系统来执行编译好的代码。

    交互方式

    • 通过 Objective-C 源代码

    大部分情况下,运行时系统在后台自动运行,我们只需编写和编译 Objective-C 源代码。当编译 Objective-C 类和方法时,编译器为实现语言动态特性将自动创建一些数据结构和函数,方便运行期发送消息。

    • 通过类 NSObject 的方法

    NSObject 有些方法能在运行时获得类的信息,并检查一些特性,比如 class 返回对象的类;isKindOfClass:isMemberOfClass: 则检查对象是否在指定的类继承体系中;respondsToSelector: 检查对象能否响应指定的消息;conformsToProtocol: 检查对象是否实现了指定协议类的方法;methodForSelector: 则返回指定方法实现的地址。

    • 通过运行时系统的函数

    运行时系统是一个有公开接口的动态库,由一些数据结构和函数的集合组成,这些数据结构和函数的声明头文件在 /usr/include/objc 中。这些函数支持用纯 C 的函数来实现和 Objective-C 同样的功能。还有一些函数构成了 NSObject 类方法的基础。这些函数使得访问运行时系统接口和提供开发工具成为可能。尽管大部分情况下它们在 Objective-C 程序不是必须的,但是有时候对于 Objecitve-C 程序来说某些函数是非常有用的。

    消息传递

    在很多语言,比如 C ,调用一个方法其实就是跳到内存中的某一点并开始执行一段代码。没有任何动态的特性,因为这在编译时就决定好了。而在 Objective-C 中,[object foo]语法并不会立即执行 foo 这个方法的代码。它是在运行时给 object 发送一条叫 foo 的消息。这个消息,也许会由 object 来处理,也许会被转发给另一个对象,或者不予理睬假装没收到这个消息。多条不同的消息也可以对应同一个方法实现。这些都是在程序运行的时候决定的。

    Objective-C 消息语句

    [object foo];
    

    编译器编译之后

    objc_msgSend(object, @selector(foo));
    

    如果有参数则为

    objc_msgSend(object, @selector(foo), arg1, arg2, ...)
    

    接下来我来看一下转换为 objc_msgSend 函数之后的传递过程

    id objc_msgSend(id self, SEL _cmd, ...);
    
    1. 检查忽略的_cmd,比如当我们运行在有垃圾回收机制的环境中,将会忽略 retain 和 release 消息。
    2. 检查 self 是否为 nil。不像其他语言,nil 在 Objective-C 中是完全合法的,并且这里有很多原因你也愿意这样,比如,至少我们省去了给一个对象发送消息前检查对象是否为空的操作。如果 self 为空,则会将 _cmd 也设置为空,并且直接返回到消息调用的地方。如果对象非空,就继续下一步。
    3. 接下来会根据 SEL 到当前类中查找对应的 IMP(方法实现的函数指针),首先会在 cache 中检索它,如果找到了就根据函数指针跳转到这个函数执行,否则进行下一步。
    4. 检索当前类对象中的方法表(method list),如果找到了,加入 cache 中,并且就跳转到这个函数之行,否则进行下一步。
    5. 从父类中寻找,直到根类(NSObject)。找到了就将方法加入对应类的 cache 表中,否则进行下一步。
    6. 动态方法解析,没有实现解析进入下一步。
    7. 消息转发,如果没有实现转发下一步。
    8. 闪退。

    动态方法解析

    当向一个对象发送一个消息时,在对象中没有找到对应的实现,那么就会进入动态解析。

    • 如果这个消息由实例方法转换而来,动态解析可以重载+ (BOOL)resolveInstanceMethod:(SEL)sel
    • 如果这个消息由类方法转换而来,动态解析可以重载+ (BOOL)resolveClassMethod:(SEL)sel

    例:动态解析实例方法

    @interface MTObject : NSObject
    @end
    
    @implementation MTObject
    
    + (BOOL)resolveInstanceMethod:(SEL)sel
    {
        if(sel == @selector(testFun))
        {
            class_addMethod([self class], sel, (IMP)dynamicMethodIMP, "v@:");
            return YES;
        }
        return [super resolveInstanceMethod:sel];
    }
    
    void dynamicMethodIMP(id self, SEL _cmd)
    {
        NSLog(@"ok");
    }
    @end
    
    

    例:动态解析类方法

    @interface MTObject : NSObject
    @end
    
    @implementation MTObject
    
    + (BOOL)resolveClassMethod:(SEL)sel
    {
        if(sel == @selector(testFun))
        {
            class_addMethod(object_getClass(self), sel, (IMP)dynamicMethodIMP, "v@:");
            return YES;
        }
        return [super resolveClassMethod:sel];
    }
    
    void dynamicMethodIMP(id self, SEL _cmd)
    {
        NSLog(@"ok");
    }
    @end
    

    注意:

    1. IMP 为函数指针定义为 typedef id (*IMP)(id, SEL, ...)
    2. IMP 可以通过传人一个块获取 IMP imp_implementationWithBlock(id block)
    3. class_addMethod 中 "v@:" 表示IMP的返回值和参数。依次表示:函数返回为空、id类型的参数、SEL类型的参数,具体参考Type Encoding 官方Type Encoding 其它

    消息转发

    • 重定向(改变消息接受者)
    • 转发

    重定向实例方法

    - (id)forwardingTargetForSelector:(SEL)aSelector
    {
        if(aSelector == @selector(xxx))
        {
            return otherObject;
        }
        return [super forwardingTargetForSelector:aSelector];
    }
    

    重定向类方法

    + (id)forwardingTargetForSelector:(SEL)aSelector
    {
        if(aSelector == @selector(xxx))
        {
            return NSClassFromString(@"className");
        }
        return [super forwardingTargetForSelector:aSelector];
    }
    

    转发(当动态方法解析返回 NO,在重定向返回的是 nil 或 self 时开始进行)

    @interface MTProxy : NSProxy
    @property (nonatomic, strong) id target;
    @end
    
    @implementation MTProxy
    
    - (instancetype)initWithTarget:(id)target
    {
        self.target = target;
        return self;
    }
    
    - (void)forwardInvocation:(NSInvocation *)anInvocation
    {
        [anInvocation invokeWithTarget:self.target];
    }
    
    - (NSMethodSignature *)methodSignatureForSelector:(SEL)sel
    {
        return [self.target methodSignatureForSelector:sel];
    }
    
    @end
    

    注意:

    1. 消息转发时,在forwardInvocation:消息发送前,Runtime系统会向对象发送methodSignatureForSelector:消息,并取到返回的方法签名用于生成NSInvocation对象。所以我们在重写forwardInvocation:的同时也要重写methodSignatureForSelector:方法。

    几个难点

    1、关于元类(Meta Class),需要先了解一些数据结构的定义

    id

    typedef struct objc_object *id;
    

    objc_object

    struct objc_object { 
        Class isa; 
    };
    

    Class

    typedef struct objc_class *Class;
    

    objc_class

    struct objc_class {
        Class isa  OBJC_ISA_AVAILABILITY;
        
    #if !__OBJC2__
        Class super_class                                        OBJC2_UNAVAILABLE;
        const char *name                                         OBJC2_UNAVAILABLE;
        long version                                             OBJC2_UNAVAILABLE;
        long info                                                OBJC2_UNAVAILABLE;
        long instance_size                                       OBJC2_UNAVAILABLE;
        struct objc_ivar_list *ivars                             OBJC2_UNAVAILABLE;
        struct objc_method_list **methodLists                    OBJC2_UNAVAILABLE;
        struct objc_cache *cache                                 OBJC2_UNAVAILABLE;
        struct objc_protocol_list *protocols                     OBJC2_UNAVAILABLE;
    #endif
    
    } OBJC2_UNAVAILABLE;
    

    从上面的数据结构可以看出,任何一个可以通过 id 表示的对象可以通过 isa 指针找到对应类的 Class。需要注意的是,在 objc_class 中还有个 isa 指针指向一个 Class,这个 Class 就是元类(Meta Class)。既然元类也是类,自然也有一个 isa 指针了,这时的 isa 指针指向的是 Root Class 的元类,而 Root Class 的元类的 isa 指针指向自己。元类中的 objc_method_list 存储的是类方法。

    总结:

    • 实例对象的 isa 指针指向对应的类
    • 类的 isa 指针指向对应的元类
    • 元类的 isa 指针指向根元类(NSObject 的元类)
    • 根元类的 isa 指针指向自己
    image_1.png

    Runtime 应用

    • 获取属性列表
    @interface MTObject : NSObject
    
    @property (nonatomic, strong) NSString *string1;
    
    @end
    
    @implementation MTObject
    {
        NSString *string2;
    }
    @end
    
    
    //核心代码
    - (void)allProperty
    {
        unsigned int count = 0;
        objc_property_t *propertyList = class_copyPropertyList([MTObject class], &count);
        for(int i = 0; i < count; i++)
        {
            NSString *propertyName = [NSString stringWithUTF8String:property_getName(propertyList[i])];
            NSLog(@"%@", propertyName);
        }
    }
    
    //输出结果
    19:15:23.177 ObjectiveDemo[1214:54491] string1
    
    
    • 获取成员变量列表
    @interface MTObject : NSObject
    
    @property (nonatomic, readonly) NSString *string1;
    
    @end
    
    @implementation MTObject
    {
        NSString *string2;
    }
    @end
    
    //核心代码
    - (void)allIvar
    {
        unsigned int count = 0;
        Ivar *ivarList = class_copyIvarList([MTObject class], &count);
        for(int i = 0; i < count; i++)
        {
            NSString *propertyName = [NSString stringWithUTF8String:ivar_getName(ivarList[i])];
            NSLog(@"%@", propertyName);
        }
    }
    
    //输出结果
    19:22:39.955 ObjectiveDemo[1287:57828] string2
    19:22:39.955 ObjectiveDemo[1287:57828] _string1
    
    注意:@property定义的成员变量都带下划线 _property;如果有 @synthesize 重新定义,则和重新定义的一致。
    
    • 获取实例方法列表
    @interface MTObject : NSObject
    
    @end
    
    @implementation MTObject
    
    - (void)instanceMethod{}
    + (void)classMethod{}
    
    @end
    
    //核心方法
    - (void)allInstanceMethod
    {
        unsigned int count = 0;
        Method *methodList = class_copyMethodList([MTObject class], &count);
        for(int i = 0; i < count; i++)
        {
            SEL sel = method_getName(methodList[i]);
            NSString *methodName = NSStringFromSelector(sel);
            NSLog(@"%@", methodName);
        }
    }
    
    //输出结果
    19:49:40.219 ObjectiveDemo[1501:69002] instanceMethod
    
    • 获取类方法列表
    @interface MTObject : NSObject
    
    @end
    
    @implementation MTObject
    
    - (void)instanceMethod{}
    + (void)classMethod{}
    
    @end
    
    //核心代码(其实就是 class_copyMethodList 传人的 Class 为对于的元类就可以了)
    - (void)allClassMethod
    {
        unsigned int count = 0;
        Method *methodList = class_copyMethodList(objc_getMetaClass(class_getName([MTObject class])), &count);
        for(int i = 0; i < count; i++)
        {
            SEL sel = method_getName(methodList[i]);
            NSString *methodName = NSStringFromSelector(sel);
            NSLog(@"%@", methodName);
        }
    }
    
    //输出结果
    19:55:45.357 ObjectiveDemo[1528:71022] classMethod
    
    • Associated Objects(可以应用分类添加属性等)
    @interface MTObject : NSObject
    
    @end
    
    @implementation MTObject
    
    @end
    
    @interface MTObject (category)
    
    @property (nonatomic, strong) NSString *categoryProperty;
    
    @end
    
    @implementation MTObject (category)
    
    - (void)setCategoryProperty:(NSString *)categoryProperty
    {
        objc_setAssociatedObject(self, 
                                @selector(categoryProperty), 
                                categoryProperty, 
                                OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    
    - (NSString *)categoryProperty
    {
        return objc_getAssociatedObject(self, @selector(categoryProperty));
    }
    
    @end
    
    
    objc_AssociationPolicy几种类型
    OBJC_ASSOCIATION_ASSIGN           指定一个关联对象的弱引用。                  相当于@property(assign)或
                                                                                  @property(unsafe_unretained)
    OBJC_ASSOCIATION_RETAIN_NONATOMIC 指定一个关联对象的强引用,不能被原子化使用。   相当于@property(nonatomic, strong)
    OBJC_ASSOCIATION_COPY_NONATOMIC   指定一个关联对象的copy引用,不能被原子化使用。 相当于@property(nonatomic, copy)
    OBJC_ASSOCIATION_RETAIN           指定一个关联对象的强引用,能被原子化使用。     相当于@property(atomic, strong)
    OBJC_ASSOCIATION_COPY             指定一个关联对象的copy引用,能被原子化使用。   相当于@property(atomic, copy)
    
    • Method Swizzling(可以应用hook等)
    @implementation UIViewController (Tracking)
    
    + (void)load {
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            Class class = [self class];
    
            SEL originalSelector = @selector(viewWillAppear:);
            SEL swizzledSelector = @selector(xxx_viewWillAppear:);
    
            Method originalMethod = class_getInstanceMethod(class, originalSelector);
            Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
    
            // 如果是类方法
            // Class class = object_getClass((id)self);
            // ...
            // Method originalMethod = class_getClassMethod(class, originalSelector);
            // Method swizzledMethod = class_getClassMethod(class, swizzledSelector);
    
            BOOL didAddMethod =
                class_addMethod(class,
                    originalSelector,
                    method_getImplementation(swizzledMethod),
                    method_getTypeEncoding(swizzledMethod));
    
            if (didAddMethod) {
                class_replaceMethod(class,
                    swizzledSelector,
                    method_getImplementation(originalMethod),
                    method_getTypeEncoding(originalMethod));
            } else {
                method_exchangeImplementations(originalMethod, swizzledMethod);
            }
        });
    }
    
    #pragma mark - Method Swizzling
    
    - (void)xxx_viewWillAppear:(BOOL)animated {
        [self xxx_viewWillAppear:animated];
        NSLog(@"viewWillAppear: %@", self);
    }
    
    @end
    

    注意:理解 selector, method, implementation 这三个概念之间关系的最好方式是:在运行时,类(Class)维护了一个消息分发列表来解决消息的正确发送。每一个消息列表的入口是一个方法(Method),这个方法映射了一对键值对,其中键值是这个方法的名字 selector(SEL),值是指向这个方法实现的函数指针 implementation(IMP)。 Method swizzling 修改了类的消息分发列表使得已经存在的 selector 映射了另一个实现 implementation,同时重命名了原生方法的实现为一个新的 selector。

    参考资料

    1. Objective-C 2.0运行时系统编程指南(官方文档翻译)
    2. Objective-C Runtime
    3. Objective-C runtime之运行时的基本特点(一)
    4. Objective-C runtime之消息(二)
    5. Objective-C runtime之消息转发机制(三)
    6. Type Encoding 官方
    7. Type Encoding 其它
    8. 使用NSProxy和NSObject设计代理类的差异
    9. runtime详解(应用)
    10. 谈Runtime机制和使用的整体化梳理(应用)
    11. Associated Objects
    12. Method Swizzling
    13. Method Swizzling的各种姿势

    相关文章

      网友评论

        本文标题:Runtime-基础与应用

        本文链接:https://www.haomeiwen.com/subject/xclnzxtx.html