首先我們來創(chuàng)建一個(gè)類,然后再創(chuàng)建這個(gè)類的一個(gè)對(duì)象
創(chuàng)建一個(gè)父類Person
@interface Person : NSObject
{
int _age;
int _no;
int _height;
}
-(void)personInstanceMethod;
+(void)personClassMethod;
@end
@implementation Person
- (void)personInstanceMethod{
}
+ (void)personClassMethod{
}
@end
創(chuàng)建Person
的子類Student
烤芦,為什么要這樣創(chuàng)建举娩,方便后面講清楚對(duì)象與類,類與父類构罗,類與元類之間的關(guān)系
@interface Student : Person
{
int _sex;
}
-(void)studentInstanceMethod;
+(void)studentClassMethod;
@end
@implementation Student
- (void)studentInstanceMethod{
}
+ (void)studentClassMethod{
}
@end
然后我們創(chuàng)建一個(gè)Student的對(duì)象
Student *student = [[Student alloc]init];
[student personInstanceMethod];
在OC中每個(gè)對(duì)象都是一個(gè)結(jié)構(gòu)體铜涉,結(jié)構(gòu)體中都包含一個(gè)isa
的成員變量,其位于成員變量的第一位遂唧。isa
的成員變量之前都是Class類型的芙代,后來蘋果將其改為isa_t
。
struct objc_object {
private:
isa_t isa;
};
上面是對(duì)象的結(jié)構(gòu)體蠢箩,你們發(fā)現(xiàn)里面只有一個(gè)isa
指針,下面我們來看看類的結(jié)構(gòu)體
查看網(wǎng)上相關(guān)資料事甜,我們會(huì)發(fā)現(xiàn)出現(xiàn)了兩種結(jié)構(gòu)體
第一種
struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class _Nullable super_class OBJC2_UNAVAILABLE;
const char * _Nonnull name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;
struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
/* Use `Class` instead of `struct objc_class *` */
但是看里面的相關(guān)代碼會(huì)發(fā)現(xiàn)有個(gè)前置條件#if !__OBJC2__
谬泌,而我們現(xiàn)在基本上用的都是objective-c 2.0
,所以上面的結(jié)構(gòu)體只能反映以前的結(jié)構(gòu)
下面是新的結(jié)構(gòu)體
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();
}
}
注意逻谦,objc_class
是繼承于objc_object
的,因此objc_class
中也包含isa_t
類型的isa
。objc_class
的定義可以理解成下面這樣:
struct objc_class {
isa_t isa;
Class superclass;
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
}
對(duì)象與類仲闽,類與父類马胧,類與元類之間的關(guān)系
了解完內(nèi)部結(jié)構(gòu)后,我們就可以進(jìn)一步來闡述對(duì)象與類滋将,類與父類邻悬,類與元類之間的關(guān)系,其中元類和類采用的是同一種數(shù)據(jù)結(jié)構(gòu)
其中isa
必須指向一個(gè)地方随闽,而superclass
當(dāng)沒有父類的時(shí)候可以為nil
對(duì)象里面的
isa
指向自己的類父丰,類里面的superclass
指向自己的父類,isa
指向元類掘宪,通過上面這張圖蛾扇,再來理解下面這張隨處可見的圖攘烛,就容易理解多了,到此镀首,我們對(duì)象與類坟漱,類與父類,類與元類的關(guān)系就說清楚了
類中的實(shí)例方法更哄、類方法和屬性的結(jié)構(gòu)
在objc_class
中芋齿,我們主要研究class_rw_t
,rw_t
是read_write_table
的縮寫,意思是可讀可寫的表。bits.data()
返回class_rw_t
竖瘾,class_rw_t
里面存放的什么信息呢?我們點(diǎn)擊進(jìn)入class_rw_t
看看:
struct class_rw_t {
// Be warned that Symbolication knows the layout of this structure.
uint32_t flags;
uint32_t version;
const class_ro_t *ro;
method_array_t methods;//方法信息
property_array_t properties;//屬性信息
protocol_array_t protocols;//協(xié)議信息
}
可以看到我們想看的方法,屬性,協(xié)議信息.只是還沒看到成員變量信息. class_rw_t
中有一個(gè)class_ro_t
,ro_t
是readOnly_table
的縮寫,意思是只讀的表.我們點(diǎn)擊進(jìn)入const class_ro_t
看看里面存放哪些信息:
struct class_ro_t {
const char * name;//類名
method_list_t * baseMethodList;
protocol_list_t * baseProtocols;
const ivar_list_t * ivars;//成員變量
}
下面一張圖,能一目了然展示Class
的內(nèi)存結(jié)構(gòu):
元類的方法列表中主要是類方法的信息
結(jié)合上面的結(jié)構(gòu)沟突,再講講面試中經(jīng)常被問到的東西
SEL
:SEL(選擇器)
是方法的selector
的指針。方法的selector
表示運(yùn)行時(shí)方法的名字捕传。OC在編譯時(shí)惠拭,會(huì)依據(jù)每一個(gè)方法的名字、參數(shù)庸论,生成一個(gè)唯一的整型標(biāo)識(shí)(Int類型的地址)职辅,這個(gè)標(biāo)識(shí)就是SEL
。
IMP
:IMP
是一個(gè)函數(shù)指針聂示,指向方法最終實(shí)現(xiàn)的首地址域携。SEL
就是為了查找方法的最終實(shí)現(xiàn)IMP
。
Method
:用于表示類定義中的方法鱼喉,它的結(jié)構(gòu)體中包含一個(gè)SEL
和IMP
秀鞭,相當(dāng)于在SEL
和IMP
之間作了一個(gè)映射。
消息機(jī)制:任何方法的調(diào)用本質(zhì)就是發(fā)送一個(gè)消息扛禽。編譯器會(huì)將消息表達(dá)式
[receiver message]
轉(zhuǎn)化為一個(gè)消息函數(shù)objc_msgSend(receiver, selector)
锋边。
objc_msgSend
做了如下事情:
- 通過對(duì)象的isa指針獲取類的結(jié)構(gòu)體。
- 在結(jié)構(gòu)體的方法表里查找方法的selector编曼。
- 如果沒有找到selector豆巨,則通過objc_msgSend結(jié)構(gòu)體中指向父類的指針找到父類,并在父類的方法表里查找方法的selector掐场。
- 依次會(huì)一直找到NSObject往扔。
- 一旦找到selector,就會(huì)獲取到方法實(shí)現(xiàn)IMP熊户。
- 傳入相應(yīng)的參數(shù)來執(zhí)行方法的具體實(shí)現(xiàn)萍膛。
- 如果最終沒有定位到selector,就會(huì)走消息轉(zhuǎn)發(fā)流程
Runtime的使用:獲取屬性列表嚷堡,獲取成員變量列表卦羡,獲得方法列表,獲取協(xié)議列表,方法交換(黑魔法)绿饵,動(dòng)態(tài)的添加方法欠肾,調(diào)用私有方法,為分類添加屬性拟赊。
1.什么是isa
指針刺桃?
isa
指針的作用:當(dāng)我們向一個(gè)對(duì)象發(fā)送消息時(shí),runtime
會(huì)根據(jù)這個(gè)對(duì)象的isa
指針找到這個(gè)對(duì)象所屬的類吸祟,在這個(gè)類的方法列表及父類的方法列表中瑟慈,尋找與消息對(duì)應(yīng)的selector
指向的方法,找到后就運(yùn)行這個(gè)方法屋匕。
2.是如何找到IMP
的葛碧?
在尋找IMP
的地址時(shí),runtime
提供了兩種方法
IMP class_getMethodImplementation(Class cls, SEL name);
IMP method_getImplementation(Method m)
而根據(jù)官方描述过吻,第一種方法可能會(huì)更快一些
對(duì)于第一種方法而言进泼,類方法和實(shí)例方法實(shí)際上都是通過調(diào)用class_getMethodImplementation()
來尋找IMP
地址的,不同之處在于傳入的第一個(gè)參數(shù)不同類方法(假設(shè)有一個(gè)類A)
class_getMethodImplementation(objc_getMetaClass("A"),@selector(methodName));
類方法當(dāng)中傳入的是元類
實(shí)例方法
class_getMethodImplementation([A class],@selector(methodName));
而對(duì)于第二種方法而言纤虽,傳入的參數(shù)只有Method
乳绕,區(qū)分類方法和實(shí)例方法在于封裝Method
的函數(shù)
類方法
Method class_getClassMethod(Class cls, SEL name)
實(shí)例方法
Method class_getInstanceMethod(Class cls, SEL name)
最后調(diào)用
IMP method_getImplementation(Method m)
獲取IMP地址
3.消息轉(zhuǎn)發(fā)機(jī)制
當(dāng)最后我們要調(diào)用的方法找不到時(shí),通常我們會(huì)報(bào)一個(gè)“沒有此方法的錯(cuò)誤”逼纸,在報(bào)錯(cuò)之前洋措,我們實(shí)際上會(huì)有三次解決該錯(cuò)誤造成閃退的機(jī)會(huì)
當(dāng)對(duì)象無法接收消息,就會(huì)啟動(dòng)消息轉(zhuǎn)發(fā)機(jī)制杰刽,通過這一機(jī)制菠发,告訴對(duì)象如何處理未知的消息。
①動(dòng)態(tài)方法解析
對(duì)象接收到未知的消息時(shí)贺嫂,首先會(huì)調(diào)用所屬類的方法
(實(shí)例方法)
+resolveInstanceMethod:
或 者
(類方法)
+resolveClassMethod:
在這個(gè)方法中滓鸠,我們有機(jī)會(huì)為該未知消息新增一個(gè)”處理方法”。使用該“處理方法”的前提是已經(jīng)實(shí)現(xiàn)涝婉,只需要在運(yùn)行時(shí)通過class_addMethod
函數(shù)哥力,動(dòng)態(tài)的添加到類里面就可以了蔗怠。
②備用接收者
如果在上一步無法處理消息墩弯,則Runtime
會(huì)繼續(xù)調(diào)下面的方法。
- (id)forwardingTargetForSelector:(SEL)aSelector
如果這個(gè)方法返回一個(gè)對(duì)象寞射,則這個(gè)對(duì)象會(huì)作為消息的新接收者渔工。
注意這個(gè)對(duì)象不能是self自身,否則就是出現(xiàn)無限循環(huán)桥温。如果沒有指定對(duì)象來處理aSelector
引矩,則應(yīng)該
return [super forwardingTargetForSelector:aSelector]。
但是我們只將消息轉(zhuǎn)發(fā)到另一個(gè)能處理該消息的對(duì)象上,無法對(duì)消息進(jìn)行處理旺韭,例如操作消息的參數(shù)和返回值氛谜。
③完整消息轉(zhuǎn)發(fā)
如果在上一步還是不能處理未知消息,則唯一能做的就是啟用完整的消息轉(zhuǎn)發(fā)機(jī)制区端。此時(shí)會(huì)調(diào)用以下方法:
- (id)forwardInvocation:(NSInvocation *)anInvocation
這是最后一次機(jī)會(huì)將消息轉(zhuǎn)發(fā)給其它對(duì)象值漫。創(chuàng)建一個(gè)表示消息的NSInvocation
對(duì)象,把與消息的有關(guān)全部細(xì)節(jié)封裝在anInvocation
中织盼,包括selector
杨何,目標(biāo)(target)
和參數(shù)。在forwardInvocation
方法中將消息轉(zhuǎn)發(fā)給其它對(duì)象沥邻。
forwardInvocation:
方法的實(shí)現(xiàn)有兩個(gè)任務(wù):
a. 定位可以響應(yīng)封裝在
anInvocation
中的消息的對(duì)象危虱。b. 使用
anInvocation
作為參數(shù),將消息發(fā)送到選中的對(duì)象唐全。anInvocation
將會(huì)保留調(diào)用結(jié)果埃跷,runtime
會(huì)提取這一結(jié)果并發(fā)送到消息的原始發(fā)送者。
在這個(gè)方法中我們可以實(shí)現(xiàn)一些更復(fù)雜的功能芦瘾,我們可以對(duì)消息的內(nèi)容進(jìn)行修改捌蚊。另外,若發(fā)現(xiàn)消息不應(yīng)由本類處理近弟,則應(yīng)調(diào)用父類的同名方法缅糟,以便繼承體系中的每個(gè)類都有機(jī)會(huì)處理。
另外祷愉,必須重寫下面的方法:
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
消息轉(zhuǎn)發(fā)機(jī)制從這個(gè)方法中獲取信息來創(chuàng)建NSInvocation對(duì)象窗宦。
NSObject
的forwardInvocation
方法只是調(diào)用了doesNotRecognizeSelector
方法,它不會(huì)轉(zhuǎn)發(fā)任何消息二鳄。如果不在以上所述的三個(gè)步驟中處理未知消息赴涵,則會(huì)引發(fā)異常。這里為什么提到NSObject
订讼,因?yàn)?code>NSObject是最后的父類髓窜,一直找不到就會(huì)找到NSObject
中。
forwardInvocation
就像一個(gè)未知消息的分發(fā)中心欺殿,將這些未知的消息轉(zhuǎn)發(fā)給其它對(duì)象寄纵。或者也可以像一個(gè)運(yùn)輸站一樣將所有未知消息都發(fā)送給同一個(gè)接收對(duì)象脖苏,取決于具體的實(shí)現(xiàn)程拭。
消息的轉(zhuǎn)發(fā)機(jī)制可以用下圖來幫助理解。
4.isa
結(jié)構(gòu)體內(nèi)容
isa_t
定義
isa_t
是一個(gè)union
的結(jié)構(gòu)對(duì)象棍潘,union
類似于C++
結(jié)構(gòu)體恃鞋,其內(nèi)部可以定義成員變量和函數(shù)崖媚。
union isa_t
{
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL
# define ISA_MAGIC_MASK 0x000003f000000001ULL
# define ISA_MAGIC_VALUE 0x000001a000000001ULL
struct {
uintptr_t nonpointer : 1; // 是32位還是64位
uintptr_t has_assoc : 1; // 對(duì)象是否含有或曾經(jīng)含有關(guān)聯(lián)引用,如果沒有關(guān)聯(lián)引用恤浪,可以更快的釋放對(duì)象
uintptr_t has_cxx_dtor : 1; // 表示是否有C++析構(gòu)函數(shù)或OC的析構(gòu)函數(shù)
uintptr_t shiftcls : 33; // 對(duì)象指向類的內(nèi)存地址畅哑,也就是isa指向的地址
uintptr_t magic : 6; // 對(duì)象是否初始化完成
uintptr_t weakly_referenced : 1; // 對(duì)象是否被弱引用或曾經(jīng)被弱引用
uintptr_t deallocating : 1; // 對(duì)象是否被釋放中
uintptr_t has_sidetable_rc : 1; // 對(duì)象引用計(jì)數(shù)太大,是否超出存儲(chǔ)區(qū)域
uintptr_t extra_rc : 19; // 對(duì)象引用計(jì)數(shù)
# define RC_ONE (1ULL<<45)
# define RC_HALF (1ULL<<18)
};
# elif __x86_64__
// ····
# else
// ····
# endif
};
具體例子參考
runtime運(yùn)行時(shí) isa指針 SEL方法選擇器 IMP函數(shù)指針 Method方法 runtime消息機(jī)制 runtime的使用
內(nèi)容有部分參考:
OC對(duì)象的底層結(jié)構(gòu)及isa水由、superClass詳解
探秘Runtime - 剖析Runtime結(jié)構(gòu)體