iOS之轻松上手block(上)

作者: codingZero | 来源:发表于2016-01-23 18:33 被阅读8487次

    导语

    不会使用block的iOS程序员,不是一个合格的程序员
    学会了block,你再也不想用繁琐的代理
    block没有你想象中的那么难,不要害怕,不要畏惧,勇敢尝试
    笔者入行iOS时已经是ARC的天下,所以这里只说ARC环境下的使用

    什么是block

    block其实就是一个代码块,把你想要执行的代码封装在这个代码块里,等到需要的时候再去调用。那block是OC对象吗?答案是肯定的

    来自官方文档

    笔者以英语3.9级的水平给大家翻译下,“block是一个OC对象,这意味着它能被添加到集合,比如NSArray、NSDictionary”

    block的定义

    1. block属性或变量
      格式:返回值类型(^block名称)(参数列表)

       /*定义属性,block属性可以用strong修饰,也可以用copy修饰
        有小伙伴留言说苹果官方建议用copy,笔者查了下文档,
        确实是这样的,不过笔者未测试出copy与strong的区别,大家喜欢啥就用啥吧*/
       @property (nonatomic, strong) void(^myBlock)();//无参无返回值
       @property (nonatomic, strong) void(^myBlock1)(NSString *);//带参数
       @property (nonatomic, strong) NSString *(^myBlock2)(NSString *);//带参数与返回值
       //定义变量
       void(^myBlock)() = nil;//无参无返回值
       void(^myBlock1)(NSString *) = nil;//带参数
       NSString *(^myBlock2)(NSString *) = nil;//带参数与返回值
      
    2. block被当做方法的参数
      格式:(block类型)参数名称

       - (void)test:(void(^)())testBlock//无惨无返回值
       - (void)test1:(void(^)(NSString *))testBlock//带参数
       - (void)test2:(NSString *(^)(NSString *))testBlock//带参数与返回值
      
    3. 使用typedef定义block

       typedef void(^myBlock)(); //以后就可以使用myBlock定义无参无返回值的block
       typedef void(^myBlock1)(NSString *); //使用myBlock1定义参数类型为NSString的block
       typedef NSString *(^myBlock2)(NSString *); //使用myBlock2定义参数类型为NSString,返回值也为NSString的block
       //定义属性
       @property (nonatomic, strong) myBlock testBlock;
       //定义变量
       myBlock testBlock = nil;
       //当做参数
       - (void)test:(myBlock)testBlock;
      

    block的赋值

    格式:block = ^返回值类型(参数列表){}

    1. 没有参数没有返回值

      myBlock testBlock = ^void(){
           NSLog(@"test");
       };
       //没有返回值,void可以省略
      myBlock testBlock1 = ^(){
           NSLog(@"test1");
       };
       //没有参数,小括号也可以省略
       myBlock testBlock2 = ^{
           NSLog(@"test2");
       };
      
    2. 有参数没有返回值

      myBlock1 testBlock = ^void(NSString *str) {
            NSLog(str);
      }
      //省略void
      myBlock1 testBlock = ^(NSString *str) {
            NSLog(str);
      }
      
    3. 有参数有返回值

      myBlock2 testBlock = ^NSString *(NSString *str) {
           NSLog(str)
           return @"hi";
      }
      //有返回值时也可以省略返回值类型
       myBlock2 testBlock2 = ^(NSString *str) {
           NSLog(str)
           return @"hi";
      }
      

    实战

    接下来,我们就结合一个实例程序,来看看block在实际开发中的简单使用



    本案例涉及到两个控制器与一个Person类

    1. 联系人列表控制器:使用tableView展示联系人列表,称为A控制器
    1. 新建联系人控制器:创建新的联系人对象,称为B控制器
    2. Person:联系人,有两个属性,name与phoneNumber

    任务需求:点击A控制器右上角“新建”按钮跳到B控制器,B控制器添加联系人后,点击“保存”按钮返回A控制器,并将新添加的联系人展示到列表中

    问题来了,如何将B控制器中的数据传递给A控制器呢?

    那还不简单,A控制器直接把联系人数组传递给B控制器,B控制器新建联系人后添加到数组中,然后返回A控制器,在A控制器的viewWillAppear方法中刷新表格就OK了。

    方法可行,但是不得不说,相当low,B控制器是用来添加联系人的,至于联系人数组什么情况,无需关心,所以,不要把数组传递给B控制器

    B控制器要做的仅仅只是,新建联系人,然后把联系人对象传递给A控制器,至于A控制器拿到联系人后会做什么,那是A的事情,与B无关

    看到这里,很多人可能已经想到了代理,没错,代理也可以实现,但...是...,B控制器定义协议,声明代理方法,A控制器设置代理,遵守协议,然后实现代理方法,B控制器在合适的地方调用代理方法,卧槽,好麻烦有木有,笔者都不想写代码了,还是回家种田去吧

    好了不废话了,进入正题

    使用block传递数据

    1. 在B控制器的.h文件中定义一个没有返回值,参数类型为Person的block属性

       @property (nonatomic, strong) void(^saveBlock)(Person *);
      
    2. 在B控制器“保存”按钮的点击方法中调用block

       - (IBAction)save:(id)sender {
           //使用事先定义好的类方法创建Person对象
        Person *person = [Person personWithName:_nameText.text phoneNumber:_phoneNumberText.text];
         /**调用block之前最好先判断block是否为空,不为空才调用,否则程序崩溃*/
         //装逼写法
       //!self.saveBlock? : self.saveBlock(person);
         //一般写法
         if (self.saveBlock) {
             self.saveBlock(person);
         }
        [self.navigationController popViewControllerAnimated:YES];
       }
      
    3. 在A控制器中,给B控制器的block属性进行赋值

         //“新建”按钮点击执行的方法
         - (void)newContact {
           AddContactViewController *addVC = [[AddContactViewController alloc] init];
           addVC.saveBlock = ^(Person *person){
                //这里就可以拿到B控制器传递过来的person对象,添加到数组然后刷新表格
            [self.contactList addObject:person];
            [self.tableView reloadData];
           };
           [self.navigationController pushViewController:addVC animated:YES];
          }
      

    三步就搞定,很简单是不是,所以说,block并没有你们想想的那么复杂,自从笔者学会了block,就再也没用过代理,除了系统的。

    block常见雷区---循环引用

    使用block有一个特别要注意的地方,循环引用,何为循环引用?你引用我,我引用你,谁也不释放谁,对象无法销毁,占用内存

    我们来看一个循环引用的一个例子



    注意看控制台输出,当点击“取消”时,B控制器被销毁,dealloc方法被调用

    把注释掉的代码打开,再运行



    点击“取消”按钮,B被移除,但是dealloc方法没有调用,所以说,B控制器并没有销毁,why?

    block对象赋值给了B控制器的属性,因此B会对block有一个强引用,而block中又用到了self(B控制器对象),block会对使用到的外部变量进行捕获,所以,block对B控制器也有一个强引用,最终造成循环引用,谁也无法释放

    循环引用解决方法

    循环引用如何解决?很简单,一行代码搞定



    使用weakSelf(名称随便取的)替代self,block将不再对self进行强引用
    图中__weak也可使用__unsafe_unretained,区别就是__weak修饰的指针,当对象销毁后,指针会被自动置为nil,而__unsafe_unretained修饰的指针,当对象销毁后会变成野指针,为了安全,推荐使用__weak

    如此简单又好用的block,你是否已经学会了呢,如果觉得笔者的文章对你有帮助,请多多关注,你们的关注,将是笔者更新的动力,笔者将在下篇文章中,讲述block在内存中的情况,以及block对外界变量的捕获

    传送门:《iOS之轻松上手block(下)》

    相关文章

      网友评论

      • PGOne爱吃饺子:哥们,在前锋培训过吧
        codingZero:@4140d18ee6fc 没有,都没听说过
      • 35d9ba1f75ec:mark 看完了,跟c#里的事件差不多,很好理解 :smile:
      • d90b41f4d116:简单易懂,感谢!
      • 4caf0a861727:获益匪浅。
      • RBNote:博主威武,以前一直纠结block到底咋用, 学习了, 有长进, 至少会照葫芦画瓢了;
      • mysteryemm:看过很多Block的讲解,其中不乏某些开发界有名气的,就属你这篇博客讲解的比较全面到位,并且没有错误。很不错!
      • ff4027bec949:很好 收藏了
      • qBryant:文章讲的很细致,学习了! :+1:
      • 94a537591303:讲的很明白,很清楚:smile::smile:谢谢啦.
      • 柏斯特湾:很感谢楼主的分享,的确很好的文章。期待做更大的贡献!
      • 77ff69143498:能否整个DEMO 让菜鸟们参考下 :cold_sweat:
        77ff69143498:@codingZero 谢谢大神 yin_jone@163.com 谢谢
        codingZero:@Yin_Jone 代码很简单的,我文章里面说的很详细了,所以demo我没保存,你要的话留个邮箱,我重写个给你
      • season_zz:牛!:clap:
      • ryugaku:好棒棒(✪▽✪)
      • 595baa6bfa6f:有没有demo啊
        codingZero: @紅玫瑰u 木有,被我删了
      • Hither:我觉得还是用copy,因为copy将Block从栈拷贝到堆,还可以防止循环引用。
      • 7f3917a28fd3:棒棒大
      • c09a3b25e5c1:和作者英语水平相当。。。阅读几乎满分,听力不行次次400多过不了
        codingZero: @ZGMF_X42S 你比我强多了,我英语没超过400
      • 长满皱纹的歌:代码块只是block的中文名称,真实身份是个函数指针。
      • MrFire_:感谢分享,期待接下来的文章!
      • 0299f53e6e22:请教一下,我没怎么看懂。B点保存不是把textfield里面的值传到A吗?怎么在A的新建按钮方法里面给B的block赋值。
        codingZero: @无漾 我文中提到的类方法是用来创建Person对象的,跟block没有关系
        0299f53e6e22:@codingZero 我是个菜鸟,按照您说的写了下demo,不太会类方法,定义了一个类方法block传值老是不行,我直接就block个NSString的属性可以了。
        codingZero:@无漾 B调用block把Person对象传进去,但是这个block里面封装什么代码是由A决定的,就是说A拿到这个对象要做什么事情,就把它写在B的这个block里面
      • SPIREJ:请教作者一个问题,为什么我在贴代码的时候 * 号会自动影藏掉?你们怎么做到的?
        codingZero: @SPIRE丶J 你为什么还要加标签,我直接贴的,贴过来后代码前面加四个空格就可以了
        SPIREJ:@codingZero 你是怎么贴的?我是<pre>代码</pre>这样贴的,是不是还少了什么东西?
        codingZero:@SPIRE丶J 不知道啊,我贴过来就是这样
      • 8ae158dda3f2:mark - 等下篇,到底是3.9比我这英语三级的强不少 :smiley:
      • 這Er:下篇讲讲ARC全局局部变量在block中的引用情况吧,一直不太清楚.
        這Er:@codingZero 这几天是特别冷,等着你的文章:)
        codingZero:@這Er 这两天会写,最近有点冷,手僵,不想打太多字
      • 8f390a2abf88:block属性建议使用copy,使用copy可以让你生成的闭包copy一份放到堆区,而使用strong则是放到了栈区,而考虑到栈区的释放由系统控制,和block的使用是在需要的时候调用,安全性可想而知,所以建议使用copy
        codingZero: @zhz 嗯嗯,谢谢,没仔细看官方文档
        8f390a2abf88:楼主强大,确实如你所说,ARC下使用strong创建的_NSconcreteMalloc类型的block,放到了堆区。但是苹果官方依然建议我们使用copy:`You should specify copy as the property attribute, because a block needs to be copied to keep track of its captured state outside of the original scope. This isn’t something you need to worry about when using Automatic Reference Counting, as it will happen automatically, but it’s best practice for the property attribute to show the resultant behavior`,这是官方文档
        codingZero: @zhz 不知道你试过没有,使用strong修饰block是放在堆里面的,并不是你说的栈,这也是我下篇要讲的block在内存中的情况,之所以很多人用copy,是因为MRC中没有strong,很多人习惯了用copy,以至于在ARC中也用,其实ARC中strong比copy要好

      本文标题:iOS之轻松上手block(上)

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