Block之iOS笔记摘录

目录


Block(即闭包) ,是C语言的扩充,带有自动变量(局部变量)的匿名函数。

匿名函数(没有函数名的函数)

/*
函数中可使用的变量类型
  自动变量(局部变量)
  函数参数
  // 以下3个,可用于函数之间传递值。
  静态局部变量
  静态全局变量
  全局变量

*/
int hello(int num){
  return num*100;
};
int x=hello(110);
// 知道函数的地址,可以这么做
int (*helloptr)(int)=&hello;
int x=(*helloptr)(110);


^ int (int num){
  return num*100;
}
相比普通函数,Block:
  没有函数名
  多了一个^
return类型必须和返回类型一致。

返回类型可以省略,有返回值则需要使用return,没有return时Block的返回类型是void。多个return返回时,返回类型必须一致。
^(int num){
  return num*100;
}

没有参数时,(int num)圆括号可忽略
^{
  NSLog(@"hello");
}

// Block类型变量可以和其他普通类型变量一样使用,可用作以上那5种类型
// 声明一个Block类型的变量,非常类似于int (*helloptr)(int)=&hello;
int (^helloBlock)(int);
helloBlock=^(int num){
  return num*100;
};
int (^helloBlock)(int)=^(int num){
  return num*100;
};
// 
int (^helloWorldBlock)(int)=helloBlock;
//
int x=helloBlock(110);

// 函数参数
void hello(int (^helloBlcok)(int)){
}
// 函数返回类型
int (^hello()(int)){
  return ^(int num){
    return num*100;
  }
}


// 宏定义
typedef int (^HelloBlock) (int);
void hello(HelloBlock helloBlock){
}
HelloBlock hello(){
}

HelloBlock *helloWorldBlock=&helloBlock;
(*helloWorldBlock)(110);

带有自动变量(可以截获自动变量)

int x=100;
void (^helloBlock)(void)=^{
  NSLog(@"%d",x);
};
x=110;
helloBlock();  // 输出100

Block表达式截获了所使用的自动变量的值,即保存了自动变量的瞬间值。
在Block中可以捕获但不能直接修改外部的局部变量,需要__block修饰外部的变量。对于数组等类型可以进行添加/删除元素。对于C语言数组未实现捕获会导致编译错误(char text[]="hello"; 解决:char *text="hello";)
Block:被转化为Block结构体类型(__main_block_impl_0)的变量。
__block变量:被转换为__Block_byref_val_0结构体类型的自动变量

Block是一个OC对象,可以赋值,可以当做参数传递,也可以放入 NSArray 和 NSDictionary 中。

1、当用于函数参数时,Block 应该放在参数列表的最后一个。
2、Block默认存储在栈中,出了作用域则销毁。在非 ARC 的情况下,对于 block 类型的属性应该使用 copy ,因为 block 需要复制到堆中,维持其作用域中捕获的变量。在 ARC 中编译器会自动对 block 进行 copy 操作,因此使用 strong 或者 copy 都可以,没有什么区别,但是苹果仍然建议使用 copy 来指明编译器的行为。
可用于
  双向传递数据
    前一个页面创建block,后一个页面判断block非空后 调用block传入数据、block返回值接收数据。
  复用一段代码
  回调


动画、多线程GCD、回调、数组排序 都用到block:
    [UIView animateWithDuration:3 animations:^{     
    }];
    dispatch_async(dispatch_get_main_queue(),^{         
    });

避免循环引用

Block会对内部使用的对象进行强引用(ARC下,变量默认使用__strong修饰,当Block从栈复制到堆时,该变量对象会被Blcok强引用,很容易造成循环引用。MRC下,当Block从栈复制到堆时,会强引用没有使用__block修饰的变量对象)。
编译器能检测出循环引用,并进行警告。

当block作为VC的属性内部又引用到了VC(包括self本身、属性、方法、成员变量),会造成循环引用。

例

