iOS Runtime詳解

1火焰、runtime(運行時機制)是什么

  • runtime是屬于OC的底層,是一套比較底層的純C語言API, 屬于1個C語言庫, 包含了很多底層的C語言API胧沫,可以進行一些非常底層的操作(用OC是無法現(xiàn)實的, 不好實現(xiàn))昌简。 在我們平時編寫的OC代碼中, 程序運行過程時, 其實最終都是轉(zhuǎn)成了runtime的C語言代碼, runtime算是OC的幕后工作者。

  • 例如:

    // OC : 
    [[MJPerson alloc] init] 
    // runtime : 
    objc_msgSend(objc_msgSend("MJPerson" , "alloc"), "init")
    

2琳袄、runtime知識圖譜

3江场、runtime數(shù)據(jù)結構

  • 3.1、objc_object
struct objc_object {
   private:
       isa_t isa;
   
   public:
   
       // ISA() assumes this is NOT a tagged pointer object
       Class ISA();
   
       // getIsa() allows this to be a tagged pointer object
       Class getIsa();
   
       // initIsa() should be used to init the isa of new objects only.
       // If this object already has an isa, use changeIsa() for correctness.
       // initInstanceIsa(): objects with no custom RR/AWZ
       // initClassIsa(): class objects
       // initProtocolIsa(): protocol objects
       // initIsa(): other objects
       void initIsa(Class cls /*indexed=false*/);
       void initClassIsa(Class cls /*indexed=maybe*/);
       void initProtocolIsa(Class cls /*indexed=maybe*/);
       void initInstanceIsa(Class cls, bool hasCxxDtor);
   
       // changeIsa() should be used to change the isa of existing objects.
       // If this is a new object, use initIsa() for performance.
       Class changeIsa(Class newCls);
   
       bool hasIndexedIsa();
       bool isTaggedPointer();
       bool isClass();
   
       // object may have associated objects?
       bool hasAssociatedObjects();
       void setHasAssociatedObjects();
   
       // object may be weakly referenced?
       bool isWeaklyReferenced();
       void setWeaklyReferenced_nolock();
   
       // object may have -.cxx_destruct implementation?
       bool hasCxxDtor();
   
       // Optimized calls to retain/release methods
       id retain();
       void release();
       id autorelease();
   
       // Implementations of retain/release methods
       id rootRetain();
       bool rootRelease();
       id rootAutorelease();
       bool rootTryRetain();
       bool rootReleaseShouldDealloc();
       uintptr_t rootRetainCount();
   
       // Implementation of dealloc methods
       bool rootIsDeallocating();
       void clearDeallocating();
       void rootDealloc();
   
   private:
       void initIsa(Class newCls, bool indexed, bool hasCxxDtor);
   
       // Slow paths for inline control
       id rootAutorelease2();
       bool overrelease_error();
   
   #if SUPPORT_NONPOINTER_ISA
       // Unified retain count manipulation for nonpointer isa
       id rootRetain(bool tryRetain, bool handleOverflow);
       bool rootRelease(bool performDealloc, bool handleUnderflow);
       id rootRetain_overflow(bool tryRetain);
       bool rootRelease_underflow(bool performDealloc);
   
       void clearDeallocating_slow();
   
       // Side table retain count overflow for nonpointer isa
       void sidetable_lock();
       void sidetable_unlock();
   
       void sidetable_moveExtraRC_nolock(size_t extra_rc, bool isDeallocating, bool weaklyReferenced);
       bool sidetable_addExtraRC_nolock(size_t delta_rc);
       size_t sidetable_subExtraRC_nolock(size_t delta_rc);
       size_t sidetable_getExtraRC_nolock();
   #endif
   
       // Side-table-only retain count
       bool sidetable_isDeallocating();
       void sidetable_clearDeallocating();
   
       bool sidetable_isWeaklyReferenced();
       void sidetable_setWeaklyReferenced_nolock();
   
       id sidetable_retain();
       id sidetable_retain_slow(SideTable& table);
   
