這些知識(shí)老生常談了,我也寫過(guò)相關(guān)博客。可是感覺(jué)還是不咋地。今天從頭來(lái)過(guò),弄個(gè)專題分模塊專門仔細(xì)研究下。
NSObject定義
objc2.0 以前
typedef struct objc_class *Class;
@interface NSObject <NSObject> {
Class isa OBJC_ISA_AVAILABILITY;
}
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;
從上圖可以看出,objc_class 中有父類的指針,類名,版本信息等。除此之外還有 變量列表指針ivars ,方法列表methodLists,緩存cache和協(xié)議protocols。
objc 2.0 以后
objc 2.0 以后,數(shù)據(jù)結(jié)構(gòu)發(fā)生了變化
@interface NSObject <NSObject> {
Class isa OBJC_ISA_AVAILABILITY;
}
從上面我們 看出來(lái),NSObject 只有一個(gè)成員變量就是 isa ,類型是Class ,接下來(lái)我們看看class 是什么。
typedef struct objc_class *Class;
從上面我們看出來(lái)了 Class 是struct objc_class結(jié)構(gòu)體指針 ,那接下來(lái)我們看看這個(gè)結(jié)構(gòu)體是什么
struct objc_class : objc_object {
Class superclass;
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits;
}
objc_class 是繼承objc_object 對(duì)象。接下來(lái)看objc_object 的結(jié)構(gòu)體(在文件objc_private.h中)
struct objc_object {
private:
isa_t isa;
}
這里又出現(xiàn)了一個(gè)union isa_t 。接下來(lái)我們看看這個(gè)isa_t 是什么
union isa_t
{
Class cls;
uintptr_t bits;
# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL
# define ISA_MAGIC_MASK 0x000003f000000001ULL
# define ISA_MAGIC_VALUE 0x000001a000000001ULL
struct {
uintptr_t indexed : 1;
uintptr_t has_assoc : 1;
uintptr_t has_cxx_dtor : 1;
uintptr_t shiftcls : 33; // MACH_VM_MAX_ADDRESS 0x1000000000
uintptr_t magic : 6;
uintptr_t weakly_referenced : 1;
uintptr_t deallocating : 1;
uintptr_t has_sidetable_rc : 1;
uintptr_t extra_rc : 19;
# define RC_ONE (1ULL<<45)
# define RC_HALF (1ULL<<18)
};
# elif __x86_64__
# define ISA_MASK 0x00007ffffffffff8ULL
# define ISA_MAGIC_MASK 0x001f800000000001ULL
# define ISA_MAGIC_VALUE 0x001d800000000001ULL
struct {
uintptr_t indexed : 1;
uintptr_t has_assoc : 1;
uintptr_t has_cxx_dtor : 1;
uintptr_t shiftcls : 44; // MACH_VM_MAX_ADDRESS 0x7fffffe00000
uintptr_t magic : 6;
uintptr_t weakly_referenced : 1;
uintptr_t deallocating : 1;
uintptr_t has_sidetable_rc : 1;
uintptr_t extra_rc : 8;
# define RC_ONE (1ULL<<56)
# define RC_HALF (1ULL<<7)
};
# else
// Available bits in isa field are architecture-specific.
# error unknown architecture
# endif
}
UML 類結(jié)構(gòu)圖

