闭包和OC的block

第一部分: 理论

什么是闭包

计算机语言中、“闭包(Closure)是由函数和与其相关的引用环境组合而成的实体.” block就是OC对闭包的实现.(很抽象有木有), Block是iOS4.0+ 和Mac OS X 10.6+ 引进的对C语言的扩展.

将“函数、函数指针、闭包”三者对比起来理解,能加深对闭包的理解:
函数: 具有特定功能的代码块;
函数指针: 指向函数的指针;
闭包:除具备“函数和函数指针”的所有功能外, 还包括声明它的上下文(如作用域内的自由变量等).

闭包的用途

维基百度科说了3点:

  1. “惰性求值”特性可用作定义控制语句;
  2. 多函数使用同一个环境;
  3. 实现对象系统.

哈哈,除了第二点,其它两点暂时体会不到, OC中主要体现也是第二点,有时候这种结构显得非常简洁直观. 网上一些其它说法:

通常来说,block都是一些简短代码片段的封装,适用作工作单元,通常用来做并发任务、遍历、以及回调。

闭包的实现

“典型实现方式是定义一个特殊的数据结构,保存了函数地址指针与闭包创建时的函数的词法环境(also lexical closures or function closures)。”

block的种类

在Objective-C语言中,一共有3种类型的block:
_NSConcreteGlobalBlock 保存在text段的全局的静态block,不会访问任何外部变量。
_NSConcreteStackBlock 保存在栈中的block,当函数返回时会被销毁。
_NSConcreteMallocBlock 保存在堆中的block,当引用计数为0时会被销毁。

block的使用注意事项

  1. 在block内直接调用类的实例变量会使self(类的实例)引用计数加1, 这样可能会引起循环引用问题(可以用__weak或local-var处理);
  2. 使用null的block程序会crash. 使用前判断一下:if(blockVar) {//do something…};
  3. 在多线程环境下(block中的weakSelf有可能被析构的情况下),需要先将self转为strong指针,避免在运行到某个关键步骤时self对象被析构。

__block变量和其它变量在block内的读写情况

类型基本数据类型在block内的读写block对OBJC对象类型的深浅拷贝
局部自动变量只读mutable copy
全局变量/extern读写deep copy
static 变量读写deep copy
__block变量读写deep copy

第二部分: 例子

三种类型的block

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSLog(@"///>>>>>>>>>>>>> 3种类型的block <<<<<<<<<<<<<<<<<///");
        //NSGlobalBlock 全局静态
        void(^blkGlobal)(void);
        blkGlobal = ^{printf("NSGlobalBlock1");};
        NSLog(@"NSGlobalBlock1: %@",blkGlobal);
        NSLog(@"NSGlobalBlock2: %@", ^{
            printf("NSGlobalBlock2");
        });

        //NSConcreteStackBlock 栈block
        int i = 0;
        NSLog(@"NSConcreteStackBlock1: %@", ^{
            printf("NSConcreteStackBlock2 :%d",i);
        });

        //NSConcreteMallocBlock 堆(Heap)block,
        void(^blkStack)(void);
        //等号是赋值运算,相当于对block进行copy,即相当于调用block的copy方法.
        blkStack = ^{ printf("NSConcreteMallocBlock1: %d",i); };
        NSLog(@"NSConcreteMallocBlock1: %@",blkStack);
        //对栈block进行copy会变成堆block.
        NSLog(@"NSConcreteMallocBlock2: %@", [^{
            printf("NSConcreteMallocBlock2 :%d",i);
        } copy]);
    }
}

结果:

NSGlobalBlock1: <__NSGlobalBlock__: 0x1000031d0>
NSGlobalBlock2: <__NSGlobalBlock__: 0x100003210>
NSConcreteStackBlock1: <__NSStackBlock__: 0x7fff5fbff7e0>
NSConcreteMallocBlock1: <__NSMallocBlock__: 0x1002003e0>
NSConcreteMallocBlock2: <__NSMallocBlock__: 0x100400000>

强/循环引用

#import "blockDemo.h"
typedef void(^blk)();
@interface blockDemo(){
    int m_var;
}
@property(nonatomic,copy) blk blk;
@end

@implementation blockDemo
- (instancetype)init{
    if (self = [super init]) {
        [self blockTest];
    }
    return self;
}
-(void)blockTest{
    self.blk = ^{
        m_var = 1;
        self.str = @"aaa";
    };
}
-(void)dealloc{
    NSLog(@"blockDemo dealloc");
}
@end

上面的例子dealloc方法不会被执行,因为block内的引用是强引用,在blockTest方法内给blk赋值时直接引用了self和实例变量, 这样会形成循环引用.
解决方法:是用__weak将self变为弱引用.

