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

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的方法。

補充 :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緩存中讀取IMP和objc_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對象和方法
- 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語言代 碼,Runtime 是 Object-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

為什么可以自動置為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();