1.在objc2.0中,所有的對(duì)象都會(huì)包含一個(gè)isa_t類型的結(jié)構(gòu)體。
- NSObject 類包含一個(gè) object_class 的指針 isa
- 在objc_class中,有3個(gè)成員變量,一個(gè)是父類的指針,一個(gè)是方法緩存,最后一個(gè)這個(gè)類的實(shí)例方法鏈表。
4.因?yàn)閛bjc_class 中也包含一個(gè)isa ,因此類也是一個(gè)對(duì)象。(為什么這么說(shuō)呢?我不認(rèn)為只要有個(gè)isa 就說(shuō)名類是個(gè)對(duì)象)
為什么說(shuō)objc_class 也是一個(gè)對(duì)象呢?
因?yàn)轭?可以和 對(duì)象 調(diào)用方法表現(xiàn)出一樣的特性。
我們都知道,當(dāng)一個(gè)對(duì)象的實(shí)例方法被調(diào)用的時(shí)候,會(huì)通過(guò)isa找到相應(yīng)的類,然后在該類的class_data_bits_t中去查找方法。class_data_bits_t是指向了類對(duì)象的數(shù)據(jù)區(qū)域。在該數(shù)據(jù)區(qū)域內(nèi)查找相應(yīng)方法的對(duì)應(yīng)實(shí)現(xiàn)。
要是想把類也當(dāng)成對(duì)象,那么我們也應(yīng)該讓類調(diào)用方法同對(duì)象調(diào)用方法一致就可以了。這樣就可以說(shuō)類也是一個(gè)對(duì)象了。
怎么才能讓類也表現(xiàn)出對(duì)象的特性呢?這里就引入了元類。
在引入元類之后,類對(duì)象和對(duì)象查找方法的機(jī)制就完全統(tǒng)一了。
對(duì)象的實(shí)例方法調(diào)用時(shí),通過(guò)對(duì)象的 isa 在類中獲取方法的實(shí)現(xiàn)。
類對(duì)象的類方法調(diào)用時(shí),通過(guò)類的 isa 在元類中獲取方法的實(shí)現(xiàn)。
meta-class之所以重要,是因?yàn)樗鎯?chǔ)著一個(gè)類的所有類方法。每個(gè)類都會(huì)有一個(gè)單獨(dú)的meta-class,因?yàn)槊總€(gè)類的類方法基本不可能完全相同。
這里我們做個(gè)簡(jiǎn)單測(cè)試。類和元類之間的isa關(guān)系
我們創(chuàng)建三個(gè)類 YoungMan ,Man, Persion,類關(guān)系如圖

測(cè)試代碼
-(void)printClassInfo:(char *)className{
Class objectClass;
Class superClass;
Class metaClass;
Class superMetaClass;
objectClass= objc_getClass(className);
superClass= class_getSuperclass(objectClass);
metaClass= objc_getMetaClass(className);
superMetaClass= class_getSuperclass(metaClass);
struct objc_object * obj = (__bridge struct objc_object *)metaClass;
NSLog(@"className %s objectClass %p superClass %p metaClass %p superMetaClass %p metaClass isa %p ",className,objectClass,superClass,metaClass,superMetaClass,obj->isa);
}
- (void)viewDidLoad {
[super viewDidLoad];
[self printClassInfo:"YoungMan"];
[self printClassInfo:"Man"];
[self printClassInfo:"Persion"];
[self printClassInfo:"NSObject"];
}
測(cè)試結(jié)果
2018-04-13 16:01:10.553250+0800 Runtime-class[2551:9731051] className YoungMan objectClass 0x10074a040 superClass 0x100749fa0 metaClass 0x10074a018 superMetaClass 0x100749f78 metaClass isa 0x101701e58
2018-04-13 16:01:10.553455+0800 Runtime-class[2551:9731051] className Man objectClass 0x100749fa0 superClass 0x100749f00 metaClass 0x100749f78 superMetaClass 0x100749ed8 metaClass isa 0x101701e58
2018-04-13 16:01:10.553594+0800 Runtime-class[2551:9731051] className Persion objectClass 0x100749f00 superClass 0x101701ea8 metaClass 0x100749ed8 superMetaClass 0x101701e58 metaClass isa 0x101701e58
2018-04-13 16:01:10.553723+0800 Runtime-class[2551:9731051] className NSObject objectClass 0x101701ea8 superClass 0x0 metaClass 0x101701e58 superMetaClass 0x101701ea8 metaClass isa 0x101701e58
測(cè)試結(jié)果繪制成圖如下圖
總結(jié)
1 實(shí)例對(duì)象的superclass 指向父類,最后指向rootClass(NSObject),RootClass 的父類是nil
- 實(shí)例對(duì)象的isa,指向元類
- 元類對(duì)象的superclass 指向元類父類最后指向RootClass的元類,而RootClass 的元類的父類執(zhí)行RootClass
4.元類對(duì)象的isa 都是指向根元類,包括根源類自己。
結(jié)構(gòu)體isa_t
我們從依賴和繼承關(guān)系來(lái)分析,isa_t 是最底層的。那么我們接下來(lái)看isa_t
isa_t 是個(gè)union類型結(jié)構(gòu)體 ,因此cls bits 或者定義的結(jié)構(gòu)體arm 或者x86等共用這一塊內(nèi)存。因?yàn)檫@些指針 、結(jié)構(gòu)體或者bits 都是64 位的。因此isa_t 也是64位。
兩種結(jié)構(gòu)體見(jiàn)圖

