Pro Multilthreading and Memory Management for iOS

Code tells everything,所以直接上代码

ARC

先来看看 ARC 下的新产物

weak 修饰符的效率

{
    id __weak o = obj; 
    NSLog(@"1 %@", o); 
    NSLog(@"2 %@", o); 
}

当使用 __weak 修饰符的时候,对象会被自动加到自动释放池里,这样自动释放池能将其自动释放,调用多次 weak 对象,对象就会被多次放到自动释放池中,如上 o 被加入两次,所以建议使用添加加一个强引用来优化如下:

{
    id __weak o = obj; 
    id tmp = o; 
    NSLog(@"1 %@", tmp); 
    NSLog(@"2 %@", tmp);
}

weak 如何在释放时赋值为 nil

{
    id __weak obj1 = obj;
}

以上代码 clang 后为

id obj1;
objc_initWeak(&obj1, obj); 

objc_initWeak 定义如下

id objc_initWeak(id *location, id newObj) {
    if (!newObj) {
        *location = nil;
        return nil;
    }
    //  这里的 template 用法是非类型的类模板参数 
    return storeWeak<false/*old*/, true/*new*/, true/*crash*/>
        (location, (objc_object*)newObj);
}

关于上面 非类型的类模板参数 也有叫非类型形参,可以优化性能,但是具体原因网上没搜索到,有空看看 《C++ Templates》 应该有具体介绍,可以探究下提升性能的原因。

template <bool HaveOld, bool HaveNew, bool CrashIfDeallocating>
static id storeWeak(id *location, objc_object *newObj) {
    Class previouslyInitializedClass = nil;
    id oldObj;
    SideTable *oldTable;
    SideTable *newTable;
    //
    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;
    }
    //
    if (HaveNew  &&  newObj) {
        Class cls = newObj->getIsa();
        // 通过调用 initialize 保证所有弱引用的 isa 非空
        if (cls != previouslyInitializedClass  &&  
            !((objc_class *)cls)->isInitialized()) {

            SideTable::unlockTwo<HaveOld, HaveNew>(oldTable, newTable);
            _class_initialize(_class_getNonMetaClass(cls, (id)newObj));
            previouslyInitializedClass = cls;
            goto retry;
        }
    }
    //
    if (HaveOld) {
        weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
    }

    if (HaveNew) {
        newObj = (objc_object *)weak_register_no_lock(&newTable->weak_table, 
                                                  (id)newObj, location, 
                                                  CrashIfDeallocating);

        if (newObj  &&  !newObj->isTaggedPointer()) {
            newObj->setWeaklyReferenced_nolock();
        }
        *location = (id)newObj;
    }
    SideTable::unlockTwo<HaveOld, HaveNew>(oldTable, newTable);
    return (id)newObj;
}

变量会存在 SideTable 中

class SideTable {
    private:
        static uint8_t table_buf[SIDE_TABLE_STRIPE * SIDE_TABLE_SIZE];
    public:
        RefcountMap refcnts;
        weak_table_t weak_table;
        // ...
}

struct weak_table_t {
    weak_entry_t *weak_entries;
    size_t    num_entries;
    // ...
};

struct weak_entry_t {
    DisguisedPtr referent;
    union {
        struct {
            weak_referrer_t *referrers;
            uintptr_t        out_of_line : 1;
            // ...
        };
        struct {
            // out_of_line=0 is LSB of one of these (don't care which)
            weak_referrer_t  inline_referrers[WEAK_INLINE_COUNT];
        };
    };
};

对象被释放的时候 dealloc 会调用到 _objc_rootDealloc -> objc_clear_deallocating,该方法主要是要找到 weak_entry_t 结构体中保存的变量,将弱引用置为 nil

void objc_clear_deallocating(id obj) 
{
    // ....
    SideTable *table = SideTable::tableForPointer(obj);
    // clear any weak table items
    // clear extra retain count and deallocating bit
    // (fixme warn or abort if extra retain count == 0 ?)
    OSSpinLockLock(&table->slock);
    if (seen_weak_refs) {
        arr_clear_deallocating(&table->weak_table, obj);
    }
    // ...
}

