网上已经有很多讨论block底层实现原理的文章,这里不讨论实现,只讨论block使用中的一些问题。
一、声明block属性时应该使用什么关键字?
答案:copy
以下内容翻译自官方文档:
定义一个跟踪block的属性的语法:
@interface XYZObject : NSObject
@property (copy) void (^blockProperty)(void);
@end
注意:你应该指定copy作为属性关键字,因为块需要被拷贝以跟踪它在原始范围外捕获的状态。由于使用自动引用计数时(Automatic Reference Counting)这是自动发生的,所以你不需要担心这些,但是属性关键字的最佳做法是展示结果行为。更多关于block的信息,参阅Blocks Programming Topics。
上面这一段翻译的官方文档原文地址:Programming with Objective-C/Working with Blocks
注意:copy对属性也是强引用,只不过这个强引用指向的是赋值给属性的那个对象的一份拷贝。官方文档copy关键字的使用有详细说明,详情参阅文档《Programming with Objective-C》中Encapsulating Data主题的Copy Properties Maintain Their Own Copies章节
二、block循环引用
循环引用的产生
以下内容翻译自官方文档:
block对任何捕获的对象保持强应用,包括self,这意味着如果一个对象对一个捕获了self的block保持了copy属性(上面第一个问题中解释过,官方文档建议block属性使用copy关键字,且copy属性self对属性也是强引用,详情请翻看问题一):
@interface XYZBlockKeeper : NSObject
@property (copy) void (^block)(void);
@end
@implementation XYZBlockKeeper
- (void)configureBlock {
self.block = ^{
[self doSomething];// 捕获对self的强引用(capturing a strong reference to self)
// 创建了一个强引用循环(creates a strong reference cycle)
};
}
...
@end
解决方法:
避免这个问题的最好方法是捕获对self的弱引用,如下:
- (void)configureBlock {
XYZBlockKeeper * __weak weakSelf = self;
self.block = ^{
[weakSelf doSomething]; // 捕获弱引用以避免循环引用(capture the weak reference to avoid the reference cycle)
}
}
通过捕获指向self的弱指针,block就不会与XYZBlockKeeper对象保持强关系。如果对象在block调用前解除分配,weakSelf指针将被简单地置为nil。
官方文档地址:Programming with Objective-C/Working with Blocks
调用的方法中有block参数,会不会导致循环引用?
问题描述:如果self或self.property调用的方法中有个block参数,在这个block参数中使用了self,会不会导致循环引用?
这里的方法其实有两种情况:self中的方法和self中属性对象中的方法
self自己的方法使用block作为参数,这个block中使用了self,会不会导致循环引用?
代码如下:
- (void)doSomething {
[self testWithBlock:^{
[self testMethod];
}];
}
- (void)testWithBlock:(void(^)())block {
block();
}
- (void)testMethod {
NSLog(@"test");
}
答案:此时block只是一个临时变量,self并没有对其持有,所有不会产生循环引用。
self的属性对象中的方法,使用block作为参数,block中使用了self,会不会产生循环引用?
这里也有两种情况:1、属性对象持有了传入的block参数 2、属性对象未持有传入的block参数
1、属性对象持有了传入的block参数
代码如下:
#import <Foundation/Foundation.h>
typedef void (^BlockType)(void);
@interface AbcObject : NSObject
- (void)methodWithBlock:(BlockType)block;
@end
#import "AbcObject.h"
@interface AbcObject()
//不作为公有属性,而是在对外方法接口中把Block传进来
@property (nonatomic, copy) BlockType block;
@end
@implementation AbcObject
- (void)methodWithBlock:(BlockType)block {
//self.block留待后面其他地方异步调用
//会产生循环引用
self.block = block;
//比如,发起一个异步请求
//...
}
//比如是在一个请求的回调中执行刚才保存的block
- (void)completionDelegate {
//调用刚才保存的block
if (self.block) {
self.block();
}
}
@end
#import "ViewController.h"
#import "AbcObject.h"
@interface ViewController ()
@property (nonatomic, strong) AbcObject *abcObject;
@property (nonatomic, copy) NSString *str;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.str = @"haha";
self.abcObject = [[AbcObject alloc] init];
[self.abcObject configurePersonBlock:^{
NSLog(@"printf str:%@", self.str);
//解决问题的办法是block中使用weakSelf
}];
]}
@end
如上面的例子,这里也有两种情况,如果传入abcObject对象的block参数被abcObject对象作为属性存储起来以备其他地方的异步调用,这种情况self持有了person,person又持有了传入的block,block中又引用了self,间接产生了循环引用。
2、属性对象未持有传入的block参数
如果上面的代码改一下,methodWithBlock中不用属性持有传入的block(其余代码不变):
#import <Foundation/Foundation.h>
typedef void (^ BlockType)(void);
@interface AbcObject : NSObject
- (void)methodWithBlock:(BlockType)block;
@end
#import "AbcObject.h"
@interface AbcObject()
@end
@implementation AbcObject
- (void)methodWithBlock:(BlockType)block {
//做其他事情
//...
//不会产生循环引用
if (block) {
block();
}
}
@end
则不会产生循环引用。