iOS底層原理:Runtime研究,玩出新花樣

image.png

Objective-C 擴展了 C 語言,並加入了面向對象特性和 Smalltalk 式的消息傳遞機制。而這個擴展的核心是一個用 C 和 編譯語言 寫的 Runtime 庫。它是 Objective-C 面向對象和動態機制的基石。html

Objective-C 是一個動態語言,這意味着它不只須要一個編譯器,也須要一個運行時系統來動態得建立類和對象、進行消息傳遞和轉發。理解 Objective-C 的 Runtime 機制能夠幫咱們更好的瞭解這個語言,適當的時候還能對語言進行擴展,從系統層面解決項目中的一些設計或技術問題。一句話: 學好Runtime , iOS躺着走git

# Runtime Versions and Platforms

There are different versions of the Objective-C runtime on different platforms.

## Legacy and Modern Versions

There are two versions of the Objective-C runtime—「modern」 and 「legacy」. The modern version was introduced with Objective-C 2.0 and includes a number of new features. The programming interface for the legacy version of the runtime is described in *Objective-C 1 Runtime Reference*; the programming interface for the modern version of the runtime is described in *[Objective-C Runtime Reference](https://developer.apple.com/documentation/objectivec/objective_c_runtime)*.

The most notable new feature is that instance variables in the modern runtime are 「non-fragile」: 

*   In the legacy runtime, if you change the layout of instance variables in a class, you must recompile classes that inherit from it.

*   In the modern runtime, if you change the layout of instance variables in a class, you do not have to recompile classes that inherit from it.

In addition, the modern runtime supports instance variable synthesis for declared properties (see [Declared Properties](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjectiveC/Chapters/ocProperties.html#//apple_ref/doc/uid/TP30001163-CH17) in *[The Objective-C Programming Language](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjectiveC/Introduction/introObjectiveC.html#//apple_ref/doc/uid/TP30001163)*).

## Platforms

iPhone applications and 64-bit programs on OS X v10.5 and later use the modern version of the runtime.

Other programs (32-bit programs on OS X desktop) use the legacy version of the runtime.

複製代碼

Runtime其實有兩個版本: 「modern」 和 「legacy」。咱們如今用的 Objective-C 2.0 採用的是現行 (Modern) 版的 Runtime 系統,只能運行在 iOSmacOS 10.5 以後的 64 位程序中。而 macOS 較老的32位程序仍採用 Objective-C 1 中的(早期)Legacy 版本的 Runtime 系統。這兩個版本最大的區別在於當你更改一個類的實例變量的佈局時,在早期版本中你須要從新編譯它的子類,而現行版就不須要。github

Runtime 基本是用 C彙編寫的,可見蘋果爲了動態系統的高效而做出的努力。你能夠在這裏下到蘋果維護的開源代碼。蘋果和GNU各自維護一個開源的 runtime/GNUStep 版本,這兩個版本之間都在努力的保持一致。objective-c

平時的業務中主要是使用官方Api,解決咱們框架性的需求。編程

高級編程語言想要成爲可執行文件須要先編譯爲彙編語言再彙編爲機器語言,機器語言也是計算機可以識別的惟一語言,可是OC並不能直接編譯爲彙編語言,而是要先轉寫爲純C語言再進行編譯和彙編的操做,從OCC語言的過渡就是由runtime來實現的。然而咱們使用OC進行面向對象開發,而C語言更多的是面向過程開發,這就須要將面向對象的類轉變爲面向過程的結構體。緩存

OK 咱們先來看看與runtime 交互的三種方式:bash

  • OC 原生底層就是runtime 會在後臺執行 好比方法的實質就是消息 對於大多數狀況下,OC運行時系統自動的在後臺運行。你只需編寫和編譯OC代碼就能使用它。 當你編譯包含OC類和方法的代碼時,編譯器建立用來實現語言動態特性的數據結構體和方法調用。數據結構獲取類和類定義的信息和協議中定義的信息,包含了在《The Objective-C Programming Language》中對「 Defining a Class and Protocols」談論的類和協議的對象,以及方法選擇,實例變量模版,和其餘蔥源代碼中提取出來的信息。運行時主要的一個功能是發送消息,正如在Messaging 中的描述。它是由源代碼的消息表達式調用的。數據結構

  • 經過調用NSObject的方法 間接調用runtimeapp

+ (BOOL)isSubclassOfClass:(Class)aClass;
+ (BOOL)instancesRespondToSelector:(SEL)aSelector;
+ (BOOL)conformsToProtocol:(Protocol *)protocol;
- (IMP)methodForSelector:(SEL)aSelector;
+ (IMP)instanceMethodForSelector:(SEL)aSelector;
- (void)doesNotRecognizeSelector:(SEL)aSelector;
- (BOOL)isKindOfClass:(Class)aClass;
- (BOOL)isMemberOfClass:(Class)aClass;
- (BOOL)conformsToProtocol:(Protocol *)aProtocol;
- (BOOL)respondsToSelector:(SEL)aSelector;

複製代碼

這裏給你們解釋一下: 以上方法都是在運行時會編譯成響應的方法:好比- (BOOL)respondsToSelector:(SEL)aSelector 咱們看編譯會來到objc 的這裏框架

BOOL class_respondsToSelector(Class cls, SEL sel)
{
    return class_respondsToSelector_inst(cls, sel, nil);
}

//繼續跟蹤 看到回來到下面的方法 ,會去查找當前sel 對應的imp是否存在
bool class_respondsToSelector_inst(Class cls, SEL sel, id inst)
{
    IMP imp;

    if (!sel  ||  !cls) return NO;

    // Avoids +initialize because it historically did so.
    // We're not returning a callable IMP anyway. imp = lookUpImpOrNil(cls, sel, inst, NO/*initialize*/, YES/*cache*/, YES/*resolver*/); return bool(imp); } //下面這裏就是真正去查找imp的方法,我會在注重介紹一下 IMP lookUpImpOrNil(Class cls, SEL sel, id inst, bool initialize, bool cache, bool resolver) { IMP imp = lookUpImpOrForward(cls, sel, inst, initialize, cache, resolver); if (imp == _objc_msgForward_impcache) return nil; else return imp; } 複製代碼

上面的兩部跳動,都是給下面的方法作鋪墊的,下面的方法也runtime很是重要的方法,下面咱們花點篇幅介紹一下

IMP lookUpImpOrForward(Class cls, SEL sel, id inst, 
                       bool initialize, bool cache, bool resolver)
{
    IMP imp = nil;
    bool triedResolver = NO;

    runtimeLock.assertUnlocked();

    // 若是cache是YES,則從緩存中查找IMP。
    if (cache) {
        // 經過cache_getImp函數查找IMP,查找到則返回IMP並結束調用
        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();
    }

    // 第一次調用當前類的話,執行initialize的代碼
    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(); // 嘗試獲取這個類的緩存 imp = cache_getImp(cls, sel); if (imp) goto done; { // 若是沒有從cache中查找到,則從方法列表中獲取Method Method meth = getMethodNoSuper_nolock(cls, sel); if (meth) { // 若是獲取到對應的Method,則加入緩存並從Method獲取IMP log_and_fill_cache(cls, meth->imp, sel, inst, cls); imp = meth->imp; goto done; } } // Try superclass caches and method lists. { unsigned attempts = unreasonableClassCount(); // 循環獲取這個類的緩存IMP 或 方法列表的IMP for (Class curClass = cls->superclass; curClass != nil; curClass = curClass->superclass) { // Halt if there is a cycle in the superclass chain. if (--attempts == 0) { _objc_fatal("Memory corruption in class list."); } // Superclass cache. // 獲取父類緩存的IMP imp = cache_getImp(curClass, sel); if (imp) { if (imp != (IMP)_objc_msgForward_impcache) { // Found the method in a superclass. Cache it in this class. // 若是發現父類的方法,而且再也不緩存中,在下面的函數中緩存方法 log_and_fill_cache(cls, imp, sel, inst, curClass); 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.
            // 在父類的方法列表中,獲取method_t對象。若是找到則緩存查找到的IMP
            Method meth = getMethodNoSuper_nolock(curClass, sel);
            if (meth) {
                log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
                imp = meth->imp;
                goto done;
            }
        }
    }

    // No implementation found. Try method resolver once.

    // 若是沒有找到,則嘗試動態方法解析
    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;
    }

    // No implementation found, and method resolver didn't help. // Use forwarding. // 若是沒有IMP被發現,而且動態方法解析也沒有處理,則進入消息轉發階段 imp = (IMP)_objc_msgForward_impcache; cache_fill(cls, sel, imp, inst); done: runtimeLock.unlockRead(); return imp; } 複製代碼

lookUpImpOrForward這個方法裏面篇幅很長裏面介紹瞭如下幾點:

  • 若是cache是YES,則從緩存中查找IMP。這裏也就是說咱們若是以前響應過的,在cache存過,就不須要下面的操做了
  • 判斷類是否已經被建立,若是沒有被建立,則將類實例化
  • 第一次調用當前類的話,執行initialize的代碼
  • 嘗試獲取這個類的緩存 (這裏不少小夥伴就會質疑,爲何還要取一次內存,要知道OC是動態語言,在咱們執行這個獲取imp的時候,外界在開鎖,解鎖的時候是能夠訪問的,動態操做)
  • 若是沒有從cache中查找到,則從方法列表中獲取Method
  • 若是尚未,就從父類緩存或者方法列表獲取imp
  • 若是沒有找到,則嘗試動態方法解析
  • 若是沒有IMP被發現,而且動態方法解析也沒有處理,則進入消息轉發階段

裏面還有關於runtimeLock運行時鎖,這裏加鎖了read()對讀取,其中runtimeLock是經過pthread_rwlock_t實現的,更加底層的,你們若是感興趣鎖能夠參考這篇互斥鎖-讀寫鎖-條件鎖

以上設計了消息,動態方法解析,還有消息轉發,咱們在接下來的篇幅中還會更加深刻研究.咱們繼續回來,第三種runtime交互

  • 直接調用runtimeAPI

根據本文章特意收集了一個教程 Runtime及時詳解 須要的話能夠關注公衆號獲取

相關文章
相關標籤/搜索