解读objc源码:weak的实现原理

一、weak的作用

这里的weak包括属性关键字weak和__weak两种:__weak用于修饰变量(variable),weak用于修饰属性(property)。

1. weak是弱引用,用weak描述修饰或者所引用对象的计数不会加1
2. weak修饰的指针在引用的对象被释放的时候自动被设置为nil,避免野指针
3. weak可以解决使用block引起的循环引用。

看到这里又想起了block和assign,复习一下这几个的区别:

1.__block不管是ARC还是MRC模式下都可以使用,可以修饰对象,还可以修饰基本数据类型。
2.__weak只能在ARC模式下使用,也只能修饰对象(NSString),不能修饰基本数据类型(int)。assign用来修饰基本数据类型
3.__block对象可以在block中被重新赋值,__weak不可以。
4.__block对象在ARC下可能会导致循环引用,非ARC下会避免循环引用,__weak只在ARC下使用,可以避免循环引用。

OK,以上说的都没有错,大家也都会用,但是你知道是怎么实现的吗


二、weak的源码实现

1、先看下objc-weak.h文件里面的API
typedef DisguisedPtr<objc_object *> weak_referrer_t;
struct weak_entry_t { ... };
struct weak_table_t { ... };
/// Adds an (object, weak pointer) pair to the weak table.
id weak_register_no_lock(weak_table_t *weak_table, id referent, 
                         id *referrer, bool crashIfDeallocating);
/// Removes an (object, weak pointer) pair from the weak table.
void weak_unregister_no_lock(weak_table_t *weak_table, id referent, id *referrer);
#if DEBUG
/// Returns true if an object is weakly referenced somewhere.
bool weak_is_registered_no_lock(weak_table_t *weak_table, id referent);
#endif
/// Called on object destruction. Sets all remaining weak pointers to nil.
void weak_clear_no_lock(weak_table_t *weak_table, id referent);

看起来很简单的结构体和几个方法:

  • weak_table_t:翻译一下注释上的说明,弱引用的哈希表table,对象id作为key,weak_entry_t结构体作为value
  • weak_register_no_lock:向weak table里面添加一个object和它的weak引用指针
  • weak_unregister_no_lock:从weak table移除一个object和它的weak引用指针
  • weak_is_registered_no_lock:如果object有弱引用返回true
  • weak_clear_no_lock:object对象被销毁,把所有指向它的弱引用指针全部置为nil

2、根据API看下.m里面的实现

2.1 weak_register_no_lock添加弱引用
源码实现:

id 
weak_register_no_lock(weak_table_t *weak_table, id referent_id, 
                      id *referrer_id, bool crashIfDeallocating)
{
    objc_object *referent = (objc_object *)referent_id;
    objc_object **referrer = (objc_object **)referrer_id;
    // ensure that the referenced object is viable
    。。。这里省略判断被引用的对象是否存在的代码,只看关键部分
    weak_entry_t *entry;
    if ((entry = weak_entry_for_referent(weak_table, referent))) {
        append_referrer(entry, referrer);
    } 
    else {
        weak_entry_t new_entry(referent, referrer);
        weak_grow_maybe(weak_table);
        weak_entry_insert(weak_table, &new_entry);
    }
    return referent_id;
}

解释一下关键地方:

  1. if ((entry = weak_entry_for_referent(weak_table, referent))):判断weak_table是否存在这个对象referent
  2. 如果table表中已经存在对象referent,那么执行append_referrer(entry, referrer);把referrer这个新的引用加入到referent已经存在的引用列表中
  3. 如果不存在,执行
    weak_entry_t new_entry(referent, referrer):给对象referent创建一个新的引用列表
    weak_grow_maybe(weak_table):weak_table增加内存
    weak_entry_insert(weak_table, &new_entry):把referent的引用列表加入到weak_table中

2.2 weak_unregister_no_lock:移除弱引用
当我们使用weak指针指向一个对象__weak typeof(self) = self的时候,会调weak_register_no_lock在weak table表中添加这个引用,但是移除引用什么时候会用呢,还是看一下源码和注释:

/** 
 * Unregister an already-registered weak reference.
 * This is used when referrer's storage is about to go away, but referent
 * isn't dead yet. (Otherwise, zeroing referrer later would be a
 * bad memory access.)
 * Does nothing if referent/referrer is not a currently active weak reference.
 * Does not zero referrer.
 */
void
weak_unregister_no_lock(weak_table_t *weak_table, id referent_id, 
                        id *referrer_id)
{
    // referent是对象也就是weak table中的key
    objc_object *referent = (objc_object *)referent_id;
    //*referrer是指向这个对象的弱引用指针
    objc_object **referrer = (objc_object **)referrer_id;

    weak_entry_t *entry;

    if (!referent) return;
    ///从weak_table中根据key(referent)找到entry(指向对象弱引用列表的指针)
    if ((entry = weak_entry_for_referent(weak_table, referent))) {
        /// 从引用列表中移除这个引用referrer, remove_referrer这个方法会把这个referrer置为nil
        remove_referrer(entry, referrer);
        /// 之后遍历对象referent的引用列表是不是空了
        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;
                }
            }
        }
        if (empty) {
            /// 如果该对象的弱引用列表已经empty,就把该对象从weak_table中移除
            weak_entry_remove(weak_table, entry);
        }
    }
}

这个应该是这种情况下使用的,比如我们在一个方法里面这样定义:

- (void)testWeakReferrer {
    __weak typeof(self) weakSelf = self;
    ...
    return;
}

weakSelf是方法的局部变量,当方法执行完的时候,局部变量weakSelf会被销毁,这个时候系统应该就会自动执行weak_unregister_no_lock方法,把weakSelf从self对象的弱引用列表中移除,并且把weakSelf置为nil。


2.3 weak_clear_no_lock清空所有的weak指针

/** 
 * Called by dealloc; nils out all weak pointers that point to the 
 * provided object so that they can no longer be used.
 * 
 * @param weak_table 
 * @param referent The object being deallocated. 
 */
void 
weak_clear_no_lock(weak_table_t *weak_table, id referent_id) 
{
    objc_object *referent = (objc_object *)referent_id;

    weak_entry_t *entry = weak_entry_for_referent(weak_table, referent);
    ...
    // zero out references
    weak_referrer_t *referrers;
    size_t count;
    ...//获取列表中的数量
    for (size_t i = 0; i < count; ++i) {
        objc_object **referrer = referrers[i];
        if (referrer) {
            if (*referrer == referent) {
                *referrer = nil;
            }
            ...
        }
    }
    
    weak_entry_remove(weak_table, entry);
}

Called by dealloc,当对象被销毁的时候调用这个方法,在这个方法里面把所有指向这个对象的弱引用指针全部置为nil,最后从weak table中移除该对象和它的弱引用列表。

ok,这也就解释了为什么weak指针会被自动置为nil了

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

推荐阅读更多精彩内容