iOS基础之block

1.block类型-存储代码块的类型

在异步编程时常需要进行函数回调,在C#中会用匿名委托或者lambda表达式讲一个操作作为参数进行传递.
ObjC中是使用对于闭包的实现,在块状中我们可以持有或引用局部变量. 同时利用Block可以将一个操作作为参数进行传递;

blcok用法:

  • 定义:返回值类型 ( ^变量名 ) ( 形参类型 );
  • 赋值:变量名=^(形参){
    代码块+形参变量
    };
  • 使用:变量(实参);

例:

    int (^myBlcok)(int ,int)=^(int m,int n){
      return  m+n;
      }; //无参数时大括号前()可省略
      
      myBlock(10,5);    //调用块,省略了接受块返回值;

总结:经过简单了解C与OC;发现从最小的一个变量到表达式再到一个函数,其实只起两点作用: 值(返回值) 与 功能(行为,方法,作用).
所以说一行代码,按它是使用了值 还是 功能来解读比较容易理解.

Block做使用场景:

  • 如果回调方法比较少,1~2,最好不要超过3个,这个时候使用block比较合适
  • 如果回调方法非常多,同时又不用每一个方法都必须实现,这个时候用delegate会比较方便!

block传值的循环引用问题:

只有当block直接或间接的被self持有时,在block使用self时才需要替换为weak self。如果在 Block 内需要多次 访问 self,则需要使用 strongSelf。

   __weak __typeof__(self) weakSelf = self;
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

        __strong __typeof(self) strongSelf = weakSelf;

        [strongSelf doSomething];

        [strongSelf doOtherThing];

    });

typedef block格式

类似函数指针,直接在定义格式之前加 typedef关键字,之后变量名就是类型的别名了.
typedef viod (^别名)(形参);
一般以后需要使用block作为函数方法的参数时,为方便最好用别名.而在block作用返回值时,一定需要别名,因为编译器不能识别做此时类型做何种解释.
延伸:经过测试,block在编译时按代码顺序,而运行时按调用顺序(变量作用域)

使用例子:
KCButton.h

#import 
@class KCButton;
typedef void(^KCButtonClick)(KCButton *);

@interface KCButton : NSObject

#pragma mark - 属性
@property (nonatomic,copy) KCButtonClick onClick;

#pragma mark 点击方法
-(void)click;
@end

KCButton.m

#import "KCButton.h"


@implementation KCButton

-(void)click{
    NSLog(@"Invoke KCButton's click method.");
    if (_onClick) {
        _onClick(self);
    }
}

@end

main.m

   KCButton *button=[[KCButton alloc]init];
    button.onClick=^(KCButton *btn){
        NSLog(@"Invoke onClick method.The button is:%@.",btn);
    };
    [button click];
    /*结果:
     Invoke KCButton's click method.
     Invoke onClick method.The button is:.
     */

block访问外部变量

  • block内部可以访问外部局部变量,但是此时是const copy方式,地址不同,相当于值传递,只读的.如果外部定义时加前缀__block时,内部可改变外部局变值.
  • block内部如果创建了和外部同名的变量,会屏蔽外部作用域.此时内部的变量也存在栈区;
    原因:block本质是代码块,ARC下创建的时候在堆区,此时代码只是单纯储存,没有功能;当调用的时候,相当于代码增加到main中,这样代码块中创建的变量就跟正常的一样; (block调用完成内部变量即释放,而堆区的只在释放block时一起释放).
  • 如果是静态变量(static修饰局变,生命周期延长,存储在数据区(同初始化的全局))和全局变量.地址传递.此时block存储在全局区.
  • 常量字符串@"abc",加__block会引用常量变量(如:a变量,a = @"abc",内部可以任意修改a 指向的内容)的地址。不加block就是@"abc"本身地址,不可变;

三种类型block

根据block在内存中的位置
"NSGlobaBlock"类似函数,存于代码区--全局block
"NNStackBlock"栈区,函数返回后的Block--栈
"NSMallocBlock"堆block--堆
  1. block内没有使用外部变量或是只使用了全局/静态变量时.存于全局代码区,为全局block;---(ARC和MRC下一致)
  2. 当使用外部变量时
    • MRC下,block代码存于栈区;如果此外部变量A存于区,那么A会被copy到block分配的区;如果A是存于区,那么A在block块内与快外相同.
    • ARC下,block代码存于堆区.如果此外部变量A存于区,那么A会被copy到block分配的区;如果A是存于区,那么A在block块内与快外相同.
  3. 如果需要修改外部变量,需要在变量前面声明__Block;
    当使用下划线Block修饰外部变量时:
    • MRC下,无论变量A存于还是区,A在block块内与快外相同;
    • ARC下,如果此外部变量A存于区,那么A会被转移而不是复制区;如果A是存于区,那么A在block块内与快外相同.

面试题:block的@property参数(内存管理参数)为什么要用copy:如果不用copy,此时不论ARC还是MRC都是栈Bolck,栈block会提前释放,导致无法继续使用;可以copy到堆区手动管理内存.(而字符串copy是防止字符串如果是非常量的,外部可变,造成非预估的结果;)

block在MRC下得内存隐患(NNStacKBlock)

Block_copy将block及内部变量拷贝到堆区.
使用完毕用Blok_release(block变量)释放此堆区空间;


block使用技巧

  1. block结构快速显示:inlineBlock...(也可右下角自定义快速显示其他格式)
  2. Block作为方法参数时,最好把参数列表部分加上,这样后面调用方法时,会自动有格式;
  3. 做方法参数时,需要加上返回值类型;
  4. 做返回值时,先定义别名,最后别忘记执行返回值;
  5. 方法中,void(^)()表示block类型同int,做参和返回值;做实例变量
    @property (nonatomic, copy) void(^变量名)()
  6. get点语法获取block类型实例变量时,自动执行,后面需加();

你可能感兴趣的:(iOS基础之block)