dealloc 釋放的對象
首先查看dealloc的底層源碼
obj->rootDealloc();
if (isTaggedPointer()) return; // fixme necessary?
// 根據(jù)isa中相關(guān)存儲格式進(jìn)行判斷
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);
}
void *objc_destructInstance(id obj)
{
if (obj) {
// Read all of the flags at once for performance. 判斷是否還包含C++方法以及關(guān)聯(lián)對象
bool cxx = obj->hasCxxDtor();
bool assoc = obj->hasAssociatedObjects();
// This order is important.
if (cxx) object_cxxDestruct(obj);
if (assoc) _object_remove_assocations(obj);
obj->clearDeallocating();
}
return obj;
}
根據(jù)上述代碼得知:
- 先查看isa相關(guān)信息來判斷是否還包含信息1是0否
-
object_dispose->objc_destructInstance來判斷是否存在hasCxxDtor、hasAssociatedObjects- 如果存在
C++方法,就從緩存中釋放object_cxxDestruct->object_cxxDestructFromClass->lookupMethodInClassAndLoadCache - 如果存在Associate,則從對應(yīng)的哈希表中查找然后依次刪除
- 如果存在
ObjectAssociationMap refs{};
{
AssociationsManager manager;
AssociationsHashMap &associations(manager.get());
AssociationsHashMap::iterator i = associations.find((objc_object *)object);
if (i != associations.end()) {
refs.swap(i->second);
associations.erase(i);
}
}
主類與分類的同名方法調(diào)用順序
- 如果方法為普通方法,包括
initialize,這時(shí)會因?yàn)榫幾g時(shí)分類方法存儲在主類方法的前面,故而優(yōu)先調(diào)用分類方法,-
initialize方法也是主動調(diào)用,即第一次消息時(shí)調(diào)用,為了不影響整個(gè)load,可以將需要提前加載的數(shù)據(jù)寫到initialize中 - 其他分類方法通過
attachCategories進(jìn)行添加在主類方法之前,但不會覆蓋主類方法
-
- 如果為
load方法,將先調(diào)用主類的load方法,再調(diào)用分類的load方法,可以在load_images中進(jìn)行驗(yàn)證
void
load_images(const char *path __unused, const struct mach_header *mh)
{
if (!didInitialAttachCategories && didCallDyldNotifyRegister) {
didInitialAttachCategories = true;
loadAllCategories();
}
// Return without taking locks if there are no +load methods here.
if (!hasLoadMethods((const headerType *)mh)) return;
recursive_mutex_locker_t lock(loadMethodLock);
// Discover load methods
{
mutex_locker_t lock2(runtimeLock);
prepare_load_methods((const headerType *)mh);
}
// Call +load methods (without runtimeLock - re-entrant)
call_load_methods();
}
void prepare_load_methods(const headerType *mhdr)
{
classref_t const *classlist =
_getObjc2NonlazyClassList(mhdr, &count);
for (i = 0; i < count; i++) {
schedule_class_load(remapClass(classlist[i]));
}
category_t * const *categorylist = _getObjc2NonlazyCategoryList(mhdr, &count);
for (i = 0; i < count; i++) {
category_t *cat = categorylist[I];
Class cls = remapClass(cat->cls);
if (!cls) continue; // category for ignored weak-linked class
if (cls->isSwiftStable()) {
_objc_fatal("Swift class extensions and categories on Swift "
"classes are not allowed to have +load methods");
}
realizeClassWithoutSwift(cls, nil);
ASSERT(cls->ISA()->isRealized());
add_category_to_loadable_list(cat);
}
}
void call_load_methods(void)
{
static bool loading = NO;
bool more_categories;
loadMethodLock.assertLocked();
// Re-entrant calls do nothing; the outermost call will finish the job.
if (loading) return;
loading = YES;
void *pool = objc_autoreleasePoolPush();
do {
// 1. Repeatedly call class +loads until there aren't any more
while (loadable_classes_used > 0) {
call_class_loads();
}
// 2. Call category +loads ONCE
more_categories = call_category_loads();
// 3. Run more +loads if there are classes OR more untried categories
} while (loadable_classes_used > 0 || more_categories);
objc_autoreleasePoolPop(pool);
loading = NO;
}
通過prepare_load_methods得知,先調(diào)用_getObjc2NonlazyClassList,然后才是_getObjc2NonlazyCategoryList
call_load_methods也是通過遍歷先call_class_loads然后call_category_loads
【面試-3】Runtime是什么?
runtime是由C和C++匯編實(shí)現(xiàn)的一套API,為OC語言加入了 面向?qū)ο?/code>、以及運(yùn)行時(shí)的功能
運(yùn)行時(shí)是指將數(shù)據(jù)類型的確定由編譯時(shí) 推遲到了 運(yùn)行時(shí)
舉例:extension 和 category 的區(qū)別
平時(shí)編寫的OC代碼,在程序運(yùn)行的過程中,其實(shí)最終會轉(zhuǎn)換成runtime的C語言代碼, runtime是OC的幕后工作者
1、category 類別、分類
專門用來給類添加新的方法
不能給類添加成員屬性,添加了成員屬性,也無法取到
注意:其實(shí)可以通過runtime 給分類添加屬性,即屬性關(guān)聯(lián),重寫setter、getter方法
分類中用@property 定義變量,只會生成變量的setter、getter方法的聲明(可以編譯通過,是不能運(yùn)行),不能生成方法實(shí)現(xiàn) 和 帶下劃線的成員變量
2、extension 類擴(kuò)展
可以說成是特殊的分類 ,也可稱作 匿名分類
可以給類添加成員屬性,但是是私有變量
可以給類添加方法,也是私有方法
【面試-4】方法的本質(zhì),sel是什么?IMP是什么?兩者之間的關(guān)系又是什么?
方法的本質(zhì):發(fā)送消息,消息會有以下幾個(gè)流程
快速查找(objc_msgSend) - cache_t緩存消息中查找
慢速查找 - 遞歸自己|父類 -lookUpImpOrForward
查找不到消息:動態(tài)方法解析 - resolveInstanceMethod
消息快速轉(zhuǎn)發(fā) -forwardingTargetForSelector
消息慢速轉(zhuǎn)發(fā) - methodSignatureForSelector & forwardInvocation
sel是方法編號 - 在read_images期間就編譯進(jìn)了內(nèi)存
imp是函數(shù)實(shí)現(xiàn)指針 ,找imp就是找函數(shù)的過程
sel 相當(dāng)于 一本書的目錄title
imp 相當(dāng)于 書本的頁碼
查找具體的函數(shù)就是想看這本書具體篇章的內(nèi)容
1、首先知道想看什么,即
目錄 title-sel2、根據(jù)目錄找到對應(yīng)的
頁碼-imp3、通過頁碼去翻到具體的內(nèi)容
【面試-5】能否向編譯后得到的類中增加實(shí)例變量?能否向運(yùn)行時(shí)創(chuàng)建的類中添加實(shí)例變量
1、不能向
編譯后的得到的類中增加實(shí)例變量2、只要類
沒有注冊到內(nèi)存還是可以添加的3、可以添加
屬性+方法
【原因】:編譯好的實(shí)例變量存儲的位置是ro,一旦編譯完成,內(nèi)存結(jié)構(gòu)就完全確定了
【經(jīng)典面試-6】 [self class]和[super class]的區(qū)別以及原理分析
[self class]就是發(fā)送消息 objc_msgSend,消息接收者是self,方法編號 class
[super class] 本質(zhì)就是objc_msgSendSuper,消息的接收者還是 self,方法編號 class,在運(yùn)行時(shí),底層調(diào)用的是_objc_msgSendSuper2【重點(diǎn)?。?!】
只是objc_msgSendSuper2 會更快,直接跳過self的查找
@interface LGTeacher : LGPerson
@implementation LGTeacher
- (instancetype)init{
self = [super init];
if (self) {
NSLog(@"%@ - %@",[self class],[super class]);
}
return self;
}
通過clang編譯成cpp文件可以看出
static instancetype _I_LGTeacher_init(LGTeacher * self, SEL _cmd) {
self = ((LGTeacher *(*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("LGTeacher"))}, sel_registerName("init"));
if (self) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_5c_6btl4svn6n5bqty614qwd7k80000gp_T_LGTeacher_d541e4_mi_0,((Class (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("class")),((Class (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("LGTeacher"))}, sel_registerName("class")));
}
return self;
}
通過實(shí)現(xiàn)方法可以看出,[super class]并不是通過LGTeacher的父類LGPerson進(jìn)行調(diào)用,而消息接收者還是self即為LGTeacher,只是結(jié)構(gòu)為__rw_objc_super
struct objc_super {
/// Specifies an instance of a class.
__unsafe_unretained _Nonnull id receiver;
/// Specifies the particular superclass of the instance to message.
#if !defined(__cplusplus) && !__OBJC2__
/* For compatibility with old objc-runtime.h header */
__unsafe_unretained _Nonnull Class class;
#else
__unsafe_unretained _Nonnull Class super_class;
#endif
/* super_class is the first class to search */
};
通過底層源碼我們可以發(fā)現(xiàn),super_class只是內(nèi)部的一個(gè)參數(shù),并非改變消息接收者,因此一開始打印的 NSLog(@"%@ - %@",[self class],[super class]);結(jié)果都為LGTeacher
【面試-7】內(nèi)存平移問題
下面方法調(diào)用打印結(jié)果是否一致
- (void)saySomething{
NSLog(@"%s",__func__);
}
在ViewController中調(diào)用
Class cls = [LGPerson class];
void *kc = &cls; //
[(__bridge id)kc saySomething];
LGPerson * person = [LGPerson alloc];
[person saySomething];
結(jié)果發(fā)現(xiàn)兩種打印是一致的,原因如下