PRIVATE_EXTERN void arr_clear_deallocating(weak_table_t *weak_table, id referent) {
    weak_entry_t *entry = weak_entry_for_referent(weak_table, referent);
    if (entry == NULL) {
        // ...
        return;
    }
    // zero out references
    for (int i = 0; i < entry->referrers.num_allocated; ++i) {
        id *referrer = entry->referrers.refs[i].referrer;
        if (referrer) {
            if (*referrer == referent) {
                *referrer = nil;
            }
            else if (*referrer) {
                _objc_inform("__weak variable @ %p holds %p instead of %p\n", referrer, *referrer, referent);
            }
        }
    }
    weak_entry_remove_no_lock(weak_table, entry);
    weak_table->num_weak_refs--;
}

如代码所示,遍历 weak_table 找到要释放的变量设置为 nil。

即 objc_initWeak(&obj1, obj) 函数把 obj 对象内存地址作为 key,value 是一个存放了弱引用指针地址的数组,注册到弱引用表中,释放 obj 时,将表中的 value 置 nil。

Blocks

标准 C 不支持匿名函数的,但是 GCC 和 Clang 是可以实现的,

GCC 的形式

( { return_type anonymous_functions_name (parameters) { function_body } anonymous_functions_name; } )

Clang 的形式

^return_type ( parameters ) { function_body }

Blocks 就是利用匿名函数实现的。

捕获局部变量

int main()
{
    int val = 10;
    const char *fmt = "val = %d\n";
    void (^blk)(void) = ^{printf(fmt, val);};
    val = 2;
    fmt = "These values were changed. val = %d\n";
    blk();
    return 0; 
}

blk 在定义的时候就捕获了 fmt 和 val 变量,所以输出内容是 val = 10。变量在 Block 内部被捕获后就是只读的,如果需要修改需要使用 __block 修饰符。

对于数组,以下的方式没有问题

id array = [[NSMutableArray alloc] init]; 
void (^blk)(void) = ^{
    id obj = [[NSObject alloc] init];
    [array addObject:obj]; 
};

但是如果改变数组指针地址就会报错

id array = [[NSMutableArray alloc] init]; 
void (^blk)(void) = ^{
    array = [[NSMutableArray alloc] init]; 
};

OC -> C

clang -rewrite-objc file_name_of_the_source_code

int main()
{
    void (^blk)(void) = ^{printf("Block\n");};
    blk();
    return 0;
}

clang 转换的代码

void (^blk)(void)  ==> void (*blk)(void)

^{printf("Block\n");}; ==> 

(void (*)(void))&__main_block_impl_0(
    (void *)__main_block_func_0, &__main_block_desc_0_DATA);

__main_block_impl_0 是结构体,其构造函数如下

struct __main_block_impl_0 {
    struct __block_impl impl;
    struct __main_block_desc_0* Desc;
    //
    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) { 
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc; 
    }
};

struct __block_impl { 
    void *isa;
    int Flags;
    int Reserved; void *FuncPtr;
};

static struct __main_block_desc_0 {
    unsigned long reserved; 
    unsigned long Block_size;
} __main_block_desc_0_DATA = { 
    0,
    sizeof(struct __main_block_impl_0) 
};

static void __main_block_func_0(struct __main_block_impl_0 *__cself)
{
    printf("Block\n");
}

其中

blk();  

转换为

((void (*)(struct __block_impl *))(
    (struct __block_impl *)blk)->FuncPtr)((struct __block_impl *)blk);

可写变量

如何在 Block 函数中改变变量的值,看如下逻辑,函数中 val 的值即使被改变,也不会把改变的值写回 cself 中,展开形式如下

static void __main_block_func_0(struct __main_block_impl_0 *__cself)
{
    const char *fmt = __cself->fmt; 
    int val = __cself->val;
    printf(fmt, val); 
}

block 修饰符

__block 修饰符类似 C 中的 static, auto, and register

__block 如何实现修改变量

struct __Block_byref_val_0 { 
    void *__isa;
    __Block_byref_val_0 *__forwarding; 
    int __flags;
    int __size;
    int val;
};

__block int val = 10;
void (^blk)(void) = ^{val = 1;};

static void __main_block_func_0(struct __main_block_impl_0 *__cself)
{
    __Block_byref_val_0 *val = __cself->val;
    (val->__forwarding->val) = 1; 
}

参考文章

Anonymous function

请我喝汽水儿