類的方法和分類的方法重名,執(zhí)行的是哪一個(gè)方法宛蚓?
首先如果重名方法不是Load
方法篮灼,那么先執(zhí)行分類方法
那么如果重名方法是Load
方法,那么先執(zhí)行主類再執(zhí)行分類方法路操。
下面來解釋一下疾渴,為什么重名方法是Load
方法時(shí),它先執(zhí)行的是主類再執(zhí)行分類屯仗;
首先我們需要了解搞坝,類中方法的加載在objc
源碼中的load_images
方法中;來看看源碼實(shí)現(xiàn):!
看上圖方框中的代碼魁袜,就是發(fā)現(xiàn)方法桩撮,而call_load_methods
就是調(diào)用方法;
下面去看看prepare_load_methods
方法實(shí)現(xiàn):
在看到源碼實(shí)現(xiàn)后峰弹,可以清楚的看到_getObjc2NonlazyClassList
非懶加載類的加載店量。然后去看看類加載的實(shí)現(xiàn)在方框中的schedule_class_load
方法(后面的方法是加載分類的非懶加載方法,其代碼與下面主類方法的加載類似):
在這個(gè)方法中鞠呈,使用了遞歸調(diào)用融师,知道cls
不存在,就找完了蚁吝。接下來就是add_class_to_loadable_list
方法:
此方法的作用就是將所有非懶加載類的方法添加到loadable_classes
這張表中旱爆;
至于方法的發(fā)現(xiàn)就已經(jīng)清楚了,首先發(fā)現(xiàn)主類方法添加到表中窘茁,在add_class_to_loadable_list
中有源碼實(shí)現(xiàn)怀伦,然后加載分類方法,同樣添加到表中山林,在add_category_to_loadable_list
方法有實(shí)現(xiàn)房待。
接下來就是對(duì)類中方法的加載了,同樣的回答load_images
方法中的call_load_methods
方法:
看到這個(gè)方法驼抹,其實(shí)很簡單桑孩,首先對(duì)主類的加載調(diào)用,然后對(duì)分類方法的加載調(diào)用砂蔽,最后釋放內(nèi)存空間洼怔。
那么方法的加載就已經(jīng)很清晰了,首先調(diào)用call_class_loads
加載主類的方法左驾,然后調(diào)用call_category_loads
加載分類的方法,而所有方法的加載都在load_images
方法中。
這道面試題其實(shí)是有關(guān)方法的調(diào)用順序的诡右,下面做一個(gè)總結(jié):
普通方法:包括initialize安岂,因?yàn)榉诸惙椒ㄊ窃陬恟ealize之后attach進(jìn)去的,插在前面,所以優(yōu)先調(diào)用分類的方法
注意 : 不是分類覆蓋主類哦!!!!
Load方法:
1: 主類load
2: 分類load (分類之間 看編譯的順序)
方法的本質(zhì)帆吻,sel是什么?IMP是什么?兩者之間的關(guān)系又是什么域那?
SEL : 方法編號(hào)
IMP : 函數(shù)指針地址
方法的本質(zhì):發(fā)送消息, 消息會(huì)有以下幾個(gè)流程
1:快速查找 (objc_msgSend)~ cache_t 緩存消息
2:慢速查找~ 遞歸自己| 父類 ~ lookUpImpOrForward
3:查找不到消息: 動(dòng)態(tài)方法解析 ~ resolveInstanceMethod
4:消息快速轉(zhuǎn)發(fā)~ forwardingTargetForSelector
5:消息慢速轉(zhuǎn)發(fā)~ methodSignatureForSelector & forwardInvocation
sel 是方法編號(hào) ~ 在read_images 期間就編譯進(jìn)入了內(nèi)存
imp 就是我們函數(shù)實(shí)現(xiàn)指針 ,找imp 就是找函數(shù)的過程
sel 就相當(dāng)于書本的目錄 tittle
imp 就是書本的?碼 查找具體的函數(shù)就是想看這本書里面具體篇章的內(nèi)容
1:我們首先知道想看什么 ~ tittle (sel)
2:根據(jù)目錄對(duì)應(yīng)的?碼 (imp)
3:翻到具體的內(nèi)容
能否向編譯后的得到的類中增加實(shí)例變量?能否想運(yùn)行時(shí)創(chuàng)建的類中添加實(shí)例變量
答案:
1:不能向編譯后的得到的類中增加實(shí)例變量
2:只要內(nèi)沒有注冊(cè)到內(nèi)存還是可以添加
原因:我們編譯好的實(shí)例變量存儲(chǔ)的位置在 ro猜煮,一旦編譯完成次员,內(nèi)存結(jié)構(gòu)就完全確定 就無法修改
可以添加屬性 + 方法
[self class]和[super class]的區(qū)別以及原理分析
下面看一段代碼:
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
Teacher *teacher = [[Teacher alloc] init];;
NSLog(@"%@",teacher);
}
return 0;
}
@implementation Teacher
- (instancetype)init{
self = [super init];
if (self) {
NSLog(@"%@ - %@",[self class],[super class]);
}
return self;
}
當(dāng)執(zhí)行程序,打印的結(jié)果都會(huì)是Teacher
王带。
那么為什么會(huì)是這樣一個(gè)結(jié)果呢淑蔚?
首先[self class]
我們是可以理解的,他的底層實(shí)現(xiàn)是返回object_getClass(self)
愕撰,而object_getClass
方法獲取的是該類的Isa
刹衫,那就很好理解了,所以打印的是Teacher
搞挣。
而[super class]
就比較難理解了带迟,首先super
是一個(gè)關(guān)鍵字,而self
其實(shí)是init
方法的隱藏參數(shù)囱桨。那么現(xiàn)在就需要去了解super
是什么東西仓犬;
通過clang
轉(zhuǎn)換cpp文件,找到init
方法實(shí)現(xiàn):
static instancetype _I_Teacher_init(Teacher * self, SEL _cmd) {
self = ((Teacher *(*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("Teacher"))}, sel_registerName("init"));
if (self) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_hr_l_56yp8j4y11491njzqx6f880000gn_T_Teacher_d9115f_mi_0,((Class (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("class")),
objc_msgSendSuper(
{(id)self, (id)class_getSuperclass(objc_getClass("Teacher"))},
sel_registerName("class")));
}
return self;
}
可以看到super
關(guān)鍵字轉(zhuǎn)換的是class_getSuperclass
舍肠;
下圖是objc_super
的結(jié)構(gòu)搀继,他的第一個(gè)參數(shù)就是receiver
,也就是一個(gè)消息接收者貌夕,而看cpp
文件中init
實(shí)現(xiàn)后律歼,super
的第一個(gè)參數(shù)就是self
,那也就可以解釋的通了啡专,為什么打印的是Teacher
了险毁。
結(jié)果是對(duì)的,但是過程不一定就對(duì)了们童;
首先我們需要明白畔况,用self
和super
的區(qū)別在哪,那就是super
少走了一層方法慧库,這個(gè)在類和isa的走位圖中有詳細(xì)解釋跷跪,首先super
的調(diào)用,讓系統(tǒng)節(jié)約了在Teacher
這一層找方法齐板,而是直接從父類開始找方法吵瞻。
而super
的底層實(shí)現(xiàn)并不是class_getSuperclass
而是objc_msgSendSuper2
葛菇,那是如何知道的呢?
在init
方法的if判斷處打上斷點(diǎn)橡羞,通過查看匯編的形式去看:
可以看到它在之前調(diào)用了objc_msgSendSuper2
方法眯停,而不是走class_getSuperclass
,這就是運(yùn)行時(shí)的處理卿泽。而objc_msgSendSuper2
的底層實(shí)現(xiàn)是用匯編寫的莺债,就不詳細(xì)介紹了。
面試題签夭,內(nèi)存平移
首先看一段代碼:
- (void)viewDidLoad {
[super viewDidLoad];
Class cls = [Person class];
void *kc = &cls; //
Person *person = [Person alloc];
[(__bridge id)kc saySomething];
[person saySomething];
}
- (void)saySomething{
NSLog(@"%s",__func__);
}
看上面的代碼:kc
是指向cls
的內(nèi)存地址齐邦,當(dāng)使用cls
和kc
執(zhí)行saySomething
方法,打印的結(jié)果都是一樣的第租;
那么現(xiàn)在修改部分代碼:
NSLog(@"%s - %@",__func__,self.name);
其中name
是Person
類的一個(gè)NSString
類型的屬性措拇,并沒有對(duì)name
賦值;
那么執(zhí)行代碼后煌妈,結(jié)果卻不一樣了:
可以看到使用person
調(diào)用方法的結(jié)果為null
儡羔,這是什么愿意導(dǎo)致的?
下面打印一下類的地址信息:
Class cls = [Person class];
void *kc = &cls; //
Person *person = [Person alloc];
NSLog(@"%p - %p",&person,kc);
void *sp = (void *)&self;
void *end = (void *)&person;
long count = (sp - end) / 0x8;
for (long i = 0; i<count; i++) {
void *address = sp - 0x8 * i;
if ( i == 1) {
NSLog(@"%p : %s",address, *(char **)address);
}else{
NSLog(@"%p : %@",address, *(void **)address);
}
}
上圖打印的信息依次為:
self cmd (id)class_getSuperclass(objc_getClass("Person")) self cls kc person
這些信息都是改控制器中的內(nèi)存地址信息璧诵,類似于棧的結(jié)構(gòu)一樣汰蜘,先進(jìn)后出。
而在kc
和person
調(diào)用方法時(shí)之宿,首先由kc調(diào)用族操,打印的是棧頂?shù)牡谝粋€(gè)元素0x7ffeece44058 : <ViewController: 0x7fe5b5c0af70>
,而當(dāng)person調(diào)用方法時(shí)比被,它會(huì)先經(jīng)過平移內(nèi)存色难,去找到name
,平移8字節(jié)等缀,得到的是0x7ffeece44050 : viewDidLoad
枷莉,而取的值與屬性類型不匹配,因此打印的結(jié)果為null
尺迂,那么就很好理解了笤妙。
如果將name
類型改為int
型,再執(zhí)行程序會(huì)出現(xiàn)問題噪裕,因?yàn)?code>int是4字節(jié)蹲盘,無法識(shí)別讀取的內(nèi)容。