上一篇文章《iOS-底層原理16-類擴(kuò)展和關(guān)聯(lián)對象底層原理》介紹了關(guān)聯(lián)對象底層原理
1.分析AssociationsManager不唯一和AssociationsHashMap唯一
模擬新建AssociationsManager和AssociationsHashMap兩個(gè)徽鼎,查看內(nèi)存地址是否一致祭务,先將AssociationsManager構(gòu)造函數(shù)中的鎖去掉夫否,否則會死鎖
不同的AssociationsManager manager和manager1創(chuàng)建的associations和associations1內(nèi)存地址一樣,唯一
AssociationsManager構(gòu)造函數(shù)去掉鎖
manager不唯一,manager內(nèi)存地址不可讀,調(diào)用初始化方法,并不會調(diào)用init方法,那么init方法在什么時(shí)候調(diào)用的呢?斷點(diǎn)調(diào)試下
AssociationsManager() { }
~AssociationsManager() { }
在arr_init()中調(diào)用AssociationsManager::init()方法饿悬,屬于類方法對AssociationsManager進(jìn)行環(huán)境準(zhǔn)備并沒有初始化,init()方法并沒有返回值,map_images-->arr_init()-->_objc_associations_init()-->AssociationsManager::init()-->_mapStorage.init()
2.加鎖的原因:會對唯一的表AssociationsHashMap中的數(shù)據(jù)進(jìn)行讀取和安放聚霜,防止多線程對數(shù)據(jù)進(jìn)行篡改而導(dǎo)致數(shù)據(jù)不同步
相當(dāng)于如下:操作前加鎖狡恬,操作后解鎖
3.整體結(jié)構(gòu):
AssociationsHashMap:整個(gè)項(xiàng)目
LGPerson LGTeacher LGStudent
對象 key -> ObjctAssociationMap (LGPerson)(name age hobby)
key -> ObjcAssociation(policy value)
Buckets桶子里面包含桶子,Buckets桶子的結(jié)構(gòu)為objc::detail::DenseMapPair<const void *, objc::ObjcAssociation>蝎宇,結(jié)構(gòu)復(fù)用膝舅,代碼復(fù)用
4.面試題:請問關(guān)聯(lián)對象設(shè)置后是否應(yīng)該移除论笔?不需要移除,為什么呢?
對象在釋放的時(shí)候會自動(dòng)移除创坞,進(jìn)入dealloc方法,- (void)dealloc
-->_objc_rootDealloc(self)
--> obj->rootDealloc()
--> object_dispose((id)this)
--> objc_destructInstance(obj)
--> _object_remove_assocations(obj)
,從總表AssociationsHashMap中挨個(gè)移除婆廊,Bucket移除
5.面試題:主類和分類同名方法极颓,優(yōu)先調(diào)用哪一個(gè)湿痢?
- 非load方法會先調(diào)用分類中的
-
2.load方法會先調(diào)用主類的,再調(diào)用分類的台囱,為什么會是先主類后分類呢淡溯?
image.png
image.png
5.load_images分析
prepare_load_methods((const headerType *)mh)
--> schedule_class_load(remapClass(classlist[i]))
--> add_class_to_loadable_list(cls)
在schedule_class_load方法中進(jìn)行遞歸,將父類中的方法進(jìn)行添加loadable_classes,若類有l(wèi)oad方法簿训,將類添加到loadable_classes中咱娶,已經(jīng)開辟的和正在使用的classes_loadable類數(shù)量是否相等,若相等强品,則進(jìn)行擴(kuò)容
method = cls->getLoadMethod(),判斷是否是load方法膘侮,返回imp
類中的load方法加載完成,再加載分類中的load方法的榛,加到loadable_categories琼了,若已經(jīng)使用的分類和開辟的相等,則進(jìn)行擴(kuò)容
調(diào)用load方法call_load_methods(),循環(huán)先調(diào)用類中l(wèi)oad方法困曙,后調(diào)用分類中l(wèi)oad方法,函數(shù)指針消息發(fā)送(*load_method)(cls, @selector(load))傳入兩個(gè)參數(shù)cls和@selector(load)
調(diào)用完畢后表伦,調(diào)用自動(dòng)釋放池回收整片內(nèi)存空間
load_images分析流程圖
面試題
initialize方法在第一次消息發(fā)送的時(shí)候調(diào)用
load方法調(diào)用是先主類后分類
其他方法并不是分類覆蓋了主類谦去,而是分類中的方法編譯時(shí)寫在了前面慷丽,會先調(diào)用
Runtime是什么
runtime是由C和C++匯編實(shí)現(xiàn)的一套API,為OC語言加入了面向?qū)ο蟊暮撸\(yùn)行時(shí)的功能
運(yùn)行時(shí)(Runtime)是指將數(shù)據(jù)類型的確定由編譯時(shí)推遲到了運(yùn)行時(shí)
舉例子:extension-category的區(qū)別
平常編寫的OC代碼,在程序運(yùn)行過程中要糊,其實(shí)最終會轉(zhuǎn)換成Runtime的C語言代碼纲熏,Runtime
是Object-C
的幕后工作者
6.[self class]和[super class]的區(qū)別
2020-12-27 19:12:35.385708+0800 KCObjc[65535:2691177] LGTeacher - LGTeacher
2020-12-27 19:12:35.386547+0800 KCObjc[65535:2691177] <LGTeacher: 0x100507b90>
- 1.[self class]會調(diào)用object_getClass(self),返回對象的isa锄俄,也就是類LGTeacher
Class object_getClass(id obj)
{
if (obj) return obj->getIsa();
else return Nil;
}
-
2.super是關(guān)鍵字局劲,clang LGTeacher.m查看LGTeacher的源碼得到
image.png
[super class]編譯為了如下代碼,__rw_objc_super為中間形態(tài),搜objc_msgSendSuper源碼
((Class (*)(__rw_objc_super *, SEL))(void
*)objc_msgSendSuper)((__rw_objc_super){(id)self,
(id)class_getSuperclass(objc_getClass("LGTeacher"))},
sel_registerName("class"))
objc_msgSendSuper結(jié)構(gòu)第一個(gè)參數(shù)為結(jié)構(gòu)體struct objc_super *super奶赠,結(jié)構(gòu)體中有兩個(gè)參數(shù)id receiver,class super_class,故源碼中是self調(diào)用sel_registerName("class"),在方法instancetype _I_LGTeacher_init中的self為LGTeacher對象鱼填,[super class]相當(dāng)于是[self class],和[self class]是同一套調(diào)用源碼流程順序,會調(diào)用object_getClass(self)毅戈,返回對象的isa苹丸,也就是類LGTeacher
// super : 關(guān)鍵字
// [super class] (class)(id self , sel _cmd)
// self->isa LGTeacher
// self 消息的接受者 LGTeacher
運(yùn)行程序,斷點(diǎn)object_getClass苇经,發(fā)現(xiàn)方法會進(jìn)入兩次赘理,間接證明輸出的是LGTeacher
3.[self class]和[super class]的區(qū)別,self再去查找class方法的時(shí)候扇单,不先從本類中去查找了商模,直接從父類中去查找,跳過了本類的查找流程蜘澜,比[self class]查找速度更快super_class is the first class to search,消息的接收者還是self施流,查找的方式變化了
假設(shè)將[super class]改為[LGPerson class]呢,查看源碼
由上面可知編譯時(shí)期[super class]調(diào)用的方法為objc_msgSendSuper,那么運(yùn)行時(shí)期呢鄙信?實(shí)質(zhì)上調(diào)用的方法為objc_msgSendSuper2
將當(dāng)前類傳入結(jié)構(gòu)體struct objc_super中瞪醋,在結(jié)構(gòu)體內(nèi)部再取當(dāng)前類的父類,而不是現(xiàn)在就把當(dāng)前類的父類傳進(jìn)去扮碧,結(jié)構(gòu)體內(nèi)部objc_super中會從類的父類super_class開始查找趟章,這一點(diǎn)和objc_msgSendSuper不一樣,消息的接收者receiver還是本類self,[super class]還是輸出LGTeacher
[self class]從本類中查找class方法,[super class]從父類中開始查找class方法
查看匯編代碼:是直接從superclass中查找class方法
完整回答:[self class]和[super class]兩個(gè)都會輸出LGTeacher,[self class]調(diào)用的本質(zhì)是消息發(fā)送msgSend慎王,通過調(diào)用class底層方法獲取到對象的isa即LGPerson元類型蚓土,類已經(jīng)加載到內(nèi)存,獲取元類類型赖淤,在map_images的readClass方法中類名已經(jīng)加載到類名表中蜀漆,讀取%@時(shí)是一個(gè)字符串類型,打印LGTeacher,super是一個(gè)關(guān)鍵字咱旱,底層調(diào)用objc_msgSendSuper2,消息接收者為self确丢,和[self class]消息接收者一模一樣绷耍,返回LGTeacher
7.內(nèi)存平移
- (void)viewDidLoad {
[super viewDidLoad];
Class cls = [LGPerson class];
void *kc = &cls;
[(__bridge id)kc saySomething];
}
#import "LGPerson.h"
@implementation LGPerson
- (void)saySomething{
NSLog(@"%s",__func__);
}
輸出的結(jié)果一模一樣,可以正常調(diào)用實(shí)例方法鲜侥,kc調(diào)用saySomething和person對象調(diào)用saySomething指向的內(nèi)存空間一致褂始,可以調(diào)用
-
在saySomething方法中增加獲取self.kc_name,[person saySomething]打印的self.kc_name為nil,[(__bridge id)kc saySomething]打印的self.kc_name為什么呢描函?
image.png
打印出來的結(jié)果為-[LGPerson saySomething] - <ViewController: 0x7f984b6063a0>
崎苗,為什么打印的是ViewController?舀寓?胆数?
-
1.分析[person saySomething]調(diào)用self.kc_name的內(nèi)存平移情況,在函數(shù)-(void)viewDidLoad中互墓,棧的情況必尼,棧是先進(jìn)后出,最先壓棧的是-(void)viewDidLoad方法中的隱藏的兩個(gè)參數(shù)(self篡撵,_cmd)判莉,之后[super viewDidLoad]方法clang編譯會生成一個(gè)結(jié)構(gòu)體
{(id)self, (id)class_getSuperclass(objc_getClass("ViewController"))}
結(jié)構(gòu)體兩個(gè)參數(shù)self和super_class分別壓入棧中
image.png 2.分析棧的情況
隱藏參數(shù)會壓入棧幀:參數(shù)從前往后一直壓棧,棧區(qū)是從高地址到低地址酸休,person先壓入棧分配的是高地址骂租,person2和person3后壓入棧,依次分配比person低的地址
image.png- 函數(shù)調(diào)用的壓棧情況[super viewDidLoad]分析:查看編譯源碼分析能得到,會生成一個(gè)結(jié)構(gòu)體{(id)self, (id)class_getSuperclass(objc_getClass("LGTeacher"))}斑司,會傳入結(jié)構(gòu)體屬性self和(id)class_getSuperclass(objc_getClass("ViewController")),那么結(jié)構(gòu)體屬性渗饮,是怎么一個(gè)壓棧情況呢
objc_msgSendSuper({(id)self, (id)class_getSuperclass(objc_getClass("LGTeacher"))}, sel_registerName("class")));
結(jié)構(gòu)體屬性的壓棧情況:結(jié)構(gòu)體里面的屬性num2的內(nèi)存地址為0x00007ffeed838178,num1的內(nèi)存地址為0x00007ffeed838170宿刮,person3的內(nèi)存地址為0x00007ffeed838168互站,說明結(jié)構(gòu)體中的屬性,后面的屬性先壓棧僵缺,即先壓入20胡桃,再壓入10,因此objc_msgSendSuper方法中的結(jié)構(gòu)體{(id)self, (id)class_getSuperclass(objc_getClass("LGTeacher"))}先入棧的是super_class,后入棧的是self,壓棧順序?yàn)閟elf-->cmd-->(id)class_getSuperclass(objc_getClass("ViewController"))-->self
image.png
完整壓棧順序:self-->cmd-->(id)class_getSuperclass(objc_getClass("ViewController"))-->self-->cls-->kc-->person
image.png
理解指針和地址的概念:p是指針變量磕潮,在棧中賦值前后p和q的地址0x7ffee5373160翠胰、0x7ffee5373158不變,給指針變量p賦值&a(a的地址0x7ffee537316c)自脯,p的地址0x7ffee5373160中存放a的地址0x7ffee537316c之景,a的內(nèi)存地址0x7ffee537316c中存放了整形數(shù)值10,q的地址0x7ffee5373158存放p的地址0x7ffee5373160,q取雙重指針得到10
image.png
image.png
- 3.理解了指針和地址的概念膏潮,查看當(dāng)前棧中內(nèi)存地址情況锻狗,*(void *)address打印address這一個(gè)內(nèi)存地址中存放的地址所
指向的區(qū)域,比如address的內(nèi)存地址中存放
kc的內(nèi)存地址,則(void **)address為kc的內(nèi)存
地址中所存放的對象或值的內(nèi)存情況轻纪,kc的內(nèi)存地址中指向的內(nèi)容為cls的內(nèi)存地址
image.png
kc的內(nèi)存地址中存放的內(nèi)容為cls的內(nèi)存地址油额,cls的內(nèi)存地址中存放的是[LGPerson class],address的內(nèi)存地址中存放的是kc的內(nèi)存地址
address的內(nèi)存地址
self為消息接收者 - LGPerson <LGPerson: 0x7ffee119e178>
從person對象中找到唯一的屬性kc_name,需要將person對象內(nèi)存地址平移一個(gè)isa指針8字節(jié)的位置刻帚,獲取kc_name的值潦嘶,0x7ffee119e178平移8字節(jié)得0x7ffee119e180,0x7ffee119e178+0x8 = 0x7ffee119e180正好是ViewController的內(nèi)存地址
(0x7ffee119e180 - <ViewController: 0x7fd47c40b3a0>)
,故會輸出-[LGPerson saySomething] - <ViewController: 0x7fd47c40b3a0>
//LGPerson: 0x7ffee119e178
//person VS LGPerson(實(shí)例化)(isa)
//kc -> LGPerson (實(shí)例化) kc_name
第二個(gè)問題:為什么第三個(gè)參數(shù)super_class返回的是ViewController我擂?(id)class_getSuperclass(objc_getClass("ViewController")),因?yàn)閛bjc_msgSendSuper2返回的是當(dāng)前的類ViewController衬以,為什么不是ViewController的父類UIViewController呢缓艳?校摩??
[super viewDidLoad]方法阶淘,運(yùn)行時(shí)經(jīng)過匯編走的代碼是objc_msgSendSuper2衙吩,在進(jìn)入?yún)R編之前要傳入兩個(gè)參數(shù),一個(gè)是結(jié)構(gòu)體指針struct objc_super * _Nonnull super
,一個(gè)是SEL _Nonnull op
為sel_registerName("viewDidLoad"),查看結(jié)構(gòu)體struct objc_super中存在兩個(gè)參數(shù)一個(gè)是receiver溪窒,一個(gè)是super_class,消息的接收者為self本類坤塞,super_class為多少呢?澈蚌?摹芙?
查看objc_msgSendSuper2的解釋,super_class傳入的是當(dāng)前類ViewController并不是當(dāng)前類的父類UIViewController宛瞄,在匯編中去查找方法viewDidLoad時(shí)才去查找當(dāng)前類的父類浮禾,若傳入的是當(dāng)前類的父類UIViewController,則在匯編中查找的是父類的父類UIResponer,
所以第三項(xiàng)打印為當(dāng)前類(本類)ViewControler份汗,objc_msgSendSuper2的匯編代碼實(shí)質(zhì)為
objc_msgSendSuper2({self, objc_getClass("ViewController")}, sel_registerName("viewDidLoad"));
改一下LGPerson中的屬性結(jié)構(gòu)盈电,結(jié)果變化如何:增加一個(gè)屬性,則平移isa + kc_hobby總共16字節(jié),0x7ffee7808178 + 0x8 + 0x8 = 0x7ffee7808188
為self ——> ViewController
再改下LGPerson的屬性結(jié)構(gòu)
此時(shí)person對象<LGPerson: 0x7ffee5d98178>平移isa(8字節(jié))再平移int類型(4字節(jié))杯活,即將內(nèi)存地址
0x7ffee5d98180 - <ViewController: 0x7ff545f0ac70>
劈開一半匆帚,讀取出來為數(shù)值1463859280,不是一段完整的數(shù)據(jù)旁钧,程序沒有崩潰
此面試題的一次吸重,外層傳一個(gè)cls,無論是什么cls,都可以用kc接收,更加實(shí)現(xiàn)多態(tài)化歪今,但是不安全嚎幸,對象屬性變化,內(nèi)存訪問不到彤委,會崩潰
kc為嘛能調(diào)用LGPerson中的對象方法
Class cls = [LGPerson class];
void *kc = &cls;//ISA
LGPerson *person = [LGPerson alloc];// person - 指針 - ISA -> LGPerson
person指針地址里面有ISA,ISA指向LGPerson,cls的首地址是ISA,kc相當(dāng)于ISA,故能調(diào)用類中的方法