Runtime

190 阅读11分钟

isa指针

在arm64架构之前,isa就是一个普通的指针,存储着Class、Meta-Class对象的内存地址。从arm64架构开始,对isa进行了优化,变成了一个共用体(union 共用一块内存)结构,还使用位域来存储更多的信息。

union isa_t {
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }

    uintptr_t bits;(用于存放所有的数据)

private:
    // Accessing the class requires custom ptrauth operations, so
    // force clients to go through setClass/getClass by making this
    // private.
    Class cls;
public:
#if defined(ISA_BITFIELD)
    struct {
        ISA_BITFIELD;  // defined in isa.h
    };
    bool isDeallocating() {
        return extra_rc == 0 && has_sidetable_rc == 0;
    }
    void setDeallocating() {
        extra_rc = 0;
        has_sidetable_rc = 0;
    }
#endif
    void setClass(Class cls, objc_object *obj);
    Class getClass(bool authenticated);
    Class getDecodedClass(bool authenticated);
};

& 按位与 特点 上下都是1才为1 掩码 用来位运算的 例如0000 0011 0000 0010
0000 0011 0000 0010

| 按位或 特点:只要有1,就是1 0000 0010
0000 0011 0000 0011

~是按位取反

结构体支持位域

部分位域

Class结构

最新的781源码

类结构

struct objc_class : objc_object {
  //objc_class(const objc_class&) = delete;
  //objc_class(objc_class&&) = delete;
  //void operator=(const objc_class&) = delete;
  //void operator=(objc_class&&) = delete;
    Class ISA;
    Class superclass;
    cache_t cache;            🌹 // 方法缓存
    class_data_bits_t bits;   🌹 // 用于获取具体的类信息
    
    class_rw_t *data() const {
        return bits.data();
    }

class_rw_t 和之前源码不同

818源码结构

struct class_rw_t {
    // Be warned that Symbolication knows the layout of this structure.
    uint32_t flags;
    uint16_t witness;
#if SUPPORT_INDEXED_ISA
    uint16_t index;
#endif

    explicit_atomic<uintptr_t> ro_or_rw_ext;

    Class firstSubclass;
    Class nextSiblingClass;
    ........

791源码结构

struct class_rw_t {
    // Be warned that Symbolication knows the layout of this structure.
    uint32_t flags;
    uint16_t version;
    uint16_t witness;

    const class_ro_t *ro;

    method_array_t methods; 🌹// 二位数组
    property_array_t properties;
    protocol_array_t protocols;

    Class firstSubclass;
    Class nextSiblingClass;

method解读

class_rw_t里面的methods、properties、protocols是二维数组,是可读可写的,包含了类的初始内容、分类的内容 其实property_array_t properties和protocol_array_t protocols都是二维数组

method_t解读

结构

struct method_t {
    SEL name; 🌹// 函数名
    const char *types; // 编码(返回值类型,参数类型)
    MethodListIMP imp; // 指向函数指针(函数地址,代表函数的具体实现)
    ......
};

SEL代表方法/函数名,一般叫选择器,底层机构跟char*类似 可以通过@selector()和sel_registerName()获得

可以通过sel_getName()和NSStringFromSelector()转成字符串

不同类中相同名字的方法,所对应的方法选择器是相同的

types:V16@0:8 V:Void

iOS中提供了一个叫做@encode的指令,可以将具体的类型表示成字符串编码

class_ro_t

struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize; 🌹// instance占用的内存空间
#ifdef __LP64__
    uint32_t reserved;
#endif

    const uint8_t * ivarLayout;
    
    const char * name; 🌹// 类名
    method_list_t * baseMethodList;
    protocol_list_t * baseProtocols;
    const ivar_list_t * ivars; 🌹 // 成员变量列表