這里我們重點(diǎn)討論 這個(gè)x86 結(jié)構(gòu)體
struct {
uintptr_t indexed : 1;
uintptr_t has_assoc : 1;
uintptr_t has_cxx_dtor : 1;
uintptr_t shiftcls : 44; // MACH_VM_MAX_ADDRESS 0x7fffffe00000
uintptr_t magic : 6;
uintptr_t weakly_referenced : 1;
uintptr_t deallocating : 1;
uintptr_t has_sidetable_rc : 1;
uintptr_t extra_rc : 8;
# define RC_ONE (1ULL<<56)
# define RC_HALF (1ULL<<7)
};
isa 的初始化
inline void
objc_object::initIsa(Class cls)
{
initIsa(cls, false, false);
}
inline void
objc_object::initIsa(Class cls, bool indexed, bool hasCxxDtor)
{
assert(!isTaggedPointer());
if (!indexed) {
isa.cls = cls;
} else {
assert(!DisableIndexedIsa);
isa.bits = ISA_MAGIC_VALUE;
// isa.magic is part of ISA_MAGIC_VALUE
// isa.indexed is part of ISA_MAGIC_VALUE
isa.has_cxx_dtor = hasCxxDtor;
isa.shiftcls = (uintptr_t)cls >> 3;
}
}
從初始化我們能看出來(lái),我們傳入的參數(shù)
indexed hasCxxDotor 都是 false
因此我們進(jìn)入的流程是 判斷語(yǔ)句的else
isa.bits = ISA_MAGIC_VALUE;
// isa.magic is part of ISA_MAGIC_VALUE
// isa.indexed is part of ISA_MAGIC_VALUE
isa.has_cxx_dtor = hasCxxDtor;
isa.shiftcls = (uintptr_t)cls >> 3;
這里有個(gè)ISA_MAGIC_VALUE 宏定義
# define ISA_MAGIC_VALUE 0x001d800000000001ULL
給bits 賦值,其實(shí)就是給_x86 結(jié)構(gòu)體賦值,因此我們需要弄懂這個(gè)結(jié)構(gòu)體給x86 的那些地方賦值了。
這里先把ISA_MAGIC_VALUE 轉(zhuǎn)換成二進(jìn)制
| 0 | 0 | 1 | d | 8 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0000 | 0000 | 0001 | 1101 | 1000 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 | 0001 |
然后和結(jié)構(gòu)體對(duì)號(hào)入座,如下圖

