iOS 从assign修饰对象说起

面试遇到的问题,回答的不理想,这里稍微总结下。

内存分配

栈空间:存储基本数据类型,保存的变量的内存在栈帧弹出时自动清理;
堆空间:存储对象类型,需直接管理。OC将堆内存管理抽象出来,不需要用malloc和free来分配或释放,所使用的内存管理架构为“自动引用计数”,即ARC。

修饰符

weak:只可修饰对象

如修饰基本数据类型,编译器报错“Property with ‘weak’ attribute must be of object type”。

修饰对象:weak修饰的对象释放后(引用计数器值为0),指针会自动被置nil。

assign:可修饰基本数据类型和对象

修饰基本数据类型:基本数据类型是分配在栈上的,由系统分配和释放,所以不会造成野指针。

修饰对象:如果用assign修饰对象,当对象被释放后,指针的地址还是存在的,也就是说指针并没有被置为nil,从而造成了野指针。因为对象是分配在堆上的,堆上的内存由程序员分配释放。而因为指针没有被置为nil,如果后续的内存分配中,刚好分配到了这块内存,就会造成崩溃。

**拓展

  • 为什么修饰代理使用weak而不是用assign

assign可用来修饰基本数据类型,也可修饰OC的对象,但如果用assign修饰对象类型指向的是一个强指针,当指向的这个指针释放之后,它仍指向这块内存,必须要手动给置为nil,否则会产生野指针,如果还通过此指针操作那块内存,会导致EXC_BAD_ACCESS错误,调用了已经被释放的内存空间;而weak只能用来修饰OC对象,而且相比assign比较安全,如果指向的对象消失了,那么它会自动置为nil,不会导致野指针。

  • runtime如何实现weak变量的自动置nil

Runtime维护了一个weak表,用于存储指向某个对象的所有weak指针。weak表其实是一个hash(哈希)表,key是所指对象的地址,value是weak指针的地址(这个地址的值是所指对象的地址)数组。
weak源码相关数据结构:

/**
 * The internal structure stored in the weak references table. 
 * It maintains and stores
 * a hash set of weak references pointing to an object.
 * If out_of_line==0, the set is instead a small inline array.
 */
#define WEAK_INLINE_COUNT 4
struct weak_entry_t {
    DisguisedPtr<objc_object> referent;
    union {
        struct {
            weak_referrer_t *referrers;
            uintptr_t        out_of_line : 1;
            uintptr_t        num_refs : PTR_MINUS_1;
            uintptr_t        mask;
            uintptr_t        max_hash_displacement;
        };
        struct {
            // out_of_line=0 is LSB of one of these (don't care which)
            weak_referrer_t  inline_referrers[WEAK_INLINE_COUNT];
        };
    };
};

/**
 * 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;
    size_t    num_entries;
    uintptr_t mask;
    uintptr_t max_hash_displacement;
};

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

referent 是指 weak 变量指向的对象,referrer 是指 weak 变量。
最外层是一个 StripedMap,以 referent 实例地址作为 key,通过哈希,平均映射到 64 个 SideTable 中。
SideTable 中最关键的一个成员是 weak_table,weak_table 的成员 weak_entries 是一个 weak_entry_t 结构体数组,每一个 weak_entry_t 结构体都保存着 referent 地址和指向这个 referent 的所有 weak 变量地址,也就是 referrers 数组。

weak 的实现原理可以概括一下三步:

1、初始化时:runtime会调用objc_initWeak函数,初始化一个新的weak指针指向对象的地址。

2、添加引用时:objc_initWeak函数会调用objc_storeWeak() 函数,objc_storeWeak() 的作用是更新指针指向,创建对应的弱引用表。

3、释放时,调用clearDeallocating函数。clearDeallocating函数首先根据对象地址获取所有weak指针地址的数组,然后遍历这个数组把其中的数据设为nil,最后把这个entry从weak表中删除,最后清理对象的记录。

置nil逻辑:
weak_clear_no_lock
这个函数主要逻辑是找到对应的 weak entry,遍历 referrers 数组,将所有的 weak 变量都置为 nil,再将 weak entry 移除掉。相关代码:

/** 
 * 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.
 * 
 * FIXME currently requires old referent value to be passed in (lame)
 * FIXME unregistration should be automatic if referrer is collected
 * 
 * @param weak_table The global weak table.
 * @param referent The object.
 * @param referrer The weak reference.
 */
void
weak_unregister_no_lock(weak_table_t *weak_table, id referent_id, 
                        id *referrer_id)
{
    objc_object *referent = (objc_object *)referent_id;
    objc_object **referrer = (objc_object **)referrer_id;

    weak_entry_t *entry;

    if (!referent) return;

    if ((entry = weak_entry_for_referent(weak_table, referent))) {
        remove_referrer(entry, referrer);
        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) {
            weak_entry_remove(weak_table, entry);
        }
    }

    // Do not set *referrer = nil. objc_storeWeak() requires that the 
    // value not change.
}

相关文章:
weak源码链接(.h/.m):https://github.com/opensource-apple/objc4/blob/master/runtime/objc-weak.h
http://jefferyfan.com/2019/11/15/programing/iOS/weak/

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值