    const uint8_t * weakIvarLayout;
    property_list_t *baseProperties;
}

class_ro_t里面的baseMethodList、baseProtocols、ivars、baseProperties是一维数组,是只读的,包含了类的初始内容

方法缓存cache

Class内部结构中有个方法缓存(cache_t),用散列表(哈希表)来缓存曾经调用过的方法,可以提高方法的查找速度

key(SEL) & mask(散列表长度 - 1) == 索引 找到索引对应的方法如果是要找的,直接返回imp,不是要找的,那就把当前索引 -1 ,进行查找,一次类推,如果还找不到,会通过superClass去父类查找,找到之后缓存到cache_t当中,当散列表长度不够的时候会进行相应的扩容(expand)操作,扩容(原来的2倍)的时候会将之前的缓存清空,重新进行缓存。

objc_msgSend执行流程

OC中的方法调用,其实都是转换为objc_msgSend函数的调用,也称为消息机制 objc_msgSend的执行流程可以分为3大阶段:
1> 消息发送
2> 动态方法解析
3> 消息转发

消息发送

入口 objc-msg-arm64.s 汇编文件

ENTRY _objc_msgSend
	UNWIND _objc_msgSend, NoFrame
    🌹p0寄存器:消息接收者 receiver
	cmp	p0, #0			// nil check and tagged pointer check
#if SUPPORT_TAGGED_POINTERS
    🌹如果无消息接收者,如果无接收者,直接返回0
    🌹b:跳转 le:条件 <= 0的意思 条件成立跳转到LNilOrTagged
	b.le	LNilOrTagged		//  (MSB tagged pointer looks negative)
#else
	b.eq	LReturnZero
#endif
	ldr	p13, [x0]		// p13 = isa
	GetClassFromIsa_p16 p13, 1, x0	// p16 = class
LGetIsaDone:
	// calls imp or objc_msgSend_uncached
    🌹// 如果条件成立,有接收者的情况下,就去查找缓存,原理就是通过传入key(SEL),获取到索引,进行查找
	CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached

#if SUPPORT_TAGGED_POINTERS
LNilOrTagged:
	b.eq	LReturnZero		// nil check
	GetTaggedClass
	b	LGetIsaDone
// SUPPORT_TAGGED_POINTERS
#endif

LReturnZero:
	// x0 is already zero
	mov	x1, #0
	movi	d0, #0
	movi	d1, #0
	movi	d2, #0
	movi	d3, #0
	ret     🌹// 相当于reture
    

	END_ENTRY _objc_msgSend

lookUpImpOrForward

IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
    Class curClass;
    IMP methodPC = nil;
    Method meth;
    bool triedResolver = NO;

    methodListLock.assertUnlocked();

    🌹// Optimistic cache lookup 条件不成立,不会再去缓存找方法
    if (behavior & LOOKUP_CACHE) {
        methodPC = _cache_getImp(cls, sel);
        if (methodPC) goto out_nolock;
    }

    // Check for freed class
    if (cls == _class_getFreedObjectClass())
        return (IMP) _freedHandler;

    // Check for +initialize
    if ((behavior & LOOKUP_INITIALIZE)  &&  !cls->isInitialized()) {
        initializeNonMetaClass (_class_getNonMetaClass(cls, inst));
        // If sel == initialize, initializeNonMetaClass will send +initialize 
        // and then the messenger will send +initialize again after this 
        // procedure finishes. Of course, if this is not being called 
        // from the messenger then it won't happen. 2778172
    }

    // The lock is held to make method-lookup + cache-fill atomic 
    // with respect to method addition. Otherwise, a category could 
    // be added but ignored indefinitely because the cache was re-filled 
    // with the old value after the cache flush on behalf of the category.
 retry:
    methodListLock.lock();

   🌹 // Try this class's cache.
    // 上面不找缓存,但在这还会找一遍 是因为为了防止在执行上面代码的过程中动态添加了方法,缓存可能有变化
    methodPC = _cache_getImp(cls, sel);
    if (methodPC) goto done;