typedef void (^CusBlock)(void);
@interface PersonModel:NSObject{
  CusBlock block;
  id _obj;
}
@property(noatomic,copy) NSString *name;
@end
@implementation PersonModel
-(id)init{
  self=[super init];
  // self本身造成循环引用
  block=^{
    NSLog(@"%@",self);
  };
/**
调用self的属性造成循环引用
  block=^{
    NSLog(@"%@",self.name);
  };
*/
/**
调用self的方法造成循环引用
  block=^{
    [self run];
  };
*/
/**
成员变量造成循环引用(也是强引用了self造成的)
  block=^{
    NSLog(@"%@",_obj);
  };
*/
  return self;
}
-(void)run{
}
-(void)dealloc{
  NSLog(@"dealloc");
}
@end

PersonModel *personM=[PersonModel new];
personM的dealloc方法不会被调用,因为存在循环引用。在赋值给block成员变量时,会隐式将block从栈复制到堆来延长作用域,此时self会被blcok强引用,而blcok又被self强引用(block的默认所有权修饰符为__strong)。

ARC

办法1:(使用__weak)

__weak typeof(self) weakSelf = self;
self.block=^(NSString *name){
  __strong typeof(self) strongSelf = weakSelf;
  // ...
};

在block存在时self一定存在,所以不需要对weakSelf判空
办法2:(__block)
在block外部,使用__block生成一个可变的变量指向self。在block作用域结束前,将__block修饰的变量(造成循环引用)置为nil,不再强引用self,主动断开了循环引用。__block可控制对象的持有期间。
这样一来需要必须执行Block,不执行依旧会导致循环引用。

__block typeof(self) blockSelf = self;
self.block=^(NSString *name){
  blockSelf.name;
  blockSelf=nil;
};

办法3:

使用__unsafe_unretained(用法同__weak)
/*
不必担心野指针崩溃。如果__unsafe_unretained修饰的对象被销毁,则block就会被销毁。
*/

MRC

使用__block。
当Block从栈复制到堆时,使用__block修饰的变量对象不会调用retain方法,所以不会造成循环引用。

MRC、ARC下

在 MRC 下,Block 默认是分配在栈上的,除非进行显式的 copy(手动从栈复制到堆),此时需要手动释放。调用了copy后就可以使用retain、release方法。

__block int val = 10;
blk stackBlock = ^{NSLog(@"val = %d", ++val);};
NSLog(@"stackBlock: %@", stackBlock); // stackBlock: <__NSStackBlock__: 0xbfffdb28>

tempBlock = [stackBlock copy];
NSLog(@"tempBlock: %@", tempBlock);  // tempBlock: <__NSMallocBlock__: 0x756bf20>
[tempBlock release];
// [tempBlock retain]; // 只有在调用copy复制到堆后才会有效,否则(对栈中的block)不起作用。
__block的用法 和ARC有很大区别。

可以使用__block来避免循环引用。
当Block从栈复制到堆中,__block修饰的变量不会进行retain操作,没有__block修饰的变量会进行retain操作。
// C语言中 从栈复制到堆、释放,和OC用法类似。
    void (^tempBlockHeap)(void)=Block_copy(tempBlockStack);
    Block_release(tempBlockHeap);
- (blk)myTestBlock {
    __block int val = 10;
    blk stackBlock = ^{NSLog(@"val = %d", ++val);};
    return [[stackBlock copy] autorelease];
}

ARC下,只有显式的 __weak 以及纯匿名 Block 是放到栈上的,赋值给 __strong(默认)指针都会导致在堆上创建 Block。

__block int val = 10;
__strong blk strongPointerBlock = ^{NSLog(@"val = %d", ++val);};
NSLog(@"strongPointerBlock: %@", strongPointerBlock); // strongPointerBlock: <__NSMallocBlock__: 0x7625120>

__weak blk weakPointerBlock = ^{NSLog(@"val = %d", ++val);};
NSLog(@"weakPointerBlock: %@", weakPointerBlock); // weakPointerBlock: <__NSStackBlock__: 0xbfffdb30>

NSLog(@"mallocBlock: %@", [weakPointerBlock copy]); // mallocBlock: <__NSMallocBlock__: 0x714ce60>

NSLog(@"test %@", ^{NSLog(@"val = %d", ++val);}); // test <__NSStackBlock__: 0xbfffdb18>
- (__unsafe_unretained blk) blockTest {
    int val = 11;
    return ^{NSLog(@"val = %d", val);};
}