可以看出是通過
person的isa去獲取類的相關(guān)信息,person的isa指向的LGPerson,而上面通過 *kc = &cls也可以看出kc指向的也是LGPerson,他們都是通過獲取LGPerson的MethodList去查找saySomething方法,故而效果一致
@property (nonatomic, copy) NSString *kc_name; // 12
- (void)saySomething{
NSLog(@"%s - %@",__func__,self.kc_name);
}

如果進(jìn)行上述更改,只增加一個(gè)屬性的打印結(jié)果我們發(fā)現(xiàn):
- 正常情況下,讀取屬性是通過
內(nèi)存平移的方式進(jìn)行讀取,在person中存儲的事isa以及相關(guān)屬性,這時(shí)就可以通過person的首地址平移8個(gè)單位獲取kc_name的地址從而進(jìn)行讀取,kc_name沒有賦值,打印為null可以理解 - 如果是通過指針地址方式調(diào)用,發(fā)現(xiàn)打印了一個(gè)
ViewController,這就很難理解,這時(shí)通過棧地址讀取的方式來查看相關(guān)內(nèi)容
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);
}
}

通過打印可以得知棧中現(xiàn)在保存的相關(guān)信息,由于棧是從高地址向低地址進(jìn)行存儲
- 當(dāng)進(jìn)行方法調(diào)用時(shí),首先傳入的是每個(gè)函數(shù)都會包含的隱藏參數(shù)
(id self,sel _cmd) - 通過實(shí)踐可以得知結(jié)構(gòu)體成員內(nèi)部的壓棧情況是
低地址->高地址,遞增的,故而如果是結(jié)構(gòu)體進(jìn)棧,則保存情況是后面變量先入棧 -
super通過clang查看底層的編譯,是objc_msgSendSuper,其第一個(gè)參數(shù)是一個(gè)結(jié)構(gòu)體__rw_objc_super(self,class_getSuperclass)
因此可以得出目前棧中所保存的信息為self - _cmd - (id)class_getSuperclass(objc_getClass("ViewController")) - self - cls - kc - person
person 與LGPerson的關(guān)系是 person是以LGPerson為模板的實(shí)例化對象,即alloc有一個(gè)指針地址,指向isa,isa指向LGPerson,它們之間關(guān)聯(lián)是有一個(gè)isa指向
由于kc也是指向LGPerson的關(guān)系,編譯器會認(rèn)為 kc也是LGPerson的一個(gè)實(shí)例化對象,即kc相當(dāng)于isa,即首地址,指向LGPerson,具有和person一樣的效果,簡單來說,我們已經(jīng)完全將編譯器騙過了,即kc也有kc_name。由于person查找kc_name是通過內(nèi)存平移8字節(jié),所以kc也是通過內(nèi)存平移8字節(jié)去查找kc_name,目前kc的首地址為0x7ffeec381098,平移0x8得出0x7ffeec3810a0,正好與棧中<ViewController: 0x7fac45514f50> 一致
【面試-8】 Runtime是如何實(shí)現(xiàn)weak的,為什么可以自動置nil
1、通過SideTable 找到我們的 weak_table
2、weak_table 根據(jù)referent找到或者創(chuàng)建 weak_entry_t
3、然后append_referrer(entry,referrer)將我的新弱引用的對象加進(jìn)去entry
4、最后 weak_entry_insert,把entry加入到我們的weak_table