//来自AFNetworking写法,堪称使用weak–strong的经典。
-(void)blockTest2{
    __weak typeof(&*self) weakSelf = self; 
    self.blk = ^{
        __strong typeof(&*weakSelf) strongSelf = weakSelf;
        strongSelf->m_var = 1;
        weakSelf.str = @"aaa";
    };
}

__block变量和各种类型的变量在block内的读写情况

NSInteger CounterGlobal;
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        extern NSInteger CounterGlobal;  //假设在此之前已经有声明.
        static NSInteger CounterStatic;
        NSInteger localCounter1 = 42 ;
        __block NSInteger localCounter2;

        void (^aBlock)( void ) = ^( void ){
            ++CounterStatic;              //可以读写。
            ++CounterGlobal;              //可以读写。
            ++localCounter2;              //设定外面定义的localCharacter。
            //++localCounter1;             //报错,可读不可写.
            CounterGlobal = localCounter1; //localCounter1可读不可写.
        };

        ++localCounter1; //不会影响的block 中的值。
        localCounter2 = 2 ; //会影响block 中的值.
        aBlock(); //执行block 的内容。
        NSLog(@"CounterGlobal = %ld, CounterStatic = %ld, localCounter1 = %ld, localCharacter2 = %ld",CounterGlobal,CounterStatic,localCounter1,localCounter2);
    }
    return 0;
}

结果:
CounterGlobal = 42, CounterStatic = 1, localCounter1 = 43, localCharacter2 = 3

回传例子1 - 参数传递

//blockDemo.h
#import <Foundation/Foundation.h>

typedef void(^blk2)(id obj); //用typedef定义block类型

@interface blockDemo : NSObject
-(void)blockResult:(blk2)complete;
@end

//blockDemo.m
#import "blockDemo.h"
@implementation blockDemo
-(void)blockResult:(blk2)complete{
    complete(@"1");
}
@end

这样调用:

blockDemo *blk = [[blockDemo alloc] init];
[blk blockResult:^(id obj) {
    NSLog(@"blockResult: %@",obj);
}];

结果:
blockResult: 1

回传例子2 - 用属性扩大block的作用域

重构回传例子1:

//blockDemo.h
#import <Foundation/Foundation.h>

typedef void(^blk2)(id obj); //用typedef定义block类型

@interface blockDemo : NSObject
-(void)blockResult:(blk2)complete;
@end

//blockDemo.m
#import "blockDemo.h"
@interface blockDemo()
@property(nonatomic,copy) blk2 block; //用copy关键字
@end

@implementation blockDemo
-(void)blockResult:(blk2)complete{
    //complete(@"1");
    _block = complete;
    [self blockReturn];
}
-(void)blockReturn{
    if (_block) { //执行空的block会使程序crash
        _block(@"2"); 
    }
}
@end

这样调用:

blockDemo *blk = [[blockDemo alloc] init];
[blk blockResult:^(id obj) {
    NSLog(@"blockResult: %@",obj);
}];

结果:
blockResult: 2

回传例子3 - 用block代替delegate

封装UIButton, 实现点击button后回调方法,用block实现:

GVBlockUIButton.h:

#import <UIKit/UIKit.h>

@class GVBlockUIButton;
typedef void(^GVBlockCallback)(GVBlockUIButton*);

@interface GVBlockUIButton : UIButton
@property(nonatomic,copy) GVBlockCallback clicked;
@end

GVBlockUIButton.m

#import "GVBlockUIButton.h"

#define returnInstance \
    if (self) { \
        [self blockBtnInit]; \
    } \
    return self;

@implementation GVBlockUIButton

-(instancetype)init{
    self = [super init];
    returnInstance
}
-(instancetype)initWithFrame:(CGRect)frame{
    self = [super initWithFrame:frame];
    returnInstance
}
-(id)initWithCoder:(NSCoder *)aDecoder{
    self = [super initWithCoder:aDecoder];
    returnInstance
}

-(void)blockBtnInit{
    [self addTarget:self action:@selector(btnAction:) forControlEvents:UIControlEventTouchUpInside];
}
-(void)btnAction:(GVBlockUIButton*)sender{
    if (_clicked) {
        _clicked(self);
    }
}
@end

调用:

...
GVBlockUIButton *btn = [[GVBlockUIButton alloc] init];
[btn setTitle:@"Block回调测试" forState:UIControlStateNormal];
[btn setTitleColor:[UIColor blueColor] forState:UIControlStateNormal];
[btn setTitleColor:[UIColor grayColor] forState:UIControlStateHighlighted];
[btn setFrame:CGRectMake(10, 100, btn.intrinsicContentSize.width, btn.intrinsicContentSize.height)];
btn.clicked = ^(GVBlockUIButton* sender){
    NSLog(@"clicked...");
};
...

