Objective-C之BlockIOS开发者学习笔记iOS入门

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

2016-09-08  本文已影响1549人  王技术

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

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;
这种方式我在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传值(逆传)简单使用
需求如下:
在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使用外部变量,是值传递,还是指针传递
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当做参数是在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);

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

//注: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
如此循环下去.......

首先,在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.

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也会销毁,
至此,大家都销毁了..

谢谢阅读
有不合适的地方请指教
喜欢请点个赞
抱拳了!

上一篇下一篇

猜你喜欢

热点阅读