NSLog(@"block return from function: %@", [self blockTest]); // block return from function: <__NSMallocBlock__: 0x7685640>

几种常见的形式

  1. 声明(牢记)
@property (nonatomic,copy) void (^myBlock)(NSString *name);

.m使用
if(self.myBlock){
    self.myBlock(@"");
}
简写
!self.myBlock?:self.myBlock(@"");
  1. 方法
// 把声明中的block名字放到外面  就是类型
-(void)setMyBlock:(void (^)(NSString *))myBlock{  
}
-(void (^)(NSString *))getMyBlock{  
}
  1. 调用方法
// ^(参数类型 参名){}
[self setMyBLock:^(NSString *name) {    
    // return 10; 又返回值时+
}];
myBlock(@"name");
  1. 局部变量
// 局部变量
void (^myBlock)(NSString *name)=^(NSString *name){
};
  1. 重定义(起别名)
    typedef void (^MyBlock) (NSString *name);
    MyBlock myBlock=^(NSString *name){
    
    };
  1. block使用外部变量
int x=100; 局部变量时(方法内部)

    int x=100;
    void (^myBlock)()=^(){
        NSLog(@"%d",x);
    };
    x=101;
    myBlock();  // 返回100,旧值


    __block int x=100;    // 需要设置__block才能在block内部修改。
    void (^myBlock)()=^(){
        NSLog(@"%d",x);
    };
    x = 101;
    myBlock();  // 返回101,最新值
int x=100; 全局变量时(方法外部)

    int x=100;
    void (^myBlock)()=^(){
        NSLog(@"%d",x);
    };
    x=101;
    myBlock();  // 返回101,新值


    int x=100;    // 全局变量时不用+__block就可以在内部修改
    void (^myBlock)()=^(){
        NSLog(@"%d",x);
        x++;
    };
    myBlock();  // 返回101,最新值
static int x=100; 静态变量时


    void (^myBlock)()=^(){
        NSLog(@"%d",x);
    };
    x=101;
    myBlock();  // 返回101,新值


    void (^myBlock)()=^(){
        NSLog(@"%d",x);
        x++;  // 静态变量时不用+__block就可以在内部修改
    };
    myBlock();  // 返回101,最新值
  1. 数组排序
    [[NSArray new]sortedArrayUsingComparator:^NSComparisonResult(id  _Nonnull obj1, id  _Nonnull obj2) {
        PersonM *personM=obj1;
        PersonM *personM2=obj2;
        if(personM.age>personM.age){
          return NSOrderedDescending; // 在这里返回降序,obj1排在obj2的后
        }
        // NSOrderedAscending = -1L,
        // NSOrderedSame,
        // NSOrderedDescending        
    }];

2. 原理

block的本质是一个结构体,当使用到block外部的变量时会在该结构体中生成对应的成员变量,在创建block时会调用block结构体的构造函数,在该函数中会对成员变量赋值。
在block中直接修改外部变量实际上修改的是block结构体中的变量,编译器会报错。
使用__block修饰外部变量时本质上是创建了一个结构体实例(栈中的__block变量的forwarding指针指向堆中的__block变量,在block从栈复制到堆时修改栈上的__block变量实质上修改的是堆中的__block变量),并在block结构体中生成对应的成员变量。

Block的类型

block也是OC对象,类型有以下3种

_NSConcreteStackBlock: 存储在栈区,需要截取变量
_NSConcreteGlobalBlock: 存储在数据区,不能使用自动变量,所以不需要截取变量。
  以下情况:
    1、全局变量
    2、不截获自动变量的Block
  都属于该类型
_NSConcreteMallocBlock: 存储在堆区
  ARC下多数情况下会自动从栈复制到堆。property属性为block需要使用copy修饰。
  例外:
    向方法或函数的参数传递Block时,但如果在方法或函数中做了copy处理,则不再需要在函数调用前手动copy了。
    (GCD、方法名含有usingBlock除外,这2种情况不需要手动copy)

    void (^blk)(void) = ^{
      printf("Hello");
    };
    blk();

终端使用 clang -rewrite-objc test.m (将OC代码转为C)

