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;
}