       uintptr_t sidetable_release(bool performDealloc = true);
       uintptr_t sidetable_release_slow(SideTable& table, bool performDealloc = true);
   
       bool sidetable_tryRetain();
   
       uintptr_t sidetable_retainCount();
   #if DEBUG
       bool sidetable_present();
   #endif
};
  • 3.2窖逗、objc_class
struct objc_class : objc_object {
    Class superclass;
    const char *name;
    uint32_t version;
    uint32_t info;
    uint32_t instance_size;
    struct old_ivar_list *ivars;
    struct old_method_list **methodLists;
    Cache cache;
    struct old_protocol_list *protocols;
    // CLS_EXT only
    const uint8_t *ivar_layout;
    struct old_class_ext *ext;

    void setInfo(uint32_t set) {
        OSAtomicOr32Barrier(set, (volatile uint32_t *)&info);
    }

    void clearInfo(uint32_t clear) {
        OSAtomicXor32Barrier(clear, (volatile uint32_t *)&info);
    }


    // set and clear must not overlap
    void changeInfo(uint32_t set, uint32_t clear) {
        assert((set & clear) == 0);

        uint32_t oldf, newf;
        do {
            oldf = this->info;
            newf = (oldf | set) & ~clear;
        } while (!OSAtomicCompareAndSwap32Barrier(oldf, newf, (volatile int32_t *)&info));
    }

    bool hasCxxCtor() {
        // set_superclass propagates the flag from the superclass.
        return info & CLS_HAS_CXX_STRUCTORS;
    }

    bool hasCxxDtor() {
        return hasCxxCtor();  // one bit for both ctor and dtor
    }

    bool hasCustomRR() { 
        return true;
    }
    void setHasCustomRR(bool = false) { }
    void setHasDefaultRR() { }
    void printCustomRR(bool) { }

    bool hasCustomAWZ() { 
        return true;
    }
    void setHasCustomAWZ(bool = false) { }
    void setHasDefaultAWZ() { }
    void printCustomAWZ(bool) { }

    bool instancesHaveAssociatedObjects() {
        return info & CLS_INSTANCES_HAVE_ASSOCIATED_OBJECTS;
    }

    void setInstancesHaveAssociatedObjects() {
        setInfo(CLS_INSTANCES_HAVE_ASSOCIATED_OBJECTS);
    }

    bool shouldGrowCache() {
        return info & CLS_GROW_CACHE;
    }

    void setShouldGrowCache(bool grow) {
        if (grow) setInfo(CLS_GROW_CACHE);
        else clearInfo(CLS_GROW_CACHE);
    }

    bool shouldFinalizeOnMainThread() {
        return info & CLS_FINALIZE_ON_MAIN_THREAD;
    }

    void setShouldFinalizeOnMainThread() {
        setInfo(CLS_FINALIZE_ON_MAIN_THREAD);
    }

    // +initialize bits are stored on the metaclass only
    bool isInitializing() {
        return getMeta()->info & CLS_INITIALIZING;
    }

    // +initialize bits are stored on the metaclass only
    void setInitializing() {
        getMeta()->setInfo(CLS_INITIALIZING);
    }

    // +initialize bits are stored on the metaclass only
    bool isInitialized() {
        return getMeta()->info & CLS_INITIALIZED;
    }

    // +initialize bits are stored on the metaclass only
    void setInitialized() {
        getMeta()->changeInfo(CLS_INITIALIZED, CLS_INITIALIZING);
    }

    bool isLoadable() {
        // A class registered for +load is ready for +load to be called
        // if it is connected.
        return isConnected();
    }

    IMP getLoadMethod();

    bool isFuture();

    bool isConnected();

    const char *mangledName() { return name; }
    const char *demangledName() { return name; }
    const char *nameForLogging() { return name; }

    bool isMetaClass() {
        return info & CLS_META;
    }

