Objective-C在C的基礎(chǔ)上添加了面向?qū)ο蟮奶匦?,同時它是一種動態(tài)編程語言,將靜態(tài)語言在編譯和鏈接時需要做的一些事情給延后到運行時執(zhí)行。例如方法的調(diào)用,只有在程序執(zhí)行的時候,才能具體定位到哪個類的哪個方法。這就需要一個運行時庫,就是Runtime。
1. 類的結(jié)構(gòu)和定義
在Objective-C中,類實際上是一個objc_class結(jié)構(gòu)體,其定義如下:
typedef struct objc_class *Class;
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE;
const char *name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list *ivars OBJC2_UNAVAILABLE;
struct objc_method_list **methodLists OBJC2_UNAVAILABLE;
struct objc_cache *cache OBJC2_UNAVAILABLE;
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
struct objc_object {
Class isa OBJC_ISA_AVAILABILITY;
};
可以看到,在objc2.0中,除了isa指針外,objc_class的其他成員變量皆已被棄用。
其中isa是objc_class結(jié)構(gòu)體的指針,它指向當(dāng)前類的meta class。
-
meta class 與 class
在objc中,class存儲類的實例方法(-),meta class存儲類的類方法(+),class的isa指針指向meta class。下文會對此詳細(xì)介紹。
objc_object結(jié)構(gòu)體就是objc中的對象,它僅包含一個isa指針,指向當(dāng)前對象所屬的類。 我們常用的 id 實質(zhì)上就是一個objc_object類型的指針。

如圖1.1所示,一個對象(Instance of Subclass)的isa指針指向它所屬的類 Subclass(class),Subclass(class)的isa指針指向 Subclass(meta),Subclass(meta)的isa指針指向Root class(meta)。Root class(meta)的isa指針指向本身。
同時,Root class(meta)的父類是Root class(class),即NSObject,NSObject的父類為nil。
2. 方法的調(diào)用
在這里需要先了解幾個概念
SEL
SEL是objc_selector類型指針,是根據(jù)特定規(guī)則生成的方法的唯一標(biāo)識。需要注意的是,只要方法名相同,生成的SEL就相同,與這個方法屬于哪個類沒有關(guān)系。
typedef struct objc_selector *SEL;
IMP
如果說,SEL是方法名,那么IMP就是方法的實現(xiàn)。IMP指針定義了一個方法的入口,指向了實現(xiàn)方法的代碼塊的內(nèi)存地址。
typedef id (*IMP)(id, SEL, ...);
objc_method
在objc中,方法實質(zhì)上是一個objc_method指針。其中,method_name相當(dāng)于objc_method的hash值,runtime通過method_name找到相應(yīng)的方法入口(method_imp),從而執(zhí)行方法的代碼塊。
struct objc_method {
SEL method_name OBJC2_UNAVAILABLE;
char *method_types OBJC2_UNAVAILABLE;
IMP method_imp OBJC2_UNAVAILABLE;
} OBJC2_UNAVAILABLE;
調(diào)用一個方法時具體做了什么?
在Objective-C中,方法的調(diào)用采用如下方式:
[object methodWithArg:arg];
在編譯期間,以上代碼會被轉(zhuǎn)化為
objc_msgSend(object, methodWithArg, arg)
可以把它看作是發(fā)送消息的過,其中object為消息的接收體,它可能是一個對象,也可能是一個類。若為對象,則是實例方法(- 方法);反之,則是類方法(+方法)。mehodWithArg、arg是具體的消息內(nèi)容。
object接收到消息之后,若是實例方法,則會從其所屬的類Subclass(class)的methodLists去尋找methodWithArg:方法。若未找著,則到其父類Superclass(class)的methodLists中尋找。以此類推,直到根類NSObject,若仍未找著,就crash。
同理,若是類方法,則從對象所屬類的meta class開始尋找。
3. 在Objective-C 2.0中的變化
前面提到過在objc2.0中,objc_class只剩下一個isa指針。由于Xcode對API進(jìn)行了一定的封裝,類的信息并未全部對開發(fā)者開放。我們不妨通過閱讀Objective-C 2.0的源碼去分析,可以通過 官網(wǎng)瀏覽,或者從github上下載源碼。
從objc-runtime-new.h中可以看到objc_class的定義(只截取關(guān)鍵代碼,下文同)
struct objc_object {
isa_t isa;
};
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();
}
};
其中,superclass指向父類,cache緩存指針、方法入口等,用于提高效率。bits用于存儲類名、類版本號、方法列表、協(xié)議列表等信息,替代了Objective-C1.0中methodLists、protocols等成員變量。
class_data_bits_t結(jié)構(gòu)體
class_data_bits_t結(jié)構(gòu)體中只有一個64位的指針bits,它相當(dāng)于 class_rw_t 指針加上 rr/alloc 等標(biāo)志位。其中class_rw_t指針存在于4~47位(從1開始計)。