/*
简化:
struct __main_block_impl_0 tmp=__main_block_impl_0(__main_block_func_0,&__main_block_desc_0_DATA);
struct __main_block_impl_0 *blk=&tmp;
*/
    void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));

/*
简化:
(*blk->impl.FuncPtr)(blk)。blk就是__main_block_func_0传入的self参数。
*/
    ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
// Block的结构体定义
struct __main_block_impl_0 {
  struct __block_impl impl;  // Block isa ,函数地址等定义
  struct __main_block_desc_0* Desc;  // Block size等信息定义

  // 自动截获的变量
  //...

  // Block信息初始化函数
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
// Block中执行代码部分的函数,_cself就是调用这个函数的调用者的指针,即BLock本身
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  printf("Hello");
}

// 以下为相同部分(固定)
// 静态全局变量
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)};
//
struct __block_impl {
  void *isa;  // 所属的类
  int Flags;
  int Reserved;
  void *FuncPtr;  // 函数地址
};
  1. 自动截获变量
    int val = 110;
    const char *fmt="hello";
    void (^blk)(void) = ^{
        int x=val+1;
        const char *y=fmt;
    };
    blk();

终端使用 clang -rewrite-objc test.m (将OC代码转为C)

    int val = 110;
    const char *fmt="hello";
    void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, val, fmt));
    ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
Block表达式所使用的自动变量,被保存到Block的结构体实例中。

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  // 追加自动截获的变量。Block表达式中没有使用到的自动变量不会被追加。因为要在__main_block_func_0中使用,已经超过了外部变量的作用域,所以需要追加到结构体中。
  int val;  // 
  const char *fmt;  // 
  
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _val, const char *_fmt, int flags=0) : val(_val), fmt(_fmt) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int val = __cself->val; // bound by copy
  const char *fmt = __cself->fmt; // bound by copy
  int x=val+1;
  const char *y=fmt;
}

// 以下为相同部分(固定)
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)};
//
struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};
  1. 使用__block
可修饰任何类型的自动变量。

 __block id obj=[NSObject new]; 等同于  __block id __strong obj=[NSObject new];
因为ARC下,所有id类型必定被附加所有权修饰符,缺省时默认为__strong。

当Block中使用到__strong修饰的id类型变量,当block从栈中复制到堆中时会调用_Block_object_assign持有该对象,当堆上的block被销毁时会调用_Block_object_dispose释放该对象
当__block修饰__strong修饰的id类型变量时,效果一样(当__block变量从栈中复制到堆中时会调用_Block_object_assign持有该对象,当堆上的__block变量被销毁时会调用_Block_object_dispose释放该对象)。

__autorelease和__block同时使用会报编译错误。
    __block int val = 10;  // 不加__block,在block表达式中修改变量会报编译错误。因为block自动截获变量时将变量的值赋值给block结构体,blcok表达式内部修改变量值,修改的是block结构体的变量,而不是外部的变量。
    void (^blk)(void) = ^{val=1;};

/*
id arr = [NSMutableArray new];
void (^blk)(void) = ^{
  [arr addObject:[NSObject new]];  // 正确,arr会作为block结构体的成员变量
  // arr=[NSMutableArray new];  // 错误
};
*/

/**
还可以使用
  静态变量(静态全局变量)(会出现在block结构体中,如static int x=3会生成int *x;因为是在含main的方法中使用,已经超出了变量作用域,所以需要放在结构体中)(这样看来,静态变量也能实现改变变量的值。使用__block而不使用静态变量的原因:超出作用域后将不能使用静态变量来访问)
  静态全局变量 (不会出现在block结构体中)
  全局变量(不会出现在block结构体中)
来做到访问和修改外部变量


C语言有以下存储域类说明符:
  typedef
  extern
  static  静态变量 存储在数据区
  auto 自动变量 存储在栈
  register
__block说明符类似于static、auto、register
*/