從這張圖中我們能看出來(lái),我們其實(shí)初始化的位置是indexed 和margic 位置。
在結(jié)構(gòu)體 x86 中 的首位 indexed 表示 isa_t 的類型
0 表示 raw isa,也就是沒(méi)有結(jié)構(gòu)體的部分,訪問(wèn)對(duì)象的 isa 會(huì)直接返回一個(gè)指向 cls 的指針,也就是在 iPhone 遷移到 64 位系統(tǒng)之前時(shí) isa 的類型。
1 表示當(dāng)前 isa 不是指針,但是其中也有 cls 的信息,只是其中關(guān)于類的指針都是保存在 shiftcls 中。
margic 的值是 111011 = 0x3b; 用于調(diào)試器判斷當(dāng)前對(duì)象是真的對(duì)象還是沒(méi)有初始化的空間。
我們?cè)倏纯?strong>has_cxx_dtor,這一位表示當(dāng)前對(duì)象有 C++ 或者 ObjC 的析構(gòu)器(destructor),如果沒(méi)有析構(gòu)器就會(huì)快速釋放內(nèi)存。
我們?cè)賮?lái)看 shiftcls ,給其賦值
isa.shiftcls = (uintptr_t)cls >> 3;
從這里看出來(lái),shiftcls 其實(shí)是保存類的指針地址。
不過(guò)這里是先將類向右移動(dòng)三位在賦值給了shiftcls。
這是因?yàn)閷?dāng)前地址右移三位的主要原因是用于將 Class 指針中無(wú)用的后三位清楚減小內(nèi)存的消耗,因?yàn)轭惖闹羔樢凑兆止?jié)(8 bits)對(duì)齊內(nèi)存,其指針后三位都是沒(méi)有意義的 0。
絕大多數(shù)機(jī)器的架構(gòu)都是 byte-addressable 的,但是對(duì)象的內(nèi)存地址必須對(duì)齊到字節(jié)的倍數(shù),這樣可以提高代碼運(yùn)行的性能,在 iPhone5s 中虛擬地址為 33 位,所以用于對(duì)齊的最后三位比特為 000,我們只會(huì)用其中的 30 位來(lái)表示對(duì)象的地址。
ObjC 中的類指針的地址后三位也為 0
對(duì)象指針的后四位都是0
使用整個(gè)指針大小的內(nèi)存來(lái)存儲(chǔ) isa 指針有些浪費(fèi),尤其在 64 位的 CPU 上。在 ARM64 運(yùn)行的 iOS 只使用了 33 位作為指針(與結(jié)構(gòu)體中的 33 位無(wú)關(guān),Mac OS 上為 47 位),而剩下的 31 位用于其它目的。類的指針也同樣根據(jù)字節(jié)對(duì)齊了,每一個(gè)類指針的地址都能夠被 8 整除,也就是使最后 3 bits 為 0,為 isa 留下 34 位用于性能的優(yōu)化。
Using an entire pointer-sized piece of memory for the isa pointer is a bit wasteful, especially on 64-bit CPUs which don't use all 64 bits of a pointer. ARM64 running iOS currently uses only 33 bits of a pointer, leaving 31 bits for other purposes. Class pointers are also aligned, meaning that a class pointer is guaranteed to be divisible by 8, which frees up another three bits, leaving 34 bits of the isa available for other uses. Apple's ARM64 runtime takes advantage of this for some great performance improvements. from [ARM64 and You](https://www.mikeash.com/pyblog/friday-qa-2013-09-27-arm64-and-you.html)
做個(gè)實(shí)驗(yàn)驗(yàn)證下。
測(cè)試代碼
NSString *binaryWithInteger(NSUInteger decInt){
NSString * string =@"";
NSUInteger dec = decInt;
while (dec>0) {
string = [[NSString stringWithFormat:@"%lu",dec&1] stringByAppendingString:string];
dec =dec>>1;
}
while(string.length!=64) {
string = [@"0" stringByAppendingString:string];
}
return string;
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
struct objc_object * obj = (__bridge struct objc_object *)[NSObject new];
NSLog(@"%@",binaryWithInteger(obj->isa));
NSLog(@"%@", binaryWithInteger((uintptr_t)[NSObject class]));
}
return 0;
}
測(cè)試結(jié)果是
2018-04-13 16:49:05.683009+0800 測(cè)試Shifts 指針[3532:9769304] 0000000001011101111111111111111110010111100101100000000101000001
2018-04-13 16:49:05.684092+0800 測(cè)試Shifts 指針[3532:9769304] 0000000000000000011111111111111110010111100101100000000101000000
我們將所有的打印指針都補(bǔ)全到64 位
對(duì)象指針:
0000000001011101111111111111111110010111100101100000000101000001
類指針:
0000000000000000011111111111111110010111100101100000000101000000
對(duì)比下,指針完全相同。
接下來(lái)我們看其他的bits
has_assoc:對(duì)象含有或者曾經(jīng)含有關(guān)聯(lián)引用,沒(méi)有關(guān)聯(lián)引用的可以更快地釋放內(nèi)存
weakly_referenced:對(duì)象被指向或者曾經(jīng)指向一個(gè) ARC 的弱變量,沒(méi)有弱引用的對(duì)象可以更快釋放
deallocating:對(duì)象正在釋放內(nèi)存
has_sidetable_rc:對(duì)象的引用計(jì)數(shù)太大了,存不下
extra_rc:對(duì)象的引用計(jì)數(shù)超過(guò) 1,會(huì)存在這個(gè)這個(gè)里面,如果引用計(jì)數(shù)為 10,extra_rc 的值就為 9
這里我們使用結(jié)構(gòu)體取代了原有的isa指針,因此,我們提供了一個(gè)方法返回原來(lái)類指針
#define ISA_MASK 0x00007ffffffffff8ULL
inline Class
objc_object::ISA()
{
return (Class)(isa.bits & ISA_MASK);
}
這里有個(gè)宏定義 ISA_MASK 這個(gè)mask 就是獲取指shiftcls位置的數(shù)據(jù)用的。
cache_t
我們看完類的結(jié)構(gòu),接下來(lái)我們看objc_object中的cache_t
結(jié)構(gòu)
struct cache_t {
struct bucket_t *_buckets;
mask_t _mask;
mask_t _occupied;
}
typedef uint32_t mask_t;
struct bucket_t {
private:
cache_key_t _key;
IMP _imp;
}
typedef uintptr_t cache_key_t;
UML 圖

