前言
- 之前我們分析過內(nèi)存管理的一些操作, OC中的內(nèi)存管理是通過引用計(jì)數(shù)器來實(shí)現(xiàn)的熊锭。一個(gè)對(duì)象的聲明周期取決于它是否還沒其他對(duì)象引用毕荐,即retainCount是否等于0垄懂。 但在有些情況下,在某個(gè)對(duì)象的生命周期中模捂,我們并不希望對(duì)象的銷毀時(shí)間由是否被其他對(duì)象應(yīng)用來決定勋拟,而是這個(gè)對(duì)象本該是什么時(shí)候銷毀就什么時(shí)候被銷毀。因此引入對(duì)象的生命周期苔货、犀概、弱引用、強(qiáng)引用的概念夜惭。
準(zhǔn)備:
- 有關(guān)內(nèi)存管理技術(shù)的理解:
iOS 底層探索:內(nèi)存管理 (上)
iOS 底層探索:內(nèi)存管理 (下)
內(nèi)容:
- 對(duì)象的生命周期
- strong & weak
- 強(qiáng)弱引用
一姻灶、對(duì)象的生命周期
定義:
在OC中一個(gè)對(duì)象的生命周期就是指,這個(gè)對(duì)象從創(chuàng)建到銷毀的運(yùn)行時(shí)(runtime)的生命過程诈茧。從內(nèi)存管理引用計(jì)數(shù)的層面來講产喉,就是引用計(jì)數(shù)從1變成0的過程。
- 在
MRC
中若皱,手動(dòng)管理內(nèi)存镊叁,一個(gè)對(duì)象的生命周期經(jīng)歷了alloc、retain走触、release晦譬、dealloc等一些列的過程,直到對(duì)象的引用計(jì)數(shù)為0互广,被釋放結(jié)束了敛腌。 - 在
ARC
中,內(nèi)存是系統(tǒng)(LLVM和Runtime的共同結(jié)果)自動(dòng)管理的惫皱,其實(shí) ARC 內(nèi)部機(jī)制原理也是來源于mrc像樊,一個(gè)對(duì)象的生命周期,大多是由系統(tǒng)自動(dòng)管理的旅敷;
對(duì)象的創(chuàng)建途徑:
- 程序顯示的創(chuàng)建并初始化生棍;
- 對(duì)象作為另一個(gè)對(duì)象的副本;
- unArchiving:從已歸檔的二進(jìn)制數(shù)據(jù)流中解碼媳谁,如果一個(gè)對(duì)象是從一個(gè)nib文件中被unArchive的話涂滴,在所有的nib文件中的對(duì)象都被裝載到內(nèi)存之后友酱,就會(huì)收到一個(gè)名叫 awakeFromNib 的消息
圖解對(duì)象生命周期
總結(jié):在對(duì)象的創(chuàng)建和初始化之后,只要對(duì)象的retainCount的值比0大柔纵,那么它就會(huì)一直存在在內(nèi)存中缔杉。通過想一個(gè)對(duì)象發(fā)送retain消息,或者進(jìn)行copy操作搁料。其他的對(duì)象可以引用并持有該對(duì)象的所有權(quán)或详。同時(shí),移除引用的時(shí)候要發(fā)送release消息郭计。
二霸琴、weak 和 strong
weak的底層實(shí)現(xiàn)原理
舉例:
int main(){
NSObject *obj = [[NSObject alloc] init];
id __weak obj1 = obj;
}
clang 編譯報(bào)錯(cuò)提示:
int main(){
NSObject *obj = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"));
id __attribute__((objc_ownership(weak))) obj1 = obj;
}
-
objc_ownership
字面意思是:獲得對(duì)象的所有權(quán),是對(duì)對(duì)象weak的初始化的一個(gè)操作
- 調(diào)用
objc_initWeak
存入sidetable表昭伸;- 調(diào)用
objc_loadWeakRetain
返回自身沈贝,并引用計(jì)數(shù)+1(refcnts的value+固定增量值);- __weak修飾的
obj1
是個(gè)作用域內(nèi)的臨時(shí)變量勋乾,所以出了作用域就被釋放了宋下。
以objc_initWeak
為出發(fā)點(diǎn)去Objc-4源碼中查看:
/**
* Initialize a fresh weak pointer to some object location.
* It would be used for code like:
*
* (The nil case)
* __weak id weakPtr;
* (The non-nil case)
* NSObject *o = ...;
* __weak id weakPtr = o;
*
* This function IS NOT thread-safe with respect to concurrent
* modifications to the weak variable. (Concurrent weak clear is safe.)
*
* @param location Address of __weak ptr.
* @param newObj Object ptr.
*/
id
objc_initWeak(id *location, id newObj)
{
if (!newObj) {
*location = nil;
return nil;
}
return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
(location, (objc_object*)newObj);
}
//location : __weak指針 的地址 ,存儲(chǔ)指針的地址辑莫,這樣便可以在最后將其指向的對(duì)象置為nil学歧。
// newObj :所引用的對(duì)象。即例子中的obj 各吨。
- 從上面的代碼可以看出
objc_initWeak
方法只是一個(gè)深層次函數(shù)調(diào)用的入口枝笨,在該方法內(nèi)部調(diào)用了storeWeak
方法。下面我們來看下storeWeak
方法的實(shí)現(xiàn)代碼揭蜒。
static id
storeWeak(id *location, objc_object *newObj)
{
ASSERT(haveOld || haveNew);
if (!haveNew) ASSERT(newObj == nil);
Class previouslyInitializedClass = nil;
id oldObj;
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:
if (haveOld) {// 如果weak ptr之前弱引用過一個(gè)obj横浑,則將這個(gè)obj所對(duì)應(yīng)的SideTable取出,賦值給oldTable
oldObj = *location;
oldTable = &SideTables()[oldObj];
} else {
oldTable = nil;// 如果weak ptr之前沒有弱引用過一個(gè)obj屉更,則oldTable = nil
}
if (haveNew) { // 如果weak ptr要weak引用一個(gè)新的obj徙融,則將該obj對(duì)應(yīng)的SideTable取出,賦值給newTable
newTable = &SideTables()[newObj];
} else {
newTable = nil; // 如果weak ptr不需要引用一個(gè)新obj瑰谜,則newTable = nil
}
// 加鎖操作欺冀,防止多線程中競(jìng)爭(zhēng)沖突
SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable);
// location 應(yīng)該與 oldObj 保持一致,如果不同萨脑,說明當(dāng)前的 location 已經(jīng)處理過 oldObj 可是又被其他線程所修改
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.
if (haveNew && newObj) {
Class cls = newObj->getIsa();
if (cls != previouslyInitializedClass &&
!((objc_class *)cls)->isInitialized()) // 如果cls還沒有初始化隐轩,先初始化,再嘗試設(shè)置weak
{
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; // 這里記錄一下previouslyInitializedClass渤早, 防止改if分支再次進(jìn)入
goto retry; // 重新獲取一遍newObj职车,這時(shí)的newObj應(yīng)該已經(jīng)初始化過了
}
}
// Clean up old value, if any.
if (haveOld) {
weak_unregister_no_lock(&oldTable->weak_table, oldObj, location); // 如果weak_ptr之前弱引用過別的對(duì)象oldObj,則調(diào)用weak_unregister_no_lock,在oldObj的weak_entry_t中移除該weak_ptr地址
}
// Assign new value, if any.
if (haveNew) { // 如果weak_ptr需要弱引用新的對(duì)象newObj
// (1) 調(diào)用weak_register_no_lock方法悴灵,將weak ptr的地址記錄到newObj對(duì)應(yīng)的weak_entry_t中
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
// (2) 更新newObj的isa的weakly_referenced bit標(biāo)志位
// 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.
// (3)*location 賦值军援,也就是將weak ptr直接指向了newObj〕蒲可以看到,這里并沒有將newObj的引用計(jì)數(shù)+1
*location = (id)newObj;
}
else {
// No new value. The storage is not changed.
}
// 解鎖涯竟,其他線程可以訪問oldTable, newTable了
SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
// 返回newObj赡鲜,此時(shí)的newObj與剛傳入時(shí)相比,weakly-referenced bit位置1
return (id)newObj;
}
storeWeak
具體流程如下:
storeWeak
方法實(shí)際上是接收了5個(gè)參數(shù)庐船,分別是 haveOld银酬、haveNew和crashIfDeallocating ,這三個(gè)參數(shù)都是以模板的方式傳入的筐钟,是三個(gè)bool類型的參數(shù)揩瞪。分別表示weak指針之前是否指向了一個(gè)弱引用,weak指針是否需要指向一個(gè)新的引用篓冲,若果被弱引用的對(duì)象正在析構(gòu)李破,此時(shí)再弱引用該對(duì)象是否應(yīng)該crash。- 該方法維護(hù)了 oldTable 和 newTable 分別表示舊的引用弱表和新的弱引用表壹将,它們都是
SideTable 的hash表
嗤攻。- 如果weak指針之前指向了一個(gè)弱引用,則會(huì)調(diào)用 weak_unregister_no_lock 方法將舊的weak指針地址移除诽俯。
- 如果weak指針需要指向一個(gè)新的引用妇菱,則會(huì)調(diào)用 weak_register_no_lock 方法將新的weak指針地址添加到
弱引用表中
。- 調(diào)用 setWeaklyReferenced_nolock 方法修改weak新引用的對(duì)象的bit標(biāo)志位
有關(guān)SideTable
在iOS 底層探索:內(nèi)存管理 (上) 中講的有喔暴区,這里主要講下:weak_table_t
/**
* The global weak references table. Stores object ids as keys,
* and weak_entry_t structs as their values.
*/
struct weak_table_t {
weak_entry_t *weak_entries; //hash數(shù)組闯团,用來存儲(chǔ)弱引用對(duì)象的相關(guān)信息weak_entry_t
size_t num_entries; //hash數(shù)組中的元素個(gè)數(shù)
uintptr_t mask; //hash數(shù)組長(zhǎng)度-1,會(huì)參與hash計(jì)算仙粱。(注意房交,這里是hash數(shù)組的長(zhǎng)度,而不是元素個(gè)數(shù)伐割。比如涌萤,數(shù)組長(zhǎng)度可能是64,而元素個(gè)數(shù)僅存了2個(gè))
uintptr_t max_hash_displacement; //可能會(huì)發(fā)生的hash沖突的最大次數(shù)口猜,用于判斷是否出現(xiàn)了邏輯錯(cuò)誤(hash表中的沖突次數(shù)絕不會(huì)超過改值)
};
-
weak_table_t
是一個(gè)典型的hash結(jié)構(gòu)
负溪。 -
weak_entries
是一個(gè)動(dòng)態(tài)數(shù)組
,用來存儲(chǔ)weak_entry_t
類型的元素济炎,這些元素實(shí)際上就是OC對(duì)象的弱引用信息
川抡。 -
weak_entry_t
是存儲(chǔ)在弱引用表中的一個(gè)內(nèi)部結(jié)構(gòu)體,它負(fù)責(zé)維護(hù)和存儲(chǔ)指向一個(gè)對(duì)象的所有弱引用hash表。其定義如下:
typedef objc_object ** weak_referrer_t; //objc_object是weak_entry_t表中weak弱引用對(duì)象的范型對(duì)象的結(jié)構(gòu)體結(jié)構(gòu)崖堤。
struct weak_entry_t {
DisguisedPtr<objc_object> referent; //范型
union {
struct {
weak_referrer_t *referrers;
uintptr_t out_of_line : 1; //最低有效位侍咱,也是標(biāo)志位。當(dāng)標(biāo)志位 0 時(shí)密幔,增加引用表指針緯度楔脯。
uintptr_t num_refs : PTR_MINUS_1; //引用數(shù)值。這里記錄弱引用表中引用有效數(shù)字胯甩,因?yàn)槿跻帽硎褂玫氖庆o態(tài) hash 結(jié)構(gòu)昧廷,所以需要使用變量來記錄數(shù)目。
uintptr_t mask; //計(jì)數(shù)輔助量偎箫。
uintptr_t max_hash_displacement; //hash 元素上限閥值木柬。
};
struct {
// out_of_line=0 is LSB of one of these (don't care which)
weak_referrer_t inline_referrers[WEAK_INLINE_COUNT];
};
}
}
weak
的整體實(shí)現(xiàn)流程如圖:
【總結(jié)】:
1:?先我們知道有?個(gè)?常?逼的家伙-sideTable
2:得到sideTable的weakTable 弱引?表
3:創(chuàng)建?個(gè)weak_entry_t
4:把referent加?到weak_entry_t的數(shù)組inline_referrers
5:把weak_table擴(kuò)容?下
6:把new_entry加?到weak_table中
objc_loadWeakRetain
的執(zhí)行流程如下:
/*
Once upon a time we eagerly cleared *location if we saw the object
was deallocating. This confuses code like NSPointerFunctions which
tries to pre-flight the raw storage and assumes if the storage is
zero then the weak system is done interfering. That is false: the
weak system is still going to check and clear the storage later.
This can cause objc_weak_error complaints and crashes.
So we now don't touch the storage until deallocation completes.
*/
id
objc_loadWeakRetained(id *location)
{
id obj;
id result;
Class cls;
SideTable *table;
retry:
// fixme std::atomic this load
obj = *location;
if (!obj) return nil;
if (obj->isTaggedPointer()) return obj;
table = &SideTables()[obj];
table->lock();
if (*location != obj) {
table->unlock();
goto retry;
}
result = obj;
cls = obj->ISA();
if (! cls->hasCustomRR()) {
// Fast case. We know +initialize is complete because
// default-RR can never be set before then.
ASSERT(cls->isInitialized());
if (! obj->rootTryRetain()) {
result = nil;
}
}
else {
// Slow case. We must check for +initialize and call it outside
// the lock if necessary in order to avoid deadlocks.
if (cls->isInitialized() || _thisThreadIsInitializingClass(cls)) {
BOOL (*tryRetain)(id, SEL) = (BOOL(*)(id, SEL))
class_getMethodImplementation(cls, @selector(retainWeakReference));
if ((IMP)tryRetain == _objc_msgForward) {
result = nil;
}
else if (! (*tryRetain)(obj, @selector(retainWeakReference))) {
result = nil;
}
}
else {
table->unlock();
class_initialize(cls, obj);
goto retry;
}
}
table->unlock();
return result;
}
- 這段代碼的核心就是:
retainWeakReference
->......rootRetain
的過程。進(jìn)行引用計(jì)數(shù)加1
weak釋放為nil過程
1淹办、調(diào)用objc_release
2眉枕、因?yàn)閷?duì)象的引用計(jì)數(shù)為0,所以執(zhí)行dealloc
3怜森、在dealloc中速挑,調(diào)用了_objc_rootDealloc函數(shù)
4、在_objc_rootDealloc中副硅,調(diào)用了object_dispose函數(shù)
5梗摇、調(diào)用objc_destructInstance
6、最后調(diào)用objc_clear_deallocating
我們只看下objc_clear_deallocating
:
void objc_clear_deallocating(id obj)
{
assert(obj);
assert(!UseGC);
if (obj->isTaggedPointer()) return;
obj->clearDeallocating();
}
//執(zhí)行 clearDeallocating方法
inline void objc_object::clearDeallocating()
{
sidetable_clearDeallocating();
}
// 執(zhí)行sidetable_clearDeallocating想许,找到weak表中的value值
void objc_object::sidetable_clearDeallocating()
{
SideTable *table = SideTable::tableForPointer(this);
// clear any weak table items
// clear extra retain count and deallocating bit
// (fixme warn or abort if extra retain count == 0 ?)
spinlock_lock(&table->slock);
RefcountMap::iterator it = table->refcnts.find(this); //這里可以再研究下
if (it != table->refcnts.end()) {
if (it->second & SIDE_TABLE_WEAKLY_REFERENCED) {
//清理對(duì)象
weak_clear_no_lock(&table->weak_table, (id)this);
}
table->refcnts.erase(it);
}
spinlock_unlock(&table->slock);
}
clearDeallocating
函數(shù)首先根據(jù)對(duì)象地址獲取所有weak指針地址的數(shù)組伶授,然后遍歷這個(gè)數(shù)組把其中的數(shù)據(jù)設(shè)為nil,最后把這個(gè)entry從weak表中刪除流纹,最后清理對(duì)象的記錄糜烹。objc_clear_deallocating
該函數(shù) 具體流程如下:
1、從weak表中獲取廢棄對(duì)象的地址為鍵值的記錄
2漱凝、將包含在記錄中的所有附有 weak修飾符變量的地址疮蹦,賦值為nil
3、將weak表中該記錄刪除
4茸炒、從引用計(jì)數(shù)表中刪除廢棄對(duì)象的地址為鍵值的記錄
總結(jié)
weak
是Runtime維護(hù)了一個(gè)hash(哈希)表愕乎,用于存儲(chǔ)指向某個(gè)對(duì)象的所有weak指針。weak表其實(shí)是一個(gè)hash(哈希)表壁公,Key是所指對(duì)象的地址感论,Value是weak指針的地址(這個(gè)地址的值是所指對(duì)象指針的地址)數(shù)組。
strong的底層實(shí)現(xiàn)原理
如果是用屬性strong紊册,同樣可以用Clang去查看源碼比肄。這里只做匯編調(diào)試如下:
我們進(jìn)入objc_storeStrong
源碼:
void
objc_storeStrong(id *location, id obj)
{
id prev = *location;
if (obj == prev) {
return;
}
objc_retain(obj);//retain新值
*location = obj;
objc_release(prev);//release舊值
}
- 可以看出:
strong
: 內(nèi)部使用retain
和release
進(jìn)行引用計(jì)數(shù)的管理。關(guān)于retain和release的分析請(qǐng)看 iOS 底層探索:內(nèi)存管理 (上)
三、弱引用 & 強(qiáng)引用
概念
弱引用
(Weak Reference):當(dāng)前對(duì)象的聲明周期不被是否由其他其他對(duì)象引用限制芳绩,它本該什么時(shí)候銷毀就什么時(shí)候銷毀掀亥。計(jì)時(shí)它的引用沒斷,但是當(dāng)它的生存周期到了就會(huì)被銷毀妥色。
強(qiáng)引用
(Strong Reference ):當(dāng)前對(duì)象被其他對(duì)象引用時(shí)搪花,會(huì)執(zhí)行retain,引用計(jì)數(shù)+1.當(dāng)retainCount=0時(shí)嘹害,該對(duì)象才會(huì)被銷毀撮竿。 默認(rèn)情況下是強(qiáng)引用方式。
簡(jiǎn)單的說:
當(dāng)用指針指向某個(gè)對(duì)象時(shí)吼拥,你可以通過retain/release管理它的內(nèi)存,也可以不管理线衫。
如果你管理了凿可,就擁有對(duì)這個(gè)對(duì)象的強(qiáng)引用;
如果你沒有管理授账,那么你擁有的就是弱引用枯跑。
使用
__weak
- weak嚴(yán)格的說應(yīng)當(dāng)叫“ 歸零弱引用 ”,weak相當(dāng)于老版本的assign白热,即當(dāng)對(duì)象被銷毀后敛助,會(huì)自動(dòng)的把它的指針置為nil,這樣可以防止野指針錯(cuò)誤屋确。
- weak 作為屬性的關(guān)鍵字的作用弱引用纳击,所引用對(duì)象的計(jì)數(shù)器不會(huì)加一,并在引用對(duì)象被釋放的時(shí)候自動(dòng)被設(shè)置為 nil攻臀。
__weak NSObject *obj;
__strong
- 變量聲明默認(rèn)都帶有strong關(guān)鍵字焕数,如果變量什么關(guān)鍵字都不寫,那么就默認(rèn)為強(qiáng)引用刨啸,strong相當(dāng)于老版本的retain 堡赔;
__strong NSObject *obj;
驗(yàn)證
將obj2聲明改為__weak
- 從上面可以看出使用__strong 和__weak的區(qū)別,因?yàn)開_strong修飾的對(duì)象會(huì)使對(duì)象本身dretainCount+1设联,而weak的并不會(huì)善已。
所以第一個(gè)例子的retainCount為2,obj1=nil之后retainCount為1离例,并不會(huì)對(duì)obj2造成影響换团,而第二個(gè)例子obj1=nil之后retainCount 為0了,內(nèi)存也跟著釋放了宫蛆,所以obj2也為nil啥寇。
weakSelf 與 self
NSLog(@"%ld",CFGetRetainCount((__bridge CFTypeRef)self));
__weak typeof(self) weakSelf = self;
NSLog(@"%ld",CFGetRetainCount((__bridge CFTypeRef)self));
打印weakSelf 和 self對(duì)象,以及指針地址:
- 當(dāng)前self取地址 和 weakSelf取指針的地址的值是不一樣的。意味著有兩個(gè)指針地址辑甜,指向的是同一片內(nèi)存空間衰絮,即weakSelf 和 self 的內(nèi)存地址是不一樣,都指向同一片內(nèi)存空間的
強(qiáng)引用的舉例分析:NSTimer(計(jì)時(shí)器)
- (void)createTimer {
self.timer = [NSTimer timerWithTimeInterval:1 target: self selector:@selector(fireHome) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
}
- (void)fireHome{
num++;
NSLog(@"hello word - %d",num);
}
- (void)dealloc{
[self.timer invalidate];
self.timer = nil;
NSLog(@"%s",__func__);
}
- 我們運(yùn)行程序磷醋,進(jìn)行push-pop跳轉(zhuǎn),發(fā)現(xiàn)定時(shí)器方法仍然在執(zhí)行猫牡,并沒有執(zhí)行B的dealloc方法;
- 我們知道:
NSTimer
創(chuàng)建后邓线,需要手動(dòng)加入到Runloop
中才可以運(yùn)行淌友,但timer
會(huì)使得當(dāng)前控制器不走dealloc
方法,導(dǎo)致timer
和控制器
都無(wú)法釋放
。
解決方式一: pop時(shí)在其他方法中銷毀timer
- 重寫didMoveToParentViewController方法
- (void)didMoveToParentViewController:(UIViewController *)parent{
// 無(wú)論push 進(jìn)來 還是 pop 出去 正常跑
// 就算繼續(xù)push 到下一層 pop 回去還是繼續(xù)
if (parent == nil) {
[self.timer invalidate];
self.timer = nil;
NSLog(@"timer 走了");
}
}
解決方式二:
定義timer時(shí)枢贿,采用閉包的形式担钮,因此不需要指定target:
- (void)blockTimer{
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
NSLog(@"timer fire - %@",timer);
}];
}
- 這兩個(gè)解決方法不是我們要研究的,為了對(duì)強(qiáng)引用進(jìn)行拓展研究器联。所以回到最開始的地方;
先看看官方文檔NSTimer
的timerWithTimeInterval:target:selector:userInfo:repeats:
方法
- 從文檔中可以看出,timer對(duì)傳入的target具有強(qiáng)持有婿崭,即timer持有self拨拓。由于timer是定義在B界面中,所以self也持有timer氓栈,因此
self -> timer -> self
構(gòu)成了循環(huán)引用 - 我們我們嘗試通過
__weak
即弱引用來解決渣磷,代碼修改如下:
//typeof(self)是獲取到self的類型,這樣定義的weakSelf就是和self一個(gè)類型的,加上__weak是建立一個(gè)弱引用
__weak typeof(self) weakSelf = self; //定義了一個(gè)弱引用性質(zhì)的替身.
self.timer = [NSTimer timerWithTimeInterval:1 target:weakSelf selector:@selector(fireHome) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
我們?cè)俅芜\(yùn)行程序,進(jìn)行push-pop跳轉(zhuǎn)授瘦。發(fā)現(xiàn)問題還是存在醋界,即定時(shí)器方法仍然在執(zhí)行,并沒有執(zhí)行B的dealloc方法提完。
為什么呢物独?因?yàn)槲覀兊姆治霾⒉蝗妫藭r(shí)還有一個(gè)Runloop對(duì)timer的強(qiáng)持有氯葬,因?yàn)镽unloop的生命周期比B界面更長(zhǎng)挡篓,所以導(dǎo)致了timer無(wú)法釋放。
拓展循環(huán)引用的模型:
timer模型
:self -> timer -> weakSelf -> self,當(dāng)前的timer捕獲的是B界面的內(nèi)存帚称,即vc對(duì)象的內(nèi)存官研,即weakSelf表示的是vc對(duì)象
Block模型
:self -> block -> weakSelf -> self,當(dāng)前的block捕獲的是指針地址闯睹,即weakSelf表示的是指向self的臨時(shí)變量的指針地址
解決方式三:中介者模式戏羽,即不使用self,依賴于其他對(duì)象
將target換成NSObject對(duì)象楼吃,將fireHome交給target執(zhí)行:
//**********1始花、定義其他對(duì)象**********
@property (nonatomic, strong) id target;
//**********2妄讯、修改target**********
self.target = [[NSObject alloc] init];
class_addMethod([NSObject class], @selector(fireHome), (IMP)fireHomeObjc, "v@:");
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self.target selector:@selector(fireHome) userInfo:nil repeats:YES];
//**********3、imp**********
void fireHomeObjc(id obj){
NSLog(@"%s -- %@",__func__,obj);
}
運(yùn)行發(fā)現(xiàn)timer還是會(huì)繼續(xù)執(zhí)行酷宵。原因是解決了中介者的釋放亥贸,但是沒有解決中介者的回收,即self.target的回收浇垦。
所以還需要釋放timer
解決方式四:自定義封裝timer
這種方式是根據(jù)思路三的原理炕置,自定義封裝timer,其實(shí)現(xiàn)如下
//*********** .h文件 ***********
@interface CJLTimerWapper : NSObject
- (instancetype)cjl_initWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;
- (void)cjl_invalidate;
@end
//*********** .m文件 ***********
#import "CJLTimerWapper.h"
#import <objc/message.h>
@interface CJLTimerWapper ()
@property(nonatomic, weak) id target;
@property(nonatomic, assign) SEL aSelector;
@property(nonatomic, strong) NSTimer *timer;
@end
@implementation CJLTimerWapper
- (instancetype)cjl_initWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo{
if (self == [super init]) {
//傳入vc
self.target = aTarget;
//傳入的定時(shí)器方法
self.aSelector = aSelector;
if ([self.target respondsToSelector:self.aSelector]) {
Method method = class_getInstanceMethod([self.target class], aSelector);
const char *type = method_getTypeEncoding(method);
//給timerWapper添加方法
class_addMethod([self class], aSelector, (IMP)fireHomeWapper, type);
//啟動(dòng)一個(gè)timer男韧,target是self朴摊,即監(jiān)聽自己
self.timer = [NSTimer scheduledTimerWithTimeInterval:ti target:self selector:aSelector userInfo:userInfo repeats:yesOrNo];
}
}
return self;
}
//一直跑runloop
void fireHomeWapper(CJLTimerWapper *wapper){
//判斷target是否存在
if (wapper.target) {
//如果存在則需要讓vc知道,即向傳入的target發(fā)送selector消息此虑,并將此時(shí)的timer參數(shù)也一并傳入甚纲,所以vc就可以得知`fireHome`方法,就這事這種方式定時(shí)器方法能夠執(zhí)行的原因
//objc_msgSend發(fā)送消息朦前,執(zhí)行定時(shí)器方法
void (*lg_msgSend)(void *,SEL, id) = (void *)objc_msgSend;
lg_msgSend((__bridge void *)(wapper.target), wapper.aSelector,wapper.timer);
}else{
//如果target不存在介杆,已經(jīng)釋放了,則釋放當(dāng)前的timerWrapper
[wapper.timer invalidate];
wapper.timer = nil;
}
}
//在vc的dealloc方法中調(diào)用况既,通過vc釋放这溅,從而讓timer釋放
- (void)cjl_invalidate{
[self.timer invalidate];
self.timer = nil;
}
- (void)dealloc
{
NSLog(@"%s",__func__);
}
@end
使用方式:
@property (nonatomic, strong) LGTimerWapper *timerWapper;
//定義
self.timerWapper = [[CJLTimerWapper alloc] cjl_initWithTimeInterval:1 target:self selector:@selector(fireHome) userInfo:nil repeats:YES];
//釋放
- (void)dealloc{
[self.timerWapper cjl_invalidate];
}
運(yùn)行結(jié)果如下:
解決方式五:利用NSProxy虛基類的子類
NSProxy子類也是處理timer強(qiáng)引用最常用的方式组民。
- 首先定義一個(gè)繼承自NSProxy的子類
//************NSProxy子類************
@interface CJLProxy : NSProxy
+ (instancetype)proxyWithTransformObject:(id)object;
@end
@interface CJLProxy()
@property (nonatomic, weak) id object;
@end
@implementation CJLProxy
+ (instancetype)proxyWithTransformObject:(id)object{
CJLProxy *proxy = [CJLProxy alloc];
proxy.object = object;
return proxy;
}
-(id)forwardingTargetForSelector:(SEL)aSelector {
return self.object;
}
- 將timer中的target傳入NSProxy子類對(duì)象棒仍,即timer持有NSProxy子類對(duì)象
//************解決timer強(qiáng)持有問題************
@property (nonatomic, strong) LGProxy *proxy;
self.proxy = [CJLProxy proxyWithTransformObject:self];
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self.proxy selector:@selector(fireHome) userInfo:nil repeats:YES];
- (void)fireHome{
num++;
NSLog(@"hello word - %d",num);
}
//在dealloc中將timer正常釋放
- (void)dealloc{
[self.timer invalidate];
self.timer = nil;
}
這樣做的主要目的是將強(qiáng)引用轉(zhuǎn)移成消息轉(zhuǎn)發(fā)。虛基類只負(fù)責(zé)消息轉(zhuǎn)發(fā)臭胜,即使用NSProxy作為中間代理莫其、中間者;
vc釋放,導(dǎo)致了proxy的釋放
dealloc方法中耸三,timer進(jìn)行了釋放乱陡,所以runloop強(qiáng)引用也釋放了