iOS之Block代码块的定义及使用


不会使用Block的iOS程序员,不是一个合格的程序员

Block没有你想象中的那么难,不要害怕,不要畏惧,勇敢尝试


Block进阶:

点击打开链接1

点击打开链接2

点击打开链接3


Block其实就是一个代码块,把你想要执行的代码封装在这个代码块里,等到需要的时候再去调用。

个人觉得Block优势如下:第一可以使代码看起来更简单明了,第二可以取代以前的delegate使代码的逻辑看起来更清晰。


Block代码块和普通函数都是一段代码,两者有什么区别?

苹果官网Block文档是这样描述的:


Block代码:是一个函数对象,是在程序运行过程中产生的;

普通函数:是一段固定代码,产生于编译期;


借一张图表达基本定义:


完整定义如下:
returnType (^blockName)(parameterTypes) = ^returnType(parameters) {...};

注1: Block的声明与赋值只是保存了一段代码段,必须调用才能执行内部代码
注2: ^被称作"脱字符"


void (^myBlock1)(void);  //无返回值,无参数
void (^myBlock2)(NSObject, int); //无返回值,有参数
NSString* (^myBlock3)(NSString* name, int age); //有返回值和参数,并且在参数类型后面加入了参数名(仅为可读性)


Block变量的声明

Block变量的声明格式为: 返回值类型(^Block名字)(参数列表);

注3:形参变量名称可以省略,只留有变量类型即可

// 声明一个无返回值,参数为两个字符串对象,叫做aBlock的Block
void(^aBlock)(NSString *x, NSString *y);

// 形参变量名称可以省略,只留有变量类型即可
void(^aBlock)(NSString *, NSString *);


巧记Block格式

很多人觉得Block格式定义很难记,其实我们可以通过与 java 函数方法对比加强记忆

int     getResult   (String a, String b)  // Java的方法声明

int  (^MyBlockName) (String a, String b)  // iOS的block声明


block的定义

/*定义属性,block属性可以用strong修饰,也可以用copy修饰 */
 @property (nonatomic, strong) void(^myBlock)();                   //无参无返回值
 @property (nonatomic, strong) void(^myBlock1)(NSString *);        //带参数无返回值
 @property (nonatomic, strong) NSString *(^myBlock2)(NSString *);  //带参数与返回值

 //定义变量
 void(^myBlock)() = nil;                   //无参无返回值
 void(^myBlock1)(NSString *) = nil;        //带参数无返回值
 NSString *(^myBlock2)(NSString *) = nil;  //带参数与返回值

block被当做方法的参数
格式:(block类型)参数名称
 - (void)test:(void(^)())testBlock                    //无参无返回值
 - (void)test1:(void(^)(NSString *))testBlock         //带参数无返回值
 - (void)test2:(NSString *(^)(NSString *))testBlock   //带参数与返回值


使用typedef定义block
 typedef void(^myBlock)();                            //以后就可以使用myBlock定义无参无返回值的block
 typedef void(^myBlock1)(NSString *);                 //使用myBlock1定义参数类型为NSString的block
 typedef NSString *(^myBlock2)(NSString *);           //使用myBlock2定义参数类型为NSString,返回值也为NSString的block
 //定义属性
 @property (nonatomic, strong) myBlock testBlock;
 //定义变量
 myBlock testBlock = nil;
 //当做参数
 - (void)test:(myBlock)testBlock;

以下两者等价:

- (void) testAnimations:(void       (^)    (void) ) animations;  // 无参数

- (void) testAnimations:(void       (^)    ()            ) animations;  // 无参数



block的赋值
格式:block = ^返回值类型 (参数列表) {函数主体}

注4: 通常情况下,可以省略返回值类型,因为编译器可以从存储代码块的变量中确定返回值的类型。

没有参数没有返回值
myBlock testBlock = ^void(){
     NSLog(@"test");
 };

没有返回值,void可以省略
myBlock testBlock1 = ^(){
     NSLog(@"test1");
 };