   🌹 // Try this class's method lists.
    // 查找方法 去类对象的rw_t中的methods(二维数组)查找方法,查找的时候如果是排好序的就二分查找,非排好序的就按顺序查找
    // 方法存在,填充缓存到当前类对象并返回imp
    meth = _class_getMethodNoSuper_nolock(cls, sel);
    if (meth) {
        log_and_fill_cache(cls, cls, meth, sel);
        methodPC = method_getImplementation(meth);
        goto done;
    }

    // Try superclass caches and method lists.
  🌹 // 自己类没有找到方法去父类找缓存和方法列表
    curClass = cls;
    while ((curClass = curClass->superclass)) {
        // Superclass cache.
        meth = _cache_getMethod(curClass, sel, _objc_msgForward_impcache);
        if (meth) {
            if (meth != (Method)1) {
                // Found the method in a superclass. Cache it in this class.
                log_and_fill_cache(cls, curClass, meth, sel);
                methodPC = method_getImplementation(meth);
                goto done;
            }
            else {
                // Found a forward:: entry in a superclass.
                // Stop searching, but don't cache yet; call method 
                // resolver for this class first.
                break;
            }
        }

        // Superclass method list.
        meth = _class_getMethodNoSuper_nolock(curClass, sel);
        if (meth) {
            log_and_fill_cache(cls, curClass, meth, sel);
            methodPC = method_getImplementation(meth);
            goto done;
        }
    }

    // No implementation found. Try method resolver once.
   🌹 // 以上都没有找到方法
   🌹  // 之前未动态解析过
    if ((behavior & LOOKUP_RESOLVER)  &&  !triedResolver) {
        methodListLock.unlock();
        _class_resolveMethod(cls, sel, inst);
        triedResolver = YES;
        goto retry;
    }

    // No implementation found, and method resolver didn't help. 
    // Use forwarding.

    _cache_addForwardEntry(cls, sel);
    methodPC = _objc_msgForward_impcache;

 done:
    methodListLock.unlock();

