代码规范

对象间的通讯

对象之间需要通信,这也是所有软件的基础。再非凡的软件也需要通过对象通信来完成复杂的目标。本章将深入讨论一些设计概念,以及如何依据这些概念来设计出良好的架构。

block

Block 是 Objective-C 版本的 lambda 或者 closure(闭包)。

使用 block 定义异步接口:

- (void)downloadObjectsAtPath:(NSString *)path completion:(void(^)(NSArray *objects, NSError *error))completion;

当你定义一个类似上面的接口的时候,尽量使用一个单独的 block 作为接口的最后一个参数。把需要提供的数据和错误信息整合到一个单独 block 中,比分别提供成功和失败的 block 要好。

以下是这样做的原因:

  • 通常这成功处理和失败处理会共享一些代码(比如让一个进度条或者提示消失)—》相同
  • Apple也是这样做的,与平台一致能够带来一些潜在的好处 —》苹果
  • block通常会有很多行代码,如果不作为最后一个参数放在后面的话,会打破调试点 —》调试
  • 使用多个block作为参数可能会让调用看起来显得很笨拙,并且增加了复杂度 —》复杂

    看上面的方法,完成处理的 block 的参数很常见:第一个参数是调用者希望获取的数据,第二个是错误相关的信息。这里需要遵循以下两点:

  • 若 objects 不为 nil,则 error 必须为 nil

  • 若 objects 为 nil,则 error 必须不为 nil

    此外,Apple 提供的一些同步接口在成功状态下向 error 参数(如果非 NULL) 写入了垃圾值,所以检查 error 的值可能出现问题。

深入 Block

一些关键点

  • block是在栈上创建的
  • block可以复制到堆上
  • block会捕获栈上的变量或指针(捕获的只是这些变量或指针的瞬时值),将其复制为自己私有的const变量
  • 如果在block中修改block块外的栈上的变量或指针,那么这些变量或指针必须用__block关键字修饰申明

如果block没有在其他地方被保持,那么他会随着栈生存并且当栈帧返回的时候消失。仅存在于栈上时。block对对象访问的内存管理和声明周期没有任何影响。

如果block需要在栈帧返回的时候存在,他们需要明确的被复制到堆上,这样,block会像其他Cocoa对象一样增加引用计数,当它们被复制的时候,他会带着他们的捕获作用域一起,retain它们所有引用的对象。

如果一个block引用了一个栈变量或指针,那么这个block初始化的时候会拥有这个变量的const副本,所以被捕获之后再在栈中改变这个变量或指针的值是不起作用的。

当一个block被复制后,__block声明的栈变量的引用被复制到了堆里,复制完成后,无论是栈上的block还是刚刚产生在堆上的block都会应用该变量在堆上的副本。

self 的循环引用

当使用代码块和异步分发的时候,要注意避免引用循环。 总是使用 weak 来引用对象,避免引用循环。(译者注:这里更为优雅的方式是采用影子变量@weakify/@strongify这里有更为详细的说明) 此外,把持有 block 的属性设置为 nil (比如 self.completionBlock = nil) 是一个好的实践。它会打破 block 捕获的作用域带来的引用循环。

例子

//单个语句
__weak __typeof(self) weakSelf = self;
[self executeBlock:^(NSData *data, NSError *error) {
    [weakSelf doSomethingWithData:data];
}];
//多个语句
__weak __typeof(self) weakSelf = self;
[self executeBlock:^(NSData *data, NSError *error) {
    __strong __typeof(weakSelf) strongSelf = weakSelf;
    if (strongSelf) {
        [strongSelf doSomethingWithData:data];
        [strongSelf doSomethingWithData:data];
    }
}];

这里我们来讨论下 block 里面的 self 的 __weak 和 __strong 限定词的一些微妙的地方。简而言之,我们可以参考 self 在 block 里面的三种不同情况。

  1. 直接在 block 里面使用关键词 self
  2. 在 block 外定义一个 __weak 的 引用到 self,并且在 block 里面使用这个弱引用
  3. 在 block 外定义一个 __weak 的 引用到 self,并在在 block 内部通过这个弱引用定义一个 __strong 的引用。
方案 1. 直接在 block 里面使用关键词 self

如果我们直接在 block 里面用 self 关键字,对象会在 block 的定义时候被 retain,(实际上 block 是 copied 但是为了简单我们可以忽略这个)。一个 const 的对 self 的引用在 block 里面有自己的位置并且它会影响对象的引用计数。

方案 2. 在 block 外定义一个__weak的引用到 self,并且在 block 里面使用这个弱引用

这样会避免循坏引用,也是通常情况下我们的block作为类的属性被self retain 的时候会做的。

__weak typeof(self) weakSelf = self;
self.completionHandler = ^{
    NSLog(@"%@", weakSelf);
};

MyViewController *myController = [[MyViewController alloc] init...];
[self presentViewController:myController
                   animated:YES
                 completion:self.completionHandler];

这个情况下 block 没有 retain 对象并且对象在属性里面 retain 了 block 。所以这样我们能保证了安全的访问 self。 不过糟糕的是,它可能被设置成 nil 的。问题是:如何让 self 在 block 里面安全地被销毁。

考虑这么个情况:block 作为属性(property)赋值的结果,从一个对象被复制到另一个对象(如 myController),在这个复制的 block 执行之前,前者(即之前的那个对象)已经被解除分配。

方案 3. 在 block 外定义一个__weak 的 引用到 self,并在在 block 内部通过这个弱引用定义一个__strong的引用

你可能会想,首先,这是避免 retain cycle 警告的一个技巧。

这不是重点,这个 self 的强引用是在block 执行时被创建的,但是否使用 self 在 block 定义时就已经定下来了, 因此self (在block执行时) 会被 retain.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值