学习笔记:@weakify(self) 和 @strongify(self) 和 __weak、__strong等

一、修饰符

ARC 环境下,所有的修饰符有以下4种

  • __strong修饰符
  • __weak修饰符
  • __unsafe_unretained修饰符
  • __autoreleasing 修饰符
  • 底层都是被函数objc_ownership(xxx)修饰,不同的修饰符,入参xxx不同。
    __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、基础知识
  • 最大作用是防止循环引用,当属性对象被释放的时候,weak属性会自动置为nil。
2、源码解析
  • __weak 底层会调用函数objc_initWeak() ,函数内部调用 objc_storeWeak()

在以下代码处断点,开启汇编窗口(Debug -> Debug Wokflow -> Always Show Disassembly ):

id __weak obj = str;
image.png

发现底层调用了objc_initWeak函数,查找源码,其实现如下

/** 
 * 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);
}

注意:实现原理在于理解 objc_storeWeak()函数的实现,详细可见 iOS底层原理:weak的实现原理

5、理解weak实现原理,重点理解内部的存储结构:
struct SideTable {
    spinlock_t slock; // 内部使用自锁鞋保证线程安全
    RefcountMap refcnts;
    weak_table_t weak_table; // 核心数据结构,存储对象弱引用指针的hash表
}

struct weak_table_t {
    weak_entry_t *weak_entries; // hash数组,,用来存储弱引用对象的相关信息weak_entry_t
    size_t    num_entries; // hash数组中的元素个数
    
    // 以下两个与hash值计算和哈希冲突有关
    uintptr_t mask;
    uintptr_t max_hash_displacement;
};

// 一个hash结构,其存储的元素是弱引用对象指针的指针
// 通过操作指针的指针,就可以使得weak 引用的指针在对象析构后,指向nil。
struct weak_entry_t {
    DisguisedPtr<objc_object> referent;
    // 联合体
    union {
        struct {
            // 动态数组
            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 {
            // 定长数组
            // out_of_line_ness field is low bits of inline_referrers[1]
            weak_referrer_t  inline_referrers[WEAK_INLINE_COUNT]; 
        };
    };
    
    // 用来判断采用哪种存储方式(定长数组 或 动态数组)
   // 当弱引用该对象的指针数目小于等于WEAK_INLINE_COUNT时,使用定长数组。
   // 当超过WEAK_INLINE_COUNT时,会将定长数组中的元素转移到动态数组中,并之后都是用动态数组存储。
    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;
    }

    // 初始化 弱引用对象 数组
    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; 

借用别人整理的图如下:


image.png
image.png
6、weak 原理总结

0、首先,编译器在处理 __weak的时候,底层调用objc_initWeak() 函数,内部调用objc_storeWeak()函数,最终调用核心函数storeWeak()

1、然后,创建或者获取弱引用表;
其中,弱引用表的结构是一个hash结构的表,Key是所指对象的地址,Value是weak指针的地址(这个地址的值是所指对象的地址)数组。

通过 对象地址 取弱引用表 table = &SideTable()[object],其中SideTable()返回的是StripedMap类型的全局SideTablesMap),其数组值为 SideTable类型,内部维护weak_table_t weak_table;

weak_table内部核心是持有weak_entry_t *weak_entries是一个动态数组,用来存储weak_entry_t类型的元素,这些元素实际上就是OC对象的弱引用信息。

weak_entry_t的结构也是一个hash结构,其存储的元素是 弱引用对象指针的指针(即weak_referrer_t *referrers 或者 weak_referrer_t inline_referrers[WEAK_INLINE_COUNT];);

这样就可以通过操作指针的指针,就可以使得 weak 引用的指针在对象析构后,指向nil。

因此,整个的数据结构如下:

  // 关于 StripedMap有兴趣的可以继续阅读,不在深入
  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的时候,按照上面的数据结构,将weak指针 和 对象 建立联系 并进行存储;

3、对象newObj释放的时候,根据上面的数据结构,将数组里面的取出 weak指针,依次设置为nil;

4、内部采用自旋锁来保证线程安全。

2.2、__strong

其修饰的对象引用计数器会+1,避免在block执行的时候,对象被提前释放了。

2.3、__weak__strong 的应用

  • block外使用:__weak typeof(self) weakSelf = self;
    block捕获的是weakSelf,而其会在对象dealoc的时候,自动被释放置为nil,因此打破循环引用。

  • block内使用:__strong typeof(weakSelf) strongSelf = weakSelf;
    1、block 内的代码在__MyObject__test_block_func_0函数内,当使用strongSelf时,会先取出__weak修饰的成员变量self
    2、然后再生成一个__strong修饰的局部变量,这时候 self 的引用计数 +1。
    3、这样的目的是在 block 内的代码块执行完之前避免 selfdealloc掉。当 block 执行完毕之后,局部变量 strongSelf 被释放,self 的引用计数 -1。

三、关于RAC的奇妙宏@weakify(self) 和 @strongify(self)

宏的使用过程很精妙,有兴趣可以查看
深入研究 Block 用 weakSelf、strongSelf、@weakify、@strongify 解决循环引用
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"; // 会导致循环引用

        //self.name = @"a"; // 不会导致循环引用
        //self->_age = @"23"; // 不会导致循环引用
    };

    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内部捕获的是MyObject *const __weak self_weak_;,因此打破了循环引用。

  • 注意1:block内部如果使用_name方式引用,也会导致循环引用;必须通过self.name 或者 self->name才能打破循环引用。
    具体原因,网上有人是说对self和self_weak_都进行了捕获,可是clang之后的代码没有发现显示引用,有待研究。

  • 注意2:@weakify@strongify必须都使用。
    测试发现如果只使用@weakify,而不调@ strongify一样会导致循环引用。原因是这种情况下捕获的还是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);
}

参考

iOS 底层解析weak的实现原理
iOS weak 底层实现原理:StripedMap

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,716评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,558评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,431评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,127评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,511评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,692评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,915评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,664评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,412评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,616评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,105评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,424评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,098评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,096评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,869评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,748评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,641评论 2 271

推荐阅读更多精彩内容