没有参数,小括号也可以省略
myBlock testBlock2 = ^{
     NSLog(@"test2");
 };

有参数没有返回值
myBlock1 testBlock = ^void(NSString *str) {
      NSLog(str);
}

省略void
myBlock1 testBlock = ^(NSString *str) {
      NSLog(str);
}

有参数有返回值
myBlock2 testBlock = ^NSString *(NSString *str) {
     NSLog(str)
     return @"hi";
}

有返回值时也可以省略返回值类型
 myBlock2 testBlock2 = ^(NSString *str) {
     NSLog(str)
     return @"hi";
}



声明Block变量的同时进行赋值
int(^myBlock)(int) = ^(int num){
    return num * 7;
};

// 如果没有参数列表,在赋值时参数列表可以省略
void(^aVoidBlock)() = ^{
    NSLog(@"I am a aVoidBlock");
};

注5:如果没有参数,= 左边用括号表示,= 右边参数可以省略


Block变量的调用

// 调用后控制台输出"Li Lei love Han Meimei"
aBlock(@"Li Lei",@"Han Meimei");

// 调用后控制台输出"result = 63"
NSLog(@"result = %d", myBlock(9));

// 调用后控制台输出"I am a aVoidBlock"
aVoidBlock();

Block作为OC函数参数
// 1.定义一个形参为Block的OC函数
- (void)useBlockForOC:(int(^)(int, int))aBlock
{
    NSLog(@"result = %d", aBlock(300,200));
}

// 2.声明并赋值定义一个Block变量
int(^addBlock)(int, int) = ^(int x, int y){
    return x+y;
};

// 3.以Block作为函数参数,把Block像对象一样传递
[self useBlockForOC:addBlock];

// 将第2点和第3点合并一起,以内联定义的Block作为函数参数
[self useBlockForOC:^(int x, int y){
    return x+y;
}];


注意:当block作为方法参数时,定义(形参)和使用(实参)格式不一样。

    // 定义block作为参数
    + (RACSignal *)createSignal:(RACDisposable *  (^) (id<RACSubscriber> subscriber)  )didSubscribe {
        return [RACDynamicSignal createSignal:didSubscribe];
    }
    
    // 使用block作为参数
    RACSignal *signal = 

    [RACSignal createSignal:^  RACDisposable *   ( id<RACSubscriber> subscriber) {

        [subscriber sendNext:letterSubject];
        [subscriber sendNext:numberSubject];
        [subscriber sendCompleted];
        return nil;  // 如果不为空,返回类型是 RACDisposable *
    }];



Block在定义时并不会执行内部的代码,只有在调用时候才会执行。

使用示例:

// 在AAViewController.h定义

@property (nonatomic, copy) void (^successBlock)(NSInteger count);

// 在AAViewController.m赋值

if (self.successBlock && !_willUpdate){
    self.successBlock([self.cards count]);
}


在BViewController.m中调用:

AAViewController *aa=[[[AAViewController alloc] init];
// 回调要如何处理
aa.successBlock=^(NSInteger count) {
    if (count==0) {
	// 处理代码     
     }
};
[aa httpRequest];


利用typedef为Block进行重命名

我们可以使用typedef为block进行一次重命名,方法跟为函数指针进行重命名是一样的:
typedef int (^Sum) (int,  int);
这样我们就利用typedef定义了一个block,这个block的名字就是Sum,需要传入两个参数。当我们需要使用时,就可以这样做了:
Sum mysum = ^(int a, int b) {
 
                n = 2;
 
                return (a + b)*n;
            };
这样就完整的定义好了一个block了,接下来的使用如下:
#import <Foundation/Foundation.h>
 
typedef int (^Sum) (int, int);
 
int main(int argc, const char * argv[])
{
    __block int n = 1;
 
    @autoreleasepool {
 
            Sum mysum = ^(int a, int b) {
 
                n = 2;
 
                return (a + b)*n;
            };
 
            NSLog(@"(3 + 5) * %i = %d", n, mysum(3, 5));
 
    }
    return 0;
}

Block在内存中的位置

根据Block在内存中的位置分为三种类型NSGlobalBlock,NSStackBlock, NSMallocBlock。

NSGlobalBlock:类似函数,位于text段;
NSStackBlock:位于栈内存,函数返回后Block将无效;
NSMallocBlock:位于堆内存。

示例1:

BlkSum blk1 = ^ long (int a, int b) {
  return a + b;
};
NSLog(@"blk1 = %@", blk1);// 打印结果:blk1 = <__NSGlobalBlock__: 0x47d0>



示例2:

int base = 100;
BlkSum blk2 = ^ long (int a, int b) {
  return base + a + b;
};
NSLog(@"blk2 = %@", blk2); // 打印结果:blk2 = <__NSStackBlock__: 0xbfffddf8>


示例3:

BlkSum blk3 = [[blk2 copy] autorelease];
NSLog(@"blk3 = %@", blk3); // 打印结果:blk3 = <__NSMallocBlock__: 0x902fda0>


blk1和blk2的区别在于:

blk1没有使用Block以外的任何外部变量,Block不需要建立局部变量值的快照,这使blk1与一般函数没有任何区别

blk2与blk1唯一不同是的使用了局部变量base,

注意1:在定义(注意“定义”,不是“运行”)blk2时,局部变量base当前值被copy到栈上,作为常量供Block使用。执行下面代码,结果是203,而不是204。

int base = 100;
  base += 100;
  BlkSum sum = ^ long (int a, int b) {
    return base + a + b;
  };
  base++;
  printf("%ld",sum(1,2));

在Block内变量base是只读的,如果想在Block内改变base的值,在定义base时要用 __block修饰:__block int base = 100;

  __block int base = 100;
  base += 100;
  BlkSum sum = ^ long (int a, int b) {
    base += 10;
    return base + a + b;
  };
  base++;
  printf("%ld\n",sum(1,2));
  printf("%d\n",base);

输出将是214,211。

注意2:Block中使用__block修饰的变量时,将取变量此刻运行时的值,而不是定义时的快照。这个例子中,执行sum(1,2)时,base将取base++之后的值,也就是201,再执行Blockbase+=10; base+a+b,运行结果是214。执行完Block时,base已经变成211了。


局部自动变量:在Block中只读。Block定义时copy变量的值,在Block中作为常量使用,所以即使变量的值在Block外改变,也不影响他在Block中的值。

int base = 100;
BlkSum sum = ^ long (int a, int b) {
  // base++; 编译错误,只读
  return base + a + b;
};
base = 0;
printf("%ld\n",sum(1,2)); // 这里输出是103,而不是3


static变量、全局变量。如果把上个例子的base改成全局的、或static。Block就可以对他进行读写了。因为全局变量或静态变量在内存中的地址是固定的,Block在读取该变量值的时候是 直接从其所在内存读出,获取到的是最新值,而不是在定义时copy的常量


static修饰变量,效果与_ _block一样

static int base = 100;
BlkSum sum = ^ long (int a, int b) {
  base++;
  return base + a + b;
};
base = 0;
printf("%d\n", base);
printf("%ld\n",sum(1,2)); // 这里输出是3,而不是103
printf("%d\n", base);

输出结果是:

0     

4     

1

表明Block外部对base的更新会影响Block中的base的取值,同样Block对base的更新也会影响Block外部的base值。


Block变量,被__block修饰的变量称作Block变量基本类型的Block变量等效于全局变量、或静态变量


retain cycle

retain cycle问题的根源在于Block和obj可能会互相强引用,互相retain对方,这样就导致了retain cycle,最后这个Block和obj就变成了孤岛,谁也释放不了谁。比如:

@implementation TsetBlock

-(id)init{

   if (self = [superinit]) {
       self.testStr =@"中国";
        self.block = ^(NSString *name, NSString *str){
           NSLog(@"arr:%@",self.testStr); // 编译警告:Capturing 'self' strongly in this block is likely to lead to a retain cycle
       };
   }
   returnself;
}
@end

网上大部分帖子都表述为"block里面引用了self导致循环引用",其实这种说法是不严谨的, 不一定要显式地出现"self"字眼才会引起循环引用。我们改一下代码,不通过属性self.testStr去访问String变量,而是通过实例变量_testStr去访问,如下:

@implementation TsetBlock

-(id)init{

   if (self = [superinit]) {
       self.testStr =@"中国";
        self.block = ^(NSString *name,NSString *str){
           NSLog(@"arr:%@", _testStr); // 同样出现: Capturing 'self' strongly in this block is likely to lead to a retain cycle
       };
   }
   returnself;
}
@end


可以发现:
即使在你的block代码中没有显式地出现"self",也会出现循环引用!只要你在block里用到了self所拥有的东西!

要分两种环境去解决:在ARC下不用__block ,而是用 __weak 为了避免出现循环引用

1.ARC:用__week

__weaktypeof (self)  weakSelf = self; 或者

__weak someClass *weakSelf = self;


2.MRC:用__block ,__block修饰的变量在Block copy时是不会retain的,所以,也可以做到破解循环引用。
__block someClass *blockSelf = self;



使用如下代码解决循环引用

weakify(self);
success:^(AFHTTPRequestOperation *operation, id responseObject) {
    strongify(self);
    if (!self__weak_) return ;
       //...................

}

1、weakify(self); 创建一个指向self的弱引用
2、strongify(self); 当加上修饰符strong时,当别处把“self”释放掉,但调用该“self”的block如果仍然没有执行结束,那么系统就会等待block执行完成后再释放,对该“self”在block中的使用起到了保护作用。当block执行结束后会自动释放掉。
3、if (!self__weak_) return ; 进行判断,如果在执行strongify(self)之前“self已经被释放掉了,则此时self=nil,所以直接return即可”





Block的copy、retain、release操作

对Block不管是retain、copy、release都不会改变引用计数retainCount,retainCount始终是1;

NSGlobalBlock:retain、copy、release操作都无效;
NSStackBlock:retain、release操作无效,必须注意的是,NSStackBlock在函数返回后,Block内存将被回收。即使retain也没用。容易犯的错误是[[mutableAarry addObject:stackBlock],在函数出栈后,从mutableAarry中取到的stackBlock已经被回收,变成了野指针。正确的做法是先将stackBlock copy到堆上,然后加入数组:[mutableAarry addObject:[[stackBlock copy] autorelease]]。支持copy,copy之后生成新的NSMallocBlock类型对象。
NSMallocBlock支持retain、release,虽然retainCount始终是1,但内存管理器中仍然会增加、减少计数。copy之后不会生成新的对象,只是增加了一次引用,类似retain;
尽量不要对Block使用retain操作。


几个应用实例:

1、代码用在字符串数组排序

NSArray *stringArray = [NSArray arrayWithObjects:@"abc 1", @"abc 21", @"abc 12",@"abc 13",@"abc 05",nil];  
NSComparator sortBlock = ^(id string1, id string2)  
{  
    return [string1 compare:string2];  
};  
NSArray *sortArray = [stringArray sortedArrayUsingComparator:sortBlock];  
NSLog(@"sortArray:%@", sortArray);  

运行结果:sortArray:( 

   "abc 05",

   "abc 1",

   "abc 12",

   "abc 13",

   "abc 21"

)

2、代码块的递归调用

代码块想要递归调用,代码块变量必须是全局变量或者是静态变量,这样在程序启动的时候代码块变量就初始化了,可以递归调用

  1. static void (^ const blocks)(int^(int i)  
  2.  
  3.     if (i 0)  
  4.         NSLog(@"num:%d"i);  
  5.         blocks(i 1);  
  6.      
  7. };  
  8. blocks(3);  
运行打印结果:

num:3

num:2

num:1





参考资料源自互联网

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值