    // NOT identical to this->ISA() when this is a metaclass
    Class getMeta() {
        if (isMetaClass()) return (Class)this;
        else return this->ISA();
    }

    // May be unaligned depending on class's ivars.
    uint32_t unalignedInstanceSize() {
        return instance_size;
    }

    // Class's ivar size rounded up to a pointer-size boundary.
    uint32_t alignedInstanceSize() {
        return (unalignedInstanceSize() + WORD_MASK) & ~WORD_MASK;
    }

    size_t instanceSize(size_t extraBytes) {
        size_t size = alignedInstanceSize() + extraBytes;
        // CF requires all objects be at least 16 bytes.
        if (size < 16) size = 16;
        return size;
    }

};
  • 3.3址否、isa
  • cache_t是用來存放每一個類中的方法緩存的數(shù)據(jù)結構,其本質(zhì)是一個可增量擴展的哈希表數(shù)據(jù)結構,使用cache_t可以快速查找方法的執(zhí)行函數(shù)佑附,也是局部性原理的最佳應用樊诺。試想一下,如果一個類中有眾多的類方法與對象方法音同,有些方法可能在程序執(zhí)行的生命周期中只會調(diào)用一次词爬,而在其他方法每一次調(diào)用時,系統(tǒng)都會遍歷類中所有的方法后找到其方法的相應IMP進行執(zhí)行权均,是一個非常浪費性能的工作顿膨,所以以此原因引入了cache_t,緩存方法列表叽赊,系統(tǒng)將一些常用方法儲存在類的緩存列表cache_t中恋沃,在每次方法調(diào)用時,先從緩存列表中查找必指,如果找到了對應方法囊咏,就直接調(diào)用其函數(shù)體,這是一種非常節(jié)約計算成本的方式塔橡。
  • 關于cache_t中的的數(shù)據(jù)結構梅割,使用的一張哈希表存儲的眾多bucket_t數(shù)據(jù)結構,在bucket_t中包含的是方法的實現(xiàn)IMP與其對應的key葛家,在一次消息傳遞的過程中户辞,系統(tǒng)首先會在對應的類中進行緩存查找,利用發(fā)送消息的SEL查找cache_t哈希表惦银,其過程是通過計算將SEL轉(zhuǎn)換成一個cache_key_t對象咆课,利用該"key"進行cache_t哈希表定位,隨后取出bucket_t扯俱,直接拿到IMP指針书蚪,進行消息的發(fā)送。
  • class_data_bits_t數(shù)據(jù)結構迅栅。class_data_bits_t主要的作用是包含類中的方法殊校、成員變量、協(xié)議等諸多主要信息读存,隨后再對一些零散信息的封裝为流。其實class_data_bits_t的主要作用也是對class_rw_t的一個封裝,重要的信息其實都在class_rw_t中
  • class_rw_t數(shù)據(jù)結構让簿。class_rw_t代表了類相關的讀寫信息敬察,以及對class_ro_t的封裝。顧名思義尔当,rw就是讀寫莲祸,ro就是只讀。class_rw_t內(nèi)的數(shù)據(jù)結構有,class_ro_t,protocols,properties,methods锐帜,第一個class_ro_t稍后詳細說明田盈。后面三個其實就是對協(xié)議、屬性缴阎、方法的封裝允瞧,這三個數(shù)據(jù)結構都是以二維數(shù)組的形式提現(xiàn)的,但是為什么是二維數(shù)組呢蛮拔?我之前寫過一篇關于分類理解的文章述暂,里面說到分類方法添加的問題,就是分類在運行時決議的過程中建炫,會把分類的方法都以數(shù)組的形式都添加到類方法中贸典,其實過程就在這里,分類中眾多method_t以數(shù)組[method1,method2,method3]的形式提現(xiàn)踱卵,但一個類可能有眾多分類,那么分類1据过,分類2惋砂,分類3在objc_class的class_data_bits_t的class_rw_t中的體現(xiàn)就是[[method1,method2,method3],[method1,method2,method3],[method1,method2,method3]],其都是method_t的數(shù)據(jù)形式绳锅。協(xié)議西饵,屬性的體現(xiàn)方式都是相似的,就同理后推就好了鳞芙。所以到這里肯定會有一個疑問眷柔,為什么class_rw_t中沒有成員變量只有屬性。因為class_rw_t中包含的是一個讀寫數(shù)據(jù)的列表原朝,換言之其實就是分類的列表驯嘱,class_rw_t只管分類中的數(shù)據(jù),之前分類那篇文章說到過喳坠,為分類添加屬性是不會生成相應的成員變量的鞠评,如果要生成對應的成員變量必須用關聯(lián)對象技術把使其達到一個類似于可讀寫效果,而其關聯(lián)對象都儲存在一個全局容器中壕鹉,這也呼應了開頭介紹objc_object數(shù)據(jù)結構中存儲的相關關聯(lián)對象的方法剃幌。好了,越繞越遠了晾浴,現(xiàn)在繼續(xù)介紹class_ro_t數(shù)據(jù)結構
  • class_ro_t數(shù)據(jù)結構负乡。這個數(shù)據(jù)結構包含的信息就是類本身的信息,包括name(類名),ivars(instence variables成員變量列表),properties(屬性列表),protocols(協(xié)議列表),methodList(方法列表)脊凰。所以這下好理解了吧抖棘,class_ro_t中裝的是類本身編譯的信息,class_rw_t中裝的是類中分類的信息,而class_data_bits_t封裝了class_rw_t封裝了class_ro_t钉答。不過需要注意區(qū)別的是class_ro_t中包含的ivas,properties,protocols,methodList都是一位數(shù)組的形式存在(因為沒有分類了嘛)础芍。所以這里還需要注意一個點就是:我們沒法向一個編譯后的類動態(tài)添加信息,比如方法数尿,成員變量仑性,屬性等,第一是因為這些都存在于class_ro_t中右蹦,其名稱含義就告訴你readOnly诊杆,人好好的存在那,說了不讓你改何陆,你硬要改晨汹,那肯定不行啊,第二是因為所謂向一個類中動態(tài)添加信息贷盲,都是指的用runtime動態(tài)添加的類淘这,通俗點講就是用代碼寫的類

  • 3.4、method_t

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; }
    };
};