根據(jù)源碼,我們知道
cache_t 持有** bucket_t 的指針**
mask:分配用來(lái)緩存bucket的總數(shù)。
occupied:表明目前實(shí)際占用的緩存bucket的個(gè)數(shù)。
bucket_t的結(jié)構(gòu)體中存儲(chǔ)了一個(gè)unsigned long和一個(gè)IMP。IMP是一個(gè)函數(shù)指針,指向了一個(gè)方法的具體實(shí)現(xiàn)。
(這里Cache_key_t _key 為什么是unsinged long,我們知道地址的長(zhǎng)度也是unsinged long 其實(shí)就是把指針轉(zhuǎn)換成了個(gè)數(shù)字)
cache_t中的bucket_t *_buckets其實(shí)就是一個(gè)散列表,用來(lái)存儲(chǔ)Method的鏈表。
Cache的作用主要是為了優(yōu)化方法調(diào)用的性能。當(dāng)對(duì)象receiver調(diào)用方法message時(shí),首先根據(jù)對(duì)象receiver的isa指針查找到它對(duì)應(yīng)的類,然后在類的methodLists中搜索方法,如果沒(méi)有找到,就使用super_class指針到父類中的methodLists查找,一旦找到就調(diào)用方法。如果沒(méi)有找到,有可能消息轉(zhuǎn)發(fā),也可能忽略它。但這樣查找方式效率太低,因?yàn)橥粋€(gè)類大概只有20%的方法經(jīng)常被調(diào)用,占總調(diào)用次數(shù)的80%。所以使用Cache來(lái)緩存經(jīng)常調(diào)用的方法,當(dāng)調(diào)用方法時(shí),優(yōu)先在Cache查找,如果沒(méi)有找到,再到methodLists查找。
class_data_bits_t bits
這里好好分析下這個(gè) class_data_bits_t bits
結(jié)構(gòu)體里對(duì)class_data_bits_t bits 定義的部分的參數(shù)和相關(guān)方法是
struct objc_class : objc_object{
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
class_rw_t *data() {
return bits.data();
}
void setData(class_rw_t *newData) {
bits.setData(newData);
}
.....
下面的方法都是針對(duì)bits
}
好多方法都是針對(duì)這個(gè)bits 的那么我們就應(yīng)該看看這個(gè)bits 是什么了。
set 方法
void setData(class_rw_t *newData) {
bits.setData(newData);
}
這里我們看出來(lái)了。這里其實(shí)是傳入了一個(gè) class_rw_t 類型的指針
bits 調(diào)用了setData 方法。bits8 是struct class_data_bits_t
調(diào)用該結(jié)構(gòu)體的setData方法。
struct class_data_bits_t{
uintptr_t bits;
public://標(biāo)記public 公共方法
class_rw_t* data() {
return (class_rw_t *)(bits & FAST_DATA_MASK);
}
void setData(class_rw_t *newData)
{
assert(!data() || (newData->flags & (RW_REALIZING | RW_FUTURE)));
// Set during realization or construction only. No locking needed.
bits = (bits & ~FAST_DATA_MASK) | (uintptr_t)newData;
}
}
在get 和 set data 方法中 ,有這個(gè)宏定義
#define FAST_DATA_MASK 0x00007ffffffffff8UL
這個(gè)我們能看出來(lái),0x00000(44 個(gè)1 )000。指針是最多44 位 的。(因?yàn)閤86 是44 位,其他的是33位,滿足最長(zhǎng)要求嘛)
接下來(lái)分析setData方法
1 第一步判斷有沒(méi)有data , 并且將第flag 的19 位 和 30 位設(shè)置值
- 就是給bits 位 賦值,其實(shí)就是記錄class_rw_t 指針。
class_rw_t 結(jié)構(gòu)體