点击button后输出结果:

clicked...

代码简洁直观

block有时候显得很简洁直观,看看OC中的对数组排序的例子:

- (void)arraySortTest{
    NSArray *arr = @[
                     @{@"name":@"a", @"age":@"1"},
                     @{@"name":@"c", @"age":@"3"},
                     @{@"name":@"d", @"age":@"4"},
                     @{@"name":@"b", @"age":@"2"}
                     ];

    NSArray *sortArr = [arr sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) {
        return [[obj1 valueForKey:@"age"] compare:[obj2 valueForKey:@"age"]]; //升序
    }];
    NSLog(@"%@",sortArr);
}

结果:

sortArr = (
        {
        age = 1;
        name = a;
    },
        {
        age = 2;
        name = b;
    },
        {
        age = 3;
        name = c;
    },
        {
        age = 4;
        name = d;
    }
)

类似地还有降序:

//降序方法1
return [[obj1 valueForKey:@"age"] compare:[obj2 valueForKey:@"age"]] * -1;
//降序方法2, 推荐
switch ([[obj1 valueForKey:@"age"] compare:[obj2 valueForKey:@"age"]]) {
    case NSOrderedAscending: return NSOrderedDescending; break;
    case NSOrderedDescending: return NSOrderedAscending; break;
    default: return NSOrderedSame; break;
}

__block的实现

前面提到block典型的实现方式是定义一个特殊的数据结构保存block的函数和上下文, 哪这个数据结构长什么样子呢, 下面用clang 的 -rewrite-objc 参数看看oc的block的c源码是如何实现的:

  1. _NSConcreteGlobalBlock类型的block实现:
    用xcode创建一个OSX的”Command Line Tool”项目, 语言选择”c”, 编辑源代码如下:
//  main.c
#include <stdio.h>
int globalI = 1;
int main() {
    ^{printf("block test!\n");}();
    printf("%p\n%p\n",^{printf("block test!\n");}, &globalI);
    return 0;
}

打印结果:

block test!
0x100001080
0x1000010a0

从打印的地址类型可看出,是一个GlobalBlock类型的block. 执行以下命令:
clang -rewrite-objc main.c
会生成一个main.app的源文件, 它就是oc的c源码翻译,简化一下大概有以下内容:

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

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; //clang和LLVM的实现有可能不一样, clang我只见_NSConcreteStackBlock类型,没有看见其它两种类型.
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

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


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

int main() {
    (void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA)();
    return 0;
}

__main_block_func_0就是block的函数指针, 由于GlobalBlock类型的block没有访问任何外部变量, 所以我再看看其它类型的block是怎么处理上下文的:

2._NSConcreteStackBlock类型的block实现:
将以下oc源码翻译和c源码:

#include <stdio.h>
int main() {
    int ai = 10;
    ^{printf("NSConcreteStackBlock,%d",ai);}();
    return 0;
}

主要内容如下:

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

struct __main_block_impl_0 {
    struct __block_impl impl;
    struct __main_block_desc_0* Desc;
    int ai;
    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _ai, int flags=0) : ai(_ai) {
        impl.isa = &_NSConcreteStackBlock; //clang和LLVM的实现有可能不一样, clang我只见_NSConcreteStackBlock类型,没有看见其它两种类型.
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
};

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    int ai = __cself->ai; // bound by copy
    printf("NSConcreteStackBlock,%d",ai);
}

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

int main() {
    int ai = 10;
    (void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, ai)();
    return 0;
}

从语句int ai = __cself->ai;得知, auto局部变量只是简单的复制, 不影响外部值, 相当于函数参数的值传递.

3._NSConcreteMallocBlock类型的block
将以下OC源码翻译为c源码现实:

#include <stdio.h>
int main() {
    __block int ai = 10;
    void(^blockVar)(int);
    blockVar = ^(int i){
        printf("NSGlobalBlock1,%d",ai);
    };
    return 0;
}

主要内容如下:

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

struct __Block_byref_ai_0 {
    void *__isa;
    __Block_byref_ai_0 *__forwarding;
    int __flags;
    int __size;
    int ai;
};

struct __main_block_impl_0 {
    struct __block_impl impl;
    struct __main_block_desc_0* Desc;
    __Block_byref_ai_0 *ai; // by ref
    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_ai_0 *_ai, int flags=0) : ai(_ai->__forwarding) {
        impl.isa = &_NSConcreteStackBlock; //clang和LLVM的实现有可能不一样, clang我只见_NSConcreteStackBlock类型,没有看见其它两种类型.
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
};

