一酵紫、修飾符
ARC 環(huán)境下扒最,所有的修飾符有以下4種
-
__strong
修飾符 -
__weak
修飾符 -
__unsafe_unretained
修飾符 -
__autoreleasing
修飾符
- 底層都是被函數(shù)
objc_ownership(xxx)
修飾丑勤,不同的修飾符,入?yún)xx不同吧趣。
__strong
--->objc_ownership(strong)
__weak
--->objc_ownership(weak)
__unsafe_unretained
--->objc_ownership(none)
__autoreleasing
--->objc_ownership(autoreleasing)
1.1法竞、以__weak
為例
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
NSString *str = @"helloworld";
id __weak objc = str;
NSLog(@"%@", objc);
return 0;
}
注意: clang -rewrite-objc main.m
會報錯,需要用以下替換强挫。
clang -rewrite-objc -fobjc-arc -stdlib=libc++ -mmacosx-version-min=10.7 -fobjc-runtime=macosx-10.7 -Wno-deprecated-declarations weak.m
int main(int argc, const char * argv[]) {
NSString *str = (NSString *)&__NSConstantStringImpl__var_folders_jf_zkvr_3r17rl3q1fw6zhz6jr40000gn_T_weak_cfe516_mi_0;
// id __weak objc = str;
id __attribute__((objc_ownership(weak))) objc = str;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_jf_zkvr_3r17rl3q1fw6zhz6jr40000gn_T_weak_cfe516_mi_1, objc);
return 0;
}
二岔霸、__weak
和__strong
的原理
2.1、__weak
原理
1俯渤、基礎(chǔ)知識
- 最大作用是防止循環(huán)引用呆细,當屬性對象被釋放的時候,weak屬性會自動置為nil八匠。
2絮爷、源碼解析
-
__weak
底層會調(diào)用函數(shù)objc_initWeak()
,函數(shù)內(nèi)部調(diào)用objc_storeWeak()
梨树。
在以下代碼處斷點坑夯,開啟匯編窗口(Debug -> Debug Wokflow -> Always Show Disassembly
):
id __weak obj = str;
發(fā)現(xiàn)底層調(diào)用了objc_initWeak
函數(shù),查找源碼劝萤,其實現(xiàn)如下
/**
* 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. // __weak指針 的地址 渊涝,存儲指針的地址。
* @param newObj Object ptr. // 所引用的對象,即例子中的obj 跨释。
*/
id objc_initWeak(id *location, id newObj) {
if (!newObj) {
*location = nil;
return nil;
}
return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
(location, (objc_object*)newObj);
}
注意:實現(xiàn)原理在于理解 objc_storeWeak()
函數(shù)的實現(xiàn)胸私,詳細可見 iOS底層原理:weak的實現(xiàn)原理 。
5鳖谈、理解weak實現(xiàn)原理岁疼,重點理解內(nèi)部的存儲結(jié)構(gòu):
struct SideTable {
spinlock_t slock; // 內(nèi)部使用自鎖鞋保證線程安全
RefcountMap refcnts;
weak_table_t weak_table; // 核心數(shù)據(jù)結(jié)構(gòu),存儲對象弱引用指針的hash表
}
struct weak_table_t {
weak_entry_t *weak_entries; // hash數(shù)組缆娃,捷绒,用來存儲弱引用對象的相關(guān)信息weak_entry_t
size_t num_entries; // hash數(shù)組中的元素個數(shù)
// 以下兩個與hash值計算和哈希沖突有關(guān)
uintptr_t mask;
uintptr_t max_hash_displacement;
};
// 一個hash結(jié)構(gòu),其存儲的元素是弱引用對象指針的指針
// 通過操作指針的指針贯要,就可以使得weak 引用的指針在對象析構(gòu)后暖侨,指向nil。
struct weak_entry_t {
DisguisedPtr<objc_object> referent;
// 聯(lián)合體
union {
struct {
// 動態(tài)數(shù)組
weak_referrer_t *referrers;
uintptr_t out_of_line_ness : 2;
uintptr_t num_refs : PTR_MINUS_2;
uintptr_t mask;
uintptr_t max_hash_displacement;
};
struct {
// 定長數(shù)組
// out_of_line_ness field is low bits of inline_referrers[1]
weak_referrer_t inline_referrers[WEAK_INLINE_COUNT];
};
};
// 用來判斷采用哪種存儲方式(定長數(shù)組 或 動態(tài)數(shù)組)
// 當弱引用該對象的指針數(shù)目小于等于WEAK_INLINE_COUNT時崇渗,使用定長數(shù)組字逗。
// 當超過WEAK_INLINE_COUNT時,會將定長數(shù)組中的元素轉(zhuǎn)移到動態(tài)數(shù)組中宅广,并之后都是用動態(tài)數(shù)組存儲葫掉。
bool out_of_line() {
return (out_of_line_ness == REFERRERS_OUT_OF_LINE);
}
weak_entry_t& operator=(const weak_entry_t& other) {
memcpy(this, &other, sizeof(other));
return *this;
}
// 初始化 弱引用對象 數(shù)組
weak_entry_t(objc_object *newReferent, objc_object **newReferrer)
: referent(newReferent)
{
inline_referrers[0] = newReferrer;
for (int i = 1; i < WEAK_INLINE_COUNT; i++) {
inline_referrers[i] = nil;
}
}
};
// weak_referrer_t類型:DisguisedPtr泛型類,這里面類型是 objc_object
typedef DisguisedPtr<objc_object *> weak_referrer_t;
借用別人整理的圖如下:
6跟狱、weak 原理總結(jié)
0俭厚、首先,編譯器在處理 __weak
的時候驶臊,底層調(diào)用objc_initWeak()
函數(shù)挪挤,內(nèi)部調(diào)用objc_storeWeak()
函數(shù),最終調(diào)用核心函數(shù)storeWeak()
关翎。
1电禀、然后,創(chuàng)建或者獲取弱引用表笤休;
其中尖飞,弱引用表的結(jié)構(gòu)是一個hash結(jié)構(gòu)的表,Key是所指對象的地址店雅,Value是weak指針的地址(這個地址的值是所指對象的地址)數(shù)組政基。
通過 對象地址 取弱引用表 table = &SideTable()[object],其中SideTable()返回的是StripedMap類型的全局SideTablesMap
)闹啦,其數(shù)組值為 SideTable
類型沮明,內(nèi)部維護weak_table_t weak_table
;
weak_table
內(nèi)部核心是持有weak_entry_t *weak_entries
是一個動態(tài)數(shù)組,用來存儲weak_entry_t
類型的元素窍奋,這些元素實際上就是OC對象的弱引用信息荐健。
weak_entry_t
的結(jié)構(gòu)也是一個hash結(jié)構(gòu)酱畅,其存儲的元素是 弱引用對象指針的指針(即weak_referrer_t *referrers 或者 weak_referrer_t inline_referrers[WEAK_INLINE_COUNT];
);
這樣就可以通過操作指針的指針江场,就可以使得 weak 引用的指針在對象析構(gòu)后纺酸,指向nil。
因此址否,整個的數(shù)據(jù)結(jié)構(gòu)如下:
// 關(guān)于 StripedMap有興趣的可以繼續(xù)閱讀餐蔬,不在深入
static objc::ExplicitInit<StripedMap<SideTable>> SideTablesMap;
static StripedMap<SideTable>& SideTables() {
return SideTablesMap.get();
}
value = &SideTable()[newObj]
value->weak_table->weak_entries[key_index].referrers[index] == weak_pointer;
// 其中 key_index ,計算算法如下:
while (weak_table->weak_entries[key_index].referent != referent) {
...
key_index = (key_index+1) & weak_table->mask;
...
}
舉個例子:
Person * newObj = [Person alloc];
id __weak p1 = newObj;
則 弱引用哈希表的 key == 實例對象user的地址
value->weak_table->weak_entries[key_index].referrers[index] == p1;
2佑附、注冊__weak的時候樊诺,按照上面的數(shù)據(jù)結(jié)構(gòu),將weak指針 和 對象 建立聯(lián)系 并進行存儲音同;
3词爬、對象newObj釋放的時候,根據(jù)上面的數(shù)據(jù)結(jié)構(gòu)权均,將數(shù)組里面的取出 weak指針缸夹,依次設(shè)置為nil;
4螺句、內(nèi)部采用自旋鎖來保證線程安全。
2.2橡类、__strong
其修飾的對象引用計數(shù)器會+1蛇尚,避免在block執(zhí)行的時候,對象被提前釋放了顾画。
2.3取劫、__weak
和 __strong
的應(yīng)用
block外使用:
__weak typeof(self) weakSelf = self;
block捕獲的是weakSelf
,而其會在對象dealoc的時候研侣,自動被釋放置為nil谱邪,因此打破循環(huán)引用。block內(nèi)使用:
__strong typeof(weakSelf) strongSelf = weakSelf;
1庶诡、block 內(nèi)的代碼在__MyObject__test_block_func_0
函數(shù)內(nèi)惦银,當使用strongSelf
時,會先取出__weak
修飾的成員變量self
末誓。
2扯俱、然后再生成一個__strong
修飾的局部變量,這時候self
的引用計數(shù) +1喇澡。
3迅栅、這樣的目的是在block
內(nèi)的代碼塊執(zhí)行完之前避免self
被dealloc
掉。當block
執(zhí)行完畢之后晴玖,局部變量strongSelf
被釋放读存,self 的引用計數(shù) -1为流。
三、關(guān)于RAC的奇妙宏@weakify(self) 和 @strongify(self)
宏的使用過程很精妙让簿,有興趣可以查看
深入研究 Block 用 weakSelf敬察、strongSelf、@weakify拜英、@strongify 解決循環(huán)引用
Reactive Cocoa中的@weakify静汤、@strongify是如何裝逼
- 兩個宏的作用等同于:
@weakify(self) = @autoreleasepool{} __weak __typeof__ (self) self_weak_ = self;
@strongify(self) = @autoreleasepool{} __strong __typeof__(self) self = self_weak_;
5.1、@weakify(self) 和 @strongify(self)
的原理分析
#import <Foundation/Foundation.h>
@interface MyObject : NSObject {
NSString *_age;
}
@property (nonatomic, strong) NSString *name;
@property (nonatomic, copy) dispatch_block_t actionBlock;
@end
@implementation MyObject
- (void)dealloc {
NSLog(@"MyObject Dealloc");
}
- (void)test {
self.name = @"n";
_age = @"10";
@autoreleasepool{} __weak __typeof__ (self) self_weak_ = self; // @weakify(self)
void(^textBlock)(void) = ^{
@autoreleasepool{} __strong __typeof__(self) self = self_weak_; // @strongify(self)
_age = @"11"; // 會導致循環(huán)引用
//self.name = @"a"; // 不會導致循環(huán)引用
//self->_age = @"23"; // 不會導致循環(huán)引用
};
textBlock();
self.actionBlock = textBlock;
}
@end
-
clang -rewrite-objc -fobjc-arc -fobjc-runtime=macosx-10.14 filename
查看底層代碼居凶。
struct __MyObject__test_block_impl_0 {
struct __block_impl impl;
struct __MyObject__test_block_desc_0* Desc;
MyObject *const __weak self_weak_; // 捕獲的是self_weak_
__MyObject__test_block_impl_0(void *fp, struct __MyObject__test_block_desc_0 *desc, MyObject *const __weak _self_weak_, int flags=0) : self_weak_(_self_weak_) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __MyObject__test_block_func_0(struct __MyObject__test_block_impl_0 *__cself) {
MyObject *const __weak self_weak_ = __cself->self_weak_; // bound by copy
/* @autoreleasepool */{ __AtAutoreleasePool __autoreleasepool; } __attribute__((objc_ownership(strong))) __typeof__(self) self = self_weak_;
(*(NSString *__strong *)((char *)self + OBJC_IVAR_$_MyObject$_age)) = (NSString *)&__NSConstantStringImpl__var_folders_jf_zkvr_3r17rl3q1fw6zhz6jr40000gn_T_person_a08aad_mi_3;
}
block默認捕獲self是強引用(
MyObject *const __strong self;
)虫给;而使用上面修飾后@weakify(self)
,block內(nèi)部捕獲的是MyObject *const __weak self_weak_;
侠碧,因此打破了循環(huán)引用抹估。注意1:block內(nèi)部如果使用
_name
方式引用,也會導致循環(huán)引用弄兜;必須通過self.name
或者self->name
才能打破循環(huán)引用药蜻。
具體原因,網(wǎng)上有人是說對self和self_weak_都進行了捕獲替饿,可是clang之后的代碼沒有發(fā)現(xiàn)顯示引用语泽,有待研究。注意2:
@weakify
和@strongify
必須都使用视卢。
測試發(fā)現(xiàn)如果只使用@weakify
踱卵,而不調(diào)@ strongify
一樣會導致循環(huán)引用。原因是這種情況下捕獲的還是self
据过,而不是self_weak_
惋砂。
struct __MyObject__test_block_impl_0 {
struct __block_impl impl;
struct __MyObject__test_block_desc_0* Desc;
MyObject *const __strong self;
__MyObject__test_block_impl_0(void *fp, struct __MyObject__test_block_desc_0 *desc, MyObject *const __strong _self, int flags=0) : self(_self) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __MyObject__test_block_func_0(struct __MyObject__test_block_impl_0 *__cself) {
MyObject *const __strong self = __cself->self; // bound by copy
((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)self, sel_registerName("setName:"), (NSString *)&__NSConstantStringImpl__var_folders_jf_zkvr_3r17rl3q1fw6zhz6jr40000gn_T_person_2fca58_mi_3);
}