Block Object

BLOCK教程系列


本文主要是阐述一下Block中如何的使用外部变量以及block本身的内存管理。

构建Block Object
//Block Object
NSString* (^intToString)(NSUInteger) = ^(NSUInteger paramInteger){
    NSString *result = [NSString stringWithFormat:@"%lu",(unsigned long)paramInteger];
    return result;
};

- (void) callIntToString{
    NSString *string = intToString(10);
    NSLog(@"string = %@", string);
}
//typedef 这个 intToStringBlock Object 的签名, 这个签名会告诉 编译器我们的 Block Object 会接受什么参数:
//这个 typedef 告诉编译器 Block Objects 接受一个整数参数并且返回一个被 IntToString Converter 命名的标 示符来展现的字符串。
typedef NSString* (^IntToStringConverter)(NSUInteger paramInteger);

- (NSString *) convertIntToString:(NSUInteger)paramInteger
                 usingBlockObject:(IntToStringConverter)paramBlockObject
{
    return paramBlockObject(paramInteger);
}

- (void) doTheConversion{
    //此处的intToString为上面定义的NSString* (^intToString)(NSUInteger)
    NSString *result = [self convertIntToString:123 usingBlockObject:intToString];
    NSLog(@"result = %@",result);
}

- (void) doTheConversionExt{
    //定义block块的内容
    IntToStringConverter inlineConverter = ^(NSUInteger paramInteger){
        NSString *result = [NSString stringWithFormat:@"%lu",(unsigned long)paramInteger];
        return result;};
    NSString *result = [self convertIntToString:123 usingBlockObject:inlineConverter];
    NSLog(@"result = %@", result);
}
//内联
- (void) doTheConversionExt2{
    //block块作为参数的写法^NSString *(NSUInteger paramInteger){}
    NSString *result =
    [self convertIntToString:123 usingBlockObject:^NSString *(NSUInteger paramInteger)
     {
         NSString *result = [NSString stringWithFormat:@"%lu",(unsigned long)paramInteger];
         return result;
     }];
    NSLog(@"result = %@", result);
}

Block Object中获取变量
//获得self
- (void) simpleMethod3{
    NSMutableArray *array = [[NSMutableArray alloc]
                             initWithObjects:@"obj1",
                             @"obj2", nil];
    [array sortUsingComparator:^NSComparisonResult(id obj1, id obj2) {
        NSLog(@"self = %@", self);
        /* Return value for our block object */
        return NSOrderedSame;
    }]; }



//无变化Block Object要获得 self需要传参数进去
void (^correctBlockObject)(id) = ^(id self){
    NSLog(@"self = %@", self);
};
- (void) callCorrectBlockObject{
    correctBlockObject(self);
}


//修改类属性
- (void) simpleMethod4{
    NSMutableArray *array = [[NSMutableArray alloc]
                             initWithObjects:@"obj1",
                             @"obj2", nil];
    [array sortUsingComparator:^NSComparisonResult(id obj1, id obj2) {
        NSLog(@"self = %@", self);
        self.stringProperty = @"Block Objects";
        NSLog(@"String property = %@", self.stringProperty);
        /* Return value for our block object */
        return NSOrderedSame;
    }];
}

//在独立 Block Object 内部,你不能使用 dot notation 读写一个已声明属性,在这个场景中可以使用这个合成属性的 getter and setter 方法来代替 dot notation:
void (^correctBlockObject1)(id) = ^(id self){
    NSLog(@"self = %@", self);
    /* This will work fine */
    [self setStringProperty:@"Block Objects"];
    /* This will work fine as well */
    NSLog(@"self.stringProperty = %@",[self stringProperty]);
};

-(void)log2{
    correctBlockObject1(self);
}

//当出现在内联 Block Objects 中,有一条非常重要的规则你必须记住:内联 Block Objects 在其词法区域 会为这些变量复制值。此处发生的事情是在执行 Block 的地方 Block Object 自身一直有一个 integerValue 变量的只读复制。
typedef void (^BlockWithNoParams)(void);
- (void) scopeTest{
    NSUInteger integerValue = 10;
    /*************** Definition of internal block object ***************/
    BlockWithNoParams myBlock = ^{
        NSLog(@"Integer value inside the block = %lu", (unsigned long)integerValue);
    };
    /*************** End definition of internal block object ***************/
    integerValue = 20;
    /* Call the block here after changing the
     value of the integerValue variable */
    myBlock();
    //此时integerValue的值并没有被改变,仍然 = 10
    NSLog(@"Integer value outside the block = %lu",(unsigned long)integerValue);
}

