苹果源码理解

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

2017-09-04  本文已影响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

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

@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总结,面试、工作你看我就足够了!

上一篇下一篇

猜你喜欢

热点阅读