首页 > 代码库 > Objective-c中的Block(块)详解

Objective-c中的Block(块)详解

Block初探

在Objective-c中NSArray是很常用的容器之一,很多时候我们需要对数组中的数据进行排序,因此与下面类似的代码会经常碰到: 

NSArray *sortedArray = [array sortedArrayUsingComparator: ^(id obj1, id obj2) {
 
    if ([obj1 integerValue] > [obj2 integerValue]) {
        return (NSComparisonResult)NSOrderedDescending;
    }
 
    if ([obj1 integerValue] < [obj2 integerValue]) {
        return (NSComparisonResult)NSOrderedAscending;
    }
    return (NSComparisonResult)NSOrderedSame;
}];
刚接触objc的朋友可能会对这段代码表示看不懂。
sortedArrayUsingComparator: ^(id obj1, id obj2) {
     // 代码省略
}];
这个以^开头的参数类型为NSComparetor,原型如下 : 

typedef NSComparisonResult (^NSComparator)(id obj1, id obj2); 

其实这个Comparetor的功能就类似于C语言中的函数指针,以及C++11、Java 8 、C#等语言中的Lambda表达式。

C语言中的函数指针

        我们首先来回顾一下C语言中的最简单的函数指针,在C语言中我们以

returntype (*name) (arg);
的格式定义一个函数指针变量。或者以 typedef的形式定义一个函数指针类型。示例代码如下 : 

// C语言的函数指针声明
int (*addInC)(int , int ) ;

// 将此类型的函数指针声明为一个AddOperation类型
typedef int (*AddOperation)(int, int);

// 普通C语言函数
int doAdd(int a, int b) {
    return a + b ;
}

//
int main(int argc, const char * argv[])
{

    @autoreleasepool {
        // addInC指针变量指向doAdd函数
        addInC = doAdd;
        NSLog(@" addInC : %i.", addInC(5, 6)) ;	// 函数指针调用
        // 声明一个aop函数指针变量,也指向doAdd
        AddOperation aop = doAdd ;
        NSLog(@" AddOperation : %i.", aop(100, 23) );
    }
    return 0;
}
输出 : 

addInC : 11.
AddOperation : 123.
首先是生命一个函数指针,addInC,然后在main函数中将addInC指向doAdd函数,这样调用addInC(5, 6)就等于调用了doAdd(5, 6)。同理AddOperation也是这个原理。Java 8中的Lambda表达式

Objc中的块

现在我们回到Objc中的Block, 就是上面NSArray中排序用到的块。我们看看官方的定义 : 

Blocks are a language-level feature added to C, Objective-C and C++, which allow you to create distinct segments of code that can be passed around to methods or functions as if they were values. Blocks are Objective-C objects, which means they can be added to collections like NSArray or NSDictionary. They also have the ability to capture values from the enclosing scope, making them similar to closures or lambdas in other programming languages.
最后一句明确说明了块类似于其他语言中的lambda以及闭包。

       我们看看块的声明格式 :

returnType (^name) (arguments) 
其中returnType代表返回值类型,name代表变量名,argments代表参数列表,如果没有参数可以写成(void)或者()。

再看看块的定义格式 :

^ [returnType] (arguments) {  statements };
其中returnType是可选的,arguments是参数列表,statements是实现功能的代码。我们看如下示例 :

// 有返回值, 但是在定义块函数时返回值可选.
int (^addUseBlock) (int, int) = ^/* int*/ (int a, int b ) { return a + b ; };
我们声明了addUseBlock变量,然后让其指向一个有两个int参数的块。在定义时返回类型int可以省略。调用代码如下:

        // 使用Block函数
        NSLog(@" add use Blockm, Result ==> %i.", addUseBlock(3,4) );
最终会返回 3 + 4的结果。

将块定义为一种类型

我们可以想C语言中的函数指针一样,使用typedef将块定义为一种类型,这样便于将块当做函数传递。下面我们定义一个Comparetor类型,有一个int返回值,有两个int行参数,示例如下:

// 定义一个Compare类型
typedef int (^Comparetor) (int arg1, int arg2) ;
这个看起来就很像NSArray中的NSComparator了,只是返回的类型不同而已。
这个时候我们使用块来当做参数就容易多了,如下示例 : 

// Block当做参数
int helpCompare(Comparetor cmp ) {
    return cmp(3, 4);
}

// 返回一个Block对象
Comparetor getComparetor(){
    return ^(int a, int b) { return a > b ? -1 : 1; };
}

//
int main(int argc, const char * argv[])
{

    @autoreleasepool {

        // 使用Comparetor类型定义Block
        Comparetor cmp = ^(int a, int b) {
            if ( a == b ) {
                return 0 ;
            }
            return a > b ? 1 : -1 ;
        } ;
        
        NSLog(@"use Comparetor, Result ==> %i.", cmp(3,4) );
        // 使用Comparetor类型的cmp当做参数
        NSLog(@"use Comparetor, Result ==> %i.", helpCompare(cmp) );
        
        // 使用Comparetor类型的匿名函数当做参数, 该匿名block固定返回123
        NSLog(@"use Comparetor, Result ==> %i.", helpCompare( ^(int a, int b) {return 123; } ) );
        // 通过函数返回一个Block, 并且把它当做参数传递给helpCompare
        NSLog(@"use Comparetor, Result ==> %i.", helpCompare( getComparetor() ) );
        
    }
    return 0;
}
输出如下 : 

use Comparetor, Result ==> -1.
use Comparetor, Result ==> -1.
use Comparetor, Result ==> 123.
use Comparetor, Result ==> 1.

块使用上下文变量

块代码也可以使用其所在范围的变量,但是块代码中不能修改该变量的值或者引用, 例如块所在函数的变量、类的成员变量。如果需要在块代码中修改变量值,可以使用__block定义变量。示例如下 : 

int main(int argc, const char * argv[])
{

    @autoreleasepool {
        // 使用上下文变量
        int valueInMain = 222 ;
        
        void (^printValue)() = ^() {
            NSLog(@"使用函数上下文的变量(不能修改) : %i.", valueInMain) ;
        } ;
        printValue();
        
        __block int anInteger = 42;
        void (^testBlock)(void) = ^{
            anInteger = 100;
            NSLog(@"使用__block声明的变量(可以修改) : %i", anInteger);
        };
        testBlock();
        
    }
    return 0;
}

输出如下 :

使用函数上下文的变量(不能修改) : 222.
使用__block声明的变量(可以修改) : 100