這里method_array_t property_array_t protocol_array_t 可以理解為一個(gè)數(shù)組吧。
因此這里把class 圖完全繪制出來(lái)
上面這張圖是objc1.0 和2.0 的對(duì)比圖
我們把objc1.0的中的元素到objc 2.0中找對(duì)應(yīng)關(guān)系
這里怎么找對(duì)應(yīng)關(guān)系呢?
就拿name 來(lái)說(shuō)吧
找到 objc_class 類,從中找獲取name 方法。
const char *mangledName() {
// fixme can't assert locks here
assert(this);
if (isRealized() || isFuture()) {
return data()->ro->name;
} else {
return ((const class_ro_t *)data())->name;
}
}
data()函數(shù)就是找到class_rw_t 指針.接著找到該指針的ro 指針,ro 是class_ro_t 結(jié)構(gòu)體,從這個(gè)結(jié)構(gòu)體中獲取name,如圖。其他一樣的方法。
圖中沒(méi)有標(biāo)記objc info 對(duì)應(yīng)0bjc2 中的位置,其實(shí)就是class_rw_t 指針的flags。
看大神博客,入院考試這里我還是忒下吧。為了以后看方便
入院考試
(一)[self class] 與 [super class]
下面代碼輸出什么?
@implementation Son : Father
- (id)init
{
self = [super init];
if (self)
{
NSLog(@"%@", NSStringFromClass([self class]));
NSLog(@"%@", NSStringFromClass([super class]));
}
return self;
}
@end
self和super的區(qū)別:
self是類的一個(gè)隱藏參數(shù),每個(gè)方法的實(shí)現(xiàn)的第一個(gè)參數(shù)即為self。
super并不是隱藏參數(shù),它實(shí)際上只是一個(gè)”編譯器標(biāo)示符”,它負(fù)責(zé)告訴編譯器,當(dāng)調(diào)用方法時(shí),去調(diào)用父類的方法,而不是本類中的方法。
在調(diào)用[super class]的時(shí)候,runtime會(huì)去調(diào)用objc_msgSendSuper方法,而不是objc_msgSend
OBJC_EXPORT void objc_msgSendSuper(void /* struct objc_super *super, SEL op, ... */ )
/// Specifies the superclass of an instance.
struct objc_super {
/// Specifies an instance of a class.
__unsafe_unretained id receiver;
/// Specifies the particular superclass of the instance to message.
#if !defined(__cplusplus) && !__OBJC2__
/* For compatibility with old objc-runtime.h header */
__unsafe_unretained Class class;
#else
__unsafe_unretained Class super_class;
#endif
/* super_class is the first class to search */
};
在objc_msgSendSuper方法中,第一個(gè)參數(shù)是一個(gè)objc_super的結(jié)構(gòu)體,這個(gè)結(jié)構(gòu)體里面有兩個(gè)變量,一個(gè)是接收消息的receiver,一個(gè)是當(dāng)前類的父類super_class。
入院考試第一題錯(cuò)誤的原因就在這里,誤認(rèn)為[super class]是調(diào)用的[super_class class]。
objc_msgSendSuper的工作原理應(yīng)該是這樣的:
從objc_super結(jié)構(gòu)體指向的superClass父類的方法列表開(kāi)始查找selector,找到后以objc->receiver去調(diào)用父類的這個(gè)selector。注意,最后的調(diào)用者是objc->receiver,而不是super_class!
那么objc_msgSendSuper最后就轉(zhuǎn)變成
// 注意這里是從父類開(kāi)始msgSend,而不是從本類開(kāi)始,謝謝@Josscii 和他同事共同指點(diǎn)出此處描述的不妥。
objc_msgSend(objc_super->receiver, @selector(class))
/// Specifies an instance of a class. 這是類的一個(gè)實(shí)例
__unsafe_unretained id receiver;
// 由于是實(shí)例調(diào)用,所以是減號(hào)方法
- (Class)class {
return object_getClass(self);
}
由于找到了父類NSObject里面的class方法的IMP,又因?yàn)閭魅氲娜雲(yún)bjc_super->receiver = self。self就是son,調(diào)用class,所以父類的方法class執(zhí)行IMP之后,輸出還是son,最后輸出兩個(gè)都一樣,都是輸出son。
(二)isKindOfClass 與 isMemberOfClass
下面代碼輸出什么?
@interface Sark : NSObject
@end
@implementation Sark
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
BOOL res1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];
BOOL res2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];
BOOL res3 = [(id)[Sark class] isKindOfClass:[Sark class]];
BOOL res4 = [(id)[Sark class] isMemberOfClass:[Sark class]];
NSLog(@"%d %d %d %d", res1, res2, res3, res4);
}
return 0;
}
這里需要對(duì)著幾個(gè)函數(shù)搞清楚
+ (Class)class {
return self;
}
- (Class)class {
return object_getClass(self);
}
Class object_getClass(id obj)
{
if (obj) return obj->getIsa();
else return Nil;
}
inline Class
objc_object::getIsa()
{
if (isTaggedPointer()) {
uintptr_t slot = ((uintptr_t)this >> TAG_SLOT_SHIFT) & TAG_SLOT_MASK;
return objc_tag_classes[slot];
}
return ISA();
}
inline Class
objc_object::ISA()
{
assert(!isTaggedPointer());
return (Class)(isa.bits & ISA_MASK);
}
+ (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = object_getClass((id)self); tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}
- (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}
+ (BOOL)isMemberOfClass:(Class)cls {
return object_getClass((id)self) == cls;
}
- (BOOL)isMemberOfClass:(Class)cls {
return [self class] == cls;
}
- (BOOL)isKindOfClass:(Class)cls方法內(nèi)部,會(huì)先去獲得object_getClass的類,而object_getClass的源碼實(shí)現(xiàn)是去調(diào)用當(dāng)前類的obj->getIsa(),最后在ISA()方法中獲得meta class的指針。
接著在isKindOfClass中有一個(gè)循環(huán),先判斷class是否等于meta class,不等就繼續(xù)循環(huán)判斷是否等于super class,不等再繼續(xù)取super class,如此循環(huán)下去。
同理,[Sark class]執(zhí)行完之后調(diào)用isKindOfClass,第一次for循環(huán),Sark的Meta Class與[Sark class]不等,第二次for循環(huán),Sark Meta Class的super class 指向的是 NSObject Meta Class, 和 Sark Class不相等。第三次for循環(huán),NSObject Meta Class的super class指向的是NSObject Class,和 Sark Class 不相等。第四次循環(huán),NSObject Class 的super class 指向 nil, 和 Sark Class不相等。第四次循環(huán)之后,退出循環(huán),所以第三行的res3輸出為NO。
如果把這里的Sark改成它的實(shí)例對(duì)象,[sark isKindOfClass:[Sark class],那么此時(shí)就應(yīng)該輸出YES了。因?yàn)樵趇sKindOfClass函數(shù)中,判斷sark的isa指向是否是自己的類Sark,第一次for循環(huán)就能輸出YES了。
isMemberOfClass的源碼實(shí)現(xiàn)是拿到自己的isa指針和自己比較,是否相等。
第二行isa 指向 NSObject 的 Meta Class,所以和 NSObject Class不相等。第四行,isa指向Sark的Meta Class,和Sark Class也不等,所以第二行res2和第四行res4都輸出NO。
**+ (BOOL)isKindOfClass:(Class)cls ** 尋找元類,以及元類的父類進(jìn)行比較,這里注意,元類的最后父類是Root (NSObject ) root 指向nil
**+ (Class)class ** 返回的是self, 是對(duì)象自己。
**- (BOOL)isKindOfClass:(Class)cls ** 比較的是class 不是元類
**+ (BOOL)isMemberOfClass:(Class)cls**, 直接比較
(三)Class與內(nèi)存地址
下面的代碼會(huì)?Compile Error / Runtime Crash / NSLog…?
這個(gè)有點(diǎn)難呀,感覺(jué)回到了高中考試一樣。
@interface Sark : NSObject
@property (nonatomic, copy) NSString *name;
- (void)speak;
@end
@implementation Sark
- (void)speak {
NSLog(@"my name's %@", self.name);
}
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
id cls = [Sark class];
void *obj = &cls;
[(__bridge id)obj speak];
}
@end
這道題有兩個(gè)難點(diǎn)。難點(diǎn)一,obj調(diào)用speak方法,到底會(huì)不會(huì)崩潰。難點(diǎn)二,如果speak方法不崩潰,應(yīng)該輸出什么?
首先需要談?wù)勲[藏參數(shù)self和_cmd的問(wèn)題。
當(dāng)[receiver message]調(diào)用方法時(shí),系統(tǒng)會(huì)在運(yùn)行時(shí)偷偷地動(dòng)態(tài)傳入兩個(gè)隱藏參數(shù)self和_cmd,之所以稱它們?yōu)殡[藏參數(shù),是因?yàn)樵谠创a中沒(méi)有聲明和定義這兩個(gè)參數(shù)。self在上面已經(jīng)講解明白了,接下來(lái)就來(lái)說(shuō)說(shuō)_cmd。_cmd表示當(dāng)前調(diào)用方法,其實(shí)它就是一個(gè)方法選擇器SEL。
難點(diǎn)一,能不能調(diào)用speak方法?
id cls = [Sark class];
void *obj = &cls;
答案是可以的。obj被轉(zhuǎn)換成了一個(gè)指向Sark Class的指針,然后使用id轉(zhuǎn)換成了objc_object類型。obj現(xiàn)在已經(jīng)是一個(gè)Sark類型的實(shí)例對(duì)象了。當(dāng)然接下來(lái)可以調(diào)用speak的方法。
調(diào)用 這個(gè)方法,發(fā)生了什么
第一步
id cls = [Sark class];
見(jiàn)圖