 out_nolock:
    if ((behavior & LOOKUP_NIL) && methodPC == (IMP)_objc_msgForward_impcache) {
        return nil;
    }
    return methodPC;
}

方法查找总结 查找方法流程
1。通过混编进行快速查找,如果查找到直接返回imp 2。如果查找不到会走到c的函数lookUpImpOrForward去查找,调用_class_getMethodNoSuper_nolock去类对象的rw_t中的methods(二维数组)查找方法,查找的时候如果是排好序的就二分查找,非排好序的就按顺序查找,找到之后填充缓存到当前类对象并返回imp
3. 如果在自己类的方法列表没有找到方法,会去父类的缓存里面找,找到会填充到当前类的缓存列表中 4. 如果不存在,会从父类的methods中找,找到就缓存,填充并返回imp

第一个阶段 消息发送流程如下图

动态方法解析

未动态解析过

// 都没有找到方法
    // 未动态解析过
    if ((behavior & LOOKUP_RESOLVER)  &&  !triedResolver) {
        methodListLock.unlock();
        _class_resolveMethod(cls, sel, inst);
        triedResolver = YES;
        goto retry;
    }

_class_resolveMethod

static void
_class_resolveMethod(id inst, SEL sel, Class cls)
{
    // 非元类
    if (! cls->isMetaClass()) {
        // try [cls resolveInstanceMethod:sel]
        _class_resolveInstanceMethod(inst, sel, cls);
    } 
    else {
        // 元类
        // try [nonMetaClass resolveClassMethod:sel]
        // and [cls resolveInstanceMethod:sel]
        _class_resolveClassMethod(inst, sel, cls);
        if (!lookUpImpOrNilTryCache(inst, sel, cls)) {
            _class_resolveInstanceMethod(inst, sel, cls);
        }
    }
}

实例操作
实现resolveInstanceMethod会重启消息发送,从当前类的cache中开始查找方法
方法一
假设类中有另一个方法other,下面的实现当调用test方法的时候会执行other的实现

方法二

动态方法解析流程:

消息转发

将消息转发给别人

消息转发流程: 先调用__forwarding__,再往下走才是下图的流程

面试题

第一题 self和super

以下代码打印结果 WYStudent继承自WYPerson

-(instancetype)init
{
    if (self = [super init]) {
//        objc_msgSend(self,@selector(class));
        NSLog(@"%@",[self class]);
        NSLog(@"%@",[self superclass]);
        
        NSLog(@"%@",[super  class]);
        NSLog(@"%@",[super superclass]);
    }
    return self;
}

打印结果: 2021-02-20 17:51:54.917020+0800 KCObjcBuild[28235:746036] WYStudent
2021-02-20 17:51:54.918404+0800 KCObjcBuild[28235:746036] WYPerson
2021-02-20 17:51:54.918675+0800 KCObjcBuild[28235:746036] WYStudent
2021-02-20 17:51:54.919159+0800 KCObjcBuild[28235:746036] WYPerson
分别为:WYStudent WYPerson WYStudent WYPerson

[self class]可以转化为objc_msgSend(self,@selector(class))
[super class]底层转化为objc_msgSendSuper({self,[WYPerson class]},@selector(class)); 第一个参数是传入了一个objc_super类型的结构体,结构体里面放的是方法接收者receiver和父类Class,意思就是说查找方法的时候是先从父类里面去找,而最终class方法的实现是在NSObject中,因此虽然是super,但是其实消息的接收者还是self子类对象,只是从父类开始进行查找而已,因此[super class]最终打印还是WYStudent

super调用,底层会转换为objc_msgSendSuper2(通过打开汇编能够发现)函数的调用,接收2个参数 struct objc_super2 SEL receiver是消息接收者
current_class是receiver的Class对象 当调用[super run]方法,会根据传入进去的Class对象,直接去类对象的父类是找方法

第二题 isMember和isKind

如下打印结果是什么?

NSLog(@"%d",[[NSObject class] isKindOfClass:[NSObject class]]);
        NSLog(@"%d",[[NSObject class] isMemberOfClass:[NSObject class]]);
        NSLog(@"%d",[[WYPerson class] isKindOfClass:[WYPerson class]]);
        NSLog(@"%d",[[WYPerson class] isMemberOfClass:[WYPerson class]]);

打印结果:1 0 0 0
先说结论:
1.会判断前者的元类是否和后者相等或者是其的子类,而[NSObject class]的元类其实就是[NSObject class]类对象,这里比较特殊,所以会打印1
2.判断的是[NSObject class]的元类是否和右边[NSObject class]类相等或者是其的子类,很明显不是 3和4,判断的是左侧的元类是否和后者的类相等或者是其是子类,显然是0,要想相等,右边必须传元类

首先我们看下底层的代码实现:
对象方法
就是判断传入对象的所属的类是否等于后面的类
- (BOOL)isMemberOfClass:(Class)cls

- (BOOL)isMemberOfClass:(Class)cls {
    return [self class] == cls;
}

由源码可以看出仅仅是判断传入的这个对象所属的类是否是当前这个类型,例如[p isMemberOfClass:[WYPerson class]],p是WYPerson类型的对象,就是判断p这个对象所属的类型WYPerson是否是WYPerson类型,此处应该是打印1

类方法
+(BOOL)isMemberOfClass:(Class)cls

源码750
+ (BOOL)isMemberOfClass:(Class)cls {
    return object_getClass((id)self) == cls;
}
源码818
+ (BOOL)isMemberOfClass:(Class)cls {
    return self->ISA() == cls;
}

从源码可以看出判断的是当前消息接收者的元类是否是后面传入的这个类,例如[WYPerson isMemberOfClass:[WYPerson class]]就是判断前面WYPerson的元类是否是后面传入的[WYPerson class]这个类,很明显打印0,如果后面传入的是元类objc_getClass([WYPerson class]),此时会打印1

对象方法
就是判断传入对象所属的类是否和后面的类相等或者是其的子类
- (BOOL)isKindOfClass:(Class)cls 底层实现

- (BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = [self class]; tcls; tcls = tcls->getSuperclass()) {
        if (tcls == cls) return YES;
    }
    return NO;
}

