美文网首页苹果源码理解
OC--isa结构、消息传递、Method Swizzling

OC--isa结构、消息传递、Method Swizzling

作者: 啊哈呵 | 来源:发表于2017-09-04 20:55 被阅读19次

    参考
    Objective-C Runtime 1小时入门教程
    Objective-C特性:Runtime

    Objc 对象的今生今世
    神经病院Objective-C Runtime入院第一天——isa和Class
    深入解析 ObjC 中方法的结构

    iOS黑魔法-Method Swizzling
    玉令天下:Objective-C Method Swizzling

    实例对象结构

    id就是一个指向类实例的指针

    typedef struct objc_object *id;
    struct objc_object {
        isa_t _Nonnull isa  OBJC_ISA_AVAILABILITY;
    };
    

    arm64 架构中的 isa_t 结构体

    #define ISA_MASK        0x0000000ffffffff8ULL
    #define ISA_MAGIC_MASK  0x000003f000000001ULL
    #define ISA_MAGIC_VALUE 0x000001a000000001ULL
    #define RC_ONE   (1ULL<<45)
    #define RC_HALF  (1ULL<<18)
    union isa_t {
        isa_t() { }
        isa_t(uintptr_t value) : bits(value) { }
    
        Class cls;
        uintptr_t bits;
    
        struct {
           uintptr_t indexed           : 1;  // 0 表示普通的 isa 指针,1 表示使用优化,存储引用计数
           uintptr_t has_assoc         : 1;  // 表示该对象是否包含 associated object,如果没有,则析构时会更快
           uintptr_t has_cxx_dtor      : 1;  // 表示该对象是否有 C++ 或 ARC 的析构函数,如果没有,则析构时更快
           uintptr_t shiftcls          : 33; // 类的指针
           uintptr_t magic             : 6;  // 固定值为 0xd2,用于在调试时分辨对象是否未完成初始化。
           uintptr_t weakly_referenced : 1;  // 表示该对象是否有过 weak 对象,如果没有,则析构时更快
           uintptr_t deallocating      : 1;  // 表示该对象是否正在析构
           uintptr_t has_sidetable_rc  : 1;  // 表示该对象的引用计数值是否过大无法存储在 isa 指针
           uintptr_t extra_rc          : 19; // 存储引用计数值减一后的结果
        };
    };
    

    类结构

    类其实也是对象,叫做类对象

    struct objc_class : objc_object {
        // Class ISA;
        Class superclass;
        cache_t cache;             // formerly cache pointer and vtable
        class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags
    
        class_rw_t *data() { 
            return bits.data();
        }
        ........
    }
    
    NSObject 结构图.png
    class_rw_t

    运行期拷贝class_ro_t中的部分信息存入此结构体中,并存放运行期添加的信息

    struct class_rw_t {
        uint32_t flags;
        uint32_t version;
        const class_ro_t *ro;
        method_array_t methods;
        property_array_t properties;
        protocol_array_t protocols;
        Class firstSubclass;
        Class nextSiblingClass;
    };
    
    class_ro_t

    记录编译期就已经确定的信息

    struct class_ro_t {
        const char * name; // 类名
        uint32_t reserved; // 预留字段
    
        uint32_t flags;
        uint32_t instanceStart;
        uint32_t instanceSize;
    
        method_list_t * baseMethodList;
        protocol_list_t * baseProtocols;
        property_list_t *baseProperties;
        const ivar_list_t * ivars;
    
        const uint8_t * ivarLayout;
        const uint8_t * weakIvarLayout;
        
    };
    
    baseMethodList、baseProtocols、baseProperties,ivars编译器确定好的方法、协议、属性、成员变量
    flags:各种信息合集
    instanceStart、instanceSize

    1、instanceStart之所以等于8,是因为每个对象的isa占用了前8个字节。
    2、instanceSize = isa + 3个ivar,$6的size只有1,但是为了对齐,也占用了8
    继承体系就在父类上面加

    // ZNObjectFather,三个成员变量
    instanceStart = 8
    instanceSize = 32 (instanceStart + 3个ivar)
    
    // ZNObjectSon,也有三个成员变量
    instanceStart = 32
    instanceSize = 56 (instanceStart + 3个ivar)
    
        size_t objSize = class_getInstanceSize([ZNObjectFather class]);// 32
        size_t objSize2 = class_getInstanceSize([ZNObjectSon class]);// 56
    
    ivarLayout和 weakIvarLayout:成员变量的strong与weak信息

    1、ivarLayout = "\x01",表示在先有0个弱属性,接着有1个连续的强属性。若之后没有强属性了,则忽略后面的弱属性
    2、weakIvarLayout = "\x11",表示先有1个强属性,然后才有1个连续的弱属性,若之后没有弱属性了,则忽略后面的强属性

    const uint8_t *
    class_getIvarLayout(Class cls)
    {
        if (cls) return cls->data()->ro->ivarLayout;
        else return nil;
    }
    
    const uint8_t *
    class_getWeakIvarLayout(Class cls)
    {
        if (cls) return cls->data()->ro->weakIvarLayout;
        else return nil;
    }
    
    
    @interface BBObject : NSObject
    {
        
        NSString *name1;
        __weak NSString *name2;
        NSString *name3;
        NSString *name4;
        NSString *name5;
        NSString *name6;
        __weak NSString *name7;
        NSString *name8;
        __weak NSString *name9;
    }
    
    const uint8_t *ivarLayout = class_getIvarLayout([BBObject class]);
    const uint8_t *weakIvarLayout = class_getWeakIvarLayout([BBObject class]);
    
    // ivarLayout=\x01\x14\x11
    // weakIvarLayout =\x11\x31\x11
    

    ivarLayout=\x01\x14\x11



    weakIvarLayout =\x11\x31\x11


    实例对象的isa的isa...是什么?各层isa是什么?

    举例子:
    BBObj *obj = [BBObj new];

    1、obj->isa是一个objc_class结构对象,存放在普通成员变量、动态方法(“-”开头的方法)、isa(metaclass)、super_class

    2、obj->isa->isa也是一个objc_class结构对象,叫做元类metaclass,存放着static类型的成员变量与static类型的方法(“+”开头的方法)

    3、obj->isa->super_class也是一个objc_class结构对象:父类实例对象

    4、obj->isa->isa->isa(metaclass->isa)是NSObject类对象

    5、 obj->isa->isa->super_class(metaclass->super_class)是父类metaclass对象

            Student  *stu = [[Student alloc]init];
    
            NSLog(@"Student's class is %@", [stu class]);
            NSLog(@"Student's meta class is %@", object_getClass([stu class]));
            NSLog(@"Student's meta class's superclass is %@", object_getClass(object_getClass([stu class])));
    
            Class currentClass = [Student class];
            for (int i = 1; i < 5; i++)
            {
                NSLog(@"Following the isa pointer %d times gives %p %@", i, currentClass,currentClass);
                currentClass = object_getClass(currentClass);
            }
    
            NSLog(@"NSObject's class is %p", [NSObject class]);
            NSLog(@"NSObject's meta class is %p", object_getClass([NSObject class]));
    
    

    输出如下:

    Student's class is Student
    Student's meta class is Student
    Student's meta class's superclass is NSObject
    Following the isa pointer 1 times gives 0x100004d90 Student
    Following the isa pointer 2 times gives 0x100004d68 Student
    Following the isa pointer 3 times gives 0x7fffba0b20f0 NSObject
    Following the isa pointer 4 times gives 0x7fffba0b20f0 NSObject
    NSObject's class is 0x7fffba0b2140
    NSObject's meta class is 0x7fffba0b20f0
    
    class与meta class关系.jpg
    objc_ivar_list *ivars是什么?

    objc_ivar_list其实就是一个链表,存储多个objc_ivar

    struct objc_ivar_list {
        int ivar_count;
    #ifdef __LP64__
        int space;
    #endif
        /* variable length structure */
        struct objc_ivar ivar_list[1];
    }
    
    objc_ivar是什么?

    objc_ivar结构体存储类的单个成员变量信息

    typedef struct objc_ivar *Ivar;
    struct objc_ivar {
        char *ivar_name; // 变量名
        char *ivar_type; // 变量类型
        int ivar_offset; // 基地址偏移字节
    #ifdef __LP64__
        int space;       // 占用空间
    #endif
    }
    
    使用对象成员变量流程

    调用 +alloc 方法来初始化一个对象时,也仅仅在内存中生成了一个objc_object结构体,并根据其instanceSize来分配空间,将其isa指针指向所属的类。
    类的成员变量ivar_t存储在class_ro_t中的ivar_list_t * ivars中,
    其中offset 是成员变量相对于对象内存地址的偏移量,正是通过它来完成变量寻址。
    当我们使用对象的成员变量时,如 myObject.var ,编译器会将其转化为object_getInstanceVariable(myObject, 'var', **value) 找到其ivar_t结构体ivar,然后调用object_getIvar(myObject, ivar)来获取成员变量的内存地址。其计算公式如下:

    id *location = (id *)((char *)obj + ivar_offset);
    基于此,虽然多个对象的isa指针指向同一个objc_class,但由于对象的内存地址不一样,所以它们的实例变量存储位置也不一样,从而实现对象与类之间的多对一关系。

    objc_method_list是什么?

    objc_method_list是一个链表,存储多个objc_method,而objc_method结构体存储类的某个方法的信息

    struct objc_method_list {
        struct objc_method_list *obsolete                        OBJC2_UNAVAILABLE;
        
        int method_count                                         OBJC2_UNAVAILABLE;
    #ifdef __LP64__
        int space                                                OBJC2_UNAVAILABLE;
    #endif
        /* variable length structure */
        struct objc_method method_list[1]                        OBJC2_UNAVAILABLE;
    }
    
    // 表示类中的某个方法
    typedef struct objc_method *Method;
    
    struct objc_method {
        SEL method_name;    // 方法名
        char *method_types; // 方法类型
        IMP method_imp;     // 方法实现
    }
    
    调用对象方法:

    当obj_object接收到消息后,通过其isa指针找到对应的objc_class,objc_class又通过其data() 方法,查询class_rw_t的methods列表。

    SEL是什么?

    SEL是selector在Objective-C中的表示类型

    typedef struct objc_selector *SEL;
    struct objc_selector {
        char *name;     // 名称
        char *types;    // 类型
    };
    
    IMP是什么?

    IMP本质上就是一个函数指针,指向方法的实现(方法的代码)

    #if !OBJC_OLD_DISPATCH_PROTOTYPES
    typedef void (*IMP)(void /* id, SEL, ... */ );
    #else
    typedef id (*IMP)(id, SEL, ...);
    #endif
    
    Cache是什么?

    Cache其实就是一个存储Method的链表,主要是为了优化方法调用的性能。

    typedef struct objc_cache *Cache                             OBJC2_UNAVAILABLE;
    struct objc_cache {
        unsigned int mask /* total = mask + 1 */                 OBJC2_UNAVAILABLE;
        unsigned int occupied                                    OBJC2_UNAVAILABLE;
        Method buckets[1]                                        OBJC2_UNAVAILABLE;
    };
    
    /*
     当对象receiver调用方法message时,
     1、先在Cache查找IMP,找到返回IMP
     2、如果没有找到,再在类的methodLists中查找,
     3、如果没有找到,就在super_class父类重复(1、2),
     4、如果找到把方法加入receiver类中的Cache、返回IMP
    

    类的相关操作函数有如下:
    1、iOS Class结构分析
    2、Objective-C Runtime 运行时之一:类与对象

    三、Objective-C的消息传递

    1、基本消息传递

    对象调用方法叫做发送消息。在编译时,程序的源代码就会从对象发送消息转换成Runtime的objc_msgSend函数调用。

    例如某实例变量receiver实现某一个方法oneMethod
    [receiver oneMethod];
    Runtime会将其转成类似这样的代码
    objc_msgSend(receiver, selector);
    

    具体会转换成什么代码呢?Runtime会根据类型自动转换成下列某一个函数:

    1、objc_msgSend:普通的消息都会通过该函数发送
    2、objc_msgSend_stret:消息中有数据结构作为返回值(不是简单值)时,通过此函数发送和接收返回值
    3、objc_msgSendSuper:和objc_msgSend类似,这里把消息发送给父类的实例
    4、objc_msgSendSuper_stret:和objc_msgSend_stret类似,这里把消息发送给父类的实例并接收返回值
    
    2、objc_msgSend函数的调用过程:

    第一步:检测这个selector是不是要忽略的。

    第二步:检测这个target是不是nil对象。nil对象发送任何一个消息都会被忽略掉。

    第三步:
    1、先在Cache查找IMP,找到返回IMP
    2、如果没有找到,再在类的methodLists中查找,
    3、如果没有找到,就在super_class父类重复(1、2),
    4、如果找到把方法加入Cache、返回IMP

    第四步:前三步都找不到就会进入动态方法解析(看下文)。

    3、消息转发及动态解析方法

    当一个对象能接收一个消息时,会走正常的方法调用流程。但如果一个对象无法接收一个消息时,就会走消息转发机制。

    消息转发机制基本上分为三个步骤:

    消息转发流程
    (1)动态增加方法阶段:方法目的是为了给类利用 class_addMethod 添加方法的机会,如下:
    //类方法
    +(BOOL)resolveClassMethod:(SEL)sel {
        // 写法与下面resolveInstanceMethod类似
    }
    //实例方法
    + (BOOL)resolveInstanceMethod:(SEL)sel{
        if (sel == @selector(XXXX)) {
            //const char *types:
            //"v@:"  返回值void类型的方法,没有参数传入。
            //"i@:"  返回值int类型的方法,没有参数传入。
            //"i@:@" 返回值int类型的方法,又一个参数传入。
            //"s@:@" 返回值string类型的方法,又一个参数传入。
            class_addMethod([self class], sel, class_getMethodImplementation([self class], @selector(AAA)), "v@:");
            return YES;
        }
        return [super resolveInstanceMethod:sel];
    }
    

    表示方法的参数和返回值,详情请参考Type Encodings

    (2)对象转发阶段:询问是否把消息给其他接收者处理(单一),返回id是执行者(非self非nil)

    如下:

    - (id)forwardingTargetForSelector:(SEL)aSelector
    {
        MessageForwarding *obj=[MessageForwarding new];// 消息执行者
        if ([obj respondsToSelector:aSelector]) {
            return obj;
        }
        return [super forwardingTargetForSelector:aSelector];
    }
    
    (3)NSInvocation执行阶段:
    //首先调用methodSignatureForSelector:方法来获取函数的参数和返回值,如果返回为nil,程序会Crash掉,并抛出unrecognized selector sent to instance异常信息
    // methodSignatureForSelector实例方法;instanceMethodSignatureForSelector类方法
    //如果返回一个函数签名,系统就会创建一个NSInvocation对象并调用-forwardInvocation:方法
    - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
    {
        NSMethodSignature *methodSignature = [super methodSignatureForSelector:aSelector];
        
        if (!methodSignature) {
            methodSignature = [NSMethodSignature signatureWithObjCTypes:"v@:*"];
        }
        
        return methodSignature;
    }
    
    - (void)forwardInvocation:(NSInvocation *)anInvocation
    {
        MessageForwarding *messageForwarding1 = [MessageForwarding new];
        if ([messageForwarding1 respondsToSelector:anInvocation.selector]) {
            [anInvocation invokeWithTarget:messageForwarding1];
        }
        //可以多个对象,区别于第二个,步骤越往后,处理消息的代价越大,到最后一个阶段时,都创建了 NSInvocation 对象了
        MessageForwarding *messageForwarding2 = [MessageForwarding new];
        if ([messageForwarding2 respondsToSelector:anInvocation.selector]) {
            [anInvocation invokeWithTarget:messageForwarding2];
        }
    }
    
    (4)以上三点都不处理,就doesNotRecognizeSelector

    只是程序主动抛出一个-[类 XXX方法]: unrecognized selector sent to instance不能识别方法的异常

    消息发送与转发路径流程图.jpg

    玉令天下:Objective-C 消息发送与转发机制原理

    lookUpImpOrForward

    IMP lookUpImpOrForward(Class cls, SEL sel, id inst, 
                           bool initialize, bool cache, bool resolver) {
        Class curClass;
        IMP imp = nil;
        Method meth;
        bool triedResolver = NO;
        runtimeLock.assertUnlocked();
        // 检查是否添加缓存锁,如果没有进行缓存查询。
        // 查到便返回IMP指针
        if (cache) {
            imp = cache_getImp(cls, sel);
            if (imp) return imp;
        }
        // 通过调用realizeClass方法,分配可读写`class_rw_t`的空间
        if (!cls->isRealized()) {
            rwlock_writer_t lock(runtimeLock);
            realizeClass(cls);
        }
        // 倘若未进行初始化,则初始化
        if (initialize  &&  !cls->isInitialized()) {
            _class_initialize (_class_getNonMetaClass(cls, inst));
        }
        // 保证方法查询,并进行缓存填充(cache-fill)
    retry:
        runtimeLock.read();
        // 是否忽略GC垃圾回收机制(仅用在macOS中)
        if (ignoreSelector(sel)) {
            imp = _objc_ignored_method;
            cache_fill(cls, sel, imp, inst);
            goto done;
        }
        // 当前类的缓存列表中进行查找
        imp = cache_getImp(cls, sel);
        if (imp) goto done;
        // 从类的方法列表中进行查询
        meth = getMethodNoSuper_nolock(cls, sel);
        if (meth) {
            log_and_fill_cache(cls, meth->imp, sel, inst, cls);
            imp = meth->imp;
            goto done;
        }
        // 从父类中循环遍历
        curClass = cls;
        while ((curClass = curClass->superclass)) {
            // 父类的缓存列表中查询
            imp = cache_getImp(curClass, sel);
            if (imp) {
                if (imp != (IMP)_objc_msgForward_impcache) {
                    // 如果在父类中发现方法,则填充到该类缓存列表
                    log_and_fill_cache(cls, imp, sel, inst, curClass);
                    goto done;
                }
                else {
                    break;
                }
            }
            // 从父类的方法列表中查询
            meth = getMethodNoSuper_nolock(curClass, sel);
            if (meth) {
                log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
                imp = meth->imp;
                goto done;
            }
        }
        // 进入method resolve过程
        if (resolver  &&  !triedResolver) {
            runtimeLock.unlockRead();
            // 调用_class_resolveMethod,解析没有实现的方法
            _class_resolveMethod(cls, sel, inst);
            // 进行二次尝试
            triedResolver = YES;
            goto retry;
        }
        // 没有找到方法,启动消息转发
        imp = (IMP)_objc_msgForward_impcache;
        cache_fill(cls, sel, imp, inst);
     done:
        runtimeLock.unlockRead();
        return imp;
    }
    

    从类的方法列表中进行查询

    static method_t *getMethodNoSuper_nolock(Class cls, SEL sel) {
        runtimeLock.assertLocked();
        // 遍历所在类的methods,这里的methods是List链式类型,里面存放的都是指针
        for (auto mlists = cls->data()->methods.beginLists(), end = cls->data()->methods.endLists(); mlists != end; ++mlists) {
            method_t *m = search_method_list(*mlists, sel);
            if (m) return m;
        }
        return nil;
    }
    

    调用_class_resolveMethod,尝试类是否现实了resolveInstanceMethod或resolveClassMethod

    void _class_resolveMethod(Class cls, SEL sel, id inst) {
        if (! cls->isMetaClass()) {
            // try [cls resolveInstanceMethod:sel]
            // 针对于对象方法的操作
            _class_resolveInstanceMethod(cls, sel, inst);
        } 
        else {
            // try [nonMetaClass resolveClassMethod:sel]
            // and [cls resolveInstanceMethod:sel]
            // 针对于类方法的操作
            _class_resolveClassMethod(cls, sel, inst);
            // 再次启动查询,并且判断是否拥有缓存中消息标记_objc_msgForward_impcache
            if (!lookUpImpOrNil(cls, sel, inst, 
                                NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) {
                // 说明可能不是 metaclass 的方法实现,当做对象方法尝试
                _class_resolveInstanceMethod(cls, sel, inst);
            }
        }
    }
    

    四、self与super

    • self是类的一个隐藏参数,每个方法的实现的第一个参数即为self。
    • super并不是隐藏参数,它实际上只是一个”编译器标示符”,它负责告诉编译器,当调用方法时,去调用父类的方法,而不是本类中的方法。

    下面的代码分别输出什么?

    @implementation Son : Father
    - (id)init
    {
        self = [super init];
        if (self)
        {
            NSLog(@"%@", NSStringFromClass([self class]));
            NSLog(@"%@", NSStringFromClass([super class]));
        }
        return self;
    }
    @end
    

    结果:输出两个Son

    解析
    当调用[self class]方法时,会转化为objc_msgSend函数。
    当调用[super class]方法时,会转化为objc_msgSendSuper。

    id objc_msgSendSuper(struct objc_super *super, SEL op, ...)

    ((Class (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("Son"))}, sel_registerName("class"));
    

    简化

    __rw_objc_super objc_super = (__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("Son"))}
    class_getSuperclass(objc_super,sel_registerName("class"))
    

    objc_super *super是什么?

    struct objc_super {
        __unsafe_unretained id receiver;
        __unsafe_unretained Class super_class;
    };
    

    在objc_msgSendSuper方法中,第一个参数是一个objc_super的结构体,这个结构体里面有两个变量,一个是接收消息的receiver,一个是当前类的父类super_class。

    objc_msgSendSuper的工作原理应该是这样的:
    从objc_super结构体指向的superClass父类的方法列表开始查找selector,找到后以objc_super的receiver(就是self)去调用父类的这个selector。注意,最后的调用者是self,而不是super_class!

    五、Method Swizzling

    比较简单、常用的方式,方案A

    void swizzleMethod(Class class, SEL originalSelector, SEL swizzledSelector)  {
        
        Method originalMethod = class_getInstanceMethod(class, originalSelector);
        Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
        
        // originalMethod 已经存在  class_addMethod 方法就会失败
        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);
        }
    }
    

    1、RSSwizzle 被很多人推荐,它用很复杂的方式解决了 What are the Dangers of Method Swizzling in Objective C? 中提到的一系列问题。不过引入它还是有一些成本的,建议在本文列举的那些极端特殊情况下才使用它,毕竟方案 A 已经能 Cover 到大部分情况了。

    2、JRSwizzle 尝试解决在不同平台和系统版本上的 Method Swizzling 与类继承关系的冲突。对各平台低版本系统兼容性较强。

    实用例子:
    iOS 万能跳转界面方法
    OC最实用的runtime总结,面试、工作你看我就足够了!

    相关文章

      网友评论

        本文标题:OC--isa结构、消息传递、Method Swizzling

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