1、Runtime是什么?
Runtime是一套由C、C++和匯編實(shí)現(xiàn)的一套API,為OC語(yǔ)言加入了面向?qū)ο蠛瓦\(yùn)行時(shí)功能。
運(yùn)行時(shí) (Runtime) 是指將數(shù)據(jù)類型的確定由編譯時(shí)推遲到了運(yùn)行時(shí)。 (例如extension - category的區(qū)別)
平時(shí)寫的OC代碼,在運(yùn)行時(shí)會(huì)被轉(zhuǎn)換成Runtime的C語(yǔ)言代碼。Runtime是OC的幕后工作者。
2、方法的本質(zhì),SEL是什么?IMP是什么??jī)烧咧g的關(guān)系又是什么?
-
方法的本質(zhì)是發(fā)送消息,發(fā)送消息又有以下幾個(gè)流程:
- 快速查找 (
objc_msgSend) ~cache_t緩存消息 - 慢速查找: 遞歸自己和父類 ~
lookUpImpOrForward - 查找不到消息: 進(jìn)行動(dòng)態(tài)方法解析
resolveInstanceMethod - 消息快速轉(zhuǎn)發(fā):
forwardingTargetForSelector - 消息慢速轉(zhuǎn)發(fā) :
methodSignatureForSelector & forwardInvocation - 未找到消息:程序crash,打印日志
- 快速查找 (
SEL是方法編號(hào),在read_images期間就編譯進(jìn)入了內(nèi)存IMP就是我們函數(shù)實(shí)現(xiàn)指針,找IMP就是找函數(shù)實(shí)現(xiàn)過(guò)程-
SEL就相當(dāng)于書本的目錄,IMP就是書本的頁(yè)碼,查找具體的函數(shù)就是想看這本書里面具體篇章的內(nèi)容:- 我們首先知道想看什么,也就是title
SEL - 根據(jù)目錄對(duì)應(yīng)的頁(yè)碼
IMP - 翻到我們想看的具體內(nèi)容
- 我們首先知道想看什么,也就是title
3、能否向編譯后得到的類中增加實(shí)例變量?能否向運(yùn)行時(shí)創(chuàng)建的類中增加實(shí)例變量?
- 不能向編譯后得到的類中增加實(shí)例變量,因?yàn)轭愒诰幾g時(shí)已經(jīng)將實(shí)例變量存儲(chǔ)到
ro中了,編譯完成,內(nèi)存結(jié)構(gòu)確定,我們無(wú)法進(jìn)行修改。 - 只要沒(méi)有注冊(cè)到內(nèi)存還是可以添加的
- 在運(yùn)行時(shí)添加屬性和方法
4、isKindOfClass和isMemberOfClass
-
isKindOfClass確定一個(gè)對(duì)象是否是一個(gè)類的成員,或者是繼承自該類的成員 -
isMemberOfClass只能確定一個(gè)對(duì)象是否是當(dāng)前類的成員,即isMemberOfClass不能檢測(cè)任何的類都是基于NSObject類這一事實(shí),而isKindOfClass可以
我們可以進(jìn)行實(shí)例測(cè)驗(yàn):
BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];
BOOL re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];
BOOL re3 = [(id)[LGPerson class] isKindOfClass:[LGPerson class]];
BOOL re4 = [(id)[LGPerson class] isMemberOfClass:[LGPerson class]];
NSLog(@" re1 :%hhd\n re2 :%hhd\n re3 :%hhd\n re4 :%hhd\n",re1,re2,re3,re4);
BOOL re5 = [(id)[NSObject alloc] isKindOfClass:[NSObject class]];
BOOL re6 = [(id)[NSObject alloc] isMemberOfClass:[NSObject class]];
BOOL re7 = [(id)[LGPerson alloc] isKindOfClass:[LGPerson class]];//1
BOOL re8 = [(id)[LGPerson alloc] isMemberOfClass:[LGPerson class]];
NSLog(@" re5 :%hhd\n re6 :%hhd\n re7 :%hhd\n re8 :%hhd\n",re5,re6,re7,re8);
我們看一下輸出結(jié)果:
2020-02-05 10:10:29.892802+0800 LGTest[3088:31663]
re1 :1
re2 :0
re3 :0
re4 :0
2020-02-05 10:10:29.893424+0800 LGTest[3088:31663]
re5 :1
re6 :1
re7 :1
re8 :1
我們看一下isKindOfClass的源碼:
+ (BOOL)isKindOfClass:(Class)cls {
// ? for循環(huán)不斷獲取self的isa指針以及父類的isa指針
for (Class tcls = object_getClass((id)self); tcls; tcls = tcls->superclass) {
// ? 將循環(huán)獲取isa指針指向與cls作對(duì)比,如果是這個(gè)類的成員,或者是繼承自該類的成員,返回YES
if (tcls == cls) return YES;
}
return NO;
}
我們?cè)诳匆幌?code>isMemberOfClass的源碼:
+ (BOOL)isMemberOfClass:(Class)cls {
// ? 只是拿到當(dāng)前self的isa指向與cls作對(duì)比,也就是只能確定這個(gè)對(duì)象是否是當(dāng)前類的成員
return object_getClass((id)self) == cls;
}
5、[self class]和[super class]
首先我們創(chuàng)建兩個(gè)類,LGPerson繼承NSObject,LGStudent繼承LGPerson,在LGStudent類init方法中進(jìn)行打印一下代碼:
NSLog(@"%@",NSStringFromClass([self class]));
NSLog(@"%@",NSStringFromClass([super class]));
看結(jié)果:
2020-02-05 16:14:44.064160+0800 LGTest[23673:249566] LGStudent
2020-02-05 16:14:44.064508+0800 LGTest[23673:249566] LGStudent
到這里就有問(wèn)題了,為什么都是LGStudent?第二個(gè)不應(yīng)該是LGPerson嗎?
我們通過(guò)clang生成.cpp文件,拿到以下代碼:
// ? 原代碼很長(zhǎng),精簡(jiǎn)之后為以下內(nèi)容
NSStringFromClass(objc_msgSend((id)self, sel_registerName("class")));
NSStringFromClass(objc_msgSendSuper({(id)self, (id)class_getSuperclass(objc_getClass("LGPerson"))}, sel_registerName("class"))));
我們可以看到super關(guān)鍵字底層發(fā)送消息不是調(diào)用的objc_msgSend,而是objc_msgSendSuper,我們?cè)趨R編中找到這樣一段代碼:
* id objc_msgSendSuper(struct objc_super *super, SEL op, ...)
*
* struct objc_super {
* id receiver;
* Class cls; // the class to search
* }
根據(jù).cpp內(nèi)容可知receiver 是self(LGStudent,super_class是 class_getSuperclass(objc_getClass("LGPerson"))即LGStudent的父類LGPerson
調(diào)用時(shí)先調(diào)用LGPerson的class方法,如果沒(méi)有,就沿繼承體系往上找直到NSObject的class,最后內(nèi)部是使用objc_msgSend(objc_super->receiver, @selector("class"))去調(diào)用,所以輸出LGStudent
-
[self class]就是發(fā)送消息objc_msgSend,消息接受者self,方法編號(hào)class -
[super class]本質(zhì)就是objc_msgSendSuper,消息的接收者還是self,方法編號(hào)class,只是objc_msgSendSuper會(huì)更快,直接跳過(guò)self的查找
6、weak實(shí)現(xiàn)原理
我們開發(fā)中經(jīng)常是使用weak關(guān)鍵字來(lái)解決循環(huán)引用的問(wèn)題,原因是被weak引用的對(duì)象它的引用計(jì)數(shù)不會(huì)增加,而且在這個(gè)對(duì)象被釋放的時(shí)候被weak修飾的變量會(huì)自動(dòng)置空,不會(huì)造成野指針的問(wèn)題,相對(duì)來(lái)說(shuō)比較安全。那么weak底層究竟是如何實(shí)現(xiàn)的呢?接下來(lái)我們一起來(lái)探究weak的實(shí)現(xiàn)原理。
我們首先定位weak的代碼:
我們?cè)?code>main函數(shù)中進(jìn)行斷點(diǎn):

