Objective-C 对象的内存管理-变量修饰符

2022-04-05  本文已影响0人  _涼城

__strong 修饰符

     __strong 修饰符是 id 类型和对象类型默认的所有权修饰符。__strong 修饰符表示对对象的强引用。当前对象被其他对象引用时,会执行 retain 操作,引用计数器+1。当 retainCount = 0 时,该对象才会被销毁。由于所有权默认类型为 __strong 修饰符,所以不需要写上 __strong

 id __strong obj = [NSObject alloc]init];

循环引用

     如果仅通过 __strong 修饰符不能解决引用计数式内存管理必然会发生的循环引用问题。循环引用容易发生内存泄露。所谓内存泄露就是应当废弃的对象在超出其生命周期后继续存在。

循环引用

__weak 修饰符

    怎么样才能避免循环引用呢?使用 __weak 修饰符可以避免循环引用。__weak 修饰符不持有对象,在超出其变量作用域时,对象即被释放。并且在对象被废弃时,弱引用自动失效且处于 nil 被赋值的状态。

__weak 修饰符的实现

/*编译器模拟代码*/
id obj1;
objc_initWeak(&obj1,obj);
objc_destoryWeak(&obj1);

objc_initWeak

    在 objc 源码中,通过 objc_initWeak 函数初始化附有 __weak 修饰符的变量,源码如下:

 * __weak id weakPtr;
 * (The non-nil case) 
 * 
 * 
 * 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. 
 * @param newObj Object ptr. 
 */
id
objc_initWeak(id *location, id newObj)
{
    if (!newObj) {
        *location = nil;
        return nil;
    }

    return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
        (location, (objc_object*)newObj);
}

storeWeak

可以看到 objc_initWeak 函数的注释表明传入参数实际上是 ptr 指针 和 o 对象 调用 storeWeak 函数,源码如下:

template <HaveOld haveOld, HaveNew haveNew,
          CrashIfDeallocating crashIfDeallocating>
static id 
storeWeak(id *location, objc_object *newObj)
{
    ASSERT(haveOld  ||  haveNew);
    if (!haveNew) ASSERT(newObj == nil);

    Class previouslyInitializedClass = nil;
    id oldObj;
    SideTable *oldTable;
    SideTable *newTable;

    // Acquire locks for old and new values.
    // Order by lock address to prevent lock ordering problems. 
    // Retry if the old value changes underneath us.
 retry:
    if (haveOld) {
        oldObj = *location;
        oldTable = &SideTables()[oldObj];
    } else {
        oldTable = nil;
    }
    if (haveNew) {
        newTable = &SideTables()[newObj];
    } else {
        newTable = nil;
    }
    //先上锁
    SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable);

    if (haveOld  &&  *location != oldObj) {
        SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
        goto retry;
    }

    // Prevent a deadlock between the weak reference machinery
    // and the +initialize machinery by ensuring that no 
    // weakly-referenced object has an un-+initialized isa.
    if (haveNew  &&  newObj) {
        Class cls = newObj->getIsa();
        if (cls != previouslyInitializedClass  &&  
            !((objc_class *)cls)->isInitialized()) 
        {
            SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
            class_initialize(cls, (id)newObj);

            // If this class is finished with +initialize then we're good.
            // If this class is still running +initialize on this thread 
            // (i.e. +initialize called storeWeak on an instance of itself)
            // then we may proceed but it will appear initializing and 
            // not yet initialized to the check above.
            // Instead set previouslyInitializedClass to recognize it on retry.
            previouslyInitializedClass = cls;

            goto retry;
        }
    }

    // Clean up old value, if any.
    if (haveOld) {
        // 如果weak指针之前指向了一个弱引用,则会调用weak_unregister_no_lock方法将旧的weak指针地址移除
        weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
    }

    // Assign new value, if any.
    if (haveNew) {
        newObj = (objc_object *)
            weak_register_no_lock(&newTable->weak_table, (id)newObj, location, 
                                  crashIfDeallocating);
        // weak_register_no_lock returns nil if weak store should be rejected

        // Set is-weakly-referenced bit in refcount table.
        if (newObj  &&  !newObj->isTaggedPointer()) {
            newObj->setWeaklyReferenced_nolock();
        }

        // Do not set *location anywhere else. That would introduce a race.
        *location = (id)newObj;
    }
    else {
        // No new value. The storage is not changed.
    }
    
    SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);

    return (id)newObj;
}