在棧上生成cls 而 Dark Class 在堆上
第二步
void *obj = &cls;
見(jiàn)圖

這張圖
這里obj 指向了cls 棧上的地址。
難點(diǎn)二,如果能調(diào)用speak,會(huì)輸出什么呢?
很多人可能會(huì)認(rèn)為會(huì)輸出sark相關(guān)的信息。這樣答案就錯(cuò)誤了。
正確的答案會(huì)輸出
my name is <ViewController: 0x7ff6d9f31c50>
內(nèi)存地址每次運(yùn)行都不同,但是前面一定是ViewController。why?
正常我們想要調(diào)用speak 方法流程是這樣的
Dark * dark = [[Dark alloc]init];
[dark speak];
看內(nèi)存變化
第一步
Dark * dark = [[Dark alloc]init];

我們會(huì)在堆區(qū)生成一個(gè)Dark對(duì)象,我們?cè)跅I嫌衐ark 對(duì)象的引用
當(dāng)我們調(diào)用 [dark speak];的時(shí)候,我們從棧上的dark 中取出地址,找到堆上的** Dark Object** dark 對(duì)象有方法表引用,在從方法表中找到speak 方法。(變量都是在堆上保存的)
而題目中上述例子,
[(__bridge id)obj speak];
流程應(yīng)該和dark 生成對(duì)象一樣。
我們找到 obj內(nèi)存中的id cls(是在棧上的)。
這里還有兩個(gè)知識(shí)點(diǎn)
實(shí)質(zhì):Objc中的對(duì)象是一個(gè)指向ClassObject地址的變量,即 id obj = &ClassObject , 而對(duì)象的實(shí)例變量 void *ivar = &obj + offset(N)
oc 方法調(diào)用的時(shí)候,我們都知道有兩個(gè)默認(rèn)參數(shù),self ,_cmd.
為什么是打印的是ViewController 呢?看大神博客吧。講 的太詳細(xì)了。(我就是把我剛看一臉懵逼的地方給重新梳理了下)