Objective-C Block

作者: SimpleFunc | 来源:发表于2019-06-30 14:39 被阅读0次

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。

相关文章

网友评论

    本文标题:Objective-C Block

    本文链接:https://www.haomeiwen.com/subject/gjkbcctx.html