虚位以待(AD)
虚位以待(AD)
首页 > 软件编程 > IOS编程/Objective-C > iOS Blocks 编程(一)

iOS Blocks 编程(一)
类别:IOS编程/Objective-C   作者:码皇   来源:yyh3663477的专栏     点击:

block可以简单看做是一组可执行的代码,block能够捕捉到已声明的同一作用域的变量。block也是闭包,在代码块声明时就将使用的变量包含到代码块范围内。block还是Objective-C对象,我们可以像对象一样传递它们。

先瞧瞧几个例子:

Example 1(Block在动画方面的应用):

    daysView.transform = CGAffineTransformMakeScale(0.1f, 0.1f);
    [UIView animateWithDuration:0.2f delay:0.0f options:UIViewAnimationCurveEaseInOut animations:^{
    daysView.alpha = 1.0f;
    daysView.transform = CGAffineTransformMakeScale(1.05f, 1.05f);
    }
    completion:^(BOOL finished) {
    [UIView animateWithDuration:0.2f delay:0.0f options:UIViewAnimationOptionCurveEaseInOut animations:^{
    daysView.transform = CGAffineTransformIdentity;
    daysView.frame = CGRectMake(point.x - 150.0/2, point.y + 10.0, 150, 100);
    }
    completion:nil];
    }
    ];

Example 2(Block在多线程方面的应用):

    dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_async(concurrentQueue, ^{
    __block UIImage *image = nil;
    dispatch_sync(concurrentQueue, ^{
    /* Download the image here */ }
    );
    dispatch_sync(dispatch_get_main_queue(), ^{
    /* Show the image to the user here on the main queue*/ }
    );
    }
    );

Example 3(Block在单例方面的应用):

    + (id)sharedInstance{
    static id sharedInstance;
    static dispatch_once_t once;
    dispatch_once(&once, ^{
    sharedInstance = [[[self class] alloc] init];
    }
    );
    return sharedInstance;
    }

Example 4(Block在网络请求方面的应用):

    __block MKNetworkOperation *op = [engine operationWithURLString:urlString params:dic httpMethod:@"POST"];
    [op addCompletionHandler:^(MKNetworkOperation *completedOperation) {
    NSString *responseString = [completedOperation responseString];
    NSLog(@"communication requestFinished");
    completeCallBack(responseString);
    }
    errorHandler:^(MKNetworkOperation *completedOperation, NSError *error) {
    NSLog(@"error:%@",error);
    completeCallBack(kHttpRequestError);
    }
    ];
    [engine enqueueOperation:op];

What

iOS4引入了一个新特性,block(也称块代码)。
block可以简单看做是一组可执行的代码,block能够捕捉到已声明的同一作用域的变量。
block也是闭包,在代码块声明时就将使用的变量包含到代码块范围内。
block还是Objective-C对象,我们可以像对象一样传递它们。
Block可以有返回值,可以接受参数。还可以内敛定义,当做独立代码块,与传统C函数类似。

Why

block普遍被认为是实现Callback的一种更为方便的模式,相比Delegate+Protocol需要声明和实现一大推方法而言,block表现得更为灵活。
它们可以让你在调用的地方编写代码实现后面将要执行的操作。因此blocks通常作为框架方法的参数。
它们允许你访问局部变量。而不需要集成所有上下文的信息的数据结构来进行回调,你可以简单的访问局部变量。
Block编写函数表达式,这些表达式可以作为API使用,或可选的存储,或被多个线程使用。
iOS4在UIKit中引入了该特性,此后越来越多的Apple API都使用了block,所以我们应该了解这一知识。

How

1、Block的写法

假设你在Objective-C中有个方法,既能接受NSInteger类的2个整数值,又能通过两个相减返回两者的差值作为NSInteger(可以这么写):

    - (NSInteger) subtract:(NSInteger)paramValue from:(NSInteger)paramFrom{
    return paramFrom - paramValue;
    }

把Objective-C代码翻译成纯粹C函数变成:

    NSInteger subtract(NSInteger paramValue, NSInteger paramFrom){
    return paramFrom - paramValue;
    }

如果是代码块又该如何编码?

    NSInteger (^subtract)(NSInteger, NSInteger) = ^(NSInteger paramValue, NSInteger paramFrom){
    return paramFrom - paramValue;
    }
    ;

Block的写法

2、各种不同情况的Block

普通情况下使用block

    NSInteger count = 10;
    NSInteger (^theBlock)(NSInteger) = ^(NSInteger num){
    return count * num;
    }
    ;
    NSLog(@"count * num: %d",theBlock(10));

结果输出:BlockTest[1037:11303] count * num: 100
那么,
1、block可以使用相同作用域范围内定义的变量。
2、如果你声明一个block作为变量,可以把它当成函数来使用。

