iOS Block详解3

——译自Apple Reference Library《Blocks Programming Topic》

简介

块对象是C语言的句法和运行时特性。它类似于标准C函数,但可以将代码、变量绑定到堆(heap)、栈(stack)。一个块还维护了一系列的状态,这些状态或数据影响着执行的结果。

可以把块组成函数表达式,用于传递给API,或者使用在多线程里。最有用的是回调,因为块在回调时能把代码和数据一起传送。

在OSX 10.6的Xcode中,可以使用块,它随GCC和 Clang一起集成。在OSX 10.6及iOS 4.0以后支持块语法。 块运行时是开源的,它能被集成到 LLVM’s compiler-rt subproject repository中。标准C工作组的 N1370: Apple’s Extensions to C中 ( 其中也包括垃圾回收 ) 对块进行了定义。O-C和C++都来自于C,块在3种语言(包括O-C++)都能工作。

这篇文档中,你会学习到什么是块对象,以及怎样在C,C++和O-C中使用它,使代码的性能和可维护性更高。

开始

声明块

^ 操作符声明一个块变量的开始(跟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));

直接使用块

很多情况下,你不必声明块变量,而简单地写一个行内块并把它当作一个参数,如下面的代码所示。

gsort_b类似标准的 gsort_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方法中使用块。这个方法使用了一个块参数。为了演示,在这里把块定义为一个NSComparator本地变量。

NSArray *stringsArray = [NSArray arrayWithObjects:                                 @"string 1",  @"String 21",@"string 12",

@"String 11", @"String 02", nil];

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修饰符来标识一个变量能够被块改变。使用下面的代码,你可以用一个块变量计算进行比较的字符串中有多少是相同的。为了演示,块是直接使用的,同时currentLocal变量对于块来说是只读的。

NSArray *stringsArray = [NSArray arrayWithObjects:

@"string 1",  @"String 21", // <-

@"string 12",  @"String 11",@"Strîng 21", // <-

@"Striñg 21", // <-

@"String 02", nil];

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派生语言如O-C和C++中,可以把块视为表达式。其他语言中,为了不与C术语中的块混淆,块也被称作closure(国内译作闭包),这里它们都称做blocks。

块的功能

块是行内的代码集合:

?        同函数一样,有类型化参数列表

?        有返回结果或者要申明返回类型

?        能获取同一作用域(定义块的相同作用域)内的状态

?        可以修改同一作用域的状态(变量)

?        与同一范围内的其他块同享变量

?        在作用域释放后能继续共享和改变同一范围内的变量

甚至可以复制块并传递到其他后续执行的线程。编译器和运行时负责把所有块引用的变量保护在所有块的拷贝的生命周期内。对于C和C++,块是变量,但对于O-C ,块仍然是对象。

块的使用

块通常代表小段的、自包含的代码片段。

因此,它们封装为可以并行执行的工作单元额外有用,要么用于在集合中进行遍历,要么在其他操作完成使作为回调。

块代替传统回调函数的意义有两个:

1.             它们允许在方法实现的调用中就近地写入代码。而且块经常被作为框架中一些方法的参数。

2.             它们允许访问本地变量。在进行线程操作时,相比回调函数需要把所需的上下文信息植入数据结构中而言,块直接访问本地变量显然更加简单。

块的声明和创建

声明块变量

块变量引用了块。它的声明语法类似函数指针,除了需要使用^代替*。

void (^blockReturningVoidWithVoidArgument)(void);

int (^blockReturningIntWithIntAndCharArguments)(int, char);

void (^arrayOfTenBlocksReturningVoidWithIntArgument[10])(int);

块支持可变参数(…)。如果块没有参数,则必需使用void来代替整个参数列表。

块是类型安全的,通过设置编译选项,编译器会检查块的调用、参数和返回类型。可以把块变量转换为指针类型,但不能使用*对其解除引用——块的长度在编译时无法确定。