4巩剖、類對象與元類對象&消息傳遞相關面試問題

  • 類對象存儲實例方法列表等信息铝穷。

  • 元類對象存儲類方法列表等信息。

為什么會有元類呢佳魔?

  • 因為在Objective-C中曙聂,對象的方法并沒有存儲在對象的結構體中(如果每個對象都存儲自己的方法,那我們程序中無數(shù)對象就都要存儲自己的方法鞠鲜,那內(nèi)存肯定就不夠用了)宁脊。當我們調(diào)用實例方法時,它通過自己的isa查找到對應的類贤姆,然后在class_data_bits_t結構體中查找對應的方法實現(xiàn)榆苞。每一個objc_class也有一個superClass指向自己的父類,可以查找到繼承的方法霞捡。那么如果調(diào)用實例方法怎么查找呢语稠?這時,就需要引入元類來保證無論調(diào)用類方法和實例方法弄砍,都可以以相同的方式來查找仙畦。

消息傳遞的機制

  • 消息傳遞機制在runtime中的提現(xiàn)是一個方法objc_msgSend(object,SEL,types),當我們在OC中用[]調(diào)起一個方法的同時,runtime內(nèi)部就會去調(diào)取這個方法進行消息方法音婶,還有一個方法是objc_msgSendSuper慨畸,參數(shù)還是一樣,這個方法是我們用關鍵字super去調(diào)起方法時runtime內(nèi)部所執(zhí)行的方法衣式,其內(nèi)部包含一個變量receiver寸士,這個變量指向的就是super的子類self自己檐什。之前看過一個案例,是在init內(nèi)部調(diào)用[self class]和[super class]弱卡,然后同時打印這兩個類名乃正。其實結果不出意外是一樣的,但分析原因的話就是因為[super class]調(diào)起時婶博,調(diào)用的是objc_msgSendSuper這個方法瓮具,其中包含一個指針receiver,指向的就是super對象調(diào)用者self自己凡人,也就是消息的接收者還是self名党,而class方法都是存在于較高父類(系統(tǒng)類中的),所以在方法遍歷的時候挠轴,[super class]和[self class]的區(qū)別就在于传睹,super是從父類開始向上遍歷直至找到class方法,而self是從本類開始向上遍歷最后找到對應方法岸晦,而調(diào)用class最后的結果取決于消息的接收者欧啤,因為兩個方法的消息接收者都是self,所以不出意外启上,最后打印出的結果都是self類本身堂油。

