Objective-C中的Block

block介绍

  1. Block是iOS4.0之后新增的一种语法结构,也称为“闭包”。
  2. Block是一个匿名的函数代码块,此代码块可以作为参数传递给其他对象。
  3. 可以把block当做Objective-C的匿名函数,block是OC中的一种数据类型,^是block的特有标记。

block原型

(返回类型)(^block名称)(参数类型)=^(参数列表){代码实现};

1
NSString * ( ^ myBlock )( int );

上面的代码声明了一个block(^)原型,名字叫做myBlock,包含一个int型的参数,返回值为NSString类型的指针。
block的定义:

1
2
3
4
myBlock = ^( int paramA )
{
return [NSString stringWithFormat:@"The number is: %i", paramA];
};

上面的代码中,将一个函数体赋值给了myBlock变量,其接收一个名为paramA的参数,返回一个NSString对象.

注意:一定不要忘记block后面的分号。

定义好block后,就可以像使用标准函数一样使用它了:

myBlock(6);

由于block数据类型的语法会降低整个代码的阅读性,所以常使用typedef来定义block类型。例如,下面的代码创建了GetPersonEducationInfo和GetPersonFamilyInfo两个新类型,这样我们就可以在下面的方法中使用更加有语义的数据类型。

1
2
3
4
5
6
7
// Person.h
typedef NSString * (^GetPersonEducationInfo)(NSString *);
typedef NSString * (^GetPersonFamilyInfo)(NSString *);
@interface Person : NSObject
- (NSString *)getPersonInfoWithEducation:(GetPersonEducationInfo)educationInfo
andFamily:(GetPersonFamilyInfo)familyInfo;
@end

我们用一张图来总结一下block的结构:
2016-07-26_23:35:50.jpg

block数据结构定义

2016-07-26_23:42:02.jpg
上图这个结构是在栈中的结构,我们来看看对应的结构体定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct Block_descriptor {
unsigned long int reserved;
unsigned long int size;
void (*copy)(void *dst, void *src);
void (*dispose)(void *);
};
struct Block_layout {
void *isa;
int flags;
int reserved;
void (*invoke)(void *, ...);
struct Block_descriptor *descriptor;
/* Imported variables. */
};

从上面代码看出,Block_layout就是对block结构体的定义:

  • isa指针:指向表明该block类型的类。
  • flags:按bit位表示一些block的附加信息,比如判断block类型、判断block引用计数、判断block是否需要执行辅助函数等。
  • reserved:保留变量,我的理解是表示block内部的变量数。
  • invoke:函数指针,指向具体的block实现的函数调用地址。
  • descriptor:block的附加描述信息,比如保留变量数、block的大小、进行copydispose的辅助函数指针。
  • variables:因为block有闭包性,所以可以访问block外部的局部变量。这些variables就是复制到结构体中的外部局部变量或变量的地址。

block使用

  • 作为一个局部变量

    1
    returnType (^blockName)(parameterTypes) = ^returnType(parameters) {...};
  • 作为属性

    1
    @property (nonatomic, copy, nullability) returnType (^blockName)(parameterTypes);
  • 作为一个方法参数

    1
    - (void)someMethodThatTakesABlock:(returnType (^nullability)(parameterTypes))blockName;
  • 作为参数的方法调用

    1
    [someObject someMethodThatTakesABlock:^returnType (parameters) {...}];
  • 作为一个typedef

    1
    2
    typedef returnType (^TypeName)(parameterTypes);
    TypeName blockName = ^returnType(parameters) {...};

使用block应当注意的问题

  • Block访问外部变量
    1. block内部可以访问外部的变量,block默认是将其复制到其数据结构中来实现访问的。
    2. 默认情况下,block内部不能修改外面的局部变量,因为通过block进行闭包的变量是const的。
    3. 给局部变量加上__block关键字,这个局部变量就可以在block内部修改,block是复制其引用地址来实现访问的。
  • Block作为属性应该用copy修饰
    1. 当用weak、assign修饰block属性时,block访问外部变量,此时block的类型是栈block。保存在栈中的block,当block所在函数\方法返回\结束,该block就会被销毁。在其他方法内部调用访问该block,就会引发野指针错误EXC_BAD_ACCESS
    2. 当用copy、strong修饰block属性时,block访问外部变量,此时block的类型是堆block。保存在堆中的block,当引用计数器为0时被销毁,该类型block是由栈类型的block从栈中复制到堆中形成的,因此可以在其他方法内部调用该block。在ARC下,strongcopy都可以用来修饰block,但是建议修饰block属性使用copy

参考链接

Jackson wechat
欢迎您扫一扫上面的微信公众号,订阅我的博客!
如果你觉得我的文章还不错,欢迎打赏~