可以创建一个块类型,这样你就可以把块当作一个可以反复多次使用的符号:

typedef float (^MyBlockType)(float, float);

MyBlockType myFirstBlock = // ... ;

MyBlockType mySecondBlock = // ... ;

创建块

块以^开始,以;结束。下面显示了块的定义:

int (^oneFrom)(int);

oneFrom = ^(int anInt) {

return anInt - 1;

};

如果未显式地声明块的返回值类型,可能会自动从块代码中推断返回类型。如果参数列表为void,而且返回类型依靠推断,你可以省略参数列表的void。否则,当块中存在return语句时,它们应当是精确匹配的(可能需要必要的类型转换)。

全局块

可以把块定义为全局变量,在文件级别上使用。

#import <stdio.h>

int GlobalInt = 0;

int (^getGlobalInt)(void) = ^{ return GlobalInt; };

块和变量

本节描述块和变量之间的交互,包括内存管理。

变量类型

在块代码内部,变量会被处理为5种不同情况。

就像函数一样,可以引用3种标准的变量:

?        全局变量,包括静态变量

?        全局函数

?        本地变量及参数(在块范围内)

此外块还支持两种变量:

1.    在函数级别,是__block变量。它们在块范围内是可变的,如果所引用的块被复制到堆后,它们也是被保护的。

2.    const imports.

在方法体内,块还可以引用O-C 实例变量,见 “ 对象和块变量 ”.

在块中使用变量有以下规则:

1.      可访问在同一范围内的全局变量包括静态变量。

2.      可以访问传递给块的参数(如同函数参数)。

3.      同一范围的栈(非static)变量视作const变量。它们的值类似块表达式。嵌套块时,从最近的作用域取值。

4.      在同一范围内声明的变量,如果有__block修饰符修饰,则值是可变的。在该范围内包括同一范围内的其他块对该变量的改变,都将影响该作用域。具体见“__block 存储类型”。

5.      在块的范围内(块体)声明的本地变量,类似于函数中的本地变量。块的每次调用都会导致重新拷贝这些变量。这些变量可作为const或参考(by-reference)变量。

下面演示本地非静态变量的使用:

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);

};

要想在块内改变x的值,需要使用__block修饰x。见“__block存储类型”。

__block 存储类型

你可以规定一个外部的变量是否可变——可读写——通过使用__block存储类型修饰符。__block存储类似但不同于register,auto和static存储类型。

__block变量在变量声明的作用域、所有同一作用域内的块,以及块拷贝之间同享存储。而且这个存储将在栈帧(stack frame)释放时得以保留,只要同一帧内申明的块的拷贝仍然存活(例如,被入栈以便再次使用)。在指定作用域内的多个块能同时使用共享变量。

作为一种优化,块存储使用栈存储,就如同块自身一样。如果使用Block_copy拷贝块(或者在O-C向块发送copy消息),变量被拷贝到堆里。而且,__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‘

}

对象和块变量

块提供了对O-C和C++对象的支持 。

O-C对象

在引用计数的情况下,当你在块中引用一个O-C对象,对象会被retained。甚至只是简单引用这个对象的实例变量,也是一样的。

但对于__block标记的对象变量,就不一样了。

注意:在垃圾回收的情况下,如果同时用__weak和__block修饰变量,块可能不一定保证它是 可用 的。

如果在方法体中使用块,对象实例变量的内存管理规则 比较微妙:

?        如果通过对象引用方式访问实例变量,self 被 retained;

?        如果通过值引用方式访问实例变量,变量是retained;

下面代码演示了这2种情况:

dispatch_async(queue, ^{

// instanceVariable is used by reference, self is retained

doSomethingWithObject(instanceVariable);

});

id localVariable = instanceVariable;

dispatch_async(queue, ^{

// localVariable is used by value, localVariable is retained (not self)

doSomethingWithObject(localVariable);

});

C++ 对象