#define FAST_IS_SWIFT (1UL<<0)
#define FAST_DATA_MASK 0x00007ffffffffff8UL
is_swift標(biāo)記位標(biāo)示是否為swift的類。通過進(jìn)行位運算可以得到一個class_rw_t類型指針。
class_rw_t結(jié)構(gòu)體的定義如下
struct class_rw_t {
uint32_t flags;
uint32_t version;
const class_ro_t *ro;
method_array_t methods;
property_array_t properties;
protocol_array_t protocols;
Class firstSubclass;
};
其中methods存儲方法列表、properties存儲屬性列表、protocols存儲協(xié)議列表。注意到這里有一個class_ro_t類型指針,我們會在下文詳細(xì)介紹。
dyld加載鏡像
dyld是objc的動態(tài)鏈接庫,在程序運行時,會將鏡像加載進(jìn)內(nèi)存。
-
鏡像
工程的編譯產(chǎn)物,包括一些動態(tài)鏈接庫、Foundation等等,是一些二進(jìn)制文件。
在程序初始化方法_objc_init中注冊了兩個回調(diào)
dyld_register_image_state_change_handler(dyld_image_state_bound,1/*batch*/, &map_2_images);
dyld_register_image_state_change_handler(dyld_image_state_dependents_initialized, 0/*not batch*/, &load_images);
其中, map_2_images方法的注釋為:Process the given images which are being mapped in by dyld,即處理由dyld映射的給定鏡像。它的調(diào)用如下:
map_2_images → map_images_nolock → _read_images → realizeAllClasses
realizeAllClasses會完成對鏡像中所有類的加載和預(yù)處理,它最終會調(diào)用realizeClass來處理每一個類,而realizeClass又通過調(diào)用methodizeClass來對類結(jié)構(gòu)體的methods列表賦值。
可以通過添加符號斷點,來直觀的查看這幾個方法的調(diào)用關(guān)系,如圖3.2。

+load方法
+load方法會在main方法之前被調(diào)用,所有使用到的類的load方法都會被調(diào)用。先調(diào)用父類的+load方法,再調(diào)用子類的+load方法;先調(diào)用主類的+load方法,再調(diào)用分類的+load方法。

