白话block

作者: dullgrass | 来源:发表于2015-11-05 10:14 被阅读1060次

    本文内容

    • 什么是block?
    • block的用途
    • block的用法
    • block在使用中遇到的问题
    • 如何使用xcode检测循环引用引发的内存问题?

    什么是block?

    什么是闭包(closure)?
    闭包是可以包含自由(未绑定到特定对象)变量的代码块。
    什么是函数式编程?
    函数可以随时创建、作为参数传递、作为返回值返回。
    在函数式编程中,把函数当参数来回传递,而这个,说成术语,我们把他叫做高阶函数。
    Objective-C在没有block之前,没有类似的机制,有了block,Objective-C也就具备了函数式编程的能力,block是对象,有自己的ISA指针,可以随时创建,作为参数传递,作为返回值返回
    block是带有局部变量的匿名函数(即没有名称的函数),就是OC中的闭包(closure),又名匿名函数,块函数,块

    block的用途

    block都是一些简短代码片段的封装,适用作工作单元,通常用来做并发任务、遍历、以及回调。

    block的用法

    1. block 语法,用插入符号"^"定义一个block字面量,无参,无返回值的block 如下

    ^{
    NSLog(@"This is A Block");
    }();

    控制台输出:

    2015-11-03 12:04:26.266 Whisper[63849:545630] This is a block

    1. block的类型(可以用isa 指针指向的地址查看)
    • NSConcreteGlobalBlock 全局的静态 block,不会访问任何外部变量。
    • NSConcreteStackBlock 保存在栈中的 block,当函数返回时会被销毁。
    • NSConcreteMallocBlock 保存在堆中的 block,当引用计数为 0 时会被销毁。
    • 在 ARC 开启的情况下,将只会有 NSConcreteGlobalBlock 和 NSConcreteMallocBlock 类型的 block
    1. block Pointer,主要用来简化block的写法,block Pointer定义如下:

    回传值 (^名字)(参数列表);

    block Pointer 具体使用

    int (^myBlock) (int a); // 声明一个名字为myBlock的block 指针,该指针指向的Block有一个int输入和一个int 输出
    myBlock = ^(int a){ return a*a;}; //将Block的实体指定给myBlock指针
    int result = myBlock(4); //调用blcok实体

    1. 使用typedef给复杂变量block定义类型别名,定义规则:
      在传统的变量声明表达式里用类型名替代变量名,然后把关键字typedef加在该语句的开头,示例如下:

    typedef int (^myBlock) (int a); //使用定义的新类型myBlock来声明对象,等价于int (^myBlock) (int a);

    一般借助block的类型别名,为特定的类添加block属性变量,做传值或者事件使用。声明block属性变量的时候,property中需设置成copy

    1. 用block来存取变量
      一个Block的内部是可以引用自身作用域外的变量的,包括static变量,extern变量或自由变量,对于自由变量,在Block中是只读的。在引入block的同时,还引入了一种特殊的__block关键字,变量存储修饰符,将变量的存储范围扩展为该函数以及该函数内定义的block的行为主体内(苹果官方文档)。
      存取静态变量

    static int outA = 8;

    int (^myPtr)(int) = ^(int a){return outA + a;};  
    outA = 5;  
    int result = myPtr(3);        //result的值是8,因为outA是static类型的变量
    

    存取自由变量

    {
    __block int num = 5;
    int (^myPtr)(int) = ^(int a){return num++;};
    int (^myPtr2)(int) = ^(int a){return num++;};
    int result = myPtr(0); //result的值为5,num的值为6
    result = myPtr2(0); //result的值为6,num的值为7
    NSLog(@"result=%d", result);
    }

    1. block作为类的属性

    typedef int (^MyBlock) (int a);
    @property(nonatomic,copy) int (^block)(int a); //使用c的方式, 不能使用OC函数形参的写法.
    @property (nonatomic,copy) MyBlock blockName;

    1. block作为函数参数,苹果官方文档建议一个方法最好只有一个block参数,并且block参数一般作为最后一个参数

    - (void)beginTaskWithCallbackBlock:(void (^)(void))callbackBlock {
    ...
    callbackBlock();
    }
    - (void)beginFetchWithCallbackBlock:(MyBlock)callbackBlock {
    ...
    callbackBlock(3);
    }

    1. 以block作为函数返回值

    -(int (^)(int))blockBack{
    return ^(int cout){ return cout;};
    }

    block在使用中遇到的问题

    1. 修改局部变量需要在局部变量前面加__block修饰符,将变量的存储范围扩展为该函数以及该函数内定义的block的行为主体内。
    2. 在属性定义一个block的时候需要使用copy,因为块是在栈上分配的,一旦离开作用域, 就会释放, 因此如果你要把块用在别的地方, 必须要复制一份
    3. 在ARC下, 以下几种情况, Block会自动被从栈复制到堆
    • 被执行copy方法
    • 作为方法返回值
    • 将Block赋值给附有__strong修饰符的id类型的类或者Blcok类型成员变量时
    • 在方法名中含有usingBlock的Cocoa框架方法或者GCD的API中传递的时候.
    1. 循环引用的问题
    • A和B两个对象,A持有B,B同时也持有A,A只有B释放之后才有可能释放,同样B只有A释放后才可能释放,当双方都在等待对方释放的时候, retain cycle就形成了,结果是,两个对象都永远不会被释放,最终内存泄露。
    • 循环引用(retain cycle)的解决
    • 尽量保持子对象引用父对象的时候使用弱引用,也就是assign,比如

    @property (nonatomic,assign) NSObject *parent;

    • 及时地将造成retain cycle中的一个变量设置为nil,将环break掉
    • block中的retain cycle

    @interface XYZBlockKeeper : NSObject
    @property (copy) void (^block)(void);
    @implementation XYZBlockKeeper
    - (void)configureBlock {
    self.block = ^{
    [self doSomething]; // capturing a strong reference to self
    // creates a reference cycle
    };
    }
    ...
    @end

    • block中retain cycle 的解决

      • 方法一 将引用的一方变成weak,从而避免循环引用

    - (void)configureBlock {
    XYZBlockKeeper * __weak weakSelf = self;
    self.block = ^{
    [weakSelf doSomething]; // capture the weak reference
    // to avoid the reference cycle
    }
    }
    或者
    - (void)configureBlock {
    __weak typeof(self) weakSelf = self;
    self.block = ^{
    //如果想防止 weakSelf 被释放,可以再次强引用
    typeof(weakSelf) strongSelf = weakSelf;

      [weakSelf doSomething];   // capture the weak reference cycle
    

    }
    }

    • 方法二.使用完某对象没有必要在保留该对象的时候,在block里面将对象释放即可打破保留环

    - (void)downloadData {
    NSURL *url = [NSURL URLWithString:@"http://www.baidu.com"];
    _networkFetcher = [[EOCNetworkFetcher alloc] initWithURL:url];
    [_networkFetcher startWithCompletionHandler:^(NSData *data) {
    _fetchedData = data;
    _networkFetcher = nil; //加上此行,此处是为了打破循环引用
    }];
    }

    • 方法三. 在调用完block之后,将该block设置为nil(block为某类的属性的时候,这么使用)

    - (void)p_requestCompleted {
    if(_completionHandler) {
    _completionHandler(_downloadData);
    }
    self.completionHandler = nil;//加上此行,此处是为了打破循环引用
    }

    如何使用xcode检测循环引用(引用自《iOS开发进阶》)?

    1. Xcode 的Instruments工具集可以很方便地检测循环引用,但是检测不出block产生的循环引用,示例如下

    - (void)viewDidLoad {
    [super viewDidLoad];
    //firstArray 持有secondArray, secondArray 持有 firstArray,形成retain cycle
    NSMutableArray *firstArray = [NSMutableArray array];
    NSMutableArray *secondArray = [NSMutableArray array];
    [firstArray addObject:secondArray];
    [secondArray addObject:firstArray];
    }

    1. 在Xcode 的菜单栏,选择“Product”--->“Profile”,在调出的界面中选择"Leaks"--->"choose",调出Instruments界面。
      Instruments会用红色的X表示一次内存泄露的产生,Instruments中可以通过切换到“Leaks”,单击“Cycles&Roots”,就可以看到以图形方式显示出来的循环引用,这样,我们就可以很方便的看到产生循环引用的对象了。
    • 具体使用步骤如下:


      Instruments

    相关文章

      网友评论

        本文标题:白话block

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