storeWeak 函数中的流程如下:

  1. 首先获取新旧引用散列表 oldTablenewTable,并加锁
  2. 如果指针当前有指向旧对象,则调用 weak_unregister_no_lock函数
  3. 如果指针要指向的新对象存在,则调用 weak_register_no_lock函数,并对新对象设置相关弱引用标志位 setWeaklyReferenced_nolock

weak_unregister_no_lock

查看 weak_unregister_no_lock 函数源码如下:

    /** 
 * 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_unregister_no_lock 的流程如下:

  1. 根据传入对象 referent_id 作为 key,通过 weak_entry_for_referent 在传入的弱引用表(weak_table)中寻找对应 weak_entry_t 类型的 value,赋值给 entryentry中保存的是一个对象的弱引用信息;

  2. 找到 entry 后,在 entry 中的 inline_referrersreferrers 遍历查找 referrer 并置 nil,referrers 中存放的都是弱引用变量的地址;

  3. 遍历 entryinline_referrers 中是否还有引用,如果无引用则移除 entry

因此,weak_unregister_no_lock 其实就是删除对应的弱引用地址

weak_register_no_lock

查看 weak_register_no_lock 函数源码如下:

/** 
 * Registers a new (object, weak pointer) pair. Creates a new weak
 * object entry if it does not exist.
 * 
 * @param weak_table The global weak table.
 * @param referent The object pointed to by the weak reference.
 * @param referrer The weak pointer address.
 */
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;

    if (!referent  ||  referent->isTaggedPointer()) return referent_id;

    // ensure that the referenced object is viable
    bool deallocating;
    if (!referent->ISA()->hasCustomRR()) {
        deallocating = referent->rootIsDeallocating();
    }
    else {
        BOOL (*allowsWeakReference)(objc_object *, SEL) = 
            (BOOL(*)(objc_object *, SEL))
            object_getMethodImplementation((id)referent, 
                                           @selector(allowsWeakReference));
        if ((IMP)allowsWeakReference == _objc_msgForward) {
            return nil;
        }
        deallocating =
            ! (*allowsWeakReference)(referent, @selector(allowsWeakReference));
    }

    if (deallocating) {
        if (crashIfDeallocating) {
            _objc_fatal("Cannot form weak reference to instance (%p) of "
                        "class %s. It is possible that this object was "
                        "over-released, or is in the process of deallocation.",
                        (void*)referent, object_getClassName((id)referent));
        } else {
            return nil;
        }
    }

    // now remember it and where it is being stored
    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);
    }

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

    return referent_id;
}

通过上面源码得知 weak_register_no_lock 的流程如下:

  1. 判断对象是否存在或者是一个 Tagged Pointer ,直接返回对象
  2. 根据 deallocating(对象是否正在释放的标志和对象是否支持弱引用)和入参 crashIfDeallocating 判断是否中止程序运行。
  3. weak_table_t 中去找 referent 对应的 weak_entry_t,如果能找到 entry,则调用 append_referrer 函数把对象弱引用的指针 referrer 插入 weak_entry_t 的哈希数组中(或者是定长为 4 的内部数组中)。
  4. 如果没有找到对应的 weak_entry_t,则首先创建一个 new_entry,然后先执行 weak_grow_maybe 扩容,然后调用 weak_entry_insertnew_entry 插入 weak_table_t 的哈希数组中。

因此,weak_register_no_lock 其实就是保存对应的弱引用地址

objc_destoryWeak

在离开作用域后,__weak 修饰的对象会调用 objc_destoryWeak 函数,源码如下:

void
objc_destroyWeak(id *location)
{
    (void)storeWeak<DoHaveOld, DontHaveNew, DontCrashIfDeallocating>
        (location, nil);
}

可以看到 objc_destroyWeak 函数依旧调用 storeWeak 函数,传递的 newObj 参数为 nil,用于清除相应的弱引用指针地址。

__uunsafe_unretained 修饰符

__unsafe_unretained 修饰符正如其名 unsafe 所示,是不安全的修饰符。附有 __unsafe_unretained 修饰符的变量不属于编译器的内存管理对象。

_autoreleasing 修饰符

__autoreleasing 修饰符 等价于 autorelease 方法。

上一篇 下一篇

猜你喜欢

热点阅读