iOS-底層原理17-類的擴(kuò)展補(bǔ)充和面試題

《iOS底層原理文章匯總》

上一篇文章《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)存地址一樣,唯一


image.png

AssociationsManager構(gòu)造函數(shù)去掉鎖


image.png

manager不唯一,manager內(nèi)存地址不可讀,調(diào)用初始化方法,并不會調(diào)用init方法,那么init方法在什么時(shí)候調(diào)用的呢?斷點(diǎn)調(diào)試下

    AssociationsManager()   {  }
    ~AssociationsManager()  {  }
image.png

在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()


image.png

2.加鎖的原因:會對唯一的表AssociationsHashMap中的數(shù)據(jù)進(jìn)行讀取和安放聚霜,防止多線程對數(shù)據(jù)進(jìn)行篡改而導(dǎo)致數(shù)據(jù)不同步

image.png

相當(dāng)于如下:操作前加鎖狡恬,操作后解鎖


image.png

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ù)用


image.png
image.png
image.png

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移除

image.png

image.png

image.png

5.面試題:主類和分類同名方法极颓,優(yōu)先調(diào)用哪一個(gè)湿痢?

    1. 非load方法會先調(diào)用分類中的
  • 2.load方法會先調(diào)用主類的,再調(diào)用分類的台囱,為什么會是先主類后分類呢淡溯?


    image.png

    image.png

5.load_images分析

image.png

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


image.png
image.png
image.png

類中的load方法加載完成,再加載分類中的load方法的榛,加到loadable_categories琼了,若已經(jīng)使用的分類和開辟的相等,則進(jìn)行擴(kuò)容

image.png
image.png

調(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)


image.png
image.png
image.png

調(diào)用完畢后表伦,調(diào)用自動(dòng)釋放池回收整片內(nèi)存空間


image.png

load_images分析流程圖


image.png

面試題

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語言代碼纲熏,RuntimeObject-C的幕后工作者

6.[self class]和[super class]的區(qū)別

image.png
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


image.png

image.png

image.png

// 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


image.png

image.png

3.[self class]和[super class]的區(qū)別,self再去查找class方法的時(shí)候扇单,不先從本類中去查找了商模,直接從父類中去查找,跳過了本類的查找流程蜘澜,比[self class]查找速度更快super_class is the first class to search,消息的接收者還是self施流,查找的方式變化了


image.png

假設(shè)將[super class]改為[LGPerson class]呢,查看源碼


image.png

由上面可知編譯時(shí)期[super class]調(diào)用的方法為objc_msgSendSuper,那么運(yùn)行時(shí)期呢鄙信?實(shí)質(zhì)上調(diào)用的方法為objc_msgSendSuper2


image.png
image.png

將當(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方法


image.png

完整回答:[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)用


image.png

image.png
  • 在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?舀寓?胆数?

image.png

  • 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)存地址

image.png

address的內(nèi)存地址
image.png

image.png

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
image.png

第二個(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為多少呢?澈蚌?摹芙?

image.png

查看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"));
image.png

image.png

改一下LGPerson中的屬性結(jié)構(gòu)盈电,結(jié)果變化如何:增加一個(gè)屬性,則平移isa + kc_hobby總共16字節(jié),0x7ffee7808178 + 0x8 + 0x8 = 0x7ffee7808188

為self ——> ViewController


image.png

再改下LGPerson的屬性結(jié)構(gòu)

image.png

image.png

此時(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)存訪問不到彤委,會崩潰

image.png

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)用類中的方法

alloc出來的內(nèi)存空間都在堆里面鞭铆,常量,指針在棧中,0x70一般代表?xiàng)#?x60一般代表堆

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末车遂,一起剝皮案震驚了整個(gè)濱河市封断,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌舶担,老刑警劉巖坡疼,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異衣陶,居然都是意外死亡柄瑰,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進(jìn)店門剪况,熙熙樓的掌柜王于貴愁眉苦臉地迎上來教沾,“玉大人,你說我怎么就攤上這事译断∈诜” “怎么了?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵孙咪,是天一觀的道長堪唐。 經(jīng)常有香客問我,道長翎蹈,這世上最難降的妖魔是什么淮菠? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮荤堪,結(jié)果婚禮上合陵,老公的妹妹穿的比我還像新娘。我一直安慰自己逞力,他們只是感情好曙寡,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著寇荧,像睡著了一般举庶。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上揩抡,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天户侥,我揣著相機(jī)與錄音,去河邊找鬼峦嗤。 笑死蕊唐,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的烁设。 我是一名探鬼主播替梨,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼钓试,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了副瀑?” 一聲冷哼從身側(cè)響起弓熏,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎糠睡,沒想到半個(gè)月后挽鞠,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡狈孔,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年信认,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片均抽。...
    茶點(diǎn)故事閱讀 40,040評論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡嫁赏,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出到忽,到底是詐尸還是另有隱情橄教,我是刑警寧澤,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布喘漏,位于F島的核電站,受9級特大地震影響华烟,放射性物質(zhì)發(fā)生泄漏翩迈。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一盔夜、第九天 我趴在偏房一處隱蔽的房頂上張望负饲。 院中可真熱鬧,春花似錦喂链、人聲如沸返十。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽洞坑。三九已至,卻和暖如春蝇率,著一層夾襖步出監(jiān)牢的瞬間迟杂,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工本慕, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留排拷,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓锅尘,卻偏偏與公主長得像监氢,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評論 2 355

推薦閱讀更多精彩內(nèi)容

  • 【面試-1】Runtime Asssociate方法關(guān)聯(lián)的對象浪腐,需要在dealloc中釋放? 當(dāng)我們對象釋放時(shí)何鸡,會...
    CrazySnow閱讀 447評論 0 3
  • iOS 底層原理 文章匯總[http://www.reibang.com/p/412b20d9a0f6] 【面試...
    Style_月月閱讀 3,565評論 6 12
  • 久違的晴天,家長會牛欢。 家長大會開好到教室時(shí)骡男,離放學(xué)已經(jīng)沒多少時(shí)間了。班主任說已經(jīng)安排了三個(gè)家長分享經(jīng)驗(yàn)傍睹。 放學(xué)鈴聲...
    飄雪兒5閱讀 7,523評論 16 22
  • 今天感恩節(jié)哎隔盛,感謝一直在我身邊的親朋好友。感恩相遇拾稳!感恩不離不棄吮炕。 中午開了第一次的黨會,身份的轉(zhuǎn)變要...
    迷月閃星情閱讀 10,567評論 0 11
  • 可愛進(jìn)取访得,孤獨(dú)成精龙亲。努力飛翔,天堂翱翔悍抑。戰(zhàn)爭美好鳄炉,孤獨(dú)進(jìn)取。膽大飛翔搜骡,成就輝煌拂盯。努力進(jìn)取,遙望记靡,和諧家園谈竿。可愛游走...
    趙原野閱讀 2,727評論 1 1