block入门和简单使用(定义,做参数,做返回值,内存管理,循环引用).

本文略为全面的介绍block的使用:
block定义方式,
block传值,
block循环引用,
block内存管理,
block做参数,
block做返回值(实现链式编程)等等,
此篇在手,block我有!
Action!

  • block的三种定义方式以及block类型

1.没有返回值,没有参数的定义方式

//返回值类型(^block的名字)(参数类型) = ^(参数类型和参数名) {};

    void(^block)() = ^(){
        NSLog(@"调用了block");
    };
//当然,没有参数的时候可以把括号省去
    void(^block)() = ^{
        NSLog(@"调用了block");
    };

2.有返回值,有参数的定义方式

//返回值类型(^block的名字)(参数类型) = ^(参数类型和参数名) {};
//如果有参数,定义的时候,必须要写参数,而且必须要有参数变量名
    int(^block)(int) = ^(int a){
        return 1;
    };

3.定义时带有返回类型的,(不常用)

    int(^block)() = ^int{//这里的int就是这个block的返回值类型
        return 1;
    };

4.系统提供了一个定义block的宏

// block快捷方式   输入:inline
//    returnType(^blockName)(parameterTypes) = ^(parameters) {
//        statements
//    };

5.block的调用

//定义block
    void(^block)() = ^{
        NSLog(@"调用了block");
    };
//调用block
    block();

6.block的类型

//block有自己的类型,就想@"string"是NSString类型一样
//格式就是 返回值(^)(参数类型)
//比如这个block的类型就是: int(^)(int)
    int(^block)(int) = ^(int a){
        return 1;
    };
//这个block的类型就是void(^)()
    void(^block)() = ^{
        NSLog(@"调用了block");
    }; 
//在ARC中把block定义成属性要用strong类型,定义方式如下:
@property (nonatomic, strong) void(^block)();//这样在类中可以拿到self.block
//当然也可以取别名:
typedef void(^BlockType)();//BlockType不是变量名,而是这种类型的block的别名
//然后就可以这样
@property (nonatomic, strong) BlockType block;
  • 使用block保存代码

这种方式我在app的"用户设置"界面中使用到,需求大约是这样:

用户设置界面每一行cell会做不同的事,有的是跳转界面,有的是switch开关,有的是需要显示一下AlertView.
这样的话,我们可以把需要执行的代码包装成block,把block放在cell的模型里面,当点击cell的时候,拿出模型中的block来执行,感觉还不错.

//这是cell的模型
typedef void(^optionBlock)();
@interface SettingItem : NSObject
@property(nonatomic,copy)NSString *icon;//图标
@property(nonatomic,copy)NSString *title;//文字
@property(nonatomic,assign) Class destVc;//需要跳转的界面
@property(nonatomic,copy) optionBlock option;//需要执行的代码
@end

然后我们可以在didSelectRowAtIndexPath里面这么做:

-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    [self.tableView deselectRowAtIndexPath:indexPath animated:YES];
    if (item.option != nil) {//判断block是否为空 不为空就执行
        item.option();
    }else if ([item isKindOfClass:[SettingArrowItem class]]) {//block为空 执行其他操作(比如界面跳转):
        SettingArrowItem *newItem = (SettingArrowItem *)item;
        UIViewController *vc = [[newItem.destVc alloc]init];
        vc.title = item.title;
        [self.navigationController pushViewController:vc animated:YES];
    }
}
  • 使用block进行传值

在学习block之前,一直在使用代理传值,但是用了block以后,再也不想用代理了.
下面介绍block传值(逆传)简单使用
需求如下:
在ViewControllerOne跳到ViewControllerTwo
ViewControllerTwo拿到数据后dismiss
在ViewControllerOne打印数据

//ViewControllerOne.m:
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{//点击控制器View的时候调用
    ModalViewController *modalVc = [[ModalViewController alloc] init];
    modalVc.block = ^(NSString *value) {
          NSLog(@"%@",value);
    };//给他block赋值
    [self presentViewController:modalVc animated:YES completion:nil];
}
//ViewControllerTwo.h:
@interface ModalViewController : UIViewController
@property (nonatomic, strong) void(^block)(NSString *value);
@end
//ViewControllerTwo.m:
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    // 传值给ViewControllerOne
    if (_block) {
        _block(@"123");
    }
[self dismissViewControllerAnimated:YES completion:nil];
}
  • block变量的传递

一个很简单的问题:block使用外部变量,是值传递,还是指针传递
1.值传递