从源码可以看出:传入对象所属的类只要和后面的类相等或者是后面类的子类就会返回YES,例如[p isKindOfClass:[WYPerson class]],就是判断p所属的类型WYPerson是否和[WYPerson class]类型相等或者是其的子类,这是打印1

类方法
就是判断传入对象所属的元类是否和后面的类相等或者是其的子类
+ (BOOL)isKindOfClass:(Class)cls

+ (BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = self->ISA(); tcls; tcls = tcls->getSuperclass()) {
        if (tcls == cls) return YES;
    }
    return NO;
}

例如[WYPerson isKindOfClass:[WYPerson class]]就是判断WYPerson的元类 是否是后面WYPerson类相等或者是其的子类,很明显不是,打印0,如果后面换成[NSObject class],将会打印1

Runtime相关接口

类相关

动态创建一个类(参数:父类,类名,额外的内存空间) Class
objc_allocateClassPair(Class superclass, const char *name, size_t extraBytes)

注册一个类(要在类注册之前添加成员变量) void objc_registerClassPair(Class cls)

销毁一个类
void objc_disposeClassPair(Class cls)

获取isa指向的Class
Class object_getClass(id obj)

设置isa指向的Class
Class object_setClass(id obj, Class cls)

判断一个OC对象是否为Class
BOOL object_isClass(id obj)

判断一个Class是否为元类
BOOL class_isMetaClass(Class cls)

获取父类
Class class_getSuperclass(Class cls)

成员变量相关

获取一个实例变量信息 Ivar class_getInstanceVariable(Class cls, const char *name)

拷贝实例变量列表(最后需要调用free释放) Ivar *class_copyIvarList(Class cls, unsigned int *outCount)

设置和获取成员变量的值 void object_setIvar(id obj, Ivar ivar, id value) id object_getIvar(id obj, Ivar ivar)

动态添加成员变量(已经注册的类是不能动态添加成员变量的) BOOL class_addIvar(Class cls, const char * name, size_t size, uint8_t alignment, const char * types)

获取成员变量的相关信息 const char *ivar_getName(Ivar v) const char *ivar_getTypeEncoding(Ivar v)

属性

获取一个属性
objc_property_t class_getProperty(Class cls, const char *name)

拷贝属性列表(最后需要调用free释放)
objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount)

动态添加属性
BOOL class_addProperty(Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount)

动态替换属性
void class_replaceProperty(Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount)

获取属性的一些信息
const char *property_getName(objc_property_t property)
const char *property_getAttributes(objc_property_t property)

其他方法

获得一个实例方法、类方法
Method class_getInstanceMethod(Class cls, SEL name) Method class_getClassMethod(Class cls, SEL name)

方法实现相关操作
IMP class_getMethodImplementation(Class cls, SEL name)
IMP method_setImplementation(Method m, IMP imp)
void method_exchangeImplementations(Method m1, Method m2) 这个方法会将catch_t中的方法缓存清除掉

拷贝方法列表(最后需要调用free释放)
Method *class_copyMethodList(Class cls, unsigned int *outCount)

动态添加方法 BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)

动态替换方法 IMP class_replaceMethod(Class cls, SEL name, IMP imp, const char *types)

获取方法的相关信息(带有copy的需要调用free去释放)
SEL method_getName(Method m)
IMP method_getImplementation(Method m)
const char *method_getTypeEncoding(Method m)
unsigned int method_getNumberOfArguments(Method m) char *method_copyReturnType(Method m)
char *method_copyArgumentType(Method m, unsigned int index)

选择器相关
const char *sel_getName(SEL sel)
SEL sel_registerName(const char *str)

用block作为方法实现
IMP imp_implementationWithBlock(id block)
id imp_getBlock(IMP anImp)
BOOL imp_removeBlock(IMP anImp)

====重点:了解下数组,字典,字符串的类簇====