2015-01-19 12:00
block即代码块,将同一逻辑的代码放在一快区域中,使代码更简洁紧凑,易于阅读,而且它比函数使用更方便,代码更美观,因而广受开发者欢迎。block 只是 Objective-C 对闭包的实现,并不是 iOS 独有的概念,在 C++、Java 等语言也有实现闭包,名称不同而已
block实现原理
源码:
int main()
{
void (^blk)(void) = ^{ ... ... };
blk();
return 0;
}
代码转换为cpp之后:
struct __block_impl
{
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
isa 指向实例对象,表明 block 本身也是一个 Objective-C 对象。block 的三种类型:_NSConcreteStackBlock、_NSConcreteGlobalBlock、_NSConcreteMallocBlock。
Flags
按位承载 block 的附加信息;
Reserved
保留变量;
FuncPtr
函数指针,指向 Block 要执行的函数,即block内的代码;
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;
}
};
impl
block 实现的结构体变量,该结构体前面已说明;
Desc
描述 block 的结构体变量;
__main_block_impl_0
结构体的构造函数,初始化结构体变量 impl、Desc;
static void __main_block_func_0(struct __main_block_impl_0 *__cself)
{
... ....
}
_main_block_func_0 是 block 要最终要执行的函数代码
static struct __main_block_desc_0
{
size_t reserved;
size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0) };
block 的描述信息结构体,reserved:结构体信息保留字段,Block_size:结构体大小.
block获取外部变量
源码:
int main()
{
int value = 1;
void (^blk)(void) = ^{ printf("value = %d\n", value); };
blk();
return 0;
}
转换为cpp之后:
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _value, int flags=0) : intValue(_intValue)
{
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself)
{
int value = __cself->value; //copy
printf("value = %d\n", value);
}
block 通过参数值传递获取到外部声明的变量。不能在 block 内修改 value 变量,在 block 内部修改 intValue 的值,会报编译错误.
在 C 语言中有三种类型的变量,可在 block 内进行读写操作
.全局变量
.全局静态变量
.静态变量
全局变量 和 全局静态变量 由于作用域在全局,所以在 block 内访问和读写这两类变量和普通函数没什么区别.静态变量作用域在 block 之外,静态变量是通过指针传递,将变量传递到block内,所以可以修改变量值。
上面的value是通过值传递的,当然无法修改。想要修改时,可以将局部变量该为静态变量,然后用指针传递进去,就可以修改了。
__block
源码:
int main()
{
__block int value = 1;
void (^blk)(void) = ^{ value = 2;};
blk();
return 0;
}
转换之后的改变:
struct __main_block_impl_0
{
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_intValue_0 *value; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_value_0 *_value, int flags=0) : intValue(_intValue->__forwarding)
{
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself)
{
__Block_byref_value_0 *value = __cself->value; // bound by ref
(value->__forwarding->value) = 1;
}
static void __main_block_copy_0(struct __main_block_impl_0 *dst, struct __main_block_impl_0 *src)
{
_Block_object_assign((void*)&dst->value, (void*)src->value, 8/*BLOCK_FIELD_IS_BYREF*/);
}
static void __main_block_dispose_0(struct __main_block_impl_0 *src)
{
_Block_object_dispose((void*)src->value, 8/*BLOCK_FIELD_IS_BYREF*/);
}
static struct __main_block_desc_0
{
size_t reserved;
size_t Block_size;
void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0,
sizeof(struct __main_block_impl_0),
__main_block_copy_0,
__main_block_dispose_0
};
__block value变成了__Block_byref_Value_0的结构指针变量,通过指针传递到 block 内。
__Block_byref_value_0:存储 __block 外部变量的结构体
struct __Block_byref_value_0
{
void *__isa; // 对象指针
__Block_byref_value_0 *__forwarding; // 指向自己的指针
int __flags; // 标志位变量
int __size; // 结构体大小
int value; // 外部变量
};
block内存管理
一般的block声明之后是被放在栈内存中的,但有很多种状况会使block从栈内存copy到堆内存
1.block 调用 copy 方法时,如果 block 在栈上,会被拷贝到堆上;
2.block 作为函数返回值返回时,编译器自动将 block 作为 _Block_copy 函数,效果等同于 block 直接调用 copy 方法;
3.block 被赋值给 __strong id 类型的对象或 block 的成员变量时,编译器自动将 block 作为 _Block_copy 函数,效果等同于 block 直接调用 copy 方法;
4.block 作为参数被传入方法名带有 usingBlock 的 Cocoa Framework 方法或 GCD 的 API 时。这些方法会在内部对传递进来的 block 调用 copy 或 _Block_copy 进行拷贝;
其他状况的时候需要手动调用block的copy方法。
__block声明的变量也一样,会随从block从栈copy到堆。当block从堆内存上被废弃时,__block声明的变量会以前block的引用。
block内存泄露
引起block内存泄露的原因就是循环引用(retain cycle),retain cycle问题的根源在于Block和obj可能会互相强引用,互相retain对方,这样就导致Block和obj在函数结束时,内存仍然无法释放,即内存泄露。
为了避免这种情况发生,可以在变量声明时用 __weak 修饰符修饰变量。让block不强引用变量。
也可以在ARC下使用__block 修饰对象,但是需在 block 内将对象置为 nil。在 MRC 下,使用 __block 说明符也可以避免循环引用。因为当 block 从栈拷贝到堆时,__block 对象类型的变量不会被 retain。没有__block 说明符的对象类型的变量则会被 retian。
网友评论