//block为值传递只有一种情况:
    int a = 3;
    void(^block)() = ^{
        NSLog(@"%d",a);
    };
    a = 5;
    block();//这里调用block打印出的是3,是值传递

结论:// 如果block访问的变量是局部变量,那么变量是值传递
2.指针传递

  static int a = 3;
    void(^block)() = ^{
        NSLog(@"%d",a);
    };
    a = 5;
    block();//这里调用block打印出的是5,是指针传递
//另外全局变量,静态变量都是指针传递

结论:如果是静态变量,那么变量是指针传递
3.经过其他测试总结:
(1)如果是局部变量,Block是值传递
(2)如果是静态变量,全局变量,__block修饰的变量,block都是指针传递

  • block做参数

第一次见到block当做参数是在AFN框架中,AFN帮你拿到数据以后,执行你传给他的block:

//这一个个对AFN进行简单封装的方法:
+(void)requestWihtMethod:(RequestMethodType)methodType
                     success:(void (^)(id response))success//是一个block
                     failure:(void (^)(NSError* err))failure//是一个block
{
    AFHTTPSessionManager *manage = [AFHTTPSessionManager manager];
            [manage GET:url parameters:params progress:^(NSProgress * _Nonnull downloadProgress) {
            } success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
                if (success) {
                    success(responseObject);//拿到数据后执行你传入的block
                }
            } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
                    failure(error);//拿到数据后执行你传入的block
            }];
        }
}

接下来我们自定义一个计算器,在block里面自定义计算方式,将block传入计算器来进行计算:

