iOS OC Block详解

2020-03-18  本文已影响0人  _菩提本无树_

全文分两部分(认认真真看完,肯定有收获)

1.Block定义主要是介绍一下block的使用
2.详解Block,深入了解Block的用法

一.Block定义

1.Block格式

//等号左侧是声明一个block,右侧是定义一个block
(1).定义block块必须以^开头,声明块以返回值类型开头
(2).定义块的返回值类型可以省略,而且经常会省略,声明不能省略
(3).定义块无需指定名字,声明块必须得有
(4).如果块没有参数,参数部分可以省略但是参数部分的括号不能省略,
但是内部可以留空,通常建议使用void作为占位符

returnType(^blockName)(parameterTypes) = ^(parameters) {
    statements
};

void(^blockName)(NSString *) = ^void(NSString*str){
    NSLog(@"%@",str);
};
blockName(@"这是一个简单的无返回值的block");

//^告诉计算机这是一个block
NSString *(^blockParam)(NSString *) = ^NSString*(NSString*n){
    NSLog(@"%@",n);
    return @"123456789";
};
NSString * str = blockParam(@"这是一个简单的带返回值的block");
str = @"123456789";

2.Block作为属性

@property(nonatomic,copy) void (^propertyBlock)(NSString*);

3.typedef定义Block

typedef void (^typedefBlock)(NSString *);
@property(nonatomic, copy)typedefBlock block;

4.Block作为参数不带返回值

- (void)viewDidLoad{
    
    [self blockAsParam:^(NSString *name) {
        NSLog(@"%@",name);//123123123
    }];
}
- (void)blockAsParam:(void (^)(NSString * _Nullable))paramBlock{
    paramBlock(@"123123123");
}

5.Block作为参数带返回值

- (void)viewDidLoad{

    [self blockAsParamReturnValue:^NSString*(NSString *name){
        NSLog(@"%@",name);
        return @"123123123123";
    }];

}

- (void)blockAsParamReturnValue:(NSString *(^)(NSString *str))returnValue{

    NSString *s = returnValue(@"123123123");
    NSLog(@"%@",s);//123123123123

}

6.Block不带返回值,作为返回值

- (void)viewDidLoad{

    void(^blockNoReturn)(NSString*) = [self blockNoReturnValue:@"11111"];
    blockNoReturn(@"000000");
}

- (void(^)(NSString *))blockNoReturnValue:(NSString*)value{

    return ^(NSString *name){
        NSLog(@"%@,%@",name,value);//000000,11111
    };

}

7.Block带返回值,作为返回值

- (void)viewDidLoad{

    NSString *(^haveReturnValue)(NSString *) = [self blockHaveReturnValue:@"2222"];
    NSString *s = haveReturnValue(@"123");
    NSLog(@"%@",s);//11111111111111111111

}

- (NSString *(^)(NSString*))blockHaveReturnValue:(NSString*)value{

    return ^NSString *(NSString*n){
        NSLog(@"%@,%@",n,value);//123,2222
        return @"11111111111111111111"
    };

}

二.Block详解(2020.03.18更新)

1.使用copy和strong修饰block有什么区别吗?

block有三种存储类型__NSGlobalBlock__,__NSStackBlock__, __NSMallocBlock__.

经测试在MRC环境下,任何位置定义的block,未引用外部变量的情况下为NSGlobalBlock类型.
引用了外部静态变量(全局/局部)的为NSGlobalBlock,引用了外部非静态变量(全局/局部)的,
如果使用copy修饰了属性并且使用了self.语法调用的为NSMallocBlock,否则是NSStackBlock类型.
(
举例
@property (nonatomic ,copy) void (^copyPropertyBlock)(NSString * _Nullable);
   
    self.copyPropertyBlock = ^(NSString *c){
        int d = globaln + 9;
    };//globaln为非静态变量,这个时候copyPropertyBlock类型是NSMallocBlock
)
在以上的基础上对所有的block执行copy操作,NSGlobalBlock的block保持原状,
NSStackBlock类型的block变为NSMallocBlock,拷贝到了堆上.所以在MRC中需要使用copy修饰
否则在栈中会随时释放