终端使用 clang -rewrite-objc test.m (将OC代码转为C)

    __attribute__((__blocks__(byref))) __Block_byref_val_0 val = {
      (void*)0,
      (__Block_byref_val_0 *)&val, 
      0, 
      sizeof(__Block_byref_val_0), 
      10
    };
    void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_val_0 *)&val, 570425344));
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_val_0 *val; // by ref,使用这样做可以被多个block使用
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_val_0 *_val, int flags=0) : val(_val->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
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};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_val_0 *val = __cself->val; // bound by ref
  (val->__forwarding->val)=1;
}
// 当从栈中复制到堆中时会调用
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {  // 赋值给block结构体的变量或对象
  _Block_object_assign((void*)&dst->val, (void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);  // __block变量为BLOCK_FIELD_IS_BYREF,对象则为BLOCK_FIELD_IS_OBJECT,对strong修饰的变量强引用
}
//当堆中的block被销毁时调用
static void __main_block_dispose_0(struct __main_block_impl_0*src) {  // release,释放block结构体中的变量或对象
  _Block_object_dispose((void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);
}
struct __Block_byref_val_0 {
  void *__isa;
__Block_byref_val_0 *__forwarding;  // 指向该__Block_byref_val_0实例自身
 int __flags;
 int __size;
 int val;
};

添加了__block, val变量的类型从int变成了结构体实例__Block_byref_val_0
  1. 栈->堆(会相当消耗资源,如果没必要则不要复制)
以下情况会发生复制
  1、调用copy
  2、block作为函数返回值返回时
  3、将block赋值给附有__strong修饰的id类型的类 或 block类型成员变量时
  4、方法名含有usingBlock的Cocoa框架方法、GCD
以上本质上都会调用_Block_copy函数

只有调用了_Block_copy函数的block才能持有截获__strong修饰的对象类型的自动变量。
block使用__strong修饰的对象类型的自动变量时,除以下情形,都需要使用copy方法:
  1、block作为函数返回值返回时
  2、将block赋值给附有__strong修饰的id类型的类 或 block类型成员变量时
  3、方法名含有usingBlock的Cocoa框架方法、GCD
向方法或函数参数中传递block(如果在方法内部做了copy则不再需要调用copy,如上述中的3)
ARC下
typedef void(^Block)(void);
Block hello(int a){
    return ^{
    };
}
会被编译器转换为
Block hello(int a){
    block tmp = ((void (*)())&__func_block_impl_0((void *)__func_block_func_0, &__func_block_desc_0_DATA));
    tmp = objc_retainBlock(tmp);
    return objc_autoreleaseReturnValue(tmp);
}

因为Block变量在函数返回后,作用域结束,会被销毁。
objc_retainBlock实际上会执行_Block_copy(tmp) 将block从栈复制到堆中。
objc_autoreleaseReturnValue将堆中的block放入自动释放池中,延迟释放。
对栈上的block进行copy:从栈复制到堆。
对堆上的block进行copy:引用计数+1。
对全局block进行copy:无效,什么也不做。

blk=[[[blk copy]copy]copy];  // 多次调用copy,不会有问题
会被转换为
/**
将blk复制到堆中
作用域结束tmp销毁,blk引用计数为1
*/
{
blk_t tmp=[blk copy];  
blk=tmp;  
}
/**
作用域结束tmp销毁,blk引用计数为1
*/
{
blk_t tmp=[blk copy];
blk=tmp;  
}
/**
作用域结束tmp销毁,blk引用计数为1
*/
{
blk_t tmp=[blk copy];
blk=tmp;
}

__block

__block变量和block一样,最开始存储在栈中。
当block从栈复制到堆时,__block修饰的变量,如果在栈中则复制到堆中并被block持有,如果在堆中则被block持有。当block已经在堆中时,再次copy,不会对__block变量造成影响。当多个Block用到__black变量,当第一个block被复制到堆时,会将__black变量复制到堆中,复制其他block时,会将__block变量的引用计数+1.
如果堆中的block被销毁时,__block变量会被释放。
栈中__block变量的__forwarding指向堆中的__block变量,所以修改栈中的__block变量和修改堆中__block变量效果一样。

__block int val=0;
void (^blk)(void)=^{
  var++;  // 修改的是堆中的变量
}
var++;  // 修改的是栈中的变量
blk();

都会被转换为++(var.__fowarding->var);最终修改的都是堆中的变量

你可能感兴趣的:(Block之iOS笔记摘录)