圖3.3是+load方法的調(diào)用棧。load_images 方法是每個鏡像加載完畢的回調(diào)。
const char *
load_images(enum dyld_image_states state, uint32_t infoCount,
const struct dyld_image_info infoList[])
{
bool found;
// Return without taking locks if there are no +load methods here.
found = false;
for (uint32_t i = 0; i < infoCount; i++) {
if (hasLoadMethods((const headerType *)infoList[i].imageLoadAddress)) {
found = true;
break;
}
}
if (!found) return nil;
recursive_mutex_locker_t lock(loadMethodLock);
// Discover load methods
{
rwlock_writer_t lock2(runtimeLock);
found = load_images_nolock(state, infoCount, infoList);
}
// Call +load methods (without runtimeLock - re-entrant)
if (found) {
call_load_methods();
}
return nil;
}
load_Images會判斷鏡像是否實現(xiàn)了+load方法,并且調(diào)用load_images_nolock方法找到所有+load方法,之后通過call_load_methods調(diào)用所有的+load方法。
class_ro_t
class_ro_t與class_rw_t的最大區(qū)別在于一個是只讀的,一個是可讀寫的,實質(zhì)上ro就是readonly的簡寫,rw是readwrite的簡寫。
struct class_ro_t {
const char * name;
method_list_t * baseMethodList;
protocol_list_t * baseProtocols;
const ivar_list_t * ivars;
};
在編譯之后,class_ro_t的baseMethodList就已經(jīng)確定。當(dāng)鏡像加載的時候,methodizeClass方法會將 baseMethodList 添加到class_rw_t的methods列表中,之后會遍歷category_list,并將category的方法也添加到methods列表中。
這里的category指的是分類,基于此,category能擴充一個類的方法。這是開發(fā)時經(jīng)常需要使用到。
class_ro_t在內(nèi)存中是不可變的。在運行期間,動態(tài)給類添加方法,實質(zhì)上是更新class_rw_t的methods列表。
baseProtocols與baseMethodList類似。
objc_object、objc_class、class_rw_t、class_ro_t的關(guān)系如圖3.4。

類的理解與方法的調(diào)用
對象方法:前面提過,調(diào)用對象方法,相當(dāng)于給對象發(fā)送消息,例如[obj methodWithArg: arg] 。 當(dāng)obj_object接收到消息后,通過其isa指針找到對應(yīng)的objc_class,objc_class又通過其data() 方法,查詢class_rw_t的methods列表。若有,則返回;否則,到其父類尋找。以此類推,直到根類,若在根類中仍沒有該方法,則crash。
類方法: 在objc中,類本身也是一個對象。objc_class繼承自objc_object,有一個isa指針,指向其所屬的類,即meta class??梢赃@樣理解,類是meta class 的對象。所以,當(dāng)調(diào)用類方法是,例如[classObj methodWithArg: arg],classObj也會通過其isa指針到其所屬的類(meta class)中尋找。這也就是為什么說,圖1.1 里class 存儲對象方法,meta class 存儲類方法。
meta class的isa指針:meta class本身也是一個對象,它的isa指針指向的也是其所屬的類。子meta class 的isa指針指向NSObjct 的meta class。 NSObjct 的meta class 的isa指針指向自身。當(dāng)然,由于蘋果進(jìn)行了封裝,在開發(fā)中基本不可能直接去使用meta class。
對象的成員變量尋址
前面提過,在objc_object中只有一個isa指針。實際上當(dāng)我們調(diào)用 +alloc 方法來初始化一個對象時,也僅僅在內(nèi)存中生成了一個objc_object結(jié)構(gòu)體,并根據(jù)其instanceSize來分配空間,將其isa指針指向所屬的類。
類的成員變量ivar_t存儲在class_ro_t中的ivar_list_t * ivars中,ivar_t的定義如下:
struct ivar_t {
int32_t *offset;
const char *name;
const char *type;
uint32_t size;
}
其中offset 是成員變量相對于對象內(nèi)存地址的偏移量,正是通過它來完成變量尋址。
當(dāng)我們使用對象的成員變量時,如 myObject.var ,編譯器會將其轉(zhuǎn)化為object_getInstanceVariable(myObject, 'var', **value) 找到其ivar_t結(jié)構(gòu)體ivar,然后調(diào)用object_getIvar(myObject, ivar)來獲取成員變量的內(nèi)存地址。其計算公式如下:
id *location = (id *)((char *)obj + ivar_offset);
基于此,雖然多個對象的isa指針指向同一個objc_class,但由于對象的內(nèi)存地址不一樣,所以它們的實例變量存儲位置也不一樣,從而實現(xiàn)對象與類之間的多對一關(guān)系。