一般,可以在块中使用C++对象。在成员函数中对成员变量进行引用,俨然是对指针的引用,可以对其进行改变。如果块被拷贝,有两种结果:

如果有__block存储类型的类,该类是基于栈的C++对象,通常会使用复制构造函数;

如果使用了其他块中的基于栈的C++对象,它必需有一个const的复制构造函数。该C++对象使用该构造函数进行拷贝。

拷贝块时,其引用的其它块可能也被拷贝(从顶部开始)。如果有块变量,并且在这个块中引用了一个块,那个块也会被拷贝。

拷贝一个基于栈的块时,你得到的是新的块。拷贝一个基于堆的块时,只是简单的增加了retain数,然后把copy方法/函数的结果返回这个块。

使用块

块的调用

如果把块申明为变量,可以把它当成函数使用,例如:

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

但时常会将块以参数形式传递给一个函数或方法,这样,就会使用行内(inline)块。

把块作为函数参数

在这种情况下,不需要块申明。简单地在需要把它作为参数的地方实现它就行。如下所示,gsort_b是一个类似标准gsort_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" }

注意,块包含在函数的参数列表中。

接下来的例子显示如何在dispath_apply函数中使用块。dispatch_apply的声明是:

void dispatch_apply(size_t iterations, dispatch_queue_t queue, void (^block)(size_t));

这个函数把块提交给dispatch队列以进行调用。它有3个参数:要操作的次数;块被提交到的队列;块——这个块有一个参数——遍历操作的当前次数。

可以用dispatch_apply简单地打印出遍历操作的索引:

#include <dispatch/dispatch.h>

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提供了大量使用块的方法。把块作为参数使用与使用其他类型的参数并无不同。

以下代码判断数组中前5个元素中含有给定filter集合的索引。

NSArray *array = [NSArray arrayWithObjects: @"A", @"B", @"C", @"A", @"B", @"Z",@"G", @"are", @"Q", nil];

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: <NSIndexSet: 0x10236f0>[number of indexes: 2 (in 2 ranges), indexes: (0 3)]

*/

以下代码判断一个NSSet对象中是否包含指定的本地变量,如果是的话把另一个本地变量(found)设置为YES(并停止搜索)。注意found被声明为__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();

对于O-C,则可向块发送copy,retain和release(以及autorelease)消息。

为避免内存泄露,一个Block_copy()总是对应一个Block_release()。每个copy/retain总是有对应的release(或autorelease)——使用垃圾回收则例外。

避免的用法

一个块声明(即^{…})是一个本地栈式数据结构(stack-local data structure)的地址,这个地址就代表了块。本地栈式数据结构是{}围住的复合语句,因此应该避免如下用法:

void dontDoThis() {

void (^blockArray[3])(void);// 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

}

// ...

}

调试

可以在块内设置断点,并进行单步调试。在GDB会话中,使用invoke-block调用块,比如:

$ invoke-block myBlock 10 20

如果需要传递C字符串,必需用双引号把它引住。例如,向doSomethignWithString块传递一个字符串:

$ invoke-block doSomethingWithString "/"this string/""

原文地址:http://blog.csdn.net/kmyhy/article/details/6447287

时间: 2024-10-10 06:18:07

iOS Block详解3的相关文章

IOS Block详解

这是我原先写的OC中关于协议和代理的文章,建议大家阅读此篇文章的时候先阅读此文章,便于大家理解:  IOS Protocol与Delegate详解(一)  IOS Protocol与Delegate详解(二) 官方中对于Block的用途为:  You can use blocks to compose function expressions that can be passed to API, optionally stored, and usedby multiple threads. Bl

iOS Block详解4

代码块本质上是和其他变量类似.不同的是,代码块存储的数据是一个函数体.使用代码块是,你可以像调用其他标准函数一样,传入参数数,并得到返回值. 脱字符(^)是块的语法标记.按照我们熟悉的参数语法规约所定义的返回值以及块的主体(也就是可以执行的代码).下图是如何把块变量赋值给一个变量的语法讲解: 按照调用函数的方式调用块对象变量就可以了:int result = myBlock(4); // result是 28 1.参数是NSString*的代码块 [cpp] view plaincopy voi