一个内联(inline)的block

    dispatch_queue_t mainQueue = dispatch_get_main_queue();
    dispatch_async(mainQueue, ^(void) {
    [[[[UIAlertView alloc] initWithTitle:@"Inline-Block" message:@"Block is amazing!" delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil,nil] autorelease] show];
    }
    );

那么,
1、很多情况下,你不需要去声明一个block变量,可以简单的写一个内联block。
2、可以看到dispatch_async函数的最后一个参数为block。

在Cocoa frameworks下的block

    NSDictionary *femaleStars = [NSDictionary dictionaryWithObjectsAndKeys:@"Fan BB",@"First",@"Liu SS",@"second",@"Liu Yifei",@"Third",@"Lin Zhiling",@"fourth", nil];
    [femaleStars enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
    NSLog(@"key : %@ , value : %@",key,obj);
    }
    ];

结果输出:

    BlockTest[1760:11303] key : First , value : Fan BBBlockTest[1760:11303] key : second , value : Liu SSBlockTest[1760:11303] key : Third , value : Liu YifeiBlockTest[1760:11303] key : fourth , value : Lin Zhiling

那么,
1、在 Cocoa frameworks 里面有部分方法使用 block 作为参数
2、一般是在操作完成的时候作为回调使用

3、声明和创建block

Block的声明和函数指针的声明类似,block使用^修饰符代替*修饰符,block类型可完全操作其他C系统类型:

    void (^blockReturningVoidWithVoidArgument)(void);
    int (^blockReturningIntWithIntAndCharArguments)(int, char);
    void (^arrayOfTenBlocksReturningVoidWithIntArgument[10])(int);

Blocks被设计成类型安全的,你可以把一个block引用强制转换成任意类型的指针。
在文件级别,你可以把 block 作为全局标示符:

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

———
+ (void)doSomethingWithIndex:(NSInteger)index block:(NSInteger(^)(NSInteger,NSInteger))block;
第一眼看上去,不容易理解。
所以为了提高可读性和避免在.h和.m中出现重复,使用typedef修改。
typedef NSInteger(^MyJob)(NSInteger,NSInteger);
+ (void)doSomethingWithIndex:(NSInteger)index block:(MyJob)block;

类方法的具体实现:
+ (void)doSomethingWithIndex:(NSInteger)index block:(MyJob)block{

    NSInteger result = 0;
    for (int i = 0 ;
    i < index;
    i++) {
    result = block(i,pow(10, i)) + result;
    }
    NSLog(@"result:%d",result);
    }

Block的创建和实现:

    [NDBlockTest doSomethingWithIndex:3 block:^(NSInteger num1, NSInteger num2) {
    return num1 + num2;
    }
    ];

4、Block与变量的关系

全局变量可访问,包括在相同作用域范围内的静态变量。
传递给 block 的参数可访问。
属于同一作用域范围的堆(非静态的)变量作为const变量(即只读)。
属于同一作用域范围内并被__block 存储修饰符标识的变量作为引用传递是可变的。
同一作用域范围内给定的多个 block 可以同时使用一个共享变量。
在独立的块代码中,你不能直接访问self,如果想访问,把Object作为参数传递给块代码。
在独立的块代码中,你不能使用dot notatiion(点表达式)读写一个已声明属性。
在内联block中,有一条重要规则:内联block在其方法作用域范围内会为这些变量复制值。

全局变量可访问,包括在相同作用域范围内的静态变量。
传递给 block 的参数可访问。

    const int number = 10;
    +(void)blockTest4 {
    int x = 123;
    void (^printXAndY)(int) = ^(int y) {
    printf("result : %d %d %dn", x, y, number);
    }
    ;
    printXAndY(100);
    }

输出结果:result : 123 100 10

属于同一作用域范围的堆(非静态的)变量作为const变量(即只读)。
属于同一作用域范围内并被__block 存储修饰符标识的变量作为引用传递是可变的。
同一作用域范 围内给定的多个 block 可以同时使用一个共享变量。

    int x = 123;
    void (^printXAndY)(int) = ^(int y) {
    x = x + y;
    //error printf("result : %d %d %dn", x, y, number);
    }
    ;
    printXAndY(100);

———

    __block int x = 123;
    void (^printXAndY)(int) = ^(int y) {
    x = x + y;
    //true printf("result : %d %d %dn", x, y, number);
    }
    ;
    printXAndY(100);

