block是什么
我开始理解它是和函数指针一样的东西,因为两者的写法比较类似。但是,查了些资料后发现它其实是一个对象,最主要原因是我们可以检测它的类型。(具体点这个链接)。而且这种对象和普通的oc对象不一样,它是在stack上创建的。
block的各种写法
中括号表示可选
变量
return_type (^blockName)(var_type) = ^[return_type](var_type varName)
{
// ...
};
和普通变量一样,你也可以将声明和赋值分开
return_type (^blockName)(var_type);
blockName = ^[return_type](var_type varName)
{
// ...
};
属性
推荐使用copy来修饰
@property (copy) return_type (^blockName) (var_type);
函数参数
官方建议使用block作为参数时,最好将它作为最后一个参数。
- (yourMethod_return_type)yourMethodName:(return_type (^)(var_type))blockName;
消息参数
[someObject doSomethingWithBlock: ^[return_type](var_type varName)
{
//...
}];
匿名block
^[return_type](var_type varName)
{
//...
};
typedef block
这个和typedef函数指针一样,之后就可以直接使用你自定义的名字,按照Apple文档的说法,当你的blok使用另一个block作为参数时十分有用,不用看到你眼花。。。
typedef return_type (^your_typedef_blockName)(var_type);
内联block
很少见的写法,也很少用到,相当于一个匿名block定义后立即调用
^return_type (var_type varName)
{
//...
}(var);
递归block
允许你的block调用自身,创建一个可以在回调和GCD调用时使用的loop。该block会被ARC管理回收(ps:不明觉历)
__block return_type (^blockName)(var_type) = [^return_type (var_type varName)
{
if (returnCondition)
{
blockName = nil;
return;
}
// ...
} copy];
blockName(varValue);
作为返回值
- (return_type(^)(var_type))methodName
{
// ...
}
block注意事项
作为property的时候要使用copy
因为block是在stack上创建的(就是所谓的stack对象,普通的oc对象都是在heap上创建的),这就和局部变量一样,意味着block只存在在创建它的那个作用域,一但离开那个作用域,就会被自动回收。
在MRC时代,你要返回一个block你要这样写:
- (void (^)(void))block { return [[^{ ... } copy] autorelease]; }
当你要在调用它的时候,你要手动向block对象发送一个copy信息,将它copy到heap上。
但是在ARC机制中,当系统检测到你在创建它的那个作用域之外调用block的时候,系统已经会自动帮你处理。只有在将block赋值给id对象才需要显式使用copy方法
id obj = [^{...} copy];
为什么我们要使用copy来修饰block的property,官方文档的解析是,即使在ARC下会自动帮你处理,使用copy进行标识,也是一种比较良好的风格。
补充一点:官方文档建议不要这样写,代码来自Apple文档
void dontDoThis() {
void (^blockArray[3])(void);
for (int i = 0; i < 3; ++i) {
blockArray[i] = ^{ printf("hello, %d\n", i); };
}
}
void dontDoThisEither() {
void (^block)(void);
int i = random():
if (i > 1000) {
block = ^{ printf("got i at: %d\n", i); };
}
}
原因就是block对象可能被回收,使代码失效。正确的写法是调用copy方法(测试了很多次都没有失效的情况发生,个人认为ARC有可能也做了处理,这个链接也指出在ARC中已经自动做了处理,但是官方文档并没有删除这一点,所以补充一下)
void dontDoThisEither() {
void (^block)(void);
int i = random():
if (i > 1000) {
block = [^{ printf("got i at: %d\n", i); } copy];
}
}
block引用self的时候要避免循环引用
有时候我们要在block中使用self,在这种情况下就会产生循环引用。
self.block = ^{
[self doSomething];
}
self持有一个block对象,block代码块内又持有self。这个时候,常见做法是创建一个__week修饰的self类型的指针,将之指向self。
__weak yourClass *weakSelf = self;
self.block = ^{
[weakSelf doSomething];
}
或者另外一种更具逼格的写法:
__weak typeof(self) weakSelf = self;
self.block = ^{
[weakSelf doSomething];
}
block代码块内可以捕捉外部变量
这一点比较常见,就是block代码块内可以直接使用外部的变量
int x = 123;
void (^printX)(void) = ^(void){
NSLog(@"%d",x);
};
很明显调用block时可以输出x的值,再看下面这种情况
int x = 123;
void (^printX)(void) = ^(void){
NSLog(@"%d",x);
};
x = 23;
printX();
这里输出的值是123,而不是23。默认情况下,block内部会copy一份x,即使之后对x进行了修改,block也无法检测到最新的值。同时,copy的那份x是const的,block不能修改x,所以以下代码是不正确的
int x = 123;
void (^printX)(void) = ^(void){
x += 1;
NSLog(@"%d",x);
};
如果要修改外部变量的值或者要检测到外部变量值的变化,可以将变量用__block修饰
__block int x = 123;
这样之后,第一个例子输出23,第二个例子输出124