iOS插件详解之----CLangFormat(代码格式化管理插件)(2016.1.12王彬)

iOS插件详解之----CLangFormat(代码格式化管理)(2016.1.12王彬) 虽然在项目创建和团队组建的初期,我们就把公共约定以及一些规范定下来了,并且由于我们的代码是通过Git来做版本控制的,web上直接就支持Markdown格式的readme文件,可以随时看到最新的版本,但是这种规范只能依靠个人的意识,或者通过代码Review来解决,而且做代码Review的时候,你也不好意思总是写上一堆诸如“这里要加个空格”.“那里要加上换行”的评论吧?如果不管,久而久之,会因为每个人的习惯不

iOS疯狂详解之开源库

youtube下载神器:https://github.com/rg3/youtube-dl vim插件:https://github.com/Valloric/YouCompleteMe vim插件配置:https://github.com/spf13/spf13-vim ----------------Mac完整项目---------- 电台:https://github.com/myoula/sostart ----------------iOS完整项目---------------- 1,

iOS Animation详解

iOS Animation详解 本篇只要讲解iOS中动画的使用. Animtion主要分为两类:UIView动画和CoreAnimation动画. UIView动画有UIView属性动画,UIViewBlock动画,UIViewTransition动画. 而CoreAnimation动画主要通过CAAnimation和CALayer,常用的有CABasicAnimation,CAKeyframeAnimation,CATransition,CAAnimationGroup. UIView动画 U

iOS SDK详解之NSScanner-分析String

原创blog,转载请注明出处 blog.csdn.net/hello_hwc 欢迎关注我的iOS SDK详解专栏,这里有很多基础的文章 http://blog.csdn.net/column/details/huangwenchen-ios-sdk.html 前言:NSScanner是分析String,把String转为substring和数字的很好的工具.它使用一个NSString初始化,使用的时候通常从开头处扫描直到结尾. 本文会先举出两个例子,然后详细的讲解NSScanner的方法.源码是

Linux的Ext2文件系统(Inode&Block)详解

前述:Linux系统管理员很重要的任务之一就是管理好自己的磁盘文件系统,每个分区不可太大也不可以太小,太大会导致磁盘容量的浪费,太小会导致产生的文件无法存储的问题.在Linux里面文件是由两部分数据组成,一部分是metadata,另一部分是data.那么这些数据都存放在文件系统的什么地方呢?这就让我们必须得了解文件系统的Inode与Block的基本原理了,而Linux最传统的磁盘文件系统使用的是Ext2,所以我们了解下它的内部原理. 第一部分:磁盘的组成和分区(基础) 磁盘的机械部分: 1.圆形

iOS ASIHTTPRequest详解

ASIHTTPRequest对CFNetwork API进行了封装,并且使用起来非常简单,用Objective-C编写,可以很好的应用在Mac OS X系统和iOS平台的应用程序中.ASIHTTPRequest适用于基本的HTTP请求,和基于REST的服务之间的交互. ASIHTTPRequest功能很强大,主要特色如下: l 通过简单的接口,即可完成向服务端提交数据和从服务端获取数据的工作 l 下载的数据,可存储到内存中或直接存储到磁盘中 l 能上传本地文件到服务端 l 可以方便的访问和操作请

iOS SDK详解之NSCoding协议

原创blog,转载请注明出处 http://blog.csdn.net/hello_hwc?viewmode=contents 欢迎关注我的iOS SDK详解专栏 http://blog.csdn.net/column/details/huangwenchen-ios-sdk.html 前言:NSCoding是对iOS中的Model类进行编码和解码必须要遵循的协议,如果一个对象要被归档,那么这个协议是必须的. NSCoding要实现两个方法 - initWithCoder: //解码 - enc