在独立的块代码中,你不能直接访问self,如果想访问,把Object作为参数传递给块代码。
在独立的块代码中,你不能使用点表达式读写一个已声明属性。

    void (^viewTest1)(NSInteger) = ^(NSInteger num) {
    self.editing = YES;
    //error}
    ;
    void (^viewTest2)(id) = ^(id self) {
    [self.view setBackgroundColor:[UIColor redColor]];
    //error}
    ;
    void (^viewTest3)(id) = ^(id self) {
    [[self view] setBackgroundColor:[UIColor redColor]];
    //true}
    ;

在内联block中,有一条重要规则:内联block在其方法作用域范围内会为这些变量复制值。
- (void)scopeTest {

    NSUInteger integerValue = 10;
    BlockWithNoParams myBlock = ^{
    NSLog(@"Integer value inside the block = %lu", (unsigned long)integerValue);
    }
    ;
    integerValue = 20;
    myBlock();
    NSLog(@"Integer value outside the block = %lu",(unsigned long)integerValue);
    }

输出结果为:

    Integer value inside the block = 10Integer value outside the block = 20

除非用存储类__block 前缀限定,否则Block Object 只会把方法作用域的局部变量传作为只读变量传递给Block Object。

5、Block的使用

如果你声明一个block作为变量,可以把它当成函数来使用。
你可以把一个 block 作为函数的参数就像其他任何参数那样。然而在很多情况下, 你不需要声明 blocks;相反你只要简单在需要它们作为参数的地方内联实现它们。
Cocoa 提供了一系列使用 block 的方法。你可以把一个block 作为方法的参数就像其他参数那样。

copy相关
作为一种优化,block 存储在栈上面,就像blocks 本身一样。如果使用Block_copy拷贝了 block 的一个副本(或者在 Objective-C 里面给 block 发送了一条 copy 消息), 变量会被拷贝到堆上面。所以一个__block 变量的地址可以随时间推移而被更改。
当你拷贝一个 block 时,任何在该 block 里面对其他 blocks 的引用都会在需要的时候被拷贝,即拷贝整个目录树(从顶部开始)。如果你有 block 变量A并在该 block 变量A里面引用其他的 block 变量B,那么那个 block 变量B会被拷贝一份。
当你拷贝一个基于栈的 block 时,你会获得一个新的 block 。但是如果你拷贝一个基于堆的 block ,你只是简单的递增了该 block 的引用数,并把原始的block 作为函数或方法的返回值。

在你希望 block 在它被声明的作用域被销毁后继续使用的话,你只需要做一份拷贝。拷贝会把 block 移到堆里面。你可以使用 C 函数来 copy 和 release 一个 block:
Block_copy();
Block_release();
如果你使用 Objective-C,你可以给一个 block 发送 copy、retain 和 release(或 autorelease)消息。
为了避免内存泄露,你必须总是平衡 Block_copy()和 Block_release()。你必须平衡 copy 或 retain 和 release(或 autorelease) —— 除非是在垃圾回收的环境里面。

编写返回代码块的方法
有时我们会需要编写一个返回块代码的方法,如:

    + (ComputationBlock)raisedToPower:(int)y {
    ComputationBlock block = ^(int x) {
    return (int)pow(x, y);
    }
    ;
    return block;
    // Don'
    t do this!}

但是当我们运行它时,会得到运行时错误”EXC_BAD_ACCESS”。
解决这个问题的关键是了解代码块是怎么分配内存的。代码块的生命周期是在栈中开始的,因为在栈中分配内存是比较快的。是栈变量也就意味着它从栈中弹出后就会被销毁。方法返回结果就会发生这样的情况。解决办法是在返回之前将代码块从栈中移到堆中。

    + (ComputationBlock)raisedToPower:(int)y {
    ComputationBlock block = ^(int x) {
    return (int)pow(x, y);
    }
    ;
    return [[block copy] autorelease];
    }

6、使用Block简化回调

使用 Block 最大的便利就是简化的回调过程。以前使用UIView 的动画,进程要控制动画结束后进行相应的处理。iOS 4.0 之后,UIView 新增了对 Block 的支持,现在只要使用简单的一个Block代码就可以在写动画的代码部分直接添加动画结束后的操作。还有就是在使用 Notification 时候 Block 也非常有帮助。

Block 代替delegate的简单实现:
http://www.cocoachina.com/bbs/job.php?action=download&aid=51512

具体可以看github的一个demo,
https://github.com/zhangxigithub/BlockUI

7、Block内存相关

在引用计数的环境里面,
1、默认情况下当你在Block里面引用一个Objective-C对象的时候,该对象会被retain;
2、当你简单的引用了一个对象的实例变量时,它也会被retain。
因为这样,你经常会添加__block存储类型修饰符标识,这样做对象的变量才不会被retain。

如果你在实现方法的时候使用了block,则你必须遵循:
1、如果你通过引用来访问一个实例变量,self会被retain。
2、如果你通过值来访问一个实例变量,那么变量会被retain。

最后,按照喵神的说法就是,如果你使用ARC,那就不要管内存的情况了。

相关热词搜索: