iOS-OC底層15:面試題

1.我們關(guān)聯(lián)的對象是否需要手動移除,為什么

不需要手動移除,在對象的dealloc中
在關(guān)聯(lián)對象時,如果是第一次,我們會設(shè)置對象的has_assoc為true,看dealloc代碼

- (void)dealloc {
    _objc_rootDealloc(self);
}

inline void
objc_object::rootDealloc()
{
    if (isTaggedPointer()) return;  // fixme necessary?

    if (fastpath(isa.nonpointer  &&  
                 !isa.weakly_referenced  &&  
                 !isa.has_assoc  &&  
                 !isa.has_cxx_dtor  &&  
                 !isa.has_sidetable_rc))
    {
        assert(!sidetable_present());
        free(this);
    } 
    else {
        object_dispose((id)this);
    }
}
RemoveAssocation.png

2.類的方法和分類的方法重名調(diào)用順序

一般方法是優(yōu)先調(diào)用分類的方法(包括initialize),因為在添加分類方法是把分類的方法插入到本類methodlist的前面。
load方法呢?
我們來看一下iOS對load的處理load_images,load_images中對load進行兩個處理
,prepare_load_methods和call_load_methods

prepare_load_methods

1.schedule_class_load
通過遞歸,通過add_class_to_loadable_list把類加入到數(shù)組中,如果父類沒有實現(xiàn)load方法則不加入,如果數(shù)組申請的內(nèi)容小了,則擴容,擴容原則是之前容量的2倍加16.
2.add_category_to_loadable_list
通過加載順序把分類和方法加入到數(shù)組中,擴容方式和schedule_class_load相同。

call_load_methods

調(diào)用順序,先調(diào)用本類的,按照schedule_class_load的數(shù)組存入先后順序,所以父類的load優(yōu)于子類先調(diào)用,然后調(diào)用分類的load的方法。


image.png

補充 :initialize是系統(tǒng)自己調(diào)用的,在類或者對象第一次調(diào)用方法時系統(tǒng)調(diào)用initialize,先父類再子類,如果分類實現(xiàn)了initialize,會調(diào)用分類的initialize,不調(diào)用本類的initialize方法,因為分類的initialize在methodlist中在本類initialize的前面。

3.[self class]和[super class]的不解之緣

@interface LGPerson : NSObject
@end
@implementation LGPerson
@end
@interface LGTeacher : LGPerson

@end
@implementation LGTeacher
- (instancetype)init{
    self = [super init];
    if (self) {
        NSLog(@"%@ - %@",[self class],[super class]);
    }
    return self;
}
@end

我們調(diào)用[ LGTeacher alloc] init];打印日志如下
LGTeacher - LGTeacher,是不是出乎我們意料,但是super 到底做了什么呢?
看匯編[super class]調(diào)到objc_msgSendSuper2方法。
objc_msgSendSuper2做了什么呢?
objc_msgSendSuper2是從父類的cache中查詢class方法,如果沒有則從父類的方法列表中查詢class,因為class的實現(xiàn)是在NSObject所以無論是[self class]還是[Super class]調(diào)用的方法都是從NSObject方法列表中找到的

- (Class)class {
    return object_getClass(self);
}

又因為[self class]和[Super class]的接收者不變,所以打印一直。
objc_msgSendSuper2的調(diào)用過程可以參考objc_msgSend緩存中讀取IMPobjc_msgSend慢速查找兩者流程大同小異,在此就不做分析了。

4.下面的調(diào)用會成功嗎?

  Class cls = [LGPerson class];
    void  *kc = &cls;  // 
    LGPerson *person = [LGPerson alloc];
 [(__bridge id)kc saySomething]; // 1 2  - <ViewController: 0x7f7f7ec09490>
    
    [person saySomething];
@interface LGPerson : NSObject
@property (nonatomic, copy) NSString* kc_name;
- (void)saySomething;
@end
@implementation LGPerson
- (void)saySomething{ 
    NSLog(@"%s ",__func__);
}
@end

打印日志

2020-10-22 22:24:19.290465+0800 004-內(nèi)存平移問題[63882:4725050] -[LGPerson saySomething]
2020-10-22 22:24:19.290639+0800 004-內(nèi)存平移問題[63882:4725050] -[LGPerson saySomething]

為什么呢?
方法調(diào)用實質(zhì)上是發(fā)送消息,發(fā)送消息objc_msgSend(objc_msgSendSuper)里面自帶兩個參數(shù)接收著和方法編號,接收者實質(zhì)上是內(nèi)存地址,然后通過快速查找和慢速查找尋找方法的實現(xiàn)IMP。查看kc和person的內(nèi)存地址情況

(lldb) x/4gx kc
0x7ffee54b6038: 0x000000010a74f648 0x00007ffea74046a0
0x7ffee54b6048: 0x000000010a74f580 0x00007fff5e0889bb
(lldb) x/4gx person
0x600000e0cca0: 0x000000010a74f648 0x0000000000000000
0x600000e0ccb0: 0x0000000000000000 0x0000000000000000
(lldb) 

[person saySomething]調(diào)用情況是,讀0x600000e0cca0地址可知isa地址0x000000010a74f648,查找sel的imp實現(xiàn)消息的發(fā)送,
[(__bridge id)kc saySomething];調(diào)用情況,讀0x7ffee54b6038地址可知isa地址0x000000010a74f648,和 [person saySomething]一樣
修改saySomething方法

- (void)saySomething{ 
    NSLog(@"%s--%@",__func__,self.kc_name);
}
    Class cls = [LGPerson class];
    void  *kc = &cls;  // 
    LGPerson *person = [LGPerson alloc];
    person.kc_name = @"name";
    [(__bridge id)kc saySomething];     
    [person saySomething];

打印結(jié)果

[LGPerson saySomething]--<ViewController: 0x7fe4406066c0>
[LGPerson saySomething]--name

這又是為什么呢?
查看內(nèi)存情況

(lldb) x/4gx kc
0x7ffee5bfb038: 0x000000010a00a5f0 0x00007fe4406066c0
0x7ffee5bfb048: 0x000000010a00a528 0x00007fff5e0889bb
(lldb) p  *(void **)0x7ffee5bfb040
(ViewController *) $1 = 0x00007fe4406066c0
(lldb) x/4gx person
0x600001b0c420: 0x000000010a00a5f0 0x000000010a005038
0x600001b0c430: 0x0000000000000000 0x0000000000000000
(lldb) p  *(void **)0x600001b0c428
(__NSCFConstantString *) $3 = 0x000000010a005038 "name"

我們?nèi)elf.kc_name的值,根據(jù)LGPerson對象的內(nèi)存布局,需要對象起始地址偏移8個字節(jié),然后讀取地址得到self.kc_name的值
kc的其實地址為0x7ffee5bfb038加8字節(jié)得到0x7ffee5bfb040讀取地址得到(ViewController *) $1 = 0x00007fe4406066c0,同理person起始地址偏移8字節(jié)得到0x600001b0c428讀取地址得到0x000000010a005038 "name"。那為什么kc偏移8地址讀取到的是ViewController呢?下面就介紹壓棧

5.壓棧

函數(shù)的壓棧規(guī)律

void kcFunction(id person, id kcSel, id kcSel2){
   NSLog(@"person = %p",&person);
   NSLog(@"person = %p",&kcSel);
   NSLog(@"person = %p",&kcSel2);
}

   LGPerson *person = [LGPerson alloc];
   kcFunction(person, person, person);

打印結(jié)果

person = 0x7ffeea97cfd8
person = 0x7ffeea97cfd0
person = 0x7ffeea97cfc8

函數(shù)中指針壓棧過程是從高地址到低地址

結(jié)構(gòu)體的壓棧規(guī)律

struct kc_struct{
    NSNumber *num1;
    NSNumber *num2;
} kc_struct;
    struct kc_struct struct1 = {@(10),@(20)};

lldb調(diào)試

(lldb) p &struct1
(kc_struct *) $8 = 0x00007ffee39b9018
(lldb) p *(NSNumber **)0x00007ffee39b9018
(__NSCFNumber *) $9 = 0xa9b8175a99c3a687 (int)10
(lldb) p *(NSNumber **)0x00007ffee39b9020
(__NSCFNumber *) $10 = 0xa9b8175a99c3a767 (int)20

結(jié)構(gòu)體中指針壓棧過程是從低地址到高地址

viewDidLoad指針壓棧情況

- (void)viewDidLoad {
    [super viewDidLoad];
    
    // ViewController 當(dāng)前的類
    // self cmd (id)class_getSuperclass(objc_getClass("LGTeacher")) self cls kc person
    
    Class cls = [LGPerson class];
    void  *kc = &cls;  // 
    LGPerson *person = [LGPerson alloc];

    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);
        }
    }

    
    // LGPerson  - 0x7ffeea0c50f8
    [(__bridge id)kc saySomething]; // 1 2  - <ViewController: 0x7f7f7ec09490>
    
    [person saySomething]; // self.kc_name = nil - (null)

    //
}

打印結(jié)果

 0x7ffee0ca3058 : <ViewController: 0x7f9be6408500>
0x7ffee0ca3050 : viewDidLoad
0x7ffee0ca3048 : ViewController
0x7ffee0ca3040 : <ViewController: 0x7f9be6408500>
0x7ffee0ca3038 : LGPerson
0x7ffee0ca3030 : <LGPerson: 0x7ffee0ca3038>

1.0x7ffee0ca3058 和0x7ffee0ca3050 :
viewDidLoad方法默認(rèn)兩個對象第一個是id第二個是cmd
所以打印ViewController對象和方法

  1. 0x7ffee0ca3048和0x7ffee0ca3040
    因為調(diào)用[super viewDidLoad];會產(chǎn)生一個結(jié)構(gòu)題第一個是接收者第二個是Class
    有因為結(jié)構(gòu)體壓棧規(guī)律是低到高,所以class是被壓倒高地址,接收者self被壓倒低地址。

6.Runtime是什么

runtime 是由C 和C++ 匯編 實現(xiàn)的一套API,為OC語言加入了面向?qū)ο?,運行時的功能
運行時(Runtime)是指將數(shù)據(jù)類型的確定由編譯時推遲到了運行時
平時編寫的OC代碼,在程序運行過程中,其實最終會轉(zhuǎn)換成Runtime的C語言代 碼,RuntimeObject-C 的幕后工作者

7.方法的本質(zhì),sel是什么?IMP是什么?兩者之間的關(guān)系又是什么?

方法的本質(zhì):發(fā)送消息 , 消息會有以下幾個流程
1:快速查找 (objc_msgSend)~ cache_t 緩存消息
2:慢速查找~ 遞歸自己| 父類 ~ lookUpImpOrForward
3:查找不到消息: 動態(tài)方法解析 ~ resolveInstanceMethod
4:消息快速轉(zhuǎn)發(fā)~ forwardingTargetForSelector
5:消息慢速轉(zhuǎn)發(fā)~ methodSignatureForSelector & forwardInvocation
sel 是方法編號 ~ 在read_images 期間就編譯進入了內(nèi)存 imp 就是我們函數(shù)實現(xiàn)指針 ,找imp 就是找函數(shù)的過程 sel 就相當(dāng)于書本的目錄 tittle
查找具體的函數(shù)就是想看這本書里面具體篇章的內(nèi)容
1:我們首先知道想看什么 ~ tittle (sel)
2:根據(jù)目錄對應(yīng)的?碼 (imp)
3.翻到具體的內(nèi)容

imp與SEL 的關(guān)系

SEL : 方法編號
IMP : 函數(shù)指針地址
SEL 相當(dāng)于書本目錄的名稱
IMP : 相當(dāng)于書本目錄的?碼
1:首先明白我們要找到書本的什么內(nèi)容 (sel 目錄里面的名稱)
2:通過名稱找到對應(yīng)的本?碼 (imp)
3:通過?碼去定位具體的內(nèi)容

8能否向編譯后的得到的類中增加實例變量?

能否想運行時創(chuàng)建的類中添加實例變量

答案:
1:不能向編譯后的得到的類中增加實例變量
2:只要類沒有注冊到內(nèi)存還是可以添加
原因:我們編譯好的實例變量存儲的位置在 ro,一旦編譯完成,內(nèi)存結(jié)構(gòu)就完全確定 就無法修改
可以添加屬性 + 方法

9.Runtime是如何實現(xiàn)weak的,為什么可以自動置nil

1.通過SideTable找到我們的weak_table
2.weak_table 根據(jù)referent 找到或者創(chuàng)建 weak_entry_t 3.然后append_referrer(entry, referrer)將我的新弱引用的對象加進去entry 4.最后weak_entry_insert 把entry加入到我們的weak_table


image.png

為什么可以自動置為nil呢
在對象dealloc時被置為nil的
dealloc->_objc_rootDealloc()->[ obj->rootDealloc()]->object_dispose((id)this)->objc_destructInstance(obj)->[obj->clearDeallocating()]-> clearDeallocating_slow();
補充 SideTable是什么時候初始化的
在map_images中初始化的
map_images->map_images_nolock->arr_init()->SideTablesMap.init();

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容