块对象是c级语法和运行时特性。它们类似于标准的C函数,但是除了可执行代码之外,它们还可能包含到自动(堆栈)或托管(堆)内存的变量绑定。因此,块可以维护一组状态(数据),它可以使用这些状态(数据)来影响执行时的行为。
您可以使用块来组合函数表达式,这些函数表达式可以传递给API,也可以存储,并由多个线程使用。块作为回调尤其有用,因为该块携带在回调上执行的代码和执行期间需要的数据。
声明和使用块
您使用^操作符来声明一个块变量和表示一块文字的开始。块体本身包含在{}中,如本例所示(与通常的C一样,;表示语句的结尾):
int multiplier = 7;
int (^myBlock)(int) = ^(int num) {
return num * multiplier;
};
注意,块能够使用定义它的相同范围内的变量。
如果你声明一个块为一个变量,那么你可以像使用函数一样使用它:
int multiplier = 7;
int (^myBlock)(int) = ^(int num) {
return num * multiplier;
};
printf("%d", myBlock(3));
// prints "21"
直接使用块
在许多情况下,您不需要声明块变量;相反,您只需在需要作为参数的地方内联编写一个块文字。下面的示例使用qsort_b函数。qsort_b类似于标准的qsort_r函数,但它的最终参数是一个块。
char *myCharacters[3] = { "TomJohn", "George", "Charles Condomine" };
qsort_b(myCharacters, 3, sizeof(char *), ^(const void *l, const void *r) {
char *left = *(char **)l;
char *right = *(char **)r;
return strncmp(left, right, 1);
});
// myCharacters is now { "Charles Condomine", "George", "TomJohn" }
块与Cocoa框架
Cocoa框架中的一些方法将一个块作为参数,通常是对一个对象集合执行一个操作,或者在一个操作完成后用作回调。下面的例子展示了如何使用NSArray方法sortedArrayUsingComparator来使用block:。这个方法只需要一个参数——block。例如,在本例中,block被定义为一个NSComparator局部变量:
NSArray *stringsArray = @[ @"string 1",
@"String 21",
@"string 12",
@"String 11",
@"String 02" ];
static NSStringCompareOptions comparisonOptions = NSCaseInsensitiveSearch | NSNumericSearch |
NSWidthInsensitiveSearch | NSForcedOrderingSearch;
NSLocale *currentLocale = [NSLocale currentLocale];
NSComparator finderSortBlock = ^(id string1, id string2) {
NSRange string1Range = NSMakeRange(0, [string1 length]);
return [string1 compare:string2 options:comparisonOptions range:string1Range locale:currentLocale];
};
NSArray *finderSortArray = [stringsArray sortedArrayUsingComparator:finderSortBlock];
NSLog(@"finderSortArray: %@", finderSortArray);
Output:
finderSortArray: (
"string 1",
"String 02",
"String 11",
"string 12",
"String 21"
)
__block变量
块的一个强大特性是它们可以在相同的词法范围内修改变量。你的信号是一个块可以使用__block存储类型修饰符来修改一个变量。通过使用Cocoa调整block中的示例,您可以使用block变量来计算与下面示例相同的字符串比较的数量。例如,在这种情况下,块是直接使用的,并使用currentLocale作为块中的只读变量:
NSArray *stringsArray = @[ @"string 1",
@"String 21", // <-
@"string 12",
@"String 11",
@"Strîng 21", // <-
@"Striñg 21", // <-
@"String 02" ];
NSLocale *currentLocale = [NSLocale currentLocale];
__block NSUInteger orderedSameCount = 0;
NSArray *diacriticInsensitiveSortArray = [stringsArray sortedArrayUsingComparator:^(id string1, id string2) {
NSRange string1Range = NSMakeRange(0, [string1 length]);
NSComparisonResult comparisonResult = [string1 compare:string2 options:NSDiacriticInsensitiveSearch range:string1Range locale:currentLocale];
if (comparisonResult == NSOrderedSame) {
orderedSameCount++;
}
return comparisonResult;
}];
NSLog(@"diacriticInsensitiveSortArray: %@", diacriticInsensitiveSortArray);
NSLog(@"orderedSameCount: %d", orderedSameCount);
Output:
diacriticInsensitiveSortArray: (
"String 02",
"string 1",
"String 11",
"string 12",
"String 21",
"Str\U00eeng 21",
"Stri\U00f1g 21"
)
orderedSameCount: 2
块对象为您提供了一种方法,可以在C语言和C派生语言(如Objective-C和c++)中创建作为表达式的特殊函数体。在其他语言和环境中,块对象有时也称为“闭包”。在这里,它们通常被通俗地称为“块”,除非有可能与代码块的标准C术语混淆。
块的功能
块是匿名的内联代码集合,其中:
1.类型化参数列表像函数一样
2.有推断的或声明的返回类型
3.从定义状态的词法范围捕获状态
4.可以选择修改词法作用域的状态码
5.与在相同词法范围内定义的其他块共享修改的潜力
6.在删除了词法作用域(堆栈框架)后,可以继续共享和修改在词法作用域(堆栈框架)内定义的状态码
您可以复制一个块,甚至将它传递给其他线程以进行延迟执行(或者在它自己的线程中传递给runloop)。编译器和运行时安排在块的所有副本的生命周期中保留从块引用的所有变量。虽然block对于纯C和c++都是可用的,但是block也是Objective-C对象。
使用
块通常表示小的、自包含的代码片段。因此,它们特别有用,可用来封装可能并发执行的工作单元,或对集合中的项进行封装,或在另一个操作完成时作为回调。
块是一个有用的替代传统的回调函数有两个主要原因:
1.它们允许您在调用时编写代码,稍后在方法实现上下文中执行这些代码。
因此块通常是框架方法的参数。
2.它们允许访问本地变量。
与其使用需要包含执行操作所需的所有上下文信息的数据结构的回调,不如直接访问本地变量。
声明块引用
块变量保存对块的引用。您声明它们使用语法类似于用来声明一个指针指向一个函数,除了你使用^代替 *。块类型与C类型系统的其余部分完全交互。以下是所有有效的块变量声明:
void (^blockReturningVoidWithVoidArgument)(void);
int (^blockReturningIntWithIntAndCharArguments)(int, char);
void (^arrayOfTenBlocksReturningVoidWithIntArgument[10])(int);
通过为编译器提供完整的元数据集来验证块的使用、传递给块的参数以及返回值的赋值,块被设计成完全类型安全的。可以将块引用转换为任意类型的指针,反之亦然。但是,不能通过指针反引用操作符(*)来反引用块,因此不能在编译时计算块的大小。
您还可以为块创建类型——当您在多个地方使用具有给定签名的块时,这样做通常被认为是最佳实践:
typedef float (^MyBlockType)(float, float);
MyBlockType myFirstBlock = // ... ;
MyBlockType mySecondBlock = // ... ;
您使用^运算符来表示一块文字表达式的开始。它后面可能跟着一个包含在()中的参数列表。块的主体包含在{}中。下面的例子定义了一个简单的块,并将其赋值给先前声明的变量(oneFrom)——在这里,块后面跟着法线;以C结束。
float (^oneFrom)(float);
oneFrom = ^(float aFloat) {
float result = aFloat - 1.0;
return result;
};
如果不显式声明块表达式的返回值,则可以从块的内容中自动推断它。如果推断了返回类型,并且参数列表为空,那么您也可以忽略(void)参数列表。如果或当出现多个返回语句时,它们必须完全匹配(必要时使用强制转换)。
Global Blocks
在文件级别,可以使用块作为全局文字:
#import
int GlobalInt = 0;
int (^getGlobalInt)(void) = ^{ return GlobalInt; };
块和变量
类型的变量
1.在块对象的代码体中,变量可以以五种不同的方式处理。
2.你可以引用三种标准类型的变量,就像你可以从一个函数:
(1)全局变量,包括静态局部变量
(2)全局函数(不是技术上的变量)
(3)来自封闭范围的局部变量和参数
block还支持另外两种类型的变量:
1.函数级是__block变量。这些变量在块(和封闭的范围)内是可变的,如果任何引用块被复制到堆中,它们将被保留。
2.const输入。
以下规则适用于块中使用的变量:
1.全局变量是可访问的,包括存在于封闭的词法作用域中的静态变量。
2.传递给块的参数是可访问的(就像函数的参数一样)。
3.封装词法作用域的本地堆栈(非静态)变量被捕获为常量变量。
4.它们的值在程序中的块表达式处取值。在嵌套块中,值是从最近的封闭范围中获取的。
5.使用__block存储修饰符声明的封闭词法作用域的局部变量由引用提供,因此是可变的。
6. 任何更改都反映在封闭词汇作用域中,包括在同一封闭词汇作用域中定义的任何其他块。这些将在__block存储类型中更详细地讨论。
7.块的词法范围内声明的局部变量,其行为与函数中的局部变量完全相同。
8.块的每次调用都会提供该变量的新副本。这些变量可以依次作为常量或引用变量使用在块内的块中。
下面的例子说明了局部非静态变量的使用:
int x = 123;
void (^printXAndY)(int) = ^(int y) {
printf("%d %d\n", x, y);
};
printXAndY(456); // prints: 123 456
如前所述,试图在块中给x分配一个新值将导致一个错误:
int x = 123;
void (^printXAndY)(int) = ^(int y) {
x = x + y; // error
printf("%d %d\n", x, y);
};
要允许在块中更改变量,可以使用__block存储类型修改器—
__block存储类型
通过应用__block存储类型修饰符,可以指定导入的变量是可变的,即读-写。块存储与本地变量的寄存器、自动存储和静态存储类型类似,但相互排斥。
__block变量存在于变量的词法作用域与在变量的词法作用域内声明或创建的所有块和块副本之间共享的存储中。因此,如果在框架中声明的块的任何副本在框架结束之后仍然存在(例如,通过排队等待以后执行),那么存储将在堆栈框架被销毁之后仍然存在。给定词法作用域中的多个块可以同时使用一个共享变量。
作为一种优化,块存储从堆栈开始—就像块本身一样。如果使用Block_copy复制块(或者在Objective-C中,当块被发送一个副本时),变量将被复制到堆中。因此,__block变量的地址可以随时间而变化。
__block变量还有两个进一步的限制:它们不能是可变长度数组,也不能是包含C99可变长度数组的结构。
下面的例子说明了__block变量的使用:
__block int x = 123; // x lives in block storage
void (^printXAndY)(int) = ^(int y) {
x = x + y;
printf("%d %d\n", x, y);
};
printXAndY(456); // prints: 579 456
// x is now 579
下面的例子展示了不同类型变量之间的交互:
extern NSInteger CounterGlobal;
static NSInteger CounterStatic;
{
NSInteger localCounter = 42;
__block char localCharacter;
void (^aBlock)(void) = ^(void) {
++CounterGlobal;
++CounterStatic;
CounterGlobal = localCounter; // localCounter fixed at block creation
localCharacter = 'a'; // sets localCharacter in enclosing scope
};
++localCounter; // unseen by the block
localCharacter = 'b';
aBlock(); // execute the block
// localCharacter now 'a'
}
objective - c的对象
复制块时,它会创建对块中使用的对象变量的强引用。如果你在一个方法的实现中使用一个块:
1.如果通过引用访问实例变量,则对self进行强引用;
2.如果按值访问实例变量,则对该变量进行强引用。
下面的例子说明了两种不同的情况:
dispatch_async(queue, ^{
// 引用使用实例变量,对self进行强引用
doSomethingWithObject(instanceVariable);
});
id localVariable = instanceVariable;
dispatch_async(queue, ^{
//值使用localVariable,对localVariable进行强引用
(不是对自己)
doSomethingWithObject(localVariable);
})
c++对象
一般情况下,您可以在块中使用c++对象。在成员函数中,对成员变量和函数的引用是通过隐式导入该指针的,因此看起来是可变的。如果复制了一个块,需要考虑两个问题:
如果您有一个__block存储类来存储基于堆栈的c++对象,那么将使用通常的复制构造函数。
如果在一个块中使用任何其他基于c++堆栈的对象,它必须有一个const复制构造函数。然后使用该构造函数复制c++对象。
块
当您复制一个块时,如果需要,将从该块中复制对其他块的任何引用—可以(从顶部)复制整个树。如果您有块变量,并且您从块中引用了一个块,那么该块将被复制。
调用一个块
如果你声明一个block作为一个变量,你可以像使用函数一样使用它,如下两个例子所示:
int (^oneFrom)(int) = ^(int anInt) {
return anInt - 1;
};
printf("1 from 10 is %d", oneFrom(10));
// Prints "1 from 10 is 9"
float (^distanceTraveled)(float, float, float) =
^(float startingSpeed, float acceleration, float time) {
float distance = (startingSpeed * time) + (0.5 * acceleration * time * time);
return distance;
};
float howFar = distanceTraveled(0.0, 9.8, 1.0);
// howFar = 4.9
但是,通常将块作为参数传递给函数或方法。在这些情况下,通常会创建一个“内联”块。
使用块作为函数参数
可以像传递其他参数一样传递一个块作为函数参数。然而,在许多情况下,您不需要声明块;相反,您只需在需要它们作为参数的地方内联实现它们。下面的示例使用qsort_b函数。qsort_b类似于标准的qsort_r函数,但以块作为其最终参数。
char *myCharacters[3] = { "TomJohn", "George", "Charles Condomine" };
qsort_b(myCharacters, 3, sizeof(char *), ^(const void *l, const void *r) {
char *left = *(char **)l;
char *right = *(char **)r;
return strncmp(left, right, 1);
});
// Block implementation ends at "}"
// myCharacters is now { "Charles Condomine", "George", "TomJohn" }
注意,该块包含在函数的参数列表中。
下一个示例演示如何使用带有dispatch_apply函数的块。dispatch_apply声明如下
void dispatch_apply(size_t iterations, dispatch_queue_t queue, void (^block)(size_t));
该函数为多个调用向调度队列提交一个块。它需要三个参数;第一个指定要执行的迭代次数;第二个指定提交块的队列;第三个是块本身,它依次接受一个参数——迭代的当前索引。
您可以简单地使用dispatch_apply来打印迭代索引,如下所示:
#include
size_t count = 10;
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_apply(count, queue, ^(size_t i) {
printf("%u\n", i);
});
使用块作为方法参数
Cocoa提供了许多使用块的方法。将block作为方法参数传递,就像传递其他参数一样。
下面的示例确定出现在给定筛选器集中的数组中的前五个元素中的任何一个的索引。
NSArray *array = @[@"A", @"B", @"C", @"A", @"B", @"Z", @"G", @"are", @"Q"];
NSSet *filterSet = [NSSet setWithObjects: @"A", @"Z", @"Q", nil];
BOOL (^test)(id obj, NSUInteger idx, BOOL *stop);
test = ^(id obj, NSUInteger idx, BOOL *stop) {
if (idx < 5) {
if ([filterSet containsObject: obj]) {
return YES;
}
}
return NO;
};
NSIndexSet *indexes = [array indexesOfObjectsPassingTest:test];
NSLog(@"indexes: %@", indexes);
Output:
indexes: [number of indexes: 2 (in 2 ranges), indexes: (0 3)]
下面的示例确定NSSet对象是否包含由局部变量指定的单词,并将另一个局部变量(已找到的)的值设置为YES(如果是,则停止搜索)。注意,find也声明为__block变量,并且块是内联定义的:
__block BOOL found = NO;
NSSet *aSet = [NSSet setWithObjects: @"Alpha", @"Beta", @"Gamma", @"X", nil];
NSString *string = @"gamma";
[aSet enumerateObjectsUsingBlock:^(id obj, BOOL *stop) {
if ([obj localizedCaseInsensitiveCompare:string] == NSOrderedSame) {
*stop = YES;
found = YES;
}
}];
// At this point, found == YES
复制块
通常,您不需要复制(或保留)块。只有当您希望在声明块的范围被销毁后使用该块时,才需要进行复制。复制将一个块移动到堆。
你可以使用C函数复制和释放块:
Block_copy();
Block_release();
为了避免内存泄漏,您必须始终平衡Block_copy()和Block_release()。
避免以下模式,
一块文字(即^ {…)是表示块的堆栈本地数据结构的地址。因此,堆栈本地数据结构的作用域是封闭的复合语句,因此您应该避免以下示例中显示的模式:
void dontDoThis() {
void (^blockArray[3])(void); // an array of 3 block references
for (int i = 0; i < 3; ++i) {
blockArray[i] = ^{ printf("hello, %d\n", i); };
// WRONG: The block literal scope is the "for" loop.
}
}
void dontDoThisEither() {
void (^block)(void);
int i = random():
if (i > 1000) {
block = ^{ printf("got i at: %d\n", i); };
// WRONG: The block literal scope is the "then" clause.
}
// ...
}
block-----较深理解
Objective-C类定义了一个结合数据和相关行为的对象。有时候,只表示单个任务或行为单元比表示方法集合更有意义。
块是添加到C、Objective-C和c++中的一种语言级特性,它允许您创建不同的代码段,这些代码段可以像值一样传递给方法或函数。block是Objective-C对象,这意味着它们可以被添加到集合中,比如NSArray或NSDictionary。它们还能够从封闭的范围捕获值,使其类似于其他编程语言中的闭包或lambdas。
块语法
语法定义一块文字使用插入符号的符号(^),像这样:
^{
NSLog(@"这是一个块");
}
与函数和方法定义一样,大括号表示块的开始和结束。在本例中,块不返回任何值,也不接受任何参数。
就像你可以使用一个函数指针来引用一个C函数一样,你可以声明一个变量来跟踪一个块,就像这样:
void (^simpleBlock)(void);
如果您不习惯处理C函数指针,那么这种语法可能有点不寻常。这个例子声明了一个名为simpleBlock的变量来引用一个不带参数且不返回值的块,这意味着该变量可以被赋值为上面所示的块文字,如下所示:
simpleBlock = ^{
NSLog(@"这是一个块");
};
这就像任何其他变量赋值一样,因此语句必须在右括号后面用分号结束。你也可以结合变量声明和赋值:
void (^simpleBlock)(void) = ^{
NSLog(@"这是一个块");
};
一旦你声明和分配了一个块变量,你可以用它来调用这个块:
simpleBlock();
注意:如果您试图使用未分配的变量(nil块变量)调用一个块,您的应用程序将崩溃。
块接受参数并返回值
块也可以接受参数和返回值,就像方法和函数一样。
例如,考虑一个变量引用一个块,该块返回两个值相乘的结果:
double (^multiplyTwoValues)(double, double);
对应的块文字可能是这样的:
^ (double firstValue, double secondValue) {
return firstValue * secondValue;
}
firstValue和secondValue用于引用在调用块时提供的值,就像任何函数定义一样。在本例中,从块中的return语句推断返回类型。
如果您愿意,可以通过在插入符号和参数列表之间指定返回类型,使其显式:
^ double (double firstValue, double secondValue) {
return firstValue * secondValue;
}
一旦你声明并定义了block,你可以像调用函数一样调用它:
double (^multiplyTwoValues)(double, double) =
^(double firstValue, double secondValue) {
return firstValue * secondValue;
};
double result = multiplyTwoValues(2,4);
NSLog(@"The result is %f", result);
块可以从封闭的范围捕获值
除了包含可执行代码外,块还能够从其封闭的范围捕获状态。
例如,如果你在一个方法中声明了一个块文字,你就可以捕获该方法范围内的任何可访问的值,就像这样:
- (void)testMethod {
int anInteger = 42;
void (^testBlock)(void) = ^{
NSLog(@"Integer is: %i", anInteger);
};
testBlock();
}
在本例中,在块外部声明一个整数,但在定义块时捕获该值。
除非另外指定,否则只捕获值。这意味着,如果在定义块的时间和调用它的时间之间更改变量的外部值,如下所示:
int anInteger = 42;
void (^testBlock)(void) = ^{
NSLog(@"Integer is: %i", anInteger);
};
anInteger = 84;
testBlock();
块捕获的值不受影响。这意味着日志输出仍然显示:
Integer is: 42
它还意味着块不能更改原始变量的值,甚至不能更改捕获的值(它作为const变量捕获)。
使用__block变量共享存储
如果您需要能够从块中更改捕获的变量的值,那么可以在原始变量声明上使用__block存储类型修饰符。这意味着变量存在于原始变量的词法范围和该范围内声明的任何块之间共享的存储中。
作为一个例子,你可以这样重写前面的例子:
__block int anInteger = 42;
void (^testBlock)(void) = ^{
NSLog(@"Integer is: %i", anInteger);
};
anInteger = 84;
testBlock();
因为整数被声明为__block变量,所以它的存储与块声明共享。这意味着日志输出现在将显示:
Integer is: 84
这也意味着block可以修改原始值,如下图所示:
__block int anInteger = 42;
void (^testBlock)(void) = ^{
NSLog(@"Integer is: %i", anInteger);
anInteger = 100;
};
testBlock();
NSLog(@"Value of original variable is now: %i", anInteger);
这一次,输出将显示:
Integer is: 42
Value of original variable is now: 100
可以将块作为参数传递给方法或函数
本章前面的每个示例都在定义块之后立即调用它。在实践中,通常将块传递给函数或方法以便在其他地方调用。例如,您可以使用Grand Central Dispatch在后台调用一个块,或者定义一个块来表示要重复调用的任务,例如枚举集合时。本章后面将讨论并发性和枚举。
块还用于回调,定义任务完成时要执行的代码。例如,您的应用程序可能需要通过创建执行复杂任务(例如从web服务请求信息)的对象来响应用户操作。因为任务可能需要很长时间,所以应该在任务发生时显示某种进度指示器,然后在任务完成时隐藏该指示器。
可以使用委托来完成此任务:您需要创建一个合适的委托协议,实现所需的方法,将对象设置为任务的委托,然后等待任务完成后它调用对象上的委托方法。
然而,块使这变得更容易,因为您可以在启动任务时定义回调行为,如下所示:
- (IBAction)fetchRemoteInformation:(id)sender {
[self showProgressIndicator];
XYZWebTask *task = ...
[task beginTaskWithCallbackBlock:^{
[self hideProgressIndicator];
}];
}
这个例子调用一个方法来显示进度指示器,然后创建任务并告诉它开始。回调块指定任务完成后要执行的代码;在本例中,它只是调用一个方法来隐藏进度指示器。注意,这个回调块捕获self,以便在调用时能够调用hideProgressIndicator方法。捕获self时一定要小心,因为很容易创建强引用循环,稍后在捕获self时要避免强引用循环。
在代码可读性方面,这个块可以很容易地在一个地方确切地看到任务完成之前和之后会发生什么,从而避免了通过委托方法跟踪以查明将要发生什么。
本例中显示的beginTaskWithCallbackBlock:方法的声明如下:
- (void)beginTaskWithCallbackBlock:(void (^)(void))callbackBlock;
(void (^)(void))指定的参数是一块不需要任何参数或返回值。该方法的实现可以以通常的方式调用块:
- (void)beginTaskWithCallbackBlock:(void (^)(void))callbackBlock {
...
callbackBlock();
}
方法参数期望一个块具有一个或多个参数,其指定方式与块变量相同:
- (void)doSomethingWithBlock:(void (^)(double, double))block {
...
block(21.0, 2.0);
}
块应该始终是方法的最后一个参数
对于一个方法,最好只使用一个块参数。如果该方法还需要其他非块参数,则块应该排在最后:
- (void)beginTaskWithName:(NSString *)name completion:(void(^)(void))callback;
这使得在内联指定块时更容易读取方法调用,如下所示:
[self beginTaskWithName:@"MyTask" completion:^{
NSLog(@"The task is complete");
}];
使用类型定义来简化块语法
如果需要定义具有相同签名的多个块,则可能需要为该签名定义自己的类型。
例如,您可以为一个没有参数或返回值的简单块定义类型,如下所示:
typedef void (^XYZSimpleBlock)(void);
然后你可以使用你的自定义类型的方法参数或当创建块变量:
XYZSimpleBlock anotherBlock = ^{
...
};
- (void)beginFetchWithCallbackBlock:(XYZSimpleBlock)callbackBlock {
...
callbackBlock();
}
在处理返回块或以其他块作为参数的块时,自定义类型定义特别有用。考虑下面的例子:
void (^(^complexBlock)(void (^)(void)))(void) = ^ (void (^aBlock)(void)) {
...
return ^{
...
};
};
复块变量指的是一个以另一个块作为参数(aBlock)并返回另一个块的块。
重写代码以使用类型定义使其更易于阅读:
XYZSimpleBlock (^betterBlock)(XYZSimpleBlock) = ^ (XYZSimpleBlock aBlock) {
...
return ^{
...
};
};
对象使用属性来跟踪块
定义跟踪块的属性的语法类似于块变量:
@interface XYZObject : NSObject
@property (copy) void (^blockProperty)(void);
@end
注意:您应该指定copy作为属性属性,因为需要复制一个块来跟踪其在原始范围之外捕获的状态。在使用自动引用计数时,不需要担心这一点,因为它会自动发生,但是属性属性显示结果行为是最佳实践
块属性像其他块变量一样被设置或调用:
self.blockProperty = ^{
...
};
self.blockProperty();
也可以对块属性声明使用类型定义,如下所示:
typedef void (^XYZSimpleBlock)(void);
@interface XYZObject : NSObject
@property (copy) XYZSimpleBlock blockProperty;
@end
捕获self时避免强引用循环
如果您需要在一个块中捕获self,例如在定义回调块时,务必考虑内存管理的影响。
块维护对任何捕获的对象的强引用,包括self,这意味着如果一个对象为捕获self的块维护一个复制属性,那么很容易以强引用循环结束:
@interface XYZBlockKeeper : NSObject
@property (copy) void (^block)(void);
@end
@implementation XYZBlockKeeper
- (void)configureBlock {
self.block = ^{
[self doSomething]; //强烈的捕获self
//创建一个强引用循环
};
}
...
@end
编译器会警告您注意类似这样的简单示例,但是更复杂的示例可能会涉及对象之间的多个强引用来创建循环,从而使诊断更加困难。
为了避免这个问题,最好的做法是捕捉到对self的弱引用,就像这样:
- (void)configureBlock {
XYZBlockKeeper * __weak weakSelf = self;
self.block = ^{
[weakSelf doSomething]; //捕获弱引用
//避免引用循环
}
}
通过捕获指向self的弱指针,块将不会维护回XYZBlockKeeper对象的强关系。如果在调用块之前释放该对象,weakSelf指针将被简单地设置为nil。
块可以简化枚举
除了一般的完成处理程序之外,许多Cocoa和Cocoa Touch API使用块来简化常见的任务,比如集合枚举。例如,NSArray类提供了三种基于块的方法,包括:
- (void)enumerateObjectsUsingBlock:(void (^)(id obj, NSUInteger idx, BOOL *stop))block;
此方法只接受一个参数,该参数是数组中每一项调用一次的块:
NSArray *array = ...
[array enumerateObjectsUsingBlock:^ (id obj, NSUInteger idx, BOOL *stop) {
NSLog(@"Object at index %lu is %@", idx, obj);
}];
块本身接受三个参数,前两个参数引用数组中的当前对象及其索引。第三个参数是一个指向布尔变量的指针,您可以使用它来停止枚举,如下所示:
[array enumerateObjectsUsingBlock:^ (id obj, NSUInteger idx, BOOL *stop) {
if (...) {
*stop = YES;
}
}];
还可以使用enumerateObjectsWithOptions:usingBlock:方法自定义枚举。例如,指定NSEnumerationReverse选项将以相反的顺序遍历集合。
如果枚举块中的代码是处理器级的,并且对于并发执行是安全的,那么可以使用NSEnumerationConcurrent选项:
[array enumerateObjectsWithOptions:NSEnumerationConcurrent
usingBlock:^ (id obj, NSUInteger idx, BOOL *stop) {
...
}];
此标志指示枚举块调用可能分布在多个线程中,如果块代码特别占用处理器,则可能提高性能。请注意,使用此选项时枚举顺序未定义。
NSDictionary类还提供了基于块的方法,包括:
NSDictionary *dictionary = ...
[dictionary enumerateKeysAndObjectsUsingBlock:^ (id key, id obj, BOOL *stop) {
NSLog(@"key: %@, value: %@", key, obj);
}];
块可以简化并发任务
块表示不同的工作单元,它将可执行代码与从周围范围捕获的可选状态相结合。这使得使用OS X和iOS可用的并发选项之一进行异步调用非常理想。您不必弄清楚如何使用线程等低级机制,只需使用块定义任务,然后让系统在处理器资源可用时执行这些任务。
OS X和iOS提供了多种并发技术,包括两种任务调度机制:操作队列和中央调度。这些机制围绕着等待调用的任务队列的思想。将块按照需要调用它们的顺序添加到队列中,当处理器时间和资源可用时,系统将它们从队列中取出以供调用。
串行队列只允许一次执行一个任务——在上一个任务完成之前,队列中的下一个任务不会从队列中取出并调用。并发队列调用尽可能多的任务,而不需要等待前面的任务完成。
对操作队列使用块操作
操作队列是实现任务调度的Cocoa和Cocoa Touch方法。创建一个NSOperation实例来封装一个工作单元和任何必要的数据,然后将该操作添加到NSOperationQueue中执行。
虽然你可以创建自己的自定义NSOperation子类来实现复杂的任务,也可以使用NSBlockOperation来创建一个使用block的操作,就像这样:
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
...
}];
手动执行操作是可能的,但操作通常添加到现有的操作队列或您自己创建的队列中,准备执行:
// schedule task on main queue:
NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];
[mainQueue addOperation:operation];
// schedule task on background queue:
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperation:operation];
如果使用操作队列,可以配置操作之间的优先级或依赖项,例如指定在一组其他操作完成之前不应执行一个操作。您还可以通过键值观察来监视操作状态的更改,这使得更新进度指示器(例如,当任务完成时)变得很容易。
使用中央调度在调度队列上调度块
如果需要为执行计划任意代码块,可以直接使用由Grand Central dispatch (GCD)控制的调度队列。分派队列使同步或异步地执行与调用者相关的任务变得很容易,并且可以按照先入先出的顺序执行它们的任务。
您可以创建自己的调度队列,也可以使用GCD自动提供的队列之一。例如,如果需要为并发执行调度任务,可以使用dispatch_get_global_queue()函数获取对现有队列的引用,并指定队列优先级,如下所示:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
要将块分派到队列,可以使用dispatch_async()或dispatch_sync()函数。dispatch_async()函数立即返回,不需要等待块被调用:
dispatch_async(queue, ^{
NSLog(@"Block for asynchronous execution");
});
dispatch_sync()函数直到块完成执行后才返回;例如,在一个并发块需要在主线程上等待另一个任务完成后才能继续使用它的情况下。