static void __main_block_func_0(struct __main_block_impl_0 *__cself, int i) {
    __Block_byref_ai_0 *ai = __cself->ai; // bound by ref
    printf("_NSConcreteMallocBlock,%d",(ai->__forwarding->ai));
}

static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
    _Block_object_assign((void*)&dst->ai, (void*)src->ai, 8/*BLOCK_FIELD_IS_BYREF*/);
}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {
    _Block_object_dispose((void*)src->ai, 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};

int main() {
    __attribute__((__blocks__(byref))) __Block_byref_ai_0 ai = {(void*)0,(__Block_byref_ai_0 *)&ai, 0, sizeof(__Block_byref_ai_0), 10};
    void(*blockVar)(int);
    blockVar = (void (*)(int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_ai_0 *)&ai, 570425344);
    return 0;
}

__block类型实际是__Block_byref_ai_0类型, 这里很容易看出, 是将变量的地址传入block了,相当于函数的地址传递.

第三部分: 测试

1.预计以下程序打印结果:

#include <stdio.h>
int globalVar = 0;
int main() {
    extern int globalVar;
    static int staticVar = 0;
    int localVar1 = 0 ;
    __block int localVar2 = 0;

    void(^blockVar)(int);
    blockVar = ^(int i){
        printf("globalVar 3 = %d staticVar 3 = %d localVar1 3 = %d localVar2 3 = %d\n", globalVar, staticVar, localVar1, localVar2);
        ++globalVar;
        ++staticVar;
        localVar2 = localVar1 + i;
        printf("globalVar 4 = %d staticVar 4 = %d localVar1 4 = %d localVar2 4 = %d\n", globalVar, staticVar, localVar1, localVar2);
    };
    printf("globalVar 1 = %d staticVar 1 = %d localVar1 1 = %d localVar2 1 = %d\n", globalVar, staticVar, localVar1, localVar2);
    ++globalVar;
    ++staticVar;
    ++localVar1;
    ++localVar2;
    printf("globalVar 2 = %d staticVar 2 = %d localVar1 2 = %d localVar2 2 = %d\n", globalVar, staticVar, localVar1, localVar2);
    blockVar(2);
    printf("globalVar 5 = %d staticVar 5 = %d localVar1 5 = %d localVar2 5 = %d\n", globalVar, staticVar, localVar1, localVar2);

    return 0;
}

鼠标选择以下内容查看答案:

globalVar 1 = 0 staticVar 1 = 0 localVar1 1 = 0 localVar2 1 = 0
globalVar 2 = 1 staticVar 2 = 1 localVar1 2 = 1 localVar2 2 = 1
globalVar 3 = 1 staticVar 3 = 1 localVar1 3 = 0 localVar2 3 = 1
globalVar 4 = 2 staticVar 4 = 2 localVar1 4 = 0 localVar2 4 = 2
globalVar 5 = 2 staticVar 5 = 2 localVar1 5 = 1 localVar2 5 = 2

2.以下两种情况,会不会影响对象释放? 如果会,该如何修改,使对象能正常释放内存?

//代码块1
typedef void(^blk)();
@interface blockDemo(){
    int m_var;
}
@property(nonatomic,strong) NSString *str;
@property(nonatomic,copy) blk blk;
@end

@implementation blockDemo
- (instancetype)init
{
    self = [super init];
    if (self) {
        [self blockTest];
    }
    return self;
}
-(void)blockTest{
    self.blk = ^{
        m_var = 1;
        self.str = @"aaa";
    };
}
@end
//代码块二
@interface blockDemo(){
    int m_var;
}
@property(nonatomic,strong) NSString *str;
@end

@implementation blockDemo
- (instancetype)init
{
    self = [super init];
    if (self) {
        self.str = @"blockTest!";
        m_var = 10;
        ^{NSLog(@"%@,%d",self.str,m_var);}();
    }
    return self;
}
@end

鼠标选择以下内容查看答案:

1. self的属性的内容里又引用了self, 导致循环引用. 解决方法用__weak将self变为弱引用, 见本文第一部分”强/循环引用”节.
2. 没有内存释放不了的问题, 虽然block内是强引用, 但当block释放后, 它持有的对象的引用计数也会恢复.

第三部分: 参考链接

http://ff20081528.iteye.com/blog/1670433
http://blog.devtang.com/blog/2013/07/28/a-look-inside-blocks/
https://zh.wikipedia.org/wiki/%E9%97%AD%E5%8C%85_(%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%A7%91%E5%AD%A6)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值