在ARC环境下任何位置定义的block,未引用外部变量的情况下为NSGlobalBlock类型,
引用了外部静态变量(全局/局部)的为NSGlobalBlock,引用了外部非静态变量(全局/局部)的为NSMallocBlock
在以上的基础上对所有的block执行copy操作,NSGlobalBlock的block保持原状,
NSMallocBlock的类型未变化(地址也未发生变化)

结论
在MRC中访问外部非静态变量的block必须需要使用copy对block进行修饰,将其拷贝到堆中.
ARC中系统已经帮我们将其从栈拷贝到堆了,所以我们不需要在进行copy处理
,使用copy或者strong修饰都可以.

网上一搜好多关于这方面的讲解,但是大多数都是复制的一篇文章,内容都一样,
而且还是比较老的文章,没有涉及ARC和MRC的对比,当然也有好的文章,建议大家自己试一下才会知道结果.

2.__weak和__block

__weak仅仅是将该对象赋值给weak修饰的对象,__weak还有一个作用就是将被修饰的对象加入缓存池
中,当缓存池释放时,会对其内部的对象release一次,因为对象是weak修饰的弱引用,所以会被释放.
不会引起循环引用的问题

__block
修饰的局部变量会包装成一个结构体对象,这个结构体对象里面包含着被修饰的对象,被block持有,
这个时候__block修饰的值是可以修改的.

3.block捕获变量
源码查看方式

#include "DDBlockStudy.h"
//全局的
int w = 23;
static int e = 10;
int f[] = {11,22,33,44};

void buildBlock(){
    
    int m = 1;
    static int n = 3;
    int arr[] = {1,2,3,4,5,6};
    
    void (^testBlock)(int)= ^(int d){
        printf("\n%d,%d,%d,%d,%d\n", w,e,*f,m,n);
        //打印的结果是12,14,32,1,10
    };
    w = 12;
    e = 14;
    *f = 32;
    m = 4;
    n = 10;
    testBlock(3);
    
}
上面的内容出现的结果可以总结一下
(1).静态变量(全局和局部)和全局变量并未捕获任何内容,用的时候直接取得,可以修改值
(2).属性值和成员变量捕获的是对象的持有者(self),可以修改值
(3).对于局部变量block捕获的是值.
理由见 static void __buildBlock_block_func_0(struct __buildBlock_block_impl_0 *__cself, int d);函数
(补充,更确切的说是在__buildBlock_block_impl_0((void *)__buildBlock_block_func_0, &__buildBlock_block_desc_0_DATA, m, &n)函数中可以看出,详细见下面的👇Block修改变量有解说)



//通过源码分析
int w = 23;
static int e = 10;
int f[] = {11,22,33,44};

//这是block的定义来自__buildBlock_block_impl_0((void *)__buildBlock_block_func_0, &__buildBlock_block_desc_0_DATA, m, &n)
struct __buildBlock_block_impl_0 {
  struct __block_impl impl;
  struct __buildBlock_block_desc_0* Desc;
  //下面的内容是被捕获的参数
  int m;
  int *n;

  //void *fp 对应 (void *)__buildBlock_block_func_0 方法的指针
  //struct __buildBlock_block_desc_0 *desc 对应  &__buildBlock_block_desc_0_DATA 这个block的一些参数
  //impl.isa = &_NSConcreteStackBlock;可以得出当前的block在栈中
  //m(_m)将_m的值赋给m,n同理

