RunTime原理之一:如何讓Objective-C支持面向?qū)ο蟮哪芰δ兀?/h2>

一、一切從面向?qū)ο箝_(kāi)始說(shuō)起

OC是一門(mén)基于C的面向?qū)ο蟮恼Z(yǔ)言,這是如何做到的呢?下面我們簡(jiǎn)單分析窺探下這背后的密碼。

「面向?qū)ο蟆褂心男┨匦阅兀?/h5>
  • 最基本的概念有,類(lèi)、對(duì)象(類(lèi)實(shí)例)、成員變量、方法。
  • OC特有的一些性質(zhì),屬性、協(xié)議、類(lèi)別/擴(kuò)展、

二、循序漸進(jìn)深入「面向?qū)ο蟆垢鞣N「概念」的封裝

1、類(lèi)(關(guān)鍵字Class

/// 類(lèi)
typedef struct objc_class *Class;

struct objc_class {
    Class isa;                                // 實(shí)現(xiàn)方法調(diào)用的關(guān)鍵
    Class super_class;                        // 父類(lèi)
    const char * name;                        // 類(lèi)名
    long version;                             // 類(lèi)的版本信息,默認(rèn)為0
    long info;                                // 類(lèi)信息,供運(yùn)行期使用的一些位標(biāo)識(shí)
    long instance_size;                       // 該類(lèi)的實(shí)例變量大小
    struct objc_ivar_list * ivars;            // 該類(lèi)的成員變量鏈表
    struct objc_method_list ** methodLists;   // 方法定義的鏈表
    struct objc_cache * cache;                // 方法緩存
    struct objc_protocol_list * protocols;    // 協(xié)議鏈表
};

其中關(guān)鍵字段:ivars(屬性鏈表)、methodLists(「類(lèi)實(shí)例方法」鏈表)、cache(方法緩存)、protocols(協(xié)議鏈表)、isa(指向類(lèi)的元類(lèi))。

OC設(shè)計(jì)者,將類(lèi)設(shè)計(jì)成一個(gè)對(duì)象!從struct objc_class里面有個(gè)isa指針,可以推斷出這點(diǎn)來(lái)。那么類(lèi)又是誰(shuí)的對(duì)象呢?其實(shí)是「元類(lèi)」,關(guān)于它將在第二章中說(shuō)明。

2、對(duì)象(關(guān)鍵字id

/// 對(duì)象
struct objc_object {
    Class isa;
};

/// id指針
typedef struct objc_object *id;

struct objc_objectisa 指向的是「對(duì)象」的「類(lèi)」;id實(shí)質(zhì) struct objc_object *指針,因此其可以指向任何對(duì)象。
例如NSObject *obj = [[NSObject alloc] init];,對(duì)象 obj 離的 isa指針 指向就是NSObject。

3、方法(關(guān)鍵字SEL IMP

OC利用 @ selector 可以獲取方法,其返回的是SEL,如SEL func = @selector(viewWillAppear:); 那么Runtime又是怎么封裝的呢?
還是 struct objc_class 里面的方法列表 struct objc_method_list ** methodLists; 說(shuō)起,看看結(jié)構(gòu)體struct objc_method_list 是怎么定義的。

// 方法列表
struct objc_method_list {
    struct objc_method_list *obsolete ;

    int method_count;

    /* variable length structure */
    struct objc_method method_list[1];
}

// Method
typedef struct objc_method *Method;
struct objc_method {
    SEL method_name;
    char * method_types;
    IMP method_imp;
};

// SEL
typedef struct objc_selector *SEL;

// IMP
typedef id (*IMP)(id, SEL, ...); 
  • SEL 是一個(gè)指向 objc_selector 結(jié)構(gòu)體的指針,而Runtime中沒(méi)有詳細(xì)的objc_selector的定義。
// 通過(guò)打印測(cè)試
SEL sel = @selector(viewWillAppear:);
NSLog(@"%s", sel);          // 輸出:viewWillAppear:

推斷而知,SEL其實(shí)就是字符串表示方法的名稱。

  • IMP,從定義而知,其實(shí)質(zhì)是一個(gè)函數(shù)指針,所指向的就是方法的實(shí)現(xiàn)。
    IMP = id + SEL + ...(即參數(shù)列表)。其中SEL就是方法名;參數(shù)列表就是方法參數(shù);id指向是self,對(duì)于實(shí)例方法來(lái)說(shuō), self 保存了當(dāng)前對(duì)象的地址;對(duì)于類(lèi)方法來(lái)說(shuō), self 保存了當(dāng)前對(duì)應(yīng)類(lèi)對(duì)象的地址。

也可以從NSObject提供的方法- (IMP)methodForSelector:(SEL)aSelector;+ (IMP)instanceMethodForSelector:(SEL)aSelector; 獲取到「方法」的具體「實(shí)現(xiàn)IMP」。

  • method_types,方法類(lèi)型,用來(lái)存儲(chǔ)方法的參數(shù)類(lèi)型和返回值類(lèi)型。

4、成員變量(關(guān)鍵字Ivar

類(lèi)結(jié)構(gòu)體 struct objc_class 里面有一個(gè)成員變量列表 struct objc_ivar_list * ivars;,保存「類(lèi)」的所有成員變量,它對(duì)應(yīng)關(guān)鍵字是Ivar,它的結(jié)構(gòu)體是struct objc_ivar。

// 成員變量列表
struct objc_ivar_list {
    int ivar_count;

    /* variable length structure */
    struct objc_ivar ivar_list[1];
};

typedef struct objc_ivar *Ivar;
struct objc_ivar {
    char *ivar_name; // 變量名稱
    char *ivar_type; // 變量類(lèi)型名
    int ivar_offset; // 基地址偏移字節(jié)
}

結(jié)構(gòu)體 struct objc_ivar 保存了「成員變量」的名稱、類(lèi)型,以及對(duì)應(yīng)的偏移地址。

5、協(xié)議,Category

typedef struct category_t *Category;

struct category_t {
    const char *name;                                // 類(lèi)名
    classref_t cls;                                  // 類(lèi),在運(yùn)行時(shí)階段通過(guò) clasee_name(類(lèi)名)對(duì)應(yīng)到類(lèi)對(duì)象
    struct method_list_t *instanceMethods;           // Category 中所有添加的對(duì)象方法列表
    struct method_list_t *classMethods;              // Category 中所有添加的類(lèi)方法列表
    struct protocol_list_t *protocols;               // Category 中實(shí)現(xiàn)的所有協(xié)議列表
    struct property_list_t *instanceProperties;      // Category 中添加的所有屬性
};

Category 的結(jié)構(gòu)體定義中也可以看出, Category 可以為類(lèi)添加對(duì)象方法、類(lèi)方法、協(xié)議、屬性。但是 Category 無(wú)法添加「成員變量」。

  • categoryextension的區(qū)別
    extension像是一個(gè)匿名的category,但是extension是在編譯期決議,它就是類(lèi)的一部分;而category是在運(yùn)行期決議的。
    extension一般用來(lái)隱藏類(lèi)的私有信息,必須有一個(gè)類(lèi)的源碼才能為一個(gè)類(lèi)添加extension;而category則不需要。

    extension可以添加實(shí)例變量,而category是無(wú)法添加實(shí)例變量的。(這是因?yàn)?code>category是在運(yùn)行期決議,而在運(yùn)行期的時(shí)候?qū)ο蟮膬?nèi)存布局已經(jīng)確定,如果添加實(shí)例變量就會(huì)破壞類(lèi)的內(nèi)部布局,這對(duì)編譯型語(yǔ)言來(lái)說(shuō)是災(zāi)難性的)。

6、屬性和屬性特性(關(guān)鍵字Property

// 屬性
typedef struct objc_property *Property;
typedef struct objc_property *objc_property_t;

// 屬性特性
typedef struct {
    const char * _Nonnull name;           /**< The name of the attribute */
    const char * _Nonnull value;          /**< The value of the attribute (usually empty) */
} objc_property_attribute_t;

通過(guò) clang 將OC代碼轉(zhuǎn)寫(xiě)為.cpp的相關(guān)代碼可知,本質(zhì)上 結(jié)構(gòu)體struct objc_property 其實(shí)就是 結(jié)構(gòu)體struct _prop_t,其大致結(jié)構(gòu)如下:

struct _prop_t {
    const char *name;  //名稱
    const char *attributes;  // readonly、assign的修飾屬性
};
成員變量與屬性的聯(lián)系

本質(zhì)上,一個(gè)屬性一定對(duì)應(yīng)一個(gè)成員變量。但是屬性又不僅僅是一個(gè)成員變量,屬性還會(huì)根據(jù)自己對(duì)應(yīng)的屬性特性的定義來(lái)對(duì)這個(gè)成員變量進(jìn)行一系列的封裝:提供 Getter/Setter 方法、內(nèi)存管理策略、線程安全機(jī)制等等。

7、協(xié)議(關(guān)鍵字Protocol

struct objc_protocol_list {
    struct objc_protocol_list * _Nullable next;
    long count;
    __unsafe_unretained Protocol * _Nullable list[1];
};

// 宏__OBJC__保證只有OC文件可以調(diào)用.h里面的頭文件,一些非OC語(yǔ)言不能調(diào)用
#ifdef __OBJC__  
@class Protocol;  // OC當(dāng)做類(lèi)
#else
typedef struct objc_object Protocol; 
#endif

實(shí)質(zhì)上 Protocol 對(duì)應(yīng)的結(jié)構(gòu)體 struct objc_object,內(nèi)部只有一個(gè)isa指針,指向?qū)?yīng)的類(lèi)。

8、cache

typedef struct objc_cache *Cache

struct objc_cache {
    unsigned int mask /* total = mask + 1 */  ;
    unsigned int occupied;
    Method buckets[1];
};

方法調(diào)用的時(shí)候,會(huì)現(xiàn)在緩存中查找,找到返回,否則再去方法列表中找。這樣設(shè)計(jì)的目的在于提高方法調(diào)用效率。

三、總結(jié)

上面學(xué)習(xí)了Runtime對(duì)于面向?qū)ο蟾拍畹囊恍┓庋b,其實(shí)質(zhì)用C語(yǔ)言的結(jié)構(gòu)體、指針、函數(shù)指針等;大量使用鏈表、map等作為存儲(chǔ);設(shè)計(jì)算法職稱,如緩存,讓Objective-C具有面向?qū)ο蟮哪芰Α?/p>

Runtime針對(duì)上面的每一個(gè)結(jié)構(gòu)體,都提供豐富API接口,允許對(duì)其進(jìn)行操作。也正是如此設(shè)計(jì),讓Objective-C能在運(yùn)行時(shí),獲取、創(chuàng)建、修改類(lèi)相關(guān)的數(shù)據(jù),具備強(qiáng)大的動(dòng)態(tài)能力。

后面章節(jié),我們將繼續(xù)學(xué)習(xí),Runtime是如何利用這些結(jié)構(gòu)體實(shí)現(xiàn)面向?qū)ο蟮哪芰Φ摹?/p>

其他

新手也看得懂的 iOS Runtime 教程

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容