而實際消息當一個方法調(diào)用的消息發(fā)出后,消息傳遞的機制為:

  • 首先判斷方法為類方法還是實例方法 ->object找到父類class(若為類方法則再向上取到metaClass)->哈希查找objc_class的緩存列表cache_t試試能否用對用SEL直接找到函數(shù)體IMP->若找到碧绞,則直接進行函數(shù)體調(diào)用后結束消息放松,若找不到則遍歷對應的類或者元類的方法列表吱窝,如果方法列表是排序好的則使用二分法遍歷讥邻,如果沒有排序好則使用普通遍歷->如果找到了則進行函數(shù)體調(diào)用,結束消息傳遞流程院峡,若找不到則繼續(xù)根據(jù)superClass指針找到父類兴使,再重復之前的工作(遍歷緩存后遍歷本類中方法列表),在途中若找到了相應函數(shù)體實現(xiàn)IMP則進行調(diào)用照激,結束消息傳遞流程发魄,如果至到NSObject中還沒有找到則進行消息轉(zhuǎn)發(fā)的流程
  • 緩存查找

    • 1、給定值是SEL俩垃,目標值是對應bucket_t中的IMP励幼。
    image
    • 2、當前類中查找

      • 1口柳、對于已排序好的列表苹粟,采用二分查找算法查找方法對應執(zhí)行函數(shù)。

      • 2跃闹、對于沒有排序的列表嵌削,采用一般遍歷查找方法對應執(zhí)行函數(shù)毛好。

    • 3、父類逐級查找


      image

5苛秕、消息轉(zhuǎn)發(fā)流程

關于消息轉(zhuǎn)發(fā)的流程肌访,其實是系統(tǒng)給的一個消息再利用的過程,當消息傳遞流程中沒有方法來響應此消息時艇劫,開發(fā)者可通過重寫以下這四個方法來實現(xiàn)消息轉(zhuǎn)發(fā)的過程吼驶,以及去讓計算機做相應的工作。

  • 1港准、+ (BOOL)resolveInstanceMethod:(SEL)sel

若返回YES旨剥,則表明消息已處理,結束流程浅缸,若返回NO則進行下一個方法的調(diào)用

  • 2轨帜、- (id)forwardingTargetForSelector:(SEL)aSelector

此方法可以返回轉(zhuǎn)發(fā)消息的目標,若返回為nil則調(diào)用下一個方法

  • 3衩椒、- (NSMethodSignature *)methodSignatureForSelector

此方法可以返回方法的簽名蚌父,若返回nil則直接拋出異常,表明方法函數(shù)體指針無法被找到毛萌,若返回方法簽名苟弛,則調(diào)用下一個方法

  • 4、- (void)forwardInvocation:(NSInvocation *)anInvocation

此方法內(nèi)部會決定是否已處理方法阁将,如果到這個方法也沒法處理膏秫,則拋出異常,報錯

以上四個方法是系統(tǒng)留給開發(fā)者處理消息轉(zhuǎn)發(fā)的入口做盅,開發(fā)者可以通過重寫以上四個方法來手動處理消息轉(zhuǎn)發(fā)的流程缤削。

image
image

6、Method-Swizzling

