Skip to content

CoderAbeliu/RuntimeDemo

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

5 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

公司技术分享时我分享的主题,从runtime 源码中一步步探索类的实现和加载过程,消息发送机制等。 runtime 源码下载

Runtime-源码分析

1.类的初始化 在外部是如何实现的? 2.初始化过程中runtime 起到了什么作用?

类的结构体

类是继承于对象的:

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();
    }
......
 }

objc_class中有定义了三个变量 ,superclass 是一个objc_class的结构体,指向的本类的父类的objc_class结构体。cache 用来处理已经调用方法的缓存。 class_data_bits_t 是objc_class 的关键,很多变量都是根据 bits来实现的。

对象的初始化

在对象初始化的时候,一般都会调用 alloc+init 方法进行实例化,或者通过new 方法。


- 第一步:调用系统的alloc 方法 或者new 方法(其中`new`方法直接调用的`callAlloc init`)
+ (instancetype)alloc OBJC_SWIFT_UNAVAILABLE("use object initializers instead");

+(id)alloc{
    return _objc_rootAlloc(self);
}
  • 第二步: runtime 内部实现调用objc_rootAlloc 方法
// Base class implementation of +alloc. cls is not nil.
// Calls [cls allocWithZone:nil].
id
_objc_rootAlloc(Class cls)
{
    return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}

  • 第三步: callAlloc 方法实现,解析: callAlloc 方法在创建对象的地方有两种方式,一种是通过 calloc 开辟内存,然后通过obj->initInstanceIsa(cls, dtor) 函数初始化这块内存。 第二种是直接调class_createInstance 函数,由内部实现初始化逻辑 ;
static ALWAYS_INLINE id
    callAlloc(Class cls, bool checkNil, bool allocWithZone=false) {
    if (fastpath(cls->canAllocFast())) {
    bool dtor = cls->hasCxxDtor();
    id obj = (id)calloc(1, cls->bits.fastInstanceSize()); 
    if (slowpath(!obj)) return callBadAllocHandler(cls); 
    obj->initInstanceIsa(cls, dtor);
    return obj;
    } else {
    id obj = class_createInstance(cls, 0);
    if (slowpath(!obj)) return callBadAllocHandler(cls); return obj;
  }
}

但是在最新的 objc-723 中,调用canAllocFast() 函数直接返回false ,所以只会执行上面所述的第二个else 代码块。

bool canAllocFast(){
    return false;
}

初始化的代码最终会调用到 _class_createInstanceFromZone 函数,这个函数是初始化的关键代码。然后通过instanceSize 函数返回的 size,并通过calloc 函数分配内存,初始化isa_t 指针。

size_t size = cls->instanceSize(extraBytes);
obj->initIsa(cls);

消息的发送机制

在OC 中方法调用时通过Runtime 来实现的,runtime 进行方法调用本质上是发送消息,通过objc_msgSend()函数来进行消息的发送 [MyClass classMethod] 在runtime运行时被转换为 ((void ()(id, SEL))(void )objc_msgSend)((id)objc_getClass("MyClass"), sel_registerName("classMethod"));

上述的方法可以理解为 向一个objc_class发送了一个SEL 。

OC中每一个Method 的结构体如下:

struct objc_method {
    SEL _Nonnull method_name                    
    char * _Nullable method_types              
    IMP _Nonnull method_imp                                 
}

在新的objc_runtime_new.hobjc_method已经没有使用了,使用的是如下的结构体,其引入的方式也发生了改变,不是直接定义在objc_class类中,而是通过getLoadMethod方法来实现间接的调用。

struct method_t {
    SEL name;
    const char *types;
    IMP imp;

    struct SortBySELAddress :
        public std::binary_function<const method_t&,
                                    const method_t&, bool>
    {
        bool operator() (const method_t& lhs,
                         const method_t& rhs)
        { return lhs.name < rhs.name; }
    };
};

objc_msgSend 就是通过SEL 来进行遍历查找的,如果两个类定义了相同名称的方法,它们的SEL 就是一样的。

objc_method 中具体参数解析如下:

  • SEL 指的就是第一步中解析方法调用得到的 sel_registerName(“methodName”)的返回值。
  • method_types 指的是返回值的类型和参数。以返回值为开始,依次把参数拼接在后面,类型对应表格链接[TYPE EDCODING]。(联想一哈,这个东西也是类似于property_gerAttrubute一样,有对应的类型关系,某个字符意味着某种类型)
  • IMP_Method 参数 是一个函数指针,指向objc_method所对应的实现部分。
objc_msgSend 工作原理

