美文网首页
关于iOS动态运行时学习笔记

关于iOS动态运行时学习笔记

作者: Mr_Ryan | 来源:发表于2017-10-18 14:42 被阅读0次

    一、前言

    1、什么是静态语言:
    所谓静态语言,就是在程序运行前决定了所有的类型判断,类的所有成员、方法,在编译阶段就确定好了内存地址。即所有类对象只能访问属于自己的成员变量和方法,否则编译器会直接报错。

    2、什么是动态语言:
    所谓动态语言,指类型的判断、类的成员变量、方法的内存地址,都是在程序的运行阶段才最终确定,并且还能动态的添加成员变量和方法。也就意味着你调用一个不存在的方法时,编译也能通过,甚至一个对象它是什么类型的并不是表面我们所看到的那样,只有运行之后才能决定其真正的类型。


    3、什么是运行时:
    所谓运行时,就是程序在运行时做的一些事。苹果提供了一套纯C语言的api,即Runtime。

    二、Runtime数据结构
    在Objective-C中,使用 [receiver message] 语法时,并不会马上执行 receiver 对象的 message 方法的代码,而是向 receiver 发送一条 message 消息,这条消息可能有 receiver 来处理,也可能转发给其他对象来处理,也可能假装没有接收到这条消息而没有处理。

    [receiver message] 被编译器转化为: id objc_msgSend(id self, SEL op, … );



    1、SEL:
    SEL 是函数 objc_msgSend 第二个参数的数据类型,表示方法选择器。

    // An opaque type that represents a method selector.
    typedef struct objc_selector *SEL;
    
    // 获取一个SEL类型的方法选择器
    SEL sel1 = @selector(funcname);
    SEL sel2 = sel_registerName("funcname");
    
    // 将SEL转化为字符串
    NSString *funcString = NSStringFromSelector(sel1);
    



    2、id:
    id 是 objc_msgSend 第一个参数的数据类型,id 是通用类型指针,能够表示任何对象,它其实就是一个指向 objc_object 结构体指针,它包含一个 Class isa 成员,根据 isa 指针就可以顺藤摸瓜找到对象所属的类

    /// Represents an instance of a class.
    struct objc_object {
        Class isa  OBJC_ISA_AVAILABILITY;
    };
    
    /// A pointer to an instance of a class.
    typedef struct objc_object *id;
    



    3、Class:
    isa 指针的数据类型就是 Class, Class 表示对象所属的类, Class 其实也是一个 objc_class 结构体指针。

    /// An opaque type that represents an Objective-C class.
    typedef struct objc_class *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;
    /* Use `Class` instead of `struct objc_class *` */
    
    • isa 表示一个对象的Class。
    • super_class 表示示例对象对应的父类。
    • name 表示类名。
    • ivars 表示多个成员变量,它指向 objc_ivar_list 结构体,objc_ivar_list 其实就是一个链表,存储多个 objc_ivar,而 objc_ivar 结构体存储类的单个成员变量信息。
    • methodLists 表示方法列表,它指向 objc_method_list 结构体的二级指针, objc_method_list也是一个链表,存储多个objc_method,而objc_method结构体存储类的某个方法的信息(可以动态修改 *methodLists的值来添加成员方法,也是Category的实现原理,同样也解析Category不能添加实例变量的原因)。
    • cache 用来缓存经常访问的方法,它指向 objc_cache 结构体。
    • protocols 用来表示遵循哪些协议。



    4、Method:
    Method 表示类中的某个方法,它指向 objc_method 结构体指针,它存储了方法名(method_name),方法类型(method_types),方法实现(method_imp)等信息

    /// An opaque type that represents a method in a class definition.
    typedef struct objc_method *Method;
    
    struct objc_method {
        SEL method_name                                          OBJC2_UNAVAILABLE;
        char *method_types                                       OBJC2_UNAVAILABLE;
        IMP method_imp                                           OBJC2_UNAVAILABLE;
    } 
    



    5、Ivar:
    Ivar 表示类中的实例变量,它指向 objc_ivar 结构体指针,包含了变量名(ivar_name),变量类型(ivar_type)等信息。

    /// An opaque type that represents an instance variable.
    typedef struct objc_ivar *Ivar;
    
    struct objc_ivar {
        char *ivar_name                                          OBJC2_UNAVAILABLE;
        char *ivar_type                                          OBJC2_UNAVAILABLE;
        int ivar_offset                                          OBJC2_UNAVAILABLE;
    #ifdef __LP64__
        int space                                                OBJC2_UNAVAILABLE;
    #endif
    } 
    



    6、IMP:
    IMP 本质上就是一个函数指针,指向方法的实现。当你向某个对象发送一条消息,可以由这个函数指针指定方法的实现,它最终就会执行那段代码,这样可以绕开消息传递阶段而去执行另一个方法实现。

     /// A pointer to the function of a method implementation. 
    #if !OBJC_OLD_DISPATCH_PROTOTYPES
    typedef void (*IMP)(void /* id, SEL, ... */ ); 
    #else
    typedef id (*IMP)(id, SEL, ...); 
    #endif
    



    7、Cache:
    Cache 其实就是一个存储 Method 的链表,用来缓存经常调用的方法,主要是为了优化方法调用的性能,当调用方法时,优先在Cache查找,如果没有找到,再到 methodLists 查找。

    三、消息发送

    在Objective-C中,任何方法的调用,本质都是发送消息。也就是说,我们OC调用一个方法是,其实质就是转换为Runtime中的一个函数 objc_msgSend(),这个函数的作用是向obj对象,发送了一条消息,告诉它,你该去执行某个方法。


    1、objc_msgSend函数:

    • 当对象 receiver 调用方法 message 时,首先根据对象 receiver 的 isa 指针查找它对应的类 class。
    • 优先在类 class 的 Cache 中查找 message 方法。
    • 如果没找到,就在类的 methodLists 中搜索方法。
    • 如果还没有找到,就使用 super_class 指针到父类中的 methodLists 查找。
    • 一旦找到 message 这个方法,就执行它实现的 IMP。
    • 如果还没找到,有可能消息转发,也有可能忽略了该方法。

    四、消息转发

    [receiver message] 调用方法时,如果 message 方法在 receiver 对象的类继承体系中,没有找到方法,一般情况下,程序在运行时就会Crash掉,抛出 unrecognized selector sent to.. 类似的异常信息。但在抛出异常之前,还有三次机会按以下顺序让你拯救程序。

    • Method Resolution: 由于Method Resolution不能像消息转发那样可以交给其他对象处理,所以只适用于在原来的类中代替掉。
    • Fast Forwarding: 可以将消息处理转发给其他对象,使用范围更广,不只是限于原来的对象。
    • Normal Forwarding: 跟Fast Forwarding一样可以消息转发,但它能能过NSInvocation对象获取更多消息发送的信息,如:target、selector、arguments和返回值等信息。



    1、Method Resolution:
    Objective-C在运行时调用 + resolveInstanceMethod: 或 + resolveClassMethod: 方法,让你添加方法的实现。如果你添加方法并返回YES,那系统在运行时就会重新启动一次消息发送的过程。如果返回NO,运行时就跳转到下一步: 消息转发(Message Forwarding)

     // 1、正常情况下
    @interface Message : NSObject
    - (void)sendMessage:(NSString *)word;
    @end
    
    @implementation Message
    - (void)sendMessage:(NSString *)word {
        NSLog(@"normal way : send message = %@",word);
    }
    @end
    
    // 2、注释掉 sendMessage 的实现方法后,覆盖 resolveInstanceMethod 方法:
    @interface Message : NSObject
    - (void)sendMessage:(NSString *)word;
    @end
    
    @implementation Message
    + (BOOL)resolveInstanceMethod:(SEL)sel {
        if (sel == @selector(sendMessage:)) {
            class_addMethod([self class],
                            sel,
                            imp_implementationWithBlock(^(id self, NSString *word){
                NSLog(@"method resolution way : send message = %@",word);
            }), "v@*");
        }
        return YES;
    }
    @end
    

    其中 v@* 表示方法的返回值和参数,详情参考 Type Encodings 。



    2、Fast Forwarding:
    如果目标对象实现 - forwardingTargetForSelector: 方法,系统会在运行时调用这个方法,只要这个方法返回的不是nil或者self,也会重启消息发送的过程,把该消息转发给其他对象来处理。否则,就会继续Normal Forwarding.

    // 3、注释掉 sendMessage 的实现方法后,覆盖 forwardingTargetForSelector 方法:
    @interface Message : NSObject
    - (void)sendMessage:(NSString *)word;
    @end
    
    @implementation Message
    - (id)forwardingTargetForSelector:(SEL)aSelector {
        if (aSelector == @selector(sendMessage:)) {
            return [[MessageForwarding alloc] init];
        }
        return nil;
    }
    @end
    
    // 转发给该对象处理
    @interface MessageForwarding : NSObject
    - (void)sendMessage:(NSString *)word;
    @end
    
    @implementation MessageForwarding
    - (void)sendMessage:(NSString *)word {
        NSLog(@"fast forwarding way : send message = %@",word);
    }
    @end
    



    3、Normal Forwarding:
    如果没有使用Fast Forwarding来消息转发,最后只有使用 Normal Forwarding来进行消息转发。

    它首先调用 - methodSignatureForSelector: 方法来获取函数的参数和返回值。如果返回nili,程序会Crash掉,并抛出unrecognized selector sent to instance异常信息。如果返回一个函数签名,系统就会创建一个NSInvocation对象并调用 - forwardInvocation: 方法。

    // 4、注释掉 sendMessage 的实现方法后,通过 methodSignatureForSelector 方法获取函数签名,并通过 forwardInvocation 方法来转发处理:
    
    @interface Message : NSObject
    - (void)sendMessage:(NSString *)word;
    @end
    
    @implementation Message
    - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
        NSMethodSignature *methodSignature = [super methodSignatureForSelector:aSelector];
        if (!methodSignature) {
            methodSignature = [NSMethodSignature signatureWithObjCTypes:"v@:*"];
        }
        return methodSignature;
    }
    
    - (void)forwardInvocation:(NSInvocation *)anInvocation {
        MessageForwarding *messageForwarding = [[MessageForwarding alloc] init];
        if ([messageForwarding respondsToSelector:anInvocation.selector]) {
            [anInvocation invokeWithTarget:messageForwarding];
        }
    }
    @end
    
    // 转发给该对象处理
    @interface MessageForwarding : NSObject
    - (void)sendMessage:(NSString *)word;
    @end
    
    @implementation MessageForwarding
    - (void)sendMessage:(NSString *)word {
        NSLog(@"fast forwarding way : send message = %@",word);
    }
    @end
    

    五、我们可以用运行时做什么

    1、互换方法的实现:
    因为 selector 和 IMP 之间的关系是在运行时才决定的,所以我们可以通过 void method_exchangeImplementations(Method m1, Method m2) 方法来改变 selector 和 IMP 的对应关系。

    @implementation NSObject (ExchangeMethod)
    
    // 此方法会在此类第一次被加进内存是调用,且仅调用一次
    + (void)load {
        // 获取系统的dealloc方法
        Method m1 = class_getInstanceMethod(self, NSSelectorFromString(@"dealloc"));
        // 获取自己声明的my_dealloc
        Method m2 = class_getInstanceMethod(self, @selector(my_dealloc));
        
        // 交换两个方法的实现,即调用dealloc方法时,会实现my_dealloc,调用my_dealloc方法时,才会调用
        method_exchangeImplementations(m1, m2);
    }
    
    - (void)my_dealloc {
        // do something
        NSLog(@"my_dealloc");
        
        // 这里需要调用自己,调用自己就是调用原来的dealloc进行释放操作
        [self my_dealloc];
    }
    
    @end
    



    2、动态添加方法:
    动态语言调用一个没有的方法时,编译阶段不会报错,但是运行时便会抛出异常闪退,但我们可以动态为某个了添加方法。这里用到的其实就是上面提到的,消息转发时的拯救程序机制。



    3、动态添加属性:
    有时我们想为系统的类或者一些不便修改的第三方框架的类,增加一些自定义的属性,以满足开发的需求。比如使用类目,但是类目只能为一个类添加方法,不能添加属性。这是便可用到下面的方法:

    #import "UIView+TintColor.h"
    #import <objc runtime="runtime">
    
    
    @implementation UIView (TintColor)
    
    - (nullable NSString *)tintColorName {
        return objc_getAssociatedObject(self, @selector(tintColorName));
    }
    
    - (void)setTintColorName:(NSString *)tintColorName {
        if (!tintColorName || tintColorName.length == 0) {
            return;
        }
        UIColor *color;
        SEL sel = NSSelectorFromString(tintColorName);
        if ([UIColor respondsToSelector:sel]) {
            color = [UIColor performSelector:sel];
        }
        if (!color) {
            return;
        }
        self.tintColor = color;
        
        objc_setAssociatedObject(self,
                                 @selector(tintColorName),
                                 tintColorName,
                                 OBJC_ASSOCIATION_COPY_NONATOMIC);
    }
    
    @end
    



    4、获取类中所有的成员变量和属性:
    在开发中,有时会遇到想要改变系统自带类的某一个值,却找不到与之对应的api,便可用运行时来获取该类的所有成员变量。

    // 获取类中所有的成员变量
    Ivar *class_copyIvarList(Class cls, unsigned int *outCount) 
    // 获取类中所有的属性
    objc_property_t class_getProperty(Class cls, const char *name)
    
    unsigned int count;
        Ivar *ivars = class_copyIvarList([UIButton class], &count);
        for (NSInteger i = 0; i < count; i++) {
            // 取出成员变量
            Ivar ivar = ivars[i];
            // 获取属性名字
            NSString *name = [NSString stringWithUTF8String:ivar_getName(ivar)];
            // 获取属性类型
            NSString *type = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];
            
            NSLog(@"%@:%@",type,name);
        }
    

    六、结束语

    将XCode升级到6后,报Too many arguments to function call, expected 0, have *,在XCode5.1里能编译通过的,到xcode6就报错

    objc_msgSend(self.beginRefreshingTaget, self.beginRefreshingAction, self);

    Too many arguments to function call, expected 0, have *

    解决办法:
    选中项目 - Project - Build Settings - Enable Strict Checking of objc_msgSend Calls 将其设置为 NO 即可

    相关文章

      网友评论

          本文标题:关于iOS动态运行时学习笔记

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