  //从这里看出(int _m, int *_n)这个时候m和n被捕获了,但是m只是值被持有了,n被持有的是指针.  
  __buildBlock_block_impl_0(void *fp, struct __buildBlock_block_desc_0 *desc, int _m, int *_n, int flags=0) : m(_m), n(_n) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
//下面的这个函数存储的是block执行的代码
static void __buildBlock_block_func_0(struct __buildBlock_block_impl_0 *__cself, int d) {
  int m = __cself->m; // bound by copy
  int *n = __cself->n; // bound by copy

        printf("\n%d,%d,%d,%d,%d\n", w,e,*f,m,(*n));
}

static struct __buildBlock_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __buildBlock_block_desc_0_DATA = { 0, sizeof(struct __buildBlock_block_impl_0)};
void buildBlock(){

    int m = 1;
    static int n = 3;
    int arr[] = {1,2,3,4,5,6};

    void (*testBlock)(int) = ((void (*)(int))&__buildBlock_block_impl_0((void *)__buildBlock_block_func_0, &__buildBlock_block_desc_0_DATA, m, &n));
    w = 12;
    e = 14;
    *f = 32;
    m = 4;
    n = 10;
    //这里是调用了这个block
    ((void (*)(__block_impl *, int))((__block_impl *)testBlock)->FuncPtr)((__block_impl *)testBlock, 3);

}

4.Block修改变量

先上代码
#import "DDBlockController.h"

@interface DDBlockController ()
@property (nonatomic, assign) int a;
@property (nonatomic, copy) NSString *str;
@end

static int b = 11;
int c = 10;
NSString *str1 = @"aaaaaa";
static NSString *str2 = @"bbbbbb";


@implementation DDBlockController
{
    int d;
    NSString *str3;
    void (^testTwo)(void);
    
}
- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.a = 123;
    self.str = @"eeeeeeeee";
    d = 4444444;
    str3 = @"rrrrrrrrrrrrrrrrr";
    [self blockTest];
    // Do any additional setup after loading the view.
}

- (void)blockTest{
    //如果修改局部非静态变量需要使用__block修饰.
    int e = 10;
    static int f = 12;
    NSString * str4= [[NSString alloc]init];;
    str4 = @"123123";
    static NSString * str5= @"1234asdads";

    void (^testOne)(void) = ^(void){
        
        NSLog(@"%d",_a);
        NSLog(@"%d",b);
        NSLog(@"%d",c);
        NSLog(@"%d",d);
        NSLog(@"%d",e);
        NSLog(@"%d",f);
        NSLog(@"%@",_str);
        NSLog(@"%@",str1);
        NSLog(@"%@",str3);
        NSLog(@"%@",str4);
        NSLog(@"%@",str5);

        
    };
    testOne();
}

- (void)dealloc{
    NSLog(@"aaaaaaaaaaaaaa");
}
@end