然后通過(guò)匯編的方法找到
weak的底層是objc_initWeak:
我們下一個(gè)objc_initWeak的符號(hào)斷點(diǎn),可直接定位到源碼:
id
objc_initWeak(id *location, id newObj)
{
if (!newObj) {
*location = nil;
return nil;
}
return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
(location, (objc_object*)newObj);
}
通過(guò)上面的源碼我們可以看到是重點(diǎn)函數(shù)storeWeak:
storeWeak(id *location, objc_object *newObj)
{
assert(haveOld || haveNew);
if (!haveNew) assert(newObj == nil);
Class previouslyInitializedClass = nil;
id oldObj;
// ? 聲明新舊兩個(gè)SideTable
SideTable *oldTable;
SideTable *newTable;
// Acquire locks for old and new values.
// Order by lock address to prevent lock ordering problems.
// Retry if the old value changes underneath us.
retry:
// ? 獲取全局SideTables,并復(fù)制給oldTable和newTable
if (haveOld) {
oldObj = *location;
oldTable = &SideTables()[oldObj];
} else {
oldTable = nil;
}
if (haveNew) {
newTable = &SideTables()[newObj];
} else {
newTable = nil;
}
SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable);
if (haveOld && *location != oldObj) {
SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
goto retry;
}
// Prevent a deadlock between the weak reference machinery
// and the +initialize machinery by ensuring that no
// weakly-referenced object has an un-+initialized isa.
// ? 防止弱引用機(jī)制和初始化出現(xiàn)死鎖,在弱引用之前確保對(duì)象已經(jīng)初始化成功
if (haveNew && newObj) {
Class cls = newObj->getIsa();
if (cls != previouslyInitializedClass &&
!((objc_class *)cls)->isInitialized())
{
SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
class_initialize(cls, (id)newObj);
// If this class is finished with +initialize then we're good.
// If this class is still running +initialize on this thread
// (i.e. +initialize called storeWeak on an instance of itself)
// then we may proceed but it will appear initializing and
// not yet initialized to the check above.
// Instead set previouslyInitializedClass to recognize it on retry.
previouslyInitializedClass = cls;
goto retry;
}
}
// Clean up old value, if any.
// ? 清空舊值
if (haveOld) {
weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
}
// Assign new value, if any.
// ? 存儲(chǔ)新值,函數(shù)weak_register_no_lock
if (haveNew) {
newObj = (objc_object *)
weak_register_no_lock(&newTable->weak_table, (id)newObj, location,
crashIfDeallocating);
// weak_register_no_lock returns nil if weak store should be rejected
// Set is-weakly-referenced bit in refcount table.
if (newObj && !newObj->isTaggedPointer()) {
newObj->setWeaklyReferenced_nolock();
}
// Do not set *location anywhere else. That would introduce a race.
*location = (id)newObj;
}
else {
// No new value. The storage is not changed.
}
SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
return (id)newObj;
}
我們看源碼的前部分都是對(duì)SideTable聲明的表進(jìn)行判斷,然后我們可以了解到弱引用指針是存在SideTable表中
- 如果
weak指針需要弱引用新的對(duì)象newObj就執(zhí)行存儲(chǔ)新值weak_register_no_lock函數(shù) - 如果
weak指針之前弱引用過(guò)別的對(duì)象oldObj,則調(diào)用weak_unregister_no_lock,在oldObj的weak_entry_t中移除該weak指針地址
我們?cè)诳匆幌?code>weak_register_no_lock函數(shù):
id
weak_register_no_lock(weak_table_t *weak_table, id referent_id,
id *referrer_id, bool crashIfDeallocating)
{
// ? 獲取弱引用對(duì)象
objc_object *referent = (objc_object *)referent_id;
objc_object **referrer = (objc_object **)referrer_id;
if (!referent || referent->isTaggedPointer()) return referent_id;
// ensure that the referenced object is viable
// ? 確保被引用的對(duì)象沒(méi)有在被釋放
bool deallocating;
if (!referent->ISA()->hasCustomRR()) {
deallocating = referent->rootIsDeallocating();
}
else {
BOOL (*allowsWeakReference)(objc_object *, SEL) =
(BOOL(*)(objc_object *, SEL))
object_getMethodImplementation((id)referent,
SEL_allowsWeakReference);
if ((IMP)allowsWeakReference == _objc_msgForward) {
return nil;
}
deallocating =
! (*allowsWeakReference)(referent, SEL_allowsWeakReference);
}
// ? 正在被釋放的對(duì)象不能被弱引用
if (deallocating) {
if (crashIfDeallocating) {
_objc_fatal("Cannot form weak reference to instance (%p) of "
"class %s. It is possible that this object was "
"over-released, or is in the process of deallocation.",
(void*)referent, object_getClassName((id)referent));
} else {
return nil;
}
}
// now remember it and where it is being stored
// ? 在 weak_table 中找到被弱引用對(duì)象 referent 對(duì)應(yīng)的 weak_entry,并將 referrer 加入到 weak_entry 中
weak_entry_t *entry;
if ((entry = weak_entry_for_referent(weak_table, referent))) {
append_referrer(entry, referrer);
}
else {
// 創(chuàng)建了這個(gè)數(shù)組 - 插入weak_table
weak_entry_t new_entry(referent, referrer);
// ? 進(jìn)行擴(kuò)容
weak_grow_maybe(weak_table);
// ? 插入weak_entry
weak_entry_insert(weak_table, &new_entry);
}
// Do not set *referrer. objc_storeWeak() requires that the
// value not change.
return referent_id;
}
如果對(duì)象可以被弱引用,則將引用弱對(duì)象所在的weak_table中的weak_entry_t取出,如果weak_entry_t不存在,則會(huì)新建一個(gè),然后將指向被弱引用對(duì)象地址的指針referrer通過(guò)函數(shù)append_referrer插入到對(duì)應(yīng)的weak_entry_t引用數(shù)組。至此就完成了弱引用。
如果weak指針在指向obj之前,已經(jīng)弱引用了其他的對(duì)象,則需要先將弱引用從指針對(duì)象其他的weak_entry_t的數(shù)組中移除。在調(diào)用weak_unregister_no_lock函數(shù)來(lái)做移除操作,我們看一下weak_unregister_no_lock函數(shù)源碼:
void
weak_unregister_no_lock(weak_table_t *weak_table, id referent_id,
id *referrer_id)
{
// ? 拿到以前弱引用的對(duì)象和對(duì)象的地址
objc_object *referent = (objc_object *)referent_id;
objc_object **referrer = (objc_object **)referrer_id;
weak_entry_t *entry;
if (!referent) return;
// ? 查找到以前弱引用的對(duì)象 referent 所對(duì)應(yīng)的 weak_entry_t
if ((entry = weak_entry_for_referent(weak_table, referent))) {
// ? 在以前弱引用的對(duì)象 referent 所對(duì)應(yīng)的 weak_entry_t 的 hash 數(shù)組中,移除弱引用 referrer
remove_referrer(entry, referrer);
// ? 移除元素之后, 要檢查一下 weak_entry_t 的 hash 數(shù)組是否已經(jīng)空了
bool empty = true;
if (entry->out_of_line() && entry->num_refs != 0) {
empty = false;
}
else {
for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
if (entry->inline_referrers[i]) {
empty = false;
break;
}
}
}
// ? 如果 weak_entry_t 的hash數(shù)組已經(jīng)空了,則需要將 weak_entry_t 從 weak_table 中移除
if (empty) {
weak_entry_remove(weak_table, entry);
}
}
// Do not set *referrer = nil. objc_storeWeak() requires that the
// value not change.
}
到這里對(duì)象的弱引用過(guò)程已經(jīng)全部執(zhí)行完畢。
我們知道當(dāng)對(duì)象的引用計(jì)數(shù)為0時(shí),系統(tǒng)會(huì)調(diào)用對(duì)象的dealloc方法進(jìn)行釋放,我們看一下具體的實(shí)現(xiàn):
- (void)dealloc {
_objc_rootDealloc(self);
}
***************?***************
void
_objc_rootDealloc(id obj)
{
assert(obj);
obj->rootDealloc();
}
***************?***************
inline void
objc_object::rootDealloc()
{
// ? 判斷對(duì)象是否采用了Tagged Pointer技術(shù)
if (isTaggedPointer()) return; // fixme necessary?
// ? 判斷是否能夠進(jìn)行快速釋放,判斷一下條件,滿足進(jìn)行 free()
if (fastpath(isa.nonpointer && // ? 對(duì)象是否采用了優(yōu)化的isa計(jì)數(shù)方式
!isa.weakly_referenced && // ? 對(duì)象沒(méi)有被弱引用
!isa.has_assoc && // ? 對(duì)象沒(méi)有關(guān)聯(lián)對(duì)象
!isa.has_cxx_dtor && // ? 對(duì)象沒(méi)有自定義的C++析構(gòu)函數(shù)
!isa.has_sidetable_rc)) // ? 對(duì)象沒(méi)有用到sideTable來(lái)做引用計(jì)數(shù)
{
assert(!sidetable_present());
free(this);
}
// ? 如果以上判斷沒(méi)有通過(guò),做下一步處理
else {
object_dispose((id)this);
}
}
我們這里對(duì)象進(jìn)行過(guò)弱引用,所以上面一些列判斷不通過(guò),進(jìn)入到object_dispose函數(shù):
id
object_dispose(id obj)
{
if (!obj) return nil;
objc_destructInstance(obj);
free(obj);
return nil;
}
***************?***************
/***********************************************************************
* objc_destructInstance
* Destroys an instance without freeing memory.
* Calls C++ destructors.
* Calls ARC ivar cleanup.
* Removes associative references.
* Returns `obj`. Does nothing if `obj` is nil.
**********************************************************************/
/**
銷毀實(shí)例而不會(huì)釋放內(nèi)存。
調(diào)用C ++析構(gòu)函數(shù)。
調(diào)用ARC ivar清理。
刪除關(guān)聯(lián)引用。
返回obj。 如果obj為零則不執(zhí)行任何操作。
*/
void *objc_destructInstance(id obj)
{
if (obj) {
// Read all of the flags at once for performance.
bool cxx = obj->hasCxxDtor();
bool assoc = obj->hasAssociatedObjects();
// This order is important.
// ? 如果有C++析構(gòu)函數(shù),則從類中銷毀C++析構(gòu)函數(shù)
if (cxx) object_cxxDestruct(obj);
// ? 如果有關(guān)聯(lián)對(duì)象,則移除所有的關(guān)聯(lián)對(duì)象,并將其自身從Association Manager的map中移除
if (assoc) _object_remove_assocations(obj);
// ? 繼續(xù)清理其它相關(guān)的引用
obj->clearDeallocating();
}
return obj;
}
從以上代碼我們可以看到最后會(huì)繼續(xù)清理其他相關(guān)引用,調(diào)用clearDeallocating函數(shù):
inline void
objc_object::clearDeallocating()
{
if (slowpath(!isa.nonpointer)) {
// Slow path for raw pointer isa.
// ? 如果要釋放的對(duì)象沒(méi)有采用了優(yōu)化過(guò)的isa引用計(jì)數(shù)
sidetable_clearDeallocating();
}
else if (slowpath(isa.weakly_referenced || isa.has_sidetable_rc)) {
// Slow path for non-pointer isa with weak refs and/or side table data.
// ? 如果要釋放的對(duì)象采用了優(yōu)化過(guò)的isa引用計(jì)數(shù),并且有弱引用或者使用了sideTable的輔助引用計(jì)數(shù)
clearDeallocating_slow();
}
assert(!sidetable_present());
}
在clearDeallocating函數(shù)內(nèi)部會(huì)根據(jù)要釋放的對(duì)象是否采用了優(yōu)化過(guò)的ISA做引用計(jì)數(shù)分成兩種情況:
1、如果要釋放的對(duì)象沒(méi)有采用了優(yōu)化過(guò)的isa引用計(jì)數(shù)
void
objc_object::sidetable_clearDeallocating()
{
// ? 在全局的SideTables中,以this指針(要釋放的對(duì)象)為key,找到對(duì)應(yīng)的SideTable
SideTable& table = SideTables()[this];
// clear any weak table items
// clear extra retain count and deallocating bit
// (fixme warn or abort if extra retain count == 0 ?)
table.lock();
// ? 在散列表SideTable中找到對(duì)應(yīng)的引用計(jì)數(shù)表RefcountMap,拿到要釋放的對(duì)象的引用計(jì)數(shù)
RefcountMap::iterator it = table.refcnts.find(this);
if (it != table.refcnts.end()) {
// ? 如果要釋放的對(duì)象被弱引用了,通過(guò)weak_clear_no_lock函數(shù)將指向該對(duì)象的弱引用指針置為nil
if (it->second & SIDE_TABLE_WEAKLY_REFERENCED) {
weak_clear_no_lock(&table.weak_table, (id)this);
}
// ? 從引用計(jì)數(shù)表中擦除該對(duì)象的引用計(jì)數(shù)
table.refcnts.erase(it);
}
table.unlock();
}
2、如果要釋放的對(duì)象采用了優(yōu)化過(guò)的isa引用計(jì)數(shù)
NEVER_INLINE void
objc_object::clearDeallocating_slow()
{
assert(isa.nonpointer && (isa.weakly_referenced || isa.has_sidetable_rc));
// ? 在全局的SideTables中,以this指針(要釋放的對(duì)象)為key,找到對(duì)應(yīng)的SideTable
SideTable& table = SideTables()[this];
table.lock();
if (isa.weakly_referenced) {
// ? 要釋放的對(duì)象被弱引用了,通過(guò)weak_clear_no_lock函數(shù)將指向該對(duì)象的弱引用指針置為nil
weak_clear_no_lock(&table.weak_table, (id)this);
}
// ? 使用了sideTable的輔助引用計(jì)數(shù),直接在SideTable中擦除該對(duì)象的引用計(jì)數(shù)
if (isa.has_sidetable_rc) {
table.refcnts.erase(this);
}
table.unlock();
}
上面兩種方法中都用到了weak_clear_no_lock函數(shù),將指向該對(duì)象的弱引用指針置為nil,我們?cè)诳匆幌?code>weak_clear_no_lock函數(shù):
void
weak_clear_no_lock(weak_table_t *weak_table, id referent_id)
{
// ? 獲取被弱引用對(duì)象的地址
objc_object *referent = (objc_object *)referent_id;
// ? 根據(jù)對(duì)象地址找到被弱引用對(duì)象referent在weak_table中對(duì)應(yīng)的weak_entry_t
weak_entry_t *entry = weak_entry_for_referent(weak_table, referent);
if (entry == nil) {
/// XXX shouldn't happen, but does with mismatched CF/objc
//printf("XXX no entry for clear deallocating %p\n", referent);
return;
}
// zero out references
weak_referrer_t *referrers;
size_t count;
// ? 找出弱引用該對(duì)象的所有weak指針地址數(shù)組
if (entry->out_of_line()) {
referrers = entry->referrers;
count = TABLE_SIZE(entry);
}
else {
referrers = entry->inline_referrers;
count = WEAK_INLINE_COUNT;
}
// ? 遍歷取出每個(gè)weak指針的地址
for (size_t i = 0; i < count; ++i) {
objc_object **referrer = referrers[i];
if (referrer) {
// ? 如果weak指針確實(shí)弱引用了對(duì)象 referent,則將weak指針設(shè)置為nil
if (*referrer == referent) {
*referrer = nil;
}
// ? 如果所存儲(chǔ)的weak指針沒(méi)有弱引用對(duì)象 referent,這可能是由于runtime代碼的邏輯錯(cuò)誤引起的,報(bào)錯(cuò)
else if (*referrer) {
_objc_inform("__weak variable at %p holds %p instead of %p. "
"This is probably incorrect use of "
"objc_storeWeak() and objc_loadWeak(). "
"Break on objc_weak_error to debug.\n",
referrer, (void*)*referrer, (void*)referent);
objc_weak_error();
}
}
}
weak_entry_remove(weak_table, entry);
}
- 當(dāng)一個(gè)對(duì)象
objc被弱指針指向時(shí),這個(gè)弱指針會(huì)以objc作為鍵,被存儲(chǔ)到sideTable類的weak_table這個(gè)散列表上對(duì)應(yīng)的一個(gè)weak指針數(shù)組里面。 - 當(dāng)一個(gè)對(duì)象
objc的dealloc方法被調(diào)用時(shí),運(yùn)行時(shí)會(huì)以objc為鍵,從sideTable的weak_table散列表中,發(fā)現(xiàn)對(duì)應(yīng)的weak指針列表,然后將里面的弱指針逐個(gè)放置為nil。