//可改变变量值测试
-(void) scopeTest2{
    __block NSUInteger integerValue = 10;
    /*************** Definition of internal block object ***************/
    BlockWithNoParams myBlock = ^{
        NSLog(@"Integer value inside the block = %lu", (unsigned long)integerValue);
    };
    /*************** End definition of internal block object ***************/
    integerValue = 20;
    /* Call the block here after changing the
     value of the integerValue variable */
    myBlock();
    //加上__block后integerValue的值就可以修改,此时值 = 20
    NSLog(@"Integer value outside the block = %lu",(unsigned long)integerValue);
}


先定义一个block变量,作为后续的例子中使用:
typedef void(^BlockCC)(void); 
BlockCC _block; 

1、block中引用外部变量

block中可以直接使用外部的变量,比如
int number = 1; 
_block = ^(){ 
    NSLog(@"number %d", number); 
}; 
 
那么实际上,在block生成的时候,是会把number当做是常量变量编码到block当中。可以看到,以下的代码,block中的number值是不会发生变化的:
int number = 1; 
_block = ^(){ 
    NSLog(@"number %d", number); 
}; 
number = 2; 
_block(); 
则输出的值为 1,而不是2。原因就是如上所说。
 
如果要在block中尝试改变外部变量的值,则会报错的。对于这个问题的解决办法是引入__block标识符。将需要在block内部修改的变量标识为__block(两个下划线“_”) scope。更改后的代码如下:
__block int number = 1; 
_block = ^(){ 
    number++; 
    NSLog(@"number %d", number); 
}; 
而这个时候,其实block外部的number和block内部的number指向了同一个值,回到刚才的在外部改变block的例子,它的输出结果将是2,而不是1。有兴趣的可以自己写一个例子试试。
 

2、block自身的内存管理

block本身是像对象一样可以retain,和release。但是,block在创建的时候,它的内存是分配在栈(stack)上,而不是在堆(heap)上他本身的作于域是属于创建时候的作用域,一旦在创建时候的作用域外面调用block将导致程序崩溃。比如下面的例子。
我在view did load中创建了一个block:
- (void)viewDidLoad 
{ 
    [superviewDidLoad]; 
  
    int number = 1; 
    _block = ^(){ 
  
    NSLog(@"number %d", number); 
    }; 
} 
并且在一个按钮的事件中调用了这个block:
- (IBAction)testDidClick:(id)sender { 
    _block(); 
} 
此时我按了按钮之后就会导致程序崩溃,解决这个问题的方法就是在创建完block的时候需要调用copy的方法。copy会把block从栈上移动到堆上,那么就可以在其他地方使用这个block了~
修改代码如下:
_block = ^(){ 
    NSLog(@"number %d", number); 
}; 
  
_block = [_block copy]; 
同理,特别需要注意的地方就是在把block放到集合类当中去的时候,如果直接把生成的block放入到集合类中,是无法在其他地方使用block,必须要对block进行copy。不过代码看上去相对奇怪一些:
[array addObject:[[^{ 
    NSLog(@"hello!"); 
} copy] autorelease]]; 

3、循环引用

这一点其实是在第一点的一个小的衍生。当在block内部使用成员变量的时候,比如
@interface ViewController : UIViewController 
{ 
    NSString *_string; 
} 
@end 
在block创建中:
_block = ^(){ 
    NSLog(@"string %@", _string); 
}; 
这里的_string相当于是self->_string;那么block是会对内部的对象进行一次retain。也就是说,self会被retain一次。当self释放的时候,需要block释放后才会对self进行释放,但是block的释放又需要等self的dealloc中才会释放。如此一来变形成了循环引用,导致内存泄露。
 
修改方案是新建一个__block scope的局部变量,并把self赋值给它,而在block内部则使用这个局部变量来进行取值。因为__block标记的变量是不会被自动retain的
__block ViewController *controller = self; 
_block = ^(){ 
    NSLog(@"string %@", controller->_string); 
}; 

4、带返回值与参数的block

//声明和定义在一起:
void (^simpleBlock)(void) = ^{
    NSLog(@"This is a block");
};

//带返回值和参数的block:
double (^multiplyTwoValues)(double, double) =
                              ^(double firstValue, double secondValue) {
                                  return firstValue * secondValue;
                              };
 
    double result = multiplyTwoValues(2,4);
 
    NSLog(@"The result is %f", result);

5、如果block是递归调用的,必须设置成 __block

__block int (^recursiveBlock)(int) = ^(int param) {
		if (param == 1) {
			return 1;
		}
		return (param--) * recursiveBlock(param);
};

recursiveBlock(5);	// 计算5的阶乘

6、CocoaBlocks 

Cocoa frameworks里面有部分方法使用block作为参数,通常不是执行一个对象的集合操作,就是在操作完成的时候作为回调使用。下面的例子显示了如何通过NSArray的方法sortedArrayUsingComparator:使用block。该方法使用一个参数,即block。为了举例说明,该情况下block被定义为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"
     )
     */



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值