//*desc和flags之间的是被捕获的内容,self(_self)将_self赋值给self,  DDBlockController *self = __cself->self; // bound by copy,从__DDBlockController__blockTest_block_impl_0中取出self的值(C++->取值符号)
__DDBlockController__blockTest_block_impl_0(void *fp, struct __DDBlockController__blockTest_block_desc_0 *desc, DDBlockController *_self, int flags=0) : self(_self) {



将block中的打印依次打开,从源码中可以看出(注捕获的意思就是持有的意思)
(1).使用当前类属性的时候self.a时,会将持有a的对象self捕获,可以修改值,但是注意循环引用(DDBlockController *_self)
__DDBlockController__blockTest_block_impl_0(void *fp, struct __DDBlockController__blockTest_block_desc_0 *desc, DDBlockController *_self, int flags=0) : self(_self) {

(2).当使用全局静态变量b的时候未捕获任何值,用的时候直接使用,可以修改值
__DDBlockController__blockTest_block_impl_0(void *fp, struct __DDBlockController__blockTest_block_desc_0 *desc, int flags=0) {

(3).当使用全局变量c的时候未捕获任何值,用的时候直接取,可以修改值
__DDBlockController__blockTest_block_impl_0(void *fp, struct __DDBlockController__blockTest_block_desc_0 *desc, int flags=0) {

(4).当使用成员变量d的是,捕获的是对其持有的self,可以修改其值,但是注意循环引用问题(DDBlockController *_self)
__DDBlockController__blockTest_block_impl_0(void *fp, struct __DDBlockController__blockTest_block_desc_0 *desc, DDBlockController *_self, int flags=0) : self(_self) {

(5).当使用局部变量e的时候会捕获其值,未捕获其指针地址,不可修改值除非使用__block修饰(int _e)
__DDBlockController__blockTest_block_impl_0(void *fp, struct __DDBlockController__blockTest_block_desc_0 *desc, int _e, int flags=0) : e(_e) {

(6).当使用局部静态变量f的时候,捕获的是f的指针,可以进行修改(int *_f,)
__DDBlockController__blockTest_block_impl_0(void *fp, struct __DDBlockController__blockTest_block_desc_0 *desc, int *_f, int flags=0) : f(_f) {

(7).当使用全局的属性self.str时,捕获的是持有str的对象self,可以修改值,但是注意循环引用(DDBlockController *_self)
__DDBlockController__blockTest_block_impl_0(void *fp, struct __DDBlockController__blockTest_block_desc_0 *desc, DDBlockController *_self, int flags=0) : self(_self) {

(8).当使用全局变量str1的时候,未捕获任何值,因此可以直接使用且可以修改值
__DDBlockController__blockTest_block_impl_0(void *fp, struct __DDBlockController__blockTest_block_desc_0 *desc, int flags=0) {

(9).当使用成员变量d的是,捕获的是对其持有的self,可以修改其值,但是注意循环引用问题(DDBlockController *_self)
__DDBlockController__blockTest_block_impl_0(void *fp, struct __DDBlockController__blockTest_block_desc_0 *desc, DDBlockController *_self, int flags=0) : self(_self) {

(10).当使用局部变量str4的时候,捕获的是str4的值并没有捕获其指针地址,所以不可修改,除非使用__block修饰(NSString *_str4)
__DDBlockController__blockTest_block_impl_0(void *fp, struct __DDBlockController__blockTest_block_desc_0 *desc, NSString *_str4, int flags=0) : str4(_str4) {

(11).当使用局部静态变量str5的时候,捕获的是str5的地址,所以可以修改(NSString **_str5)
__DDBlockController__blockTest_block_impl_0(void *fp, struct __DDBlockController__blockTest_block_desc_0 *desc, NSString **_str5, int flags=0) : str5(_str5) {

总结
//auto变量(局部非静态变量)捕获的是值所以不能操作修改值.
//注意因为当前的block是局部的并未被self持有,所以使用self不会引起循环引用
//__block的作用是将修饰的变量包装成一个结构体对象
下面的这个是__block修饰的int e;被block捕获的后,系统为其创建的一个结构体
struct __Block_byref_e_0 {
    void *__isa;
  __Block_byref_e_0 *__forwarding;//__forwarding指向的是__Block_byref_e_0自己,自己指向了自己
   int __flags;
   int __size;
   int e;
  };
  
//对于可变变量(NSMutableArray,NSMutableString等)即使是局部变量也可以进行增删改查的操作,
//因为可变内容在初始话的时候分配了一块内存,操作的时候都是在这块内存操作的,只要是不改变该变量的指针对应的内容即可


总结一下就是对于局部非静态变量而言,可以修改局部变量的属性等操作,但是不能改变其指向的内存的地址,否则就会报错,
使用__block之后就是block持有了这个对象,没有使用__block的话只是copy了一份,但是只有只读权限,不能改变对象,但是可以改变对象的属性.

意思就是秘书来了权限不够不好使,只能动一部分内容.老板亲自来了权限就大了可以做的事就更多了.

5.Block循环引用,并不是所有的block都会循环引用
6.Block作为形参的时候不会引起循环引用,这也就解释了为什么GCD或者UIView动画这些操作不会引起循环引用
7.Block存储域

上一篇下一篇

猜你喜欢

热点阅读