block_blockchain - CSDN
精华内容
参与话题
  • 又见block(一):block是什么?

    千次阅读 2018-09-10 00:01:08
    block的原理实质问题是一道分水岭,区分中高级程序的试金石,一般的大公司,他可能不会问你内存管理甚至多线程编程与简单的一笔带过,但是block一定会仔细问。无奈的是平时只知使用,对其原理与实质没有深究,因此数...
    block的原理是一道分水岭,区分中高级程序的试金石,一般的大公司,他可能不会问你内存管理,甚至多线程编程问题也简单的一笔带过,但是block一定会仔细问。可惜的是平时只知使用,对其原理与实质没有深究,因此数次面试倒在这道坎上,真是心有不甘,这次好好理理关于block的前因后果,以供后续随手翻阅查证。

    block的定义:带有自动变量(局部变量)的匿名函数

    想要说明什么是匿名函数,先来了解下什么是函数以及函数指针

    • 函数:函数是C语言程序中的重要组成部分,不同函数调用的组合实现程序的各个功能。
      格式如下:
        returnType funcName(paramter List){
            //funcBody
        }

    returnType:函数返回值
    funcName:函数名
    paramter List:参数列表
    funcBody:函数体
    一个简单的函数如下:

        int funcSum (int count) {
            return count + 10;
        }

    定义了一个名为funcSum的函数,有一个int类型的形参,返回值是int类型
    函数调用如下:

        int result = funcSum(18);
    • 函数指针:除了直接调用函数之外,我们还可以定义一个函数指针来指向这个函数地址,通过调用函数指针进而调用指向的函数,函数指针的定义如下:
        returnType (*FuncPointer)(Paramter List)

    继续上面定义的funcSum函数,现在定一个函数指针指向这个函数

        int*funcPointer)(int)= &funcSum;

    调用函数指针方式如下:

        int result = (*funcPointer)(18);

    通过函数指针可以来调用函数,但是在给函数指针赋值的过程中,依然用到了函数名,因此在C语言中要想调用函数,无论是直接调用还是通过函数指针来调用都无法绕过函数名。

    但是OC中的block,就可以使用不带函数名称的函数,即匿名函数,被看着是C语言的扩充功能。

    在了解什么是匿名函数后,再来说说“带有自动变量(局部变量)”是什么意思?
    变量的类型以及存储位置

    • 全局变量:全局静态区
    • 全局静态变量:全局静态区
    • 局部静态变量:栈
    • 局部变量:栈
      内存分布
      局部变量:包括静态局部变量都是存在栈中的,超过作用域后就会被销毁
      全局变量:包括全局静态变量,既不是存在栈中,也不是存在堆中,而是存放在“全局区/静态区”,占用静态的存储单元

    block做为匿名函数,不但能访问全局变量,更重要的是拥有捕获函数外局部变量的功能,一般情况下,block中访问一个外部局部变量,block会持用它的临时状态,自动捕获变量值,捕获后修改外部的局部变量不会影响block内部捕获的状态
    比较经典的例子:

        int val = 23;
        void (^block) (void) = ^{
            NSLog(@"val = %d",val);
        };
        val = 2;
        block();

    代码输出为:val = 23,不是2
    block在定义实现时,就会对它捕获的外部局部变量进行一次只读拷贝,然后在block中使用该只读拷贝,即外部局部变量的副本,所以block外部修改局部变量,对其内部的副本无影响,就是说block捕获的是局部变量的瞬时值
    那么这种情况下能否在block内对局部变量val值进行修改呢?尝试报错截图如下:
    这里写图片描述
    根据错误提示,我们修改代码如下

        __block int val = 23;
        void (^block) (void) = ^{
            NSLog(@"val = %d",val);
        };
        val = 2;
        block();

    代码输出结果为:val = 2,并且此时可以在block中修改外部的局部变量值


    为什么在局部变量定义前加__block就能在block中修改捕获的局部变量值了,后面再继续分析,本节完,待续……

    参考文章
    又见block(二):block语法定义
    又见block(三):block实质
    又见block(四):block捕获自动变量
    又见block(五): __block变量和对象
    又见block(六):block存储域与__block变量存储域
    又见block(七):截获对象

    展开全文
  • 【小程序】block标签的介绍和使用

    万次阅读 2018-05-26 15:31:23
    block/> 并不是一个组件,它仅仅是一个包装元素,不会在页面中做任何渲染,只接受控制属性。因为 wx:if 是一个控制属性,需要将它添加到一个标签上。如果要一次性判断多个组件标签,可以使用一个 &lt...
    转自:https://blog.csdn.net/qq_36530458/article/details/79778373
    1. <block/> 并不是一个组件,它仅仅是一个包装元素,不会在页面中做任何渲染,只接受控制属性。
    2. 因为 wx:if 是一个控制属性,需要将它添加到一个标签上。如果要一次性判断多个组件标签,可以使用一个 <block/> 标签将多个组件包装起来,并在上边使用 wx:if 控制属性。
            <block wx:if="{{true}}">
                 <view> view1 </view>
                <view> view2 </view>
            </block>
    1. 类似 block wx:if,也可以将 wx:for 用在<block/>标签上,以渲染一个包含多节点的结构块。例如:
        <block wx:for="{{[1, 2, 3]}}">
        <view> {{index}}: </view>
         <view> {{item}} </view>

    </block>


    在安卓中我们经常会使用ListView/GridView/RecyclerView来实现展示循环数据。那么小程序中怎么到呢。其实很简单,使用block就可以了。

    下面我们先看下效果图:

    这个布局其实很简单,大致分为3部分,上+下(左75%,右25%)。这里就不在细说了。那么这里要怎么写wxml呢。下面贴代码:

    这边很清晰的可以看出<block></block>这对标签,而数据源便是wx:for="{{goodlist}}"中的goodlist了。接着往下走,我们可以看到点击标签的时候有bindtap事件,这里就不做说明了。我们重点看下{{item.StartCity}},这是什么意思呢,其实这就是获取数据源中的数据,而item代表的是goodlist中的一条数据,StrrtCity等都是数据源中的一些具体属性。你可以更据需要直接调头你想要的字段名就可以了。block到此基本结束了。最后此处设置了一个view,用来代替当数据源为空时显示无数据页面提示。


    展开全文
  • Block 详解

    2019-08-20 15:41:50
    Block 是 Objective-C 对于闭包的实现。 其中,Block: 可以嵌套定义,定义 Block 方法和定义函数方法相似 Block 可以定义在方法内部或外部 只有调用 Block 时候,才会执行其{}体内的代码 本质是对象,使代码...


    原文链接:www.imlifengfeng.com

    一、概述

    闭包 = 一个函数「或指向函数的指针」+ 该函数执行的外部的上下文变量「也就是自由变量」;Block 是 Objective-C 对于闭包的实现。

    其中,Block:

    • 可以嵌套定义,定义 Block 方法和定义函数方法相似
    • Block 可以定义在方法内部或外部
    • 只有调用 Block 时候,才会执行其{}体内的代码
    • 本质是对象,使代码高聚合

    使用 clang 将 OC 代码转换为 C++ 文件查看 block 的方法:

    • 在命令行输入代码 clang -rewrite-objc 需要编译的OC文件.m
    • 这时查看当前的文件夹里 多了一个相同的名称的 .cpp 文件,在命令行输入 open main.cpp 查看文件

    二、Block的定义与使用

    1、无参数无返回值

    //1,无参数,无返回值,声明和定义
    
    void(^MyBlockOne)(void) = ^(void){
    
    NSLog(@"无参数,无返回值");  
    
    };  
    MyBlockOne();//block的调用
    

    2、有参数无返回值

    //2,有参数,无返回值,声明和定义
    
    void(^MyblockTwo)(int a) = ^(int a){
    
    NSLog(@"@ = %d我就是block,有参数,无返回值",a);
    
      };  
    MyblockTwo(100);
    

    3、有参数有返回值

    //3,有参数,有返回值
    
    int(^MyBlockThree)(int,int) = ^(int a,int b){    
    
      NSLog(@"%d我就是block,有参数,有返回值",a + b);returna + b; 
    
     };  
    MyBlockThree(12,56);
    

    4、无参数有返回值(很少用到)

    //4,无参数,有返回值
    
    int(^MyblockFour)(void) = ^{NSLog(@"无参数,有返回值");
            return45;
      };
    MyblockFour();
    

    5、实际开发中常用typedef 定义Block

    例如,用typedef定义一个block:

    typedef int (^MyBlock)(int , int);
    

    这时,MyBlock就成为了一种Block类型
    在定义类的属性时可以这样:

    @property (nonatomic,copy) MyBlock myBlockOne;
    

    使用时:

    self.myBlockOne = ^int (int ,int){
        //TODO
    }
    

    三、Block与外界变量

    1、截获自动变量(局部变量)值

    (1)默认情况

    对于 block 外的变量引用,block 默认是将其复制到其数据结构中来实现访问的。也就是说block的自动变量截获只针对block内部使用的自动变量, 不使用则不截获, 因为截获的自动变量会存储于block的结构体内部, 会导致block体积变大。特别要注意的是默认情况下block只能访问不能修改局部变量的值。

    int age = 10;
    myBlock block = ^{
        NSLog(@"age = %d", age);
    };
    age = 18;
    block();
    

    输出结果:

    age = 10
    

    (2) __block 修饰的外部变量

    对于用 __block 修饰的外部变量引用,block 是复制其引用地址来实现访问的。block可以修改__block 修饰的外部变量的值。

    __block int age = 10;
    myBlock block = ^{
        NSLog(@"age = %d", age);
    };
    age = 18;
    block();
    

    输出为:

    age = 18
    

    为什么使用__block 修饰的外部变量的值就可以被block修改呢?

    我们使用 clang 将 OC 代码转换为 C++ 文件:

    clang -rewrite-objc 源代码文件名
    

    便可揭开其真正面纱:

    __block int val = 10;
    转换成
    __Block_byref_val_0 val = {
        0,
        &val,
        0,
        sizeof(__Block_byref_val_0),
        10
    };
    

    会发现一个局部变量加上__block修饰符后竟然跟block一样变成了一个__Block_byref_val_0结构体类型的自动变量实例!!!!

    此时我们在block内部访问val变量则需要通过一个叫__forwarding的成员变量来间接访问val变量(下面会对__forwarding进行详解)

    四、Block的copy操作

    1、Block的存储域及copy操作

    在开始研究Block的copy操作之前,先来思考一下:Block是存储在栈上还是堆上呢?

    我们先来看看一个由C/C++/OBJC编译的程序占用内存分布的结构:

    其实,block有三种类型:

    • 全局块(_NSConcreteGlobalBlock)
    • 栈块(_NSConcreteStackBlock)
    • 堆块(_NSConcreteMallocBlock)

    这三种block各自的存储域如下图:

    • 全局块存在于全局内存中, 相当于单例.
    • 栈块存在于栈内存中, 超出其作用域则马上被销毁
    • 堆块存在于堆内存中, 是一个带引用计数的对象, 需要自行管理其内存

    简而言之,存储在栈中的Block就是栈块、存储在堆中的就是堆块、既不在栈中也不在堆中的块就是全局块。

    遇到一个Block,我们怎么这个Block的存储位置呢?

    (1)Block不访问外界变量(包括栈中和堆中的变量)

    Block 既不在栈又不在堆中,在代码段中,ARC和MRC下都是如此。此时为全局块。

    (2)Block访问外界变量

    MRC 环境下:访问外界变量的 Block 默认存储中。
    ARC 环境下:访问外界变量的 Block 默认存储在中(实际是放在栈区,然后ARC情况下自动又拷贝到堆区),自动释放。

    ARC下,访问外界变量的 Block为什么要自动从栈区拷贝到堆区呢?

    栈上的Block,如果其所属的变量作用域结束,该Block就被废弃,如同一般的自动变量。当然,Block中的__block变量也同时被废弃。如下图:

    为了解决栈块在其变量作用域结束之后被废弃(释放)的问题,我们需要把Block复制到堆中,延长其生命周期。开启ARC时,大多数情况下编译器会恰当地进行判断是否有需要将Block从栈复制到堆,如果有,自动生成将Block从栈上复制到堆上的代码。Block的复制操作执行的是copy实例方法。Block只要调用了copy方法,栈块就会变成堆块。

    如下图:

    例如下面一个返回值为Block类型的函数:

    typedef int (^blk_t)(int);
    
    blk_t func(int rate) {
        return ^(int count) { return rate * count; };
    }
    

    分析可知:上面的函数返回的Block是配置在栈上的,所以返回函数调用方时,Block变量作用域就结束了,Block会被废弃。但在ARC有效,这种情况编译器会自动完成复制。

    在非ARC情况下则需要开发者调用copy方法手动复制,由于开发中几乎都是ARC模式,所以手动复制内容不再过多研究。

    将Block从栈上复制到堆上相当消耗CPU,所以当Block设置在栈上也能够使用时,就不要复制了,因为此时的复制只是在浪费CPU资源。

    Block的复制操作执行的是copy实例方法。不同类型的Block使用copy方法的效果如下表:

    根据表得知,Block在堆中copy会造成引用计数增加,这与其他Objective-C对象是一样的。虽然Block在栈中也是以对象的身份存在,但是栈块没有引用计数,因为不需要,我们都知道栈区的内存由编译器自动分配释放。

    不管Block存储域在何处,用copy方法复制都不会引起任何问题。在不确定时调用copy方法即可。

    在ARC有效时,多次调用copy方法完全没有问题:

    blk = [[[[blk copy] copy] copy] copy];
    // 经过多次复制,变量blk仍然持有Block的强引用,该Block不会被废弃。
    

    2、__block变量与__forwarding

    在copy操作之后,既然__block变量也被copy到堆上去了, 那么访问该变量是访问栈上的还是堆上的呢?__forwarding 终于要闪亮登场了,如下图:

    通过__forwarding, 无论是在block中还是 block外访问__block变量, 也不管该变量在栈上或堆上, 都能顺利地访问同一个__block变量。

    五、防止 Block 循环引用

    Block 循环引用的情况:
    某个类将 block 作为自己的属性变量,然后该类在 block 的方法体里面又使用了该类本身,如下:

    self.someBlock = ^(Type var){
        [self dosomething];
    };
    

    解决办法:

    (1)ARC 下:使用 __weak

    __weak typeof(self) weakSelf = self;
    self.someBlock = ^(Type var){
       [weakSelf dosomething];
    };
    

    (2)MRC 下:使用 __block

    __block typeof(self) blockSelf = self;
    self.someBlock = ^(Type var){
       [blockSelf dosomething];
    };
    

    值得注意的是,在ARC下,使用 __block 也有可能带来的循环引用,如下:

    // 循环引用 self -> _attributBlock -> tmp -> self
    typedef void (^Block)();
    @interface TestObj : NSObject
    {
        Block _attributBlock;
    }
    @end
    
    @implementation TestObj
    - (id)init {
        self = [super init];
        __block id tmp = self;
        self.attributBlock = ^{
            NSLog(@"Self = %@",tmp);
            tmp = nil;
       };
    }
    
    - (void)execBlock {
        self.attributBlock();
    }
    @end
    
    // 使用类
    id obj = [[TestObj alloc] init];
    [obj execBlock]; // 如果不调用此方法,tmp 永远不会置 nil,内存泄露会一直在
    

    六、Block的使用示例

    1、Block作为变量(Xcode快捷键:inlineBlock)

    int (^sum) (int, int); // 定义一个 Block 变量 sum
    // 给 Block 变量赋值
    // 一般 返回值省略:sum = ^(int a,int b)…
    sum = ^int (int a,int b){  
        return a+b;
    }; // 赋值语句最后有 分号
    int a = sum(10,20); // 调用 Block 变量
    

    2、Block作为属性(Xcode 快捷键:typedefBlock)

    // 1\. 给  Calculate 类型 sum变量 赋值「下定义」
    typedef int (^Calculate)(int, int); // calculate就是类型名
    Calculate sum = ^(int a,int b){ 
        return a+b;
    };
    int a = sum(10,20); // 调用 sum变量
    
    // 2\. 作为对象的属性声明,copy 后 block 会转移到堆中和对象一起
    @property (nonatomic, copy) Calculate sum;    // 使用   typedef
    @property (nonatomic, copy) int (^sum)(int, int); // 不使用 typedef
    
    // 声明,类外
    self.sum = ^(int a,int b){
        return a+b;
    };
    // 调用,类内
    int a = self.sum(10,20);
    

    3、作为 OC 中的方法参数

    // ---- 无参数传递的 Block ---------------------------
    // 实现
    - (CGFloat)testTimeConsume:(void(^)())middleBlock {
        // 执行前记录下当前的时间
        CFTimeInterval startTime = CACurrentMediaTime();
        middleBlock();
        // 执行后记录下当前的时间
        CFTimeInterval endTime = CACurrentMediaTime();
        return endTime - startTime;
    
    }
    
    // 调用
    [self testTimeConsume:^{
           // 放入 block 中的代码 
    
    }];
    
    // ---- 有参数传递的 Block ---------------------------
    // 实现
    - (CGFloat)testTimeConsume:(void(^)(NSString * name))middleBlock {
        // 执行前记录下当前的时间
        CFTimeInterval startTime = CACurrentMediaTime();
        NSString *name = @"有参数";
        middleBlock(name);
        // 执行后记录下当前的时间
        CFTimeInterval endTime = CACurrentMediaTime();
        return endTime - startTime;
    }
    
    // 调用
    [self testTimeConsume:^(NSString *name) {
       // 放入 block 中的代码,可以使用参数 name
       // 参数 name 是实现代码中传入的,在调用时只能使用,不能传值    
    
    }];
    

    4、Block回调

    Block回调是关于Block最常用的内容,比如网络下载,我们可以用Block实现下载成功与失败的反馈。开发者在block没发布前,实现回调基本都是通过代理的方式进行的,比如负责网络请求的原生类NSURLConnection类,通过多个协议方法实现请求中的事件处理。而在最新的环境下,使用的NSURLSession已经采用block的方式处理任务请求了。各种第三方网络请求框架也都在使用block进行回调处理。这种转变很大一部分原因在于block使用简单,逻辑清晰,灵活等原因。

    如下:

    //DownloadManager.h
    #import <Foundation/Foundation.h>
    
    @interface DownloadManager : NSObject <NSURLSessionDownloadDelegate>
    
    // block 重命名
    typedef void (^DownloadHandler)(NSData * receiveData, NSError * error);
    
    - (void)downloadWithURL:(NSString *)URL parameters:(NSDictionary *)parameters handler:(DownloadHandler)handler ;
    
    @end
    
    //DownloadManager.m
    #import "DownloadManager.h"
    
    @implementation DownloadManager
    
    - (void)downloadWithURL:(NSString *)URL parameters:(NSDictionary *)parameters handler:(DownloadHandler)handler
    {
        NSURLRequest * request = [NSURLRequest requestWithURL:[NSURL URLWithString:URL]];
        NSURLSession * session = [NSURLSession sharedSession];
    
        //执行请求任务
        NSURLSessionDataTask * task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
            if (handler) {
                dispatch_async(dispatch_get_main_queue(), ^{
                    handler(data,error);
                });
            }
        }];
        [task resume];
    
    }
    

    上面通过封装NSURLSession的请求,传入一个处理请求结果的block对象,就会自动将请求任务放到工作线程中执行实现,我们在网络请求逻辑的代码中调用如下:

    - (IBAction)buttonClicked:(id)sender {
        #define DOWNLOADURL @"https://codeload.github.com/AFNetworking/AFNetworking/zip/master"
        //下载类
        DownloadManager * downloadManager = [[DownloadManager alloc] init];
        [downloadManager downloadWithURL: DOWNLOADURL parameters:nil handler:^(NSData *receiveData, NSError *error) {
            if (error) {
                NSLog(@"下载失败:%@",error);
            }else {
                NSLog(@"下载成功,%@",receiveData);
            }
        }];
    }
    

    为了加深理解,再来一个简单的小例子:

    A,B两个界面,A界面中有一个label,一个buttonA。点击buttonA进入B界面,B界面中有一个UITextfield和一个buttonB,点击buttonB退出B界面并将B界面中UITextfield的值传到A界面中的label。

    A界面中,也就是ViewController类中:

    //关键demo:
    - (IBAction)buttonAction {  
        MyFirstViewController *myVC = [[MyFirstViewController alloc] init];
        [self presentViewController:myVC animated:YES completion:^{    
        }];
        __weak typeof(self) weakSelf = self;//防止循环引用
    //用属性定义的注意:这里属性是不会自动补全的,方法就会自动补全
        [myVC setBlock:^(NSString *string){
            weakSelf.labelA.text = string;
        }];
    }
    

    B界面中,也就是MyFirstViewController类中.m文件:

    - (IBAction)buttonBAction {
        [self dismissViewControllerAnimated:YES completion:^{
        }];
          self.block(_myTextfielf.text);
    }
    

    .h文件:

    #import <UIKit/UIKit.h>
    
    //typedef定义一下block,为了更好用
    typedef void(^MyBlock)(NSString *string);
    
    @interface MyFirstViewController : UIViewController
    
    @property (nonatomic, copy) MyBlock block;
    
    @end
    

    看了以上两个Block回调示例,是不是感觉比delegate清爽了不少?


    展开全文
  • 本系列博文总结自《Pro Multithreading and Memory Management for iOS and OS X with ...在上一篇文章中,我们讲了很多关于 block 和基础变量的内存管理,接着我们聊聊 block 和对象的内存管理,如 block 经常会碰到的

    本系列博文总结自《Pro Multithreading and Memory Management for iOS and OS X with ARC》

    如果您觉得我的博客对您有帮助,请通过关注我的新浪微博  MicroCai 支持我,谢谢!


    在上一篇文章中,我们讲了很多关于 block 和基础变量的内存管理,接着我们聊聊 block 和对象的内存管理,如 block 经常会碰到的循环引用问题等等。


    获取对象

    照例先来段代码轻松下,瞧瞧 block 是怎么获取外部对象的

    1. /********************** capturing objects **********************/
    2. typedef void (^blk_t)(id obj);
    3. blk_t blk;
    4. - (void)viewDidLoad
    5. {
    6. [self captureObject];
    7. blk([[NSObject alloc] init]);
    8. blk([[NSObject alloc] init]);
    9. blk([[NSObject alloc] init]);
    10. }
    11. - (void)captureObject
    12. {
    13. id array = [[NSMutableArray alloc] init];
    14. blk = [^(id obj) {
    15. [array addObject:obj];
    16. NSLog(@"array count = %ld", [array count]);
    17. } copy];
    18. }

    翻译后的关键代码摘录如下

    1. /* a struct for the Block and some functions */
    2. struct __main_block_impl_0
    3. {
    4. struct __block_impl impl;
    5. struct __main_block_desc_0 *Desc;
    6. id __strong array;
    7. __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, id __strong _array, int flags=0) : array(_array)
    8. {
    9. impl.isa = &_NSConcreteStackBlock;
    10. impl.Flags = flags;
    11. impl.FuncPtr = fp;
    12. Desc = desc;
    13. }
    14. };
    15. static void __main_block_func_0(struct __main_block_impl_0 *__cself, id obj)
    16. {
    17. id __strong array = __cself->array;
    18. [array addObject:obj];
    19. NSLog(@"array count = %ld", [array count]);
    20. }
    21. static void __main_block_copy_0(struct __main_block_impl_0 *dst, __main_block_impl_0 *src)
    22. {
    23. _Block_object_assign(&dst->array, src->array, BLOCK_FIELD_IS_OBJECT);
    24. }
    25. static void __main_block_dispose_0(struct __main_block_impl_0 *src)
    26. {
    27. _Block_object_dispose(src->array, BLOCK_FIELD_IS_OBJECT);
    28. }
    29. struct static struct __main_block_desc_0
    30. {
    31. unsigned long reserved;
    32. unsigned long Block_size;
    33. void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
    34. void (*dispose)(struct __main_block_impl_0*);
    35. } __main_block_desc_0_DATA = { 0,
    36. sizeof(struct __main_block_impl_0),
    37. __main_block_copy_0,
    38. __main_block_dispose_0
    39. };
    40. /* Block literal and executing the Block */
    41. blk_t blk;
    42. {
    43. id __strong array = [[NSMutableArray alloc] init];
    44. blk = &__main_block_impl_0(__main_block_func_0,
    45. &__main_block_desc_0_DATA,
    46. array,
    47. 0x22000000);
    48. blk = [blk copy];
    49. }
    50. (*blk->impl.FuncPtr)(blk, [[NSObject alloc] init]);
    51. (*blk->impl.FuncPtr)(blk, [[NSObject alloc] init]);
    52. (*blk->impl.FuncPtr)(blk, [[NSObject alloc] init]);

    在本例中,当变量变量作用域结束时,array 被废弃,强引用失效,NSMutableArray 类的实例对象会被释放并废弃。在这危难关头,block 及时调用了 copy 方法,在 _Block_object_assign 中,将 array 赋值给 block 成员变量并持有。所以上面代码可以正常运行,打印出来的 array count 依次递增。

    总结代码可正常运行的原因关键就在于 block 通过调用 copy 方法,持有了 __strong 修饰的外部变量,使得外部对象在超出其作用域后得以继续存活,代码正常执行。

    在以下情形中, block 会从栈拷贝到堆:

    • 当 block 调用 copy 方法时,如果 block 在栈上,会被拷贝到堆上;
    • 当 block 作为函数返回值返回时,编译器自动将 block 作为 _Block_copy 函数,效果等同于 block 直接调用 copy 方法;
    • 当 block 被赋值给 __strong id 类型的对象或 block 的成员变量时,编译器自动将 block 作为 _Block_copy函数,效果等同于 block 直接调用 copy 方法;
    • 当 block 作为参数被传入方法名带有 usingBlock 的 Cocoa Framework 方法或 GCD 的 API 时。这些方法会在内部对传递进来的 block 调用 copy 或 _Block_copy 进行拷贝;

    其实后三种情况在上篇文章block的自动拷贝已经做过说明

    除此之外,都需要手动调用。

    延伸阅读:Objective-C 结构体中的 __strong 成员变量

    注意到 __main_block_impl_0 结构体有什么异常没?在 C 结构体中出现了 __strong 关键字修饰的变量。

    通常情况下, Objective-C 的编译器因为无法检测 C 结构体初始化和释放的时间,不能进行有效的内存管理,所以 Objective-C 的 C 结构体成员是不能用 __strong__weak 等等这类关键字修饰。然而 runtime 库是可以在运行时检测到 block 的内存变化,如 block 何时从栈拷贝到堆,何时从堆上释放等等,所以就会出现上述结构体成员变量用 __strong 修饰的情况。


    __block 变量和对象

    __block 说明符可以修饰任何类型的自动变量。下面让我们再看个小例子,啊,愉快的代码时间又到啦。

    1. /******* block 修饰对象 *******/
    2. __block id obj = [[NSObject alloc] init];

    ARC 下,对象所有权修饰符默认为 __strong,即

    1. __block id __strong obj = [[NSObject alloc] init];
    1. /******* block 修饰对象转换后的代码 *******/
    2. /* struct for __block variable */
    3. struct __Block_byref_obj_0
    4. {
    5. void *__isa;
    6. __Block_byref_obj_0 *__forwarding;
    7. int __flags;
    8. int __size;
    9. void (*__Block_byref_id_object_copy)(void*, void*);
    10. void (*__Block_byref_id_object_dispose)(void*);
    11. __strong id obj;
    12. };
    13. static void __Block_byref_id_object_copy_131(void *dst, void *src)
    14. {
    15. _Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
    16. }
    17. static void __Block_byref_id_object_dispose_131(void *src)
    18. {
    19. _Block_object_dispose(*(void * *) ((char*)src + 40), 131);
    20. }
    21. /* __block variable declaration */
    22. __Block_byref_obj_0 obj = { 0,
    23. &obj,
    24. 0x2000000,
    25. sizeof(__Block_byref_obj_0),
    26. __Block_byref_id_object_copy_131,
    27. __Block_byref_id_object_dispose_131,
    28. [[NSObject alloc] init]
    29. };

    __block id __strong obj 的作用和 id __strong obj 的作用十分类似。当 __block id __strong obj 从栈上拷贝到堆上时,_Block_object_assign 被调用,block 持有 obj;当 __block id __strong obj 从堆上被废弃时,_Block_object_dispose 被调用用以释放此对象,block 引用消失。

    所以,只要是堆上的 __strong 修饰符修饰的 __block 对象类型的变量,和 block 内获取到的 __strong 修饰符修饰的对象类型的变量,编译器都能对它们的内存进行适当的管理。

    如果上面的 __strong 换成 __weak,结果会怎样呢?

    1. /********************** capturing __weak objects **********************/
    2. typedef void (^blk_t)(id obj);
    3. blk_t blk;
    4. - (void)viewDidLoad
    5. {
    6. [self captureObject];
    7. blk([[NSObject alloc] init]);
    8. blk([[NSObject alloc] init]);
    9. blk([[NSObject alloc] init]);
    10. }
    11. - (void)captureObject
    12. {
    13. id array = [[NSMutableArray alloc] init];
    14. id __weak array2 = array;
    15. blk = [^(id obj) {
    16. [array2 addObject:obj];
    17. NSLog(@"array2 count = %ld", [array2 count]);
    18. } copy];
    19. }

    结果是:

    1. array2 count = 0
    2. array2 count = 0
    3. array2 count = 0

    原因很简单,array2 是弱引用,当变量作用域结束,array 所指向的对象内存被释放,array2 指向 nil,向 nil 对象发送 count 消息就返回结果 0 了。

    如果 __weak 再改成 __unsafe_unretained 呢?__unsafe_unretained 修饰的对象变量指针就相当于一个普通指针。使用这个修饰符有点需要注意的地方是,当指针所指向的对象内存被释放时,指针变量不会被置为 nil。所以当使用这个修饰符时,一定要注意不要通过悬挂指针(指向被废弃内存的指针)来访问已经被废弃的对象内存,否则程序就会崩溃。

    如果 __unsafe_unretained 再改成 __autoreleasing 会怎样呢?会报错,编译器并不允许你这么干!如果你这么写

    1. __block id __autoreleasing obj = [[NSObject alloc] init];

    编译器就会报下面的错误,意思就是 __block 和 __autoreleasing 不能同时使用。

    error: __block variables cannot have __autoreleasing ownership __block id __autoreleasing obj = [[NSObject alloc] init];

    循环引用

    千辛万苦,重头戏终于来了。block 如果使用不小心,就容易出现循环引用,导致内存泄露。到底哪里泄露了呢?通过前面的学习,各位童鞋应该有个底了,下面就让我们一起进入这泄露地区瞧瞧,哪儿出了问题!

    愉快的代码时间到

    1. // ARC enabled
    2. /************** MyObject Class **************/
    3. typedef void (^blk_t)(void);
    4. @interface MyObject : NSObject
    5. {
    6. blk_t blk_;
    7. }
    8. @end
    9. @implementation MyObject
    10. - (id)init
    11. {
    12. self = [super init];
    13. blk_ = ^{NSLog(@"self = %@", self);};
    14. return self;
    15. }
    16. - (void)dealloc
    17. {
    18. NSLog(@"dealloc");
    19. }
    20. @end
    21. /************** main function **************/
    22. int main()
    23. {
    24. id myObject = [[MyObject alloc] init];
    25. NSLog(@"%@", myObject);
    26. return 0;
    27. }

    由于 self 是 __strong 修饰,在 ARC 下,当编译器自动将代码中的 block 从栈拷贝到堆时,block 会强引用和持有 self,而 self 恰好也强引用和持有了 block,就造成了传说中的循环引用。

    由于循环引用的存在,造成在 main() 函数结束时,内存仍然无法释放,即内存泄露。编译器也会给出警告信息

    warning: capturing 'self' strongly in this block is likely to lead to a retain cycle [-Warc-retain-cycles] 
    blk_ = ^{NSLog(@"self = %@", self);}; 

    note: Block will be retained by an object strongly retained by the captured object 
    blk_ = ^{NSLog(@"self = %@", self);};

    为了避免这种情况发生,可以在变量声明时用 __weak 修饰符修饰变量 self,让 block 不强引用 self,从而破除循环。iOS4 和 Snow Leopard 由于对 weak 的支持不够完全,可以用 __unsafe_unretained 代替。

    1. - (id)init
    2. {
    3. self = [super init];
    4. id __weak tmp = self;
    5. blk_ = ^{NSLog(@"self = %@", tmp);};
    6. return self;
    7. }

    再看一个例子

    1. @interface MyObject : NSObject
    2. {
    3. blk_t blk_;
    4. id obj_;
    5. }
    6. @end
    7. @implementation MyObject
    8. - (id)init
    9. {
    10. self = [super init];
    11. blk_ = ^{ NSLog(@"obj_ = %@", obj_); };
    12. return self;
    13. }
    14. ...
    15. ...
    16. @end

    上面的例子中,虽然没有直接使用 self,却也存在循环引用的问题。因为对于编译器来说,obj_ 就相当于 self->obj_,所以上面的代码就会变成

    1. blk_ = ^{ NSLog(@"obj_ = %@", self->obj_); };

    所以这个例子只要用 __weak,在 init 方法里面加一行即可

    1. id __weak obj = obj_;

    破解循环引用还有一招,使用 __block 修饰对象,在 block 内将对象置为 nil 即可,如下

    1. typedef void (^blk_t)(void);
    2. @interface MyObject : NSObject
    3. {
    4. blk_t blk_;
    5. }
    6. @end
    7. @implementation MyObject
    8. - (id)init
    9. {
    10. self = [super init];
    11. __block id tmp = self;
    12. blk_ = ^{
    13. NSLog(@"self = %@", tmp);
    14. tmp = nil;
    15. };
    16. return self;
    17. }
    18. - (void)execBlock
    19. {
    20. blk_();
    21. }
    22. - (void)dealloc
    23. {
    24. NSLog(@"dealloc");
    25. }
    26. @end
    27. int main()
    28. {
    29. id object = [[MyObject alloc] init];
    30. [object execBlock];
    31. return 0;
    32. }

    这个例子挺有意思的,如果执行 execBlock 方法,就没有循环引用,如果不执行就有循环引用,挺值得玩味的。一方面,使用 __block 挺危险的,万一代码中不执行 block ,就造成了循环引用,而且编译器还没法检查出来;另一方面,使用 __block 可以让我们通过 __block 变量去控制对象的生命周期,而且有可能在一些非常老旧的 MRC 代码中,由于不支持 __weak,我们可以使用此方法来代替 __unsafe_unretained,从而避免悬挂指针的问题。

    还有个值得一提的时,在 MRC 下,使用 __block 说明符也可以避免循环引用。因为当 block 从栈拷贝到堆时,__block 对象类型的变量不会被 retain,没有 __block 说明符的对象类型的变量则会被 retian。正是由于 __block 在 ARC 和 MRC 下的巨大差异,我们在写代码时一定要区分清楚到底是 ARC 还是 MRC。

    尽管 ARC 已经如此普及,我们可能已经可以不用去管 MRC 的东西,但要有点一定要明白,ARC 和 MRC 都是基于引用计数的内存管理,其本质上是一个东西,只不过 ARC 在编译期自动化的做了内存引用计数的管理,使得系统可以在适当的时候保留内存,适当的时候释放内存。

    循环引用到此为止,东西并不多。如果明白了之前的知识点,就会了解循环引用不过是前面知识点的自然延伸点罢了。

    Copy 和 Release

    在 ARC 下,有时需要手动拷贝和释放 block。在 MRC 下更是如此,可以直接用 copy 和 release 来拷贝和释放

    1. void (^blk_on_heap)(void) = [blk_on_stack copy];
    2. [blk_on_heap release];

    拷贝到堆后,就可以 用 retain 持有 block

    1. [blk_on_heap retain];

    然而如果 block 在栈上,使用 retain 是毫无效果的,因此推荐使用 copy 方法来持有 block。

    block 是 C 语言的扩展,所以可以在 C 中使用 block 的语法。比如,在上面的例子中,可以直接使用 Block_copy 和 Block_release 函数来代替 copy 和 release 方法

    1. void (^blk_on_heap)(void) = Block_copy(blk_on_stack);
    2. Block_release(blk_on_heap);

    Block_copy 的作用相当于之前看到过的 _Block_copy 函数,而且 Objective-C runtime 库在运行时拷贝 block 用的就是这个函数。同理,释放 block 时,runtime 调用了 Block_release 函数。

    最后这里有一篇总结 block 的文章的很不错,推荐大家看看:http://tanqisen.github.io/blog/2013/04/19/gcd-block-cycle-retain/



    本文转自:https://www.zybuluo.com/MicroCai/note/58470

    展开全文
  • Block 底层实现原理

    2020-04-10 18:54:05
    Block 底层实现原理 在理解 Block 的底层实现原理的过程中,可以通过 clang 命令将一个 block 例子转换为 C 代码,从而解读其具体的代码实现逻辑。 clang main.m -rewrite-objc -o dest.cpp 上面的命令,是将 main.m...
  • ios __block修饰词底层实现原理

    千次阅读 2018-10-23 14:25:28
    注:此文章为自己学习笔记,部分来自欧阳大哥博客https://www.jianshu.com/p/595a1776ba3a 让我们看下代码: //文件test.m #import &lt;Foundation/Foundation.h&gt; void test() ... ...
  • C++ Block(代码块)

    千次阅读 2016-07-27 16:40:09
    Block是什么?Block是C语言的一个语法特性,同时也是C语言的运行时特性,它很像C中的函数指针,因为你可以像使用函数指针一样的去使用block对象;它也很像C++中的函数对象,因为除了要执行的代码,block还可以携带和...
  • Table of Contents ...HDFS中存储数据是以块(block,这只是一个逻辑概念)的形式存储在DataNode,block大小可通过设置HADOOP_HOME/etc/hadoop/hdfs-site.xml中dfs.blocksize实现(设置时先stop集群,修改.
  • Simple File System

    2019-01-04 13:12:28
    This is my operating system class design. 1.一段代码及其bug. class Program { static void Main(string[] args) { string file = "weidiao"; if (File.Exis...
  • 理解Linux文件系统之 inode

    千次阅读 2018-06-26 22:47:52
    操作系统读取硬盘的时候,不会一个个扇区地读取,这样效率太低,而是一次性连续读取多个扇区,即一次性读取一个”块”(block)。这种由多个扇区组成的”块”,是文件存取的最小单位。”块”的大小,最常见的是4KB,...
  • expected an indented block

    万次阅读 2018-01-09 10:26:16
    导致excepted an indented block这个错误的原因一般有两个: 1. 冒号后面是要写上一定的内容的(新手容易遗忘这一点),例如 try: something else: #else后面的内容由于不是很重要而忘了写 2. 缩进不...
  • Python中expected an indented block

    万次阅读 2016-12-26 14:05:18
    Python严格地要求代码缩进,缩进的代码块相对于上一级是从属关系。用if语句举个例子: if a==2: print 'hello world'if语句的条件为真时会执行print语句,print语句是if语句的... block。与if语句类似的,必须包含从属
  • Given final block not properly padded

    万次阅读 2013-11-06 10:38:36
    获取Cipher对象的时候一定要写成 Cipher cipher = Cipher.getInstance("DES/ECB/NoPadding"); 不要写成 ...Cipher cipher = Cipher.getInstance("DES");...Given final block not properly padded 原因
  • 解决方法:javax.crypto.BadPaddingException: Given final block not properly padded 解决方法
  • WPF控件TextBlock中文字自动换行

    万次阅读 2014-09-16 15:54:57
    如题,在很多的WPF项目中,往往
  • python报错IndentationError: expected an indented block,解决办法很简单,是脚本缩进的问题,检查脚本缩进是否有空格和tab混用的情况或者是缩进明显不对的情况。
  • HDFS中Block size的默认大小

    万次阅读 2018-06-06 11:13:07
    关于block size的默认大小,有的说是64 MB,有的说是128 MB。那么具体是从哪个版本由64 MB变成128 MB的?有的说是Hadoop 1.X版本是64MB,2.X版本是128MB,有的说Apache Hadoop 是64MB,Clouder Hadoop是128MB。我闲...
  • iOS中block实现的探究

    万次阅读 多人点赞 2012-08-17 00:00:07
    Brief introduction of block] Block是iOS4.0+ 和Mac OS X 10.6+ 引进的对C语言的扩展,用来实现匿名函数的特性。 用维基百科的话来说,Block是Apple Inc.为C、C++以及Objective-C添加的特性,使得这些语言可以用...
  • invalid stored block lengths

    万次阅读 2013-01-08 10:30:29
    invalid stored block lengths invalid stored block lengths此报错信息一般出现在oracle database 安装或者upgrade patch时 造成此错误的原因有以下几种 1.下载的安装包有问题 2.解压过程中出现问题 3....
  • WPF TextBlock的使用

    万次阅读 2011-05-23 12:07:00
    比如: <TextBlock Name="txtBlockOutpuMessage" Text="hello" /> TextBlock默认是不自动换行的,如果想TextBlock换行,可以设定属性TextWrapping="Wrap"。   TextBlock其实可以添加很多子...
1 2 3 4 5 ... 20
收藏数 1,219,812
精华内容 487,924
关键字:

block