//CalculatorManager.h:
@interface CacultorManager : NSObject
@property (nonatomic, assign) NSInteger result;//要计算的数据
-(void)calculatorWithMethod:(NSInteger(^)(NSInteger // 计算方法parameterresult))methodBlock;
@end
//CalculatorManager.m:
-(void)cacultor:(NSInteger (^)())cacultorBlock
{
    if (cacultorBlock) {//判断block是否为空
      _result =  cacultorBlock(_result);//执行block中的计算方法
    }
}
//ViewController.m:使用计算器
-(void)viewDidLoad {
    [super viewDidLoad];
    // 创建计算器管理者
    CalculatorManager *mgr = [[CalculatorManager alloc] init];
    [mgr calculator:^(NSInteger result){//自定义计算方法block,作为参数传进去
        result += 5;
        result += 6;
        result *= 2;
        return result;
    }];
    NSLog(@"%ld",mgr.result);
  • block做方法返回值(链式编程)

我们平时写一些工具类的方法的时候(比如计算器)

//注:result是CalculatorManager的属性,用来保存计算结果
//如果计算方法这么写
-(int)add:(int)value{
    _result += value;
    return result;
}
//就要这么调用:
CalculatorManager *mgr = [[CalculatorManager alloc] init];
[mgr add:[mgr add:[mgr add:[mgr add:[mgr add:[mgr add:5]]]]]];//返回值是数字,要继续用mar调用add来执行操作
//如果计算方法这么写:
-(CalculatorManager *)add:(int)value
{
    _result += value;
    return self;
}
//就要这么调用:
CalculatorManager *mgr = [[CalculatorManager alloc] init];
[[[[[mgr add:5] add:5] add:5] add:6] add:7];//返回值是计算器本身,可以继续调用add方法

现在我们要用返回值是block的方法来实现链式编程:

//计算方法这么写,返回值是 返回值为CalculatorManager的block.
-(CalculatorManager *(^)(int))add//相当于一个get方法
{
    return ^(int value){
        _result += value;
        return self;
    };
}
//就可以这么调用
    CalculatorManager *mgr = [[CalculatorManager alloc] init];
    mgr.add(5).add(5).add(5).add(5);

下面简单介绍一下调用原理:
mgr.add相当于get方法的调用: [mgr add];
mgr.add返回的是一个block,所以你可以给他一个参数5,于是写成这样:mgr.add(5)
然后block返回的又是CalculatorManager,所以继续调用add
如此循环下去.......
点这里是我用 block 做返回值写的一个屏幕适配小工具

  • block内存管理

首先,在oc中block是一个对象,只有对象才涉及到内存管理
block的内存管理在MRC和ARC中有不同的地方,接下来将分别介绍

1.MRC:

block存放位置:

    int a = 3;//这是一个局部变量
    void(^block)() = ^{
        NSLog(@"调用block%d",a);
    };
    NSLog(@"%@",block);
//打印结果:<__NSStackBlock__: 0x7fc498746000>
//此时引用了外部局部变量,block放在栈里面
static int b = 2;
-(void)viewDidLoad {
    [super viewDidLoad];
    void(^block)() = ^{
        NSLog(@"调用block%d",b);
    };
    NSLog(@"%@",block);
}
//打印结果:<__NSGloBalBlock__: 0x7fc498746000>
//此时引用了全局变量,block放在全局区

MRC:管理block总结:
只要block没有引用外部局部变量,block放在全局区
只要Block引用外部局部变量,block放在栈里面.
定义属性时:
@property (nonatomic, copy) void(^block)();
block只能使用copy,不能使用retain,
因为使用retain,block还是在栈里面 代码块过了方block就销毁了,再次访问self.block会出现坏内存访问
使用copy是放在堆里面,代码块过了不会销毁

2.ARC:

block存放位置:

    int a = 3;//这还是一个局部变量
    void(^block)() = ^{
        NSLog(@"调用block%d",a);
    };
    NSLog(@"%@",block);
//打印结果:<__NSMallocBlock__: 0x7fc498746000>
//此时引用了外部局部变量,block放在堆里面
static int b = 2;
-(void)viewDidLoad {
    [super viewDidLoad];
    void(^block)() = ^{
        NSLog(@"调用block%d",b);
    };
    NSLog(@"%@",block);
}
//打印结果:<__NSGloBalBlock__: 0x7fc498746000>
//此时引用了全局变量,block放在全局区

ARC:管理block总结:
只要block没有引用外部局部变量,block放在全局区
只要block引用外部局部变量,block放在堆里面.
定义属性时:
@property (nonatomic, strong) void(^block)();
block只能使用strong,不要使用copy
因为当使用copy的时候,set方法是调用了copy帮你深拷贝一次,没有这个必要.
就像NSString一样,他一般都是@"a"这种常量,没必要再去深拷贝一次,所以NSString常量也用strong不用copy.

  • block循环引用

1. block的循环引用问题

1.为什么会产生循环引用:
因为block会给内部的强指针对象进行一次强引用,比如常见的传入block中的self进行强引用
并且在self中,block又是strong的,self对block是强引用
所以,你强引用我,我强引用你,谁也不会被释放,就造成了循环引用
所以,为了避免循环引用,我们要在block使用self之前,进行这一步操作:

__weak typeof(self) weakSelf = self;

在block中使用weakSelf,就不会产生循环引用问题了.
使用了__weak修饰符的对象,作用等同于定义为weak的property。自然不会导致循环引用问题.

2.接下来是双层block的循环引用问题:

先来看这样一个例子:

    __weak typeof(self) weakSelf = self;
    block = ^{
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{//延时2秒执行下面的代码
             NSLog(@"%@",weakSelf);
        });
    };
    block();

这样执行下去,如果在延时时间2秒还没到,控制器就dismiss或者pop(总之是销毁)了,打印出的weakSelf是null,
为什么呢?
因为只有push self或者present self的控制器对self强引用,当self dismiss了或者pop了,就没有人对self强引用了(block对self没有强引用),根据ARC的内存管理原则,当没有人对一个对象强引用的时候,该对象就会销毁.
所以,当self dismiss了或者pop了,self就销毁了,2秒后block再访问self的时候,self已经不再了.
这时,我们要做如下处理:

    __weak typeof(self) weakSelf = self;
    block = ^{
        __strong typeof(weakSelf) strongSelf = weakSelf;//这个是局部变量 栈内的强指针 当这个block执行完毕  这个指针就会释放  
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{//延时2秒执行下面的代码
             NSLog(@"%@",strongSelf);
        });
    };
    block();

加上这一句:__strong typeof(weakSelf) strongSelf = weakSelf;
__strong 就相当于定义为strong的property
那么当self销毁的之前(pop或者dismiss之前),有两个人对self强引用(一个是push self或者present self的控制器,一个是这个block中定义的strongSelf).
当控制器销毁的时候(pop或者dismiss时),push self或者present self的控制器不在强引用self,self失去一个强引用,但是self不会销毁,因为block中定义的strongSelf还在对self强引用.
但是你会问,那这么不会造成循环引用吗?不着急,继续往下看:
当延时2秒到了,block可以访问到strongSelf
当延时block代码块过了,strongSelf就会指向nil了(因为strongSelf是局部变量,存在栈内的强指针 当这个block执行完毕,这个指针就会释放)
此时就没有人对self进行强引用了,self也会销毁,
至此,大家都销毁了..

感谢阅读
你的支持是我写作的唯一动力

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容