一、一切從面向?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é)議鏈表
};
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_object的 isa 指向的是「對(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ú)法添加「成員變量」。
-
category和extension的區(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>