OC--Block总结

作者: 啊哈呵 | 来源:发表于2017-02-18 20:15 被阅读120次

    参考

    Block编译代码解读:
    block没那么难(一、二、三)
    iOS进阶——iOS(Objective-C) 内存管理&Block

    Block源码解读:
    漫谈Block
    苹果爹爹Block实现源码

    一、Block代码

    (1) 没有返回值、没有参数的block

         void (^voidBlock)() = ^{};// 定义
         voidBlock();// 使用
    

    (2) 没有返回值、有一个参数的block

        void (^parameterBlock)(NSInteger parameter) = ^(NSInteger parameter){
            NSLog(@"=====parameterBlock");
        };
        parameterBlock(1);
    

    (3)没有返回值、有两个参数的block

        void (^parametersBlock)(int par1, int par2) = ^(int par1, int par2){
            NSLog(@"=====parameterBlock");
        };
        parametersBlock(2,3);
    

    (4) 有返回值、有参数的block

        NSString *(^returnBlock)(NSInteger a) = ^(NSInteger a){
            return @"returnBlock";
        };
        NSLog(@"=====%@",returnBlock);
    

    (5) block的typedef定义、使用

        typedef void(^TypedefBlock)(NSInteger type);
        TypedefBlock typedefBlock = ^(NSInteger type){
            NSLog(@"TypedefBlock");
        };
        typedefBlock(1);
    

    二、Block的类型,什么是NSGlobalBlock、NSStackBlock、NSMallocBlock?

    1、NSGlobalBlock

    (1)没有捕获外部变量

        void(^blockA)(void) = ^() {
            NSLog(@"=====%@",@"asdasd");
        };
        NSLog(@"=====%@",blockA); // <__NSGlobalBlock__: 0x10debd190>
    

    (2)只捕获全局变量,全局静态变量,局部静态变量。

    int b = 20; // 全局变量
    static int c = 30;// 全局静态变量
    - (void)viewDidLoad {
        [super viewDidLoad];
    
        static int a = 10; // 局部静态变量
        void(^blockA)(void) = ^() {
            a++;
            b++;
            c++;
        };
        NSLog(@"=====%@",blockA); // <__NSGlobalBlock__: 0x10af19190>
    }
    

    另外:GlobalBlock的copy与retain还是GlobalBlock

    (2) NSStackBlock:使用了外部变量的block,将 Block 赋值给附有 __weak 修饰符变量。栈区
        int a = 10;
        typedef void (^BBBlock)();
        __weak BBBlock aBlock = ^(){
             NSLog(@"a = %i",a);
        };
        NSLog(@"=====%@",^{NSLog(@"a = %i",a);}); // ====<__NSStackBlock__: 0x7fff50af3ce0>
        NSLog(@"=====%@",aBlock); // =====<__NSStackBlock__: 0x7fff50af3d08>
    
    (3) NSMallocBlock:有以下几种情况

    1、[stackBlock copy];stackBlock的copy方法;
    2、return stackBlock;stackBlock作为返回值;
    3、将stackBlock赋值给附有 __strong 修饰符的成员变量;
    4、在方法名中含有 usingBlock 的 Cocoa 框架方法或 GCD 的 API 中传递stackBlock,方法里面先Block block = [stackBlock copy],然后使用block

    [stackBlock copy]

        int a = 10;
        typedef void (^BBBlock)();
        __weak BBBlock stackBlock = ^(){
            NSLog(@"a = %i",a);
        };
        
        BBBlock block1 = [stackBlock copy];
        NSLog(@"=====%@",block1);// <__NSMallocBlock__: 0x60000045d8b0>
    

    stackBlock做返回值

    //返回值是stackBlock的,变成NSMallocBlock
    - (void(^)())getBlock {
            int a = 0;
            return ^{NSLog(@"=====%d",a);};
    }
    NSLog(@"%@",[self getBlock]);// <__NSMallocBlock__: 0x60000045d8b0>
    

    将stackBlock赋值给附有 __strong 修饰符的成员变量时,(变量默认修饰符是__strong)

        int a = 2;
        void(^aBlock)() = ^{
            NSLog(@"a=%ld",(long)a);
        };
        NSLog(@"=====%@", aBlock);// <__NSMallocBlock__: 0x60000045d8b0>
    

    三、block捕捉变量,有__block修饰、无__block修饰到底发生了什么?

    无__block 修饰:

    block捕获变量,相当于在block结构体中开一个同名的变量(如变量是对象,则也同strong、weak修饰)

    有__block 修饰:

    __block局部变量,是包装成一个__Block_byref_XXX_n结构体的对象,当这个__Block_byref对象被block使用且block_copy上堆时候,__Block_byref对象也会被复制到堆上(block源码:_Block_object_assign中执行_Block_byref_copy如下)。

    _Block_object_assign
    void _Block_object_assign(void *destArg, const void *object, const int flags) {
        const void **dest = (const void **)destArg;
        switch (os_assumes(flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {
            case BLOCK_FIELD_IS_OBJECT:
                /*******
                 id object = ...;
                 [^{ object; } copy];
                 ********/
                
                // 截获变量是对象:MRC是retain,ARC是空
                _Block_retain_object(object);
                *dest = object;
                break;
                
            case BLOCK_FIELD_IS_BLOCK:
                /*******
                 void (^object)(void) = ...;
                 [^{ object; } copy];
                 ********/
                // 截获变量是block:Block_copy
                *dest = _Block_copy(object);
                break;
                
            case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:
            case BLOCK_FIELD_IS_BYREF:
                /*******
                 // copy the onstack __block container to the heap
                 // Note this __weak is old GC-weak/MRC-unretained.
                 // ARC-style __weak is handled by the copy helper directly.
                 __block ... x;
                 __weak __block ... x;
                 [^{ x; } copy];
                 ********/
                
                // 处理Block截获的__block修饰的变量(对象或者常量,_Block_byref结构体栈上复制到堆上,如果是对象要_Block_object_assign一下,进入下面的 case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT)
                *dest = _Block_byref_copy(object);
                break;
    
            case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:
            case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:
                /*******
                 // copy the actual field held in the __block container
                 // Note this is MRC unretained __block only.
                 // ARC retained __block is handled by the copy helper directly.
                 __block id object;
                 __block void (^object)(void);
                 [^{ object; } copy];
                 ********/
                // 处理Block_byref结构体中截获的对象
                *dest = object;
                break;
                
            case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT | BLOCK_FIELD_IS_WEAK:
            case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK  | BLOCK_FIELD_IS_WEAK:
                /*******
                 // copy the actual field held in the __block container
                 // Note this __weak is old GC-weak/MRC-unretained.
                 // ARC-style __weak is handled by the copy helper directly.
                 __weak __block id object;
                 __weak __block void (^object)(void);
                 [^{ object; } copy];
                 ********/
                
                *dest = object;
                break;
                
            default:
                break;
        }
    }
    
    
    
    _Block_byref_copy
    // __block修饰的对象copy,栈上复制到堆上
    static struct Block_byref *_Block_byref_copy(const void *arg) {
        struct Block_byref *src = (struct Block_byref *)arg;
        
        if ((src->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0) {
            
            // 该方法先在堆上生成同样大小的Block_byref赋值给堆上的Block,
            struct Block_byref *copy = (struct Block_byref *)malloc(src->size);
            copy->isa = NULL;
            // 并把flags设置为src->flags | BLOCK_BYREF_NEEDS_FREE | 4
            copy->flags = src->flags | BLOCK_BYREF_NEEDS_FREE | 4;
            copy->forwarding = copy; // 堆上的forwarding指向自己
            src->forwarding = copy;  // 栈上的forwarding指向堆
            copy->size = src->size;
            
            if (src->flags & BLOCK_BYREF_HAS_COPY_DISPOSE) {
                // __block修饰对象,这里的意思就是Block_byref要处理这个对象,
                // 执行__Block_byref结构体里面的__Block_byref_id_object_copy_xxx >>> _Block_object_assign,
    
                struct Block_byref_2 *src2 = (struct Block_byref_2 *)(src+1);
                struct Block_byref_2 *copy2 = (struct Block_byref_2 *)(copy+1);
                copy2->byref_keep = src2->byref_keep;
                copy2->byref_destroy = src2->byref_destroy;
                
                if (src->flags & BLOCK_BYREF_LAYOUT_EXTENDED) {
                    struct Block_byref_3 *src3 = (struct Block_byref_3 *)(src2+1);
                    struct Block_byref_3 *copy3 = (struct Block_byref_3*)(copy2+1);
                    copy3->layout = src3->layout;
                }
                
                (*src2->byref_keep)(copy, src);
            }
            else {
                // Bitwise copy.
                // This copy includes Block_byref_3, if any.
                memmove(copy+1, src+1, src->size - sizeof(*src));
            }
        }
        // 这个block已经在堆上了,引用计数++
        else if ((src->forwarding->flags & BLOCK_BYREF_NEEDS_FREE) == BLOCK_BYREF_NEEDS_FREE) {
            latching_incr_int(&src->forwarding->flags);
        }
        
        return src->forwarding;
    }
    

    __block NSObject *obj = [NSObject new];
    Block里面使用obj的地方编译为:obj.__forwarding->obj

    struct __Block_byref_obj_0 {
      void *__isa;
    __Block_byref_obj_0 *__forwarding;
     int __flags;
     int __size;
     void (*__Block_byref_id_object_copy)(void*, void*);
     void (*__Block_byref_id_object_dispose)(void*);
     NSObject *obj;
    };
    
    Block_byref对象:栈-->堆
    首先,了解一下内存分配 iOS开发中的内存分配(堆和栈)

    (1)栈(stack):由编译器自动分配释放,存放方法的参数值,局部变量等。栈是向低地址括展的数据结构,是连续的区域。栈顶的地址和栈的最大容量是系统预先规定好的。
    优点:快速高效,
    缺点:容量有限制
    静态分配:静态分配是编译器完成的,比如局部变量的分配。
    动态分配:动态分配由alloca函数进行分配,由编译器进行释放,无需我们手工实现。
    (2)堆(heap):程序员分配释放,向高地址括展的数据结构,是不连续的区域。
    (3)全局区(静态区)(static):全局变量和静态变量的存储,分初始化和未初始化bss(data、bss 两块区域),程序结束系统释放。
    (4)常量区:字符串常量,const常量,程序结束系统释放。
    (5)代码区:存放函数体的二进制代码。也就是说是它是可执行程序在内存种的镜像。代码段需要防止在运行时被非法修改,所以只准许读取操作,而不允许写入(修改)操作——它是不可写的。

    看代码

    int age = 24;//全局初始化区(数据区)
    NSString *name;//全局未初始化区(BSS区)
    static NSString *sName = @"Dely";//指针sName的地址在全局(静态初始化)区,"Dely"本身在常量去,字符串常量
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
    
        int tmpAge;//栈
        NSString *tmpName = @"Dely";  //指针tmpName的地址在栈区,"Dely"本身在常量去,字符串常量
        NSString *number = @"123456"; //指针number的地址在栈上,123456 在常量区,
        NSMutableArray *array = [NSMutableArray arrayWithCapacity:1];//分配而来的8字节的区域就在堆中,指针array在栈中,指向堆区的地址
        NSInteger total = [self getTotalNumber:1 number2:1];
    
    }
    
    - (NSInteger)getTotalNumber:(NSInteger)number1 number2:(NSInteger)number2{
        return number1 + number2;//number1和number2 栈区
    }
    
    1、局部变量
    (1)无_block修饰:

    基本类型

        int a = 10;
        NSLog(@"block定义前a地址=%p", &a);
        void (^aBlock)() = ^(){
            NSLog(@"block定义内部a地址=%p", &a);
        };
        NSLog(@"block定义后a地址=%p", &a);
        aBlock();
        
        /*
         结果:
         block定义前a地址=0x7fff5bdcea8c
         block定义后a地址=0x7fff5bdcea8c
         block定义内部a地址=0x7fa87150b850
         */
        
        /*
         流程:
         1. block定义前:a在栈区
         2. block定义内部:里面的a是根据外面的a拷贝到堆中的,不是一个a,
         3. block定义后:a在栈区
         */
    

    指针类型:(block捕获变量,相当于在block结构体中开一个同名的变量,指向局部变量的对象,被block持有,引用数+1)

        NSObject *obj = [[NSObject alloc] init]; // 局部变量--指针变量,多提一下:“=”赋值右边的(alloc init)分配的地址是在堆上,左边obj指针地址是在栈上
        NSLog(@"block定义前:[obj本身的地址=%p,obj指向的地址=%p]",&obj,obj);
        void (^aBlock)() = ^(){
            NSLog(@"block定义内部:[obj本身的地址=%p,obj指向的地址=%p]",&obj,obj);
        };
        NSLog(@"block定义后:[obj本身的地址=%p,obj指向的地址=%p]",&obj,obj);
        aBlock();
    
        /*
         NSLog输出:
         block定义前:[obj本身的地址=0x7fff5bebed30,obj指向的地址=0x60000001f750]
         block定义后:[obj本身的地址=0x7fff5bebed30,obj指向的地址=0x60000001f750]
         block定义内部:[obj本身的地址=0x600000059910,obj指向的地址=0x60000001f750]
         */
        
        /*
         解析:
         1. block定义前:指针obj本身的地址在栈区,指针obj指向的对象是在堆上
         2. block定义内部:在block结构体中(block在堆上)开一个同名的变量(堆上),指向obj指向的对象,被block持有,引用数+1
         3. block定义后:obj没有变化。
         */
    
    (2)有_block修饰

    基本数据类型

        __block int b = 10;
        NSLog(@"block定义前b地址=%p", &b);
        void (^bBlock)() = ^(){
            b = 20;
            NSLog(@"block定义内b地址=%p", &b);
        };
        NSLog(@"block定义后b地址=%p", &b);
        bBlock();
        
        /*
         结果:
        block定义前b地址=0x7fff5c446c28
        block定义后b地址=0x60400042b318
        block定义内b地址=0x60400042b318
         */
    
    

    指针类型:

        __block NSString *obj = [[NSString alloc] initWithFormat:@"111"]; // 局部变量--指针变量,多提一下:“=”赋值右边的(alloc init)分配的地址是在堆上,左边obj指针地址是在栈上
        NSLog(@"block定义前:[obj本身的地址=%p,obj指向的地址=%p]",&obj,obj);
        void (^aBlock)() = ^(){
            NSLog(@"block定义内:[obj本身的地址=%p,obj指向的地址=%p]",&obj,obj);
        };
        NSLog(@"block定义后:[obj本身的地址=%p,obj指向的地址=%p]",&obj,obj);
        aBlock();
        
        /*
         结果:
         block定义前:[obj本身的地址=0x7fff5d32fd38,obj指向的地址=0xa000000003131313]
         block定义后:[obj本身的地址=0x600000052298,obj指向的地址=0xa000000003131313]
         block定义内:[obj本身的地址=0x600000052298,obj指向的地址=0xa000000003131313]
         */
    
    
    2、全局变量或者全局静态变量:因为都是在全局区,在程序结束前不会被销毁

    block内部不需要处理全局变量或者全局静态变,直接使用

        //所以block直接访问了对应的变量(没有对变量处理)
        int global_var = 100;
        static int static_global_var = 200;
        void(^globalVarBlock)() = ^{
            
            NSInteger var = global_var; // 可以使用
            global_var = 1000; // 可以直接访问或者赋值
            static_global_var = 10000; // 可以直接访问或者赋值
            
        };
    
    3、局部静态变量:将静态变量的指针传递给block,block通过静态局部变量的地址来进行访问

    {
        static NSInteger staticLocalVar = 10; 
        void(^staticLocalVarBlock)() = ^{
            
            NSInteger bvar = staticLocalVar;//可以使用
            staticLocalVar = 1000;//可以使用
            
        };
    }
    

    block的@property

    ARC会自动帮strong类型且捕获外部变量的block进行copy,所以在定义block类型的属性时也可以使用strong,不一定使用copy

        // 假如有栈block赋给以下两个属性
        // ARC,当栈block中会捕获外部变量时, 这个block会被copy进堆中
        // 如果没有捕获外部变量,这个block会变为全局类型
        // 不管怎么样,它都脱离了栈生命周期的约束
        @property (nonatomic,strong) Block *strongBlock;
        @property (nonatomic,copy) Block *copyBlock;
    

    四、block使用中出现retain cycle的问题、解决办法

    最简单的循环问题代码

    self.myBlock = ^{
            NSLog(@"=====%@",self);//这种明显的循环引用,xcode会提示。
        };
    

    解决办法:(常用)

        __weak __typeof(self)weakSelf = self;
        self.myBlock = ^{
            __strong __typeof(weakSelf)strongSelf = weakSelf;;//这个作用是防止block执行过程中self释放导致block执行了一半就GG。
            NSLog(@"=====%@", strongSelf);
        };
    

    解决办法:(不常用)
    Block 多开一个参数,传入self,然后使用self的一些属性方法,这样子不会retain cycle

    介绍做法:
    为 block 多加一个参数,也就是 self 所属类型的参数,那么在 block 内部,该参数就会和 strongSelf 的效果一致。同时你也可以不写 weakSelf,直接使用使用该参数(作用等同于直接使用 strongSelf )。这样就达到了:“多加一个参数,省掉两行代码”的效果。原理就是利用了“参数”的特性:参数是存放在栈中的(或寄存器中),系统负责回收,开发者无需关心。因为解决问题的思路是:将 block 会捕获变量到堆上的问题,化解为了:变量会被分配到栈(或寄存器中)上,所以我把种做法起名叫 Heap-Stack Dance 。
    引用来自--iOS程序猿 使用 Heap-Stack Dance 替代 Weak-Strong Dance,优雅避开循环引用。

    #import "Foo.h"
    typedef void (^Completion)(Foo *foo);
    @interface Foo ()
    @property (nonatomic, copy) Completion completion1;
    @end
    
    @implementation Foo
    - (instancetype)init {
        if (!(self = [super init])) {
            return nil;
        }
        self.completion2 = ^(Foo *foo) {
            //使用foo的各种属性、方法
        };
        self.completion2(self);
        return self;
    }
    - (void)dealloc {
        NSLog(@"dealloc");//这能执行
    }
    @end
    

    五、block的思考与实际使用的疑问,为什么有些系统SDK或者第三方库API中有block的使用中会引发循环引用,有些不会?

    (1) GCD不会 (内部是copy这个block)
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        // something
        NSLog(@"=====%@",self.view);
    });
    
    (2) presentViewController不会 (这个block没有被任何对象持有,只是传进去执行)
    UIViewController *vc = [[UIViewController alloc] init];
    [self presentViewController:vc animated:YES completion:^{
        NSLog(@"=====%@",self.view);
    }];
    
    (3) AFNetWork
    //self -> manager(AFHTTPSessionManager)->mutableTaskDelegatesKeyedByTaskIdentifier->delegate->block->self
    //AFNetwork 在请求结束后会自动释放掉 delegate,打破那个环,所以循环引用也就不存在了.
    manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
    NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://www.hao123.com"]];
    NSURLSessionDataTask *dataTask = [manager dataTaskWithRequest:request completionHandler:^(NSURLResponse *response, id responseObject, NSError *error) {
        NSLog(@"=====%@",self.view);
    }];
    [dataTask resume];
    
    (4) SDWebImage
    //slef -> view- -> imageView -> operationDictionary -> SDWebImageOperation -> OperationCompletedBlock  -> imageViewCompletedBlock -> self
    //跟AFNetwork一样,等待operation请求完成自动remove,断开连接
    UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
    [self.view addSubview:imageView];//self.view会retain imageView。
    [imageView sd_setImageWithURL:[NSURL URLWithString:@"http://img17.3lian.com/d/file/201702/18/3e8536054dd699a2134f86c200f7c079.jpg"] completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {
        NSLog(@"=====%@",self.view);
    }];
    

    AFNetwork、SDWebImage都是要等请求结束才能断开retain cycle,self得不到快速释放。假设请求时间100年,这个self就100年得不到释放。只是假设,哈哈

    (5) 其他方式出现的retain cycle,NSNotification、NSTimer、自己写的delegate都需要好好注意一下!!!!
    [[NSNotificationCenter defaultCenter] addObserverForName:@"SecondViewController" object:nil queue:NULL usingBlock:^(NSNotification *note) {
    }];
    [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {}];

    相关文章

      网友评论

        本文标题:OC--Block总结

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