循環(huán)引用的理解
首先說一下循環(huán)引用老赤,為什么沒用 __weak
修飾就直接用 self.
屬性轮洋,有時(shí)候不會(huì)造成循環(huán)引用,有時(shí)候會(huì)造成循環(huán)引用呢抬旺。
循環(huán)引用是指兩個(gè)或者多個(gè)對象循環(huán)持有造成的無法釋放(即引用計(jì)數(shù)減不到0)弊予。
例如:類 Person
有個(gè)屬性 block
, 在 block
實(shí)現(xiàn)后, 此時(shí) self
持有 block
开财,如果在 block
中汉柒,直接使用 self
,block
將持有 self
床未,造成循環(huán)引用, 如果 block
本身不是 self
的屬性竭翠,則 self
不持有 block
,即使在 block
中直接使用 self
也不會(huì)造成循環(huán)引用薇搁,但是為了避免多個(gè)對象的循環(huán)引用斋扰,所以 block
中最好還是用 __weak
,防止這種情況出現(xiàn)啃洋。代理用 weak
與此同理传货。
__weak、__block宏娄、__strong的作用
-
__weak:弱引用變量修飾詞问裕,引用計(jì)數(shù)不會(huì) +1。本身可以避免循環(huán)引用的問題的孵坚,但是其會(huì)導(dǎo)致外部對象釋放了之后粮宛,Block 內(nèi)部也訪問不到這個(gè)對象的問題窥淆,我們可以通過在 Block 內(nèi)部聲明一個(gè)
__strong
的變量來指向weakObj
,使外部對象既能在 Block 內(nèi)部保持住巍杈,又能避免循環(huán)引用的問題忧饭。 -
__block:Block內(nèi)部修改外部變量修飾詞,使外部變量可以在 Block 內(nèi)部進(jìn)行修改筷畦。本身無法避免循環(huán)引用的問題词裤,但是我們可以通過在 Block 內(nèi)部手動(dòng)把
blockObj
賦值為nil
的方式來避免循環(huán)引用的問題。另外一點(diǎn)就是__block
修飾的變量在 Block 內(nèi)外都是唯一的鳖宾,要注意這個(gè)特性可能帶來的隱患吼砂。
但是__block
有一點(diǎn):這只是限制在ARC環(huán)境下。在非ARC下鼎文,__block
是可以避免引用循環(huán)的渔肩。 -
__strong:強(qiáng)引用變量修飾詞,引用計(jì)數(shù)會(huì)+1拇惋。常用于 Block 內(nèi)部對
blockObj
的引用修飾赖瞒,如上面??__weak
的說明。
代碼示例
示例1(__weak的使用)
- (void)methond_1
{
NSString *string = @"1";
__weak NSString *weakStr = string;
void (^ block)() = ^ {
// 此處 weakStr 不能被修改蚤假,會(huì)報(bào)紅
//weakStr = @"2";
};
block();
NSLog(@"string = %@ pointer = %p pointer_content = %p", string, &string, string);
NSLog(@"weakStr = %@ pointer = %p pointer_content = %p", weakStr, &weakStr, weakStr);
// &string 得到的是變量 string 本身的存儲(chǔ)地址,而 number 得到的是存儲(chǔ)的內(nèi)容 @"1" 的地址吧兔。
// log:
// string = 1 pointer = 0x7fff587269f8 pointer_content = 0x1075d6ee0
// weakStr = 1 pointer = 0x7fff587269f0 pointer_content = 0x1075d6ee0
}
示例2(__block的使用)
- (void)methond_2
{
// __block:使外部變量可以在 Block 內(nèi)部進(jìn)行修改.
NSNumber *number = @1;
__block NSNumber *blockNum = number;
void (^ block)() = ^ {
blockNum = @2;
};
block();
NSLog(@"number = %@ pointer = %p pointer_content = %p", number, &number, number);
NSLog(@"blockNum = %@ pointer = %p pointer_content = %p", blockNum, &blockNum, blockNum);
// log:
// number = 1 pointer = 0x7fff5e35dad0 pointer_content = 0xb000000000000012
// blockNum = 2 pointer = 0x618000051008 pointer_content = 0xb000000000000022
// 可見 Block 會(huì)拷貝原來對象, __block 修飾的對象可被 Block 內(nèi)外同時(shí)修改.
}
示例3(在堆區(qū)的變量與在棧區(qū)的變量對比)
- (void)methond_3
{
// model 變量是在堆區(qū)
BaseModel *model = [[BaseModel alloc] init];
__weak BaseModel *weakModel = model;
__weak __block TestVC *blockSelf = self;
self.blockModel = ^ {
// 如果 blockSelf 不用 __block 修飾磷仰,則在此處不能修改 testString 值,如果不用 __weak 修飾境蔼,則會(huì)引起循環(huán)引
blockSelf.testString = @"此時(shí) model = nil灶平,model 已被釋放,所以 weakModel = nil";
};
model = nil;
self.blockModel();
NSLog(@"model = %@ pointer = %p pointer_content = %p", model, &model, model);
NSLog(@"weakMoedl = %@ pointer = %p pointer_content = %p", weakModel, &weakModel, weakModel);
// log:
// model = (null) pointer = 0x7fff595a9ad0 pointer_content = 0x0
// weakMoedl = (null) pointer = 0x7fff595a9ac8 pointer_content = 0x0
// number 變量是在棧區(qū)箍土, 值@1是在常量區(qū)
NSNumber *number = @1;
__weak NSNumber *blockNum = number;
number = nil;
NSLog(@"number = %@ pointer = %p pointer_content = %p", number, &number, number);
NSLog(@"blockNum = %@ pointer = %p pointer_content = %p", blockNum, &blockNum, blockNum);
// log:
// number = (null) pointer = 0x7fff5a31cad0 pointer_content = 0x0
// blockNum = 1 pointer = 0x7fff5a31cac8 pointer_content = 0xb000000000000012
// string 變量是在棧區(qū)逢享,值@"string"是在常量區(qū)
NSString *string = @"string";
__weak NSString *weakString = string;
string = nil;
NSLog(@"string = %@ pointer = %p pointer_content = %p", string, &string, string);
NSLog(@"weakString = %@ pointer = %p pointer_content = %p", weakString, &weakString, weakString);
// log:
// string = (null) pointer = 0x7fff5f627ad0 pointer_content = 0x0
// weakString = string pointer = 0x7fff5f627ac8 pointer_content = 0x1006d4e00
// 字符串常量是存在常量區(qū)的,棧內(nèi)存并不會(huì)動(dòng)態(tài)釋放吴藻,而是當(dāng)當(dāng)前線程執(zhí)行完畢后瞒爬,釋放當(dāng)前線程的棧內(nèi)存。所有的常量都存在常量區(qū)沟堡,
// 所以上面的例子中即使使用__ weak 修飾, 但是 @1 和 @"string" 這2個(gè)常量并沒有被釋放, 所以 weak 的地址指向依然存在值.
}
示例4(__weak與__block作用的對比)
- (void)methond_4
{
BaseModel *model = [[BaseModel alloc] init];
__weak BaseModel *weakModel = model;
void (^ block)() = ^ {
// weakModel 弱引用侧但, 此時(shí) model = nil ,所以 strongModel = weakModel = nil
__strong BaseModel *strongModel = weakModel;
NSLog(@"strongModel = %@ pointer = %p pointer_content = %p", strongModel, &strongModel, strongModel);
};
model = nil;
block();
NSLog(@"model = %@ pointer = %p pointer_content = %p", model, &model, model);
NSLog(@"weakMoedl = %@ pointer = %p pointer_content = %p", weakModel, &weakModel, weakModel);
// 在 model 置為 nil 之前, block 的 __ strong 并沒有執(zhí)行, 所以當(dāng)時(shí) model 對象被當(dāng)前的區(qū)塊持有, 當(dāng) model 置為 nil 時(shí), 該對象已經(jīng)被釋放, 所以 __strong 的時(shí)候, weakModel 地址的內(nèi)存已經(jīng)被釋放, strongModel 指向 nil, 所以 model 對象引用計(jì)數(shù)并沒有加 1.
// log:
// strongModel = (null) pointer = 0x7fff5e6669a8 pointer_content = 0x0
// model = (null) pointer = 0x7fff5e666ad0 pointer_content = 0x0
// weakMoedl = (null) pointer = 0x7fff5e666ac8 pointer_content = 0x0
BaseModel *model_2 = [[BaseModel alloc] init];
__block BaseModel *blockModel = model_2;
void (^ blockModel_2)() = ^ {
// weakModel 只是被 __block 修飾航罗,并不是弱引用禀横,所以 model = nil 并不影響 weakModel 的值,所以 strongModel = weakModel != nil
__strong BaseModel *strongModel = blockModel;
NSLog(@"strongModel = %@ pointer = %p pointer_content = %p", strongModel, &strongModel, strongModel);
};
model = nil;
blockModel_2();
NSLog(@"model = %@ pointer = %p pointer_content = %p", model_2, &model_2, model_2);
NSLog(@"blockModel = %@ pointer = %p pointer_content = %p", blockModel, &blockModel, blockModel);
// log:
// strongModel = <BaseModel: 0x60800001b590> pointer = 0x7fff558af9e8 pointer_content = 0x60800001b590
// model = (null) pointer = 0x7fff558afad0 pointer_content = 0x0
// weakMoedl = <BaseModel: 0x60800001b590> pointer = 0x7fff558afac8 pointer_content = 0x60800001b590
}
示例5(__weak和__strong的使用)
- (void)methond_5
{
BaseModel *model = [[BaseModel alloc] init];
__weak BaseModel *weakModel = model;
void (^ block)() = ^ {
__strong BaseModel *strongModel = weakModel;
NSLog(@"雖然此時(shí) model = nil, weakModel 也被 __weak 修飾粥血,但是在下面??線程中 weakModel 被 threadStrong 強(qiáng)引用,weakModel 的引用計(jì)數(shù) +1 糠亩,當(dāng) model = nil 時(shí)森渐,weakModel 也不會(huì)被釋放,所以此時(shí) strongModel = weakModel != nil");
NSLog(@"strongModel = %@ pointer = %p pointer_content = %p", strongModel, &strongModel, strongModel);
};
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
__strong BaseModel *threadStrong = weakModel;
NSLog(@"weakModel 在線程中被強(qiáng)引用缭嫡,引用計(jì)數(shù)+1");
sleep(5);
NSLog(@"threadStrong = %@ pointer = %p pointer_content = %p", threadStrong, &threadStrong, threadStrong);
});
sleep(1);
// 此時(shí) weakModel 在線程中已被強(qiáng)引用,引用計(jì)數(shù) +1茫舶,model = nil 并不能使得 weakModel 也等于 nil
model = nil;
block();
NSLog(@"model = %@ pointer = %p pointer_content = %p", model, &model, model);
NSLog(@"weakMoedl = %@ pointer = %p pointer_content = %p", weakModel, &weakModel, weakModel);
// weakModel 在線程中被強(qiáng)引用械巡,引用計(jì)數(shù)+1
// 雖然此時(shí) model = nil, weakModel 也被 __weak 修飾,但是在下面??線程中 weakModel 被 threadStrong 強(qiáng)引用饶氏,weakModel 的引用計(jì)數(shù) +1 讥耗,當(dāng) model = nil 時(shí),weakModel 也不會(huì)被釋放疹启,所以此時(shí) strongModel = weakModel != nil
// log:
// strongModel = <BaseModel: 0x60000000ad90> pointer = 0x7fff50507958 pointer_content = 0x60000000ad90
// model = (null) pointer = 0x7fff50507ad0 pointer_content = 0x0
// weakMoedl = <BaseModel: 0x60000000ad90> pointer = 0x7fff50507ac8 pointer_content = 0x60000000ad90
// threadStrong = <BaseModel: 0x60000000ad90> pointer = 0x7000041c4d88 pointer_content = 0x60000000ad90
}
示例6(__weak和__strong的使用 -> block 內(nèi)修改全局變量)
@interface TestVC ()
{
NSString *testVar;
}
@property (nonatomic, strong) NSString *testString;
@property (nonatomic, copy) void (^ blockModel)();
@end
@implementation TestVC
- (void)methond_6
{
// 正確使用
__weak __block TestVC *weakSelf = self;
self.blockModel = ^{
__strong TestVC *strongSelf = weakSelf;
strongSelf.testString = @"testString";
strongSelf -> testVar = @"可以修改全局變量古程,并且不會(huì)導(dǎo)致 block 無法釋放";
//當(dāng)然也可以直接把這個(gè)全局變量改為屬性聲明,直接用 weakSelf. 或 strongSelf. 就行
};
//blockModel 被 self 持有喊崖,所以在 block 內(nèi)部必須使用 __weak 修飾的 weakSelf挣磨,又因?yàn)橐薷娜肿兞?testVar 使用 "->", 所以又使用 __strong 修飾的 strongSelf
// 編譯不通過
self.blockModel = ^{
weakSelf.testString = @"testString";
//被 __weak 修飾過的 weakSelf 不能使用 "->"
//weakSelf -> testVar = @"這樣寫編譯不通過荤懂,直接報(bào)紅";
};
//報(bào)紅:"dereferencing a __weak pointer is not allowed die to possible null value caused by a race condition, assign it to strong variable first"
// 無法修改全局變量 testVar(原理同示例3 ??)
__weak __block NSString *weakVar = testVar;
self.blockModel = ^{
weakSelf.testString = @"testString";
weakVar = @"無法修改全局變量 testVar";
};
self.blockModel = ^{
weakSelf.testString = @"testString";
self -> testVar = @"可以修改全局變量 testVar茁裙,但會(huì)引起 block 無法被釋放,導(dǎo)致內(nèi)存泄漏";
};
}
@end
總結(jié)
- 當(dāng)在 block 內(nèi)部修改外部局部變量時(shí)节仿,需要用
__block
修飾晤锥;
e.g.:
NSNumber *number = @1;
__block NSNumber *blockNum = number;
void (^ block)() = ^ {
blockNum = @2;
};
- 當(dāng) block 被
self
持有,并且不對self
做修改廊宪,如self = nil;
(對self
的屬性修改不算是對self
的修改)矾瘾,或者是不對self
的全局變量做修改(因?yàn)闀?huì)用到 "->"),只需要用__weak
修飾即可箭启;
e.g.:
__weak TestVC *weakSelf = self;
self.blockModel = ^{
weakSelf.testString = @"testString";
[weakSelf testMethod];
};
- 當(dāng) block 被
self
持有壕翩,并且對self
做修改,如self = nil;
傅寡,則需要用__weak
和__block
修飾放妈;
e.g.:
__weak __block TestVC *weakSelf = self;
self.blockModel = ^{
weakSelf.testString = @"testString";
[weakSelf testMethod];
weakSelf = nil;
};
- 當(dāng) block 和
self
相互持有時(shí),或者 block 內(nèi)需要修改self
的全局變量時(shí)赏僧,則 block 外部需要用__weak
修飾大猛,block 內(nèi)部需要使用__strong
修飾的變量(為了安全起見,block 內(nèi)部最好還是使用__strong
修飾的變量吧淀零,不明白的請看上面??示例5(__weak和__strong的使用))挽绩;
e.g.:
__weak TestVC *weakSelf = self;
self.blockModel = ^{
__strong TestVC *strongSelf = weakSelf;
strongSelf.testString = @"testString";
strongSelf -> testVar = @"可以修改全局變量,并且不會(huì)導(dǎo)致 block 無法釋放";
};
- 當(dāng) block 和
self
相互持有時(shí)驾中,并且有修改self
唉堪,則外部需要用__weak
和__block
修飾模聋,block 內(nèi)部需要使用__strong
修飾的變量;
e.g.:
__weak __block TestVC *weakSelf = self;
self.blockModel = ^{
__strong TestVC *strongSelf = weakSelf;
strongSelf.testString = @"testString";
strongSelf -> testVar = @"可以修改全局變量唠亚,并且不會(huì)導(dǎo)致 block 無法釋放";
strongSelf = nil;
};
以上5種情況基本說明了各個(gè)修飾詞的使用場景链方,如果把握不來的,或者不理解的灶搜,為了安全起見直接按地種情況去寫祟蚀,老鐵,沒毛病割卖。反正記住 以下幾點(diǎn):
-
__weak
是防止循環(huán)引用的前酿; -
__block
是在 block 內(nèi)部可以修改外部變量的 (在非ARC環(huán)境下也可以防止循環(huán)引用); -
__strong
是在內(nèi)部防止外部的weak
變量被提前釋放鹏溯,在內(nèi)部無法獲取weak
變量罢维;
以上示例代碼基本可以說明__weak
、__block
丙挽、__strong
的使用規(guī)則了肺孵,如果還有哪些不清楚的,沒有在示例代碼中展現(xiàn)出來颜阐,建議自己動(dòng)手寫寫看平窘。如有對內(nèi)存分配不太理解的小伙伴可以看看這篇文章《iOS程序中的內(nèi)存分配(棧區(qū)和堆區(qū)的對比)》。
補(bǔ)充(weak的實(shí)現(xiàn)原理)
weak
變量在引用計(jì)數(shù)為0時(shí)凳怨,會(huì)被自動(dòng)設(shè)置成nil
初婆,這個(gè)特性是如何實(shí)現(xiàn)的?
很少有人知道weak
表其實(shí)是一個(gè)hash(哈希)表猿棉,Key是所指對象的地址,Value是 weak
指針的地址數(shù)組屑咳。更多人的人只是知道 weak
是弱引用萨赁,所引用對象的計(jì)數(shù)器不會(huì)加一,并在引用對象被釋放的時(shí)候自動(dòng)被設(shè)置為nil
兆龙。通常用于解決循環(huán)引用問題杖爽。但現(xiàn)在單知道這些已經(jīng)不足以應(yīng)對面試了,好多公司會(huì)問 weak
的原理紫皇。weak
的原理是什么呢慰安?具體細(xì)節(jié)分析請看《iOS 底層解析weak的實(shí)現(xiàn)原理(包含weak對象的初始化,引用聪铺,釋放的分析)》化焕。
weak 實(shí)現(xiàn)原理的概括
Runtime
維護(hù)了一個(gè) weak
表,用于存儲(chǔ)指向某個(gè)對象的所有 weak
指針铃剔。weak表其實(shí)是一個(gè)hash(哈希)表撒桨,Key是所指對象的地址查刻,Value是weak
指針的地址(這個(gè)地址的值是所指對象的地址)數(shù)組。
weak 的實(shí)現(xiàn)原理可以概括一下三步:
1凤类、初始化時(shí):runtime
會(huì)調(diào)用 objc_initWeak
函數(shù)穗泵,初始化一個(gè)新的 weak
指針指向?qū)ο蟮牡刂贰?br>
2、添加引用時(shí):objc_initWeak
函數(shù)會(huì)調(diào)用 objc_storeWeak()
函數(shù)谜疤, objc_storeWeak()
的作用是更新指針指向佃延,創(chuàng)建對應(yīng)的弱引用表。
3夷磕、釋放時(shí)履肃,調(diào)用 clearDeallocating
函數(shù)。clearDeallocating
函數(shù)首先根據(jù)對象地址獲取所有 weak
指針地址的數(shù)組企锌,然后遍歷這個(gè)數(shù)組把其中的數(shù)據(jù)設(shè)為 nil
榆浓,最后把這個(gè) entry
從 weak
表中刪除,最后清理對象的記錄撕攒。