当一个对象被创建,系统会为通过上述的callalloc 函数分配一个内存size 并给他初始化一个isa 指针,可以通过指针访问其类对象,并且通过对类对象访问其所有继承者链中的类。

  1. objc_msgSend 底层实现没有完全的暴露出来,但是通过源码中的objc-msg-simulator-x86_64.s的第672行代码开始可以看到部分实现,也可以通过Xcode断点来查看运行的堆栈信息。其实现原理主要是通过2个方法来完成,首先是CacheLookup方法,在缓存中没有存在的情况下会去执行 __objc_msgSend_uncachedMethodTable查找SEL

    	GetIsaCheckNil NORMAL		// r10 = self->isa, or return zero
    	CacheLookup NORMAL, CALL	// calls IMP on success
    
    	GetIsaSupport NORMAL
    	NilTestReturnZero NORMAL
    
    // cache miss: go search the method lists
    LCacheMiss:
    	// isa still in r10
    	MESSENGER_END_SLOW
    	jmp	__objc_msgSend_uncached
    
    	END_ENTRY _objc_msgSend

    __objc_msgSend_uncached 方法查找

    	STATIC_ENTRY __objc_msgSend_uncached
    	UNWIND __objc_msgSend_uncached, FrameWithNoSaves
    
    	// THIS IS NOT A CALLABLE C FUNCTION
    	// Out-of-band x16 is the class to search
    	
    	MethodTableLookup
    	br	x17
    
    	END_ENTRY __objc_msgSend_uncached
    
    
    	STATIC_ENTRY __objc_msgLookup_uncached
    	UNWIND __objc_msgLookup_uncached, FrameWithNoSaves
    
    	// THIS IS NOT A CALLABLE C FUNCTION
    	// Out-of-band x16 is the class to search
    	
    	MethodTableLookup
    	ret
  2. 在执行MethodTableLookup方法时其中调用到了__class_lookupMethodAndLoadCache3 去找到需要的Class参数和SEL,内部实现找IMP 的是操作 方法是lookUpImpOrForward

  3. 当对象接受到消息时,runtime会沿着消息函数的isa查找对应的类对象,然后是先在objc_cache中去查找当前的SEL 的缓存,如果缓存中存在SEL,就直接返回该IMP也就是该实现方法的指针。

  4. 如果cache 中不存在缓存,需要先判断该类是否已经被创建,如果没有,则将类实例化,第一次调用当前类的话,执行initialized 代码,再开始读取这个类的缓存,还是没有的情况下才在method list 中查找方法selector。本类如果没有,就会到父类的method list中去查找缓存和method list 中的SEL,直到NSObject类 。

//如果缓存在就直接返回
 if (cache) {
        imp = cache_getImp(cls, sel);
        if (imp) return imp;
    }
 runtimeLock.read();
// 看看类有没有被初始化,没有初始化就直接初始化
    if (!cls->isRealized()) {
        // Drop the read-lock and acquire the write-lock.
        // realizeClass() checks isRealized() again to prevent
        // a race while the lock is down.
        runtimeLock.unlockRead();
        runtimeLock.write();

        realizeClass(cls);

        runtimeLock.unlockWrite();
        runtimeLock.read();
    }
   //走一遍 initialized 方法
 if (initialize  &&  !cls->isInitialized()) {
        runtimeLock.unlockRead();
        _class_initialize (_class_getNonMetaClass(cls, inst));
        runtimeLock.read();
        // If sel == initialize, _class_initialize 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
    }
 retry:    
    runtimeLock.assertReading();

4.如果在类的继承体系中都没有找到SEL,则会进行动态消息解析,给自己保留处理找不到方法的机会,

// 没有找到该方法,会执行下面的分解方法
    if (resolver  &&  !triedResolver) {
        runtimeLock.unlockRead();
        _class_resolveMethod(cls, sel, inst);
        runtimeLock.read();
        // Don't cache the result; we don't hold the lock so it may have 
        // changed already. Re-do the search from scratch instead.
        triedResolver = YES;
        goto retry;
    }

其中_class_resolveMethod 的源码解析为:

if(!cls->isMetaClass()){
   _class_resolveInstanceMethod(cls, sel, inst);
}else{
    _class_resolveClassMethod(cls, sel, inst);
     if (!lookUpImpOrNil(cls, sel, inst, 
                            NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
        {
            _class_resolveInstanceMethod(cls, sel, inst);
        }
}
  1. 动态消息解析如果没有做出响应,则进入动态消息转发阶段,如果还没有人响应,就会触发doesNotRecognizeSelector 此时可以在动态消息转发阶段做一些处理,否则就会Crash.

About

iOS runtime源码探究和解读

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published