image
image

7吹榴、動態(tài)添加方法

image

8亭敢、動態(tài)方法解析

  • @dynamic

    • 動態(tài)運行時語言將函數(shù)決議推遲到運行時。
最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末图筹,一起剝皮案震驚了整個濱河市帅刀,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌远剩,老刑警劉巖扣溺,帶你破解...
    沈念sama閱讀 216,324評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異瓜晤,居然都是意外死亡娇妓,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,356評論 3 392
  • 文/潘曉璐 我一進店門活鹰,熙熙樓的掌柜王于貴愁眉苦臉地迎上來哈恰,“玉大人只估,你說我怎么就攤上這事∽疟粒” “怎么了蛔钙?”我有些...
    開封第一講書人閱讀 162,328評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長荠医。 經(jīng)常有香客問我吁脱,道長,這世上最難降的妖魔是什么彬向? 我笑而不...
    開封第一講書人閱讀 58,147評論 1 292
  • 正文 為了忘掉前任兼贡,我火速辦了婚禮,結果婚禮上娃胆,老公的妹妹穿的比我還像新娘遍希。我一直安慰自己,他們只是感情好里烦,可當我...
    茶點故事閱讀 67,160評論 6 388
  • 文/花漫 我一把揭開白布凿蒜。 她就那樣靜靜地躺著,像睡著了一般胁黑。 火紅的嫁衣襯著肌膚如雪废封。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,115評論 1 296
  • 那天丧蘸,我揣著相機與錄音漂洋,去河邊找鬼。 笑死力喷,一個胖子當著我的面吹牛刽漂,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播冗懦,決...
    沈念sama閱讀 40,025評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼仇祭!你這毒婦竟也來了披蕉?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,867評論 0 274
  • 序言:老撾萬榮一對情侶失蹤乌奇,失蹤者是張志新(化名)和其女友劉穎没讲,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體礁苗,經(jīng)...
    沈念sama閱讀 45,307評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡爬凑,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,528評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了试伙。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片嘁信。...
    茶點故事閱讀 39,688評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡于样,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出潘靖,到底是詐尸還是另有隱情穿剖,我是刑警寧澤,帶...
    沈念sama閱讀 35,409評論 5 343
  • 正文 年R本政府宣布卦溢,位于F島的核電站糊余,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏单寂。R本人自食惡果不足惜贬芥,卻給世界環(huán)境...
    茶點故事閱讀 41,001評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望宣决。 院中可真熱鬧蘸劈,春花似錦、人聲如沸疲扎。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,657評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽椒丧。三九已至壹甥,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間壶熏,已是汗流浹背句柠。 一陣腳步聲響...
    開封第一講書人閱讀 32,811評論 1 268
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留棒假,地道東北人溯职。 一個月前我還...
    沈念sama閱讀 47,685評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像帽哑,于是被迫代替她去往敵國和親谜酒。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,573評論 2 353

推薦閱讀更多精彩內(nèi)容

  • 轉(zhuǎn)至元數(shù)據(jù)結尾創(chuàng)建: 董瀟偉妻枕,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 1,709評論 0 9
  • Runtime的特性主要是消息(方法)傳遞僻族,如果消息(方法)在對象中找不到,就進行轉(zhuǎn)發(fā)屡谐,具體怎么實現(xiàn)的呢述么。我們從下...
    jackyshan閱讀 137,866評論 79 765
  • 本文基于objc4-709源碼進行分析。關于源碼編譯:objc - 編譯Runtime源碼objc4-706 ob...
    WeiHing閱讀 819評論 1 3
  • 引導 runtime是運行時愕掏,對于從事iOS開發(fā)度秘,想要深入學習OC的人,runtime是必須熟悉掌握的東西饵撑。 ru...
    叫我小黑閱讀 904評論 1 4
  • 主要參考鏈接: http://yulingtianxia.com/blog/2014/11/05/objectiv...
    Kevin_Junbaozi閱讀 3,311評論 0 10