iOS代码块block使用

     代码块的本质是和其他的变量类似,不同的是,代码块存储的数据是一个函数体。使用代码块,你可以像调用其他标准函数一样的调用,可以传入参数,并得到返回值。
     脱字符是代码块的语法标记。下图表示代码块的定义。

1.代码块的基本使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//无参数无返回值
void  (^myblock)() = ^()
{
     NSLog (@ "Hello, World!" );
};
myblock();
 
//带参数无返回值
void  (^myblock2)( NSString  *string) = ^( NSString  *string){   NSLog (@ "%@" ,string);};
myblock2(@ "Hello, World myblock2!" );
 
  //无参数有返回值
int  (^myblocksss)() = ^( int  i){ return  12;};
int  c = myblocksss();
NSLog (@ "%i" ,c);
 
//有参数有返回值
int  (^myblock3)( int ) = ^( int  i){  return  12 * i; };
int  i = myblock3(3);
NSLog (@ "%i" ,i);

2,利用typedef为Block进行重命名

使用typedef为block进行一次重命名,方法跟为函数指针进行重命名是一样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//  Copyright © 2016年 liujun. All rights reserved.
//
 
#import <Foundation/Foundation.h>
 
typedef  int  (^ MyBlock)( int  a, int  b);
 
int  main( int  argc,  const  char  * argv[]) {
     @autoreleasepool  {
         // insert code here...
         
         __block  int  n = 100;
         
         MyBlock block = ^( int  a, int  b)
         {
             n = 20;  //不过没有用__block 修饰 代码不会编译通过
             
             return  n + a + b;
         };
         
         NSLog (@ "%i   %i" , n ,block(3,4));  //输出结果  100   27
         
         NSLog (@ "%i   %i" , block(3,4) ,n); //输出结果   27    20
         //以上输出。说明代码块是在调用的时候才会被执行
         
         NSLog (@ "Hello, World!" );
     }
     return  0;
}

  

3.Block在内存中的位置

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
//  Copyright © 2016年 liujun. All rights reserved.
//
 
#import <Foundation/Foundation.h>
 
typedef  long  (^Sum)( int , int );
 
int  main( int  argc,  const  char  * argv[]) {
     @autoreleasepool  {
         // insert code here...v
         
         Sum sum1 = ^  long  ( int  a,  int  b) {
             return  a + b ;
         };
         NSLog (@ "sum1 = %@" , sum1); // 打印结果:sum1 = <__NSGlobalBlock__: 0x47d0>
         
         
         
         
         int  base = 100;
         Sum sum2 = ^  long  ( int  a,  int  b) {
             return  base + a + b;
         };
         NSLog (@ "sum2 = %@" , sum2);  // 打印结果:sum2 = <__NSMallocBlock__: 0xbfffddf8>
         
         
         
         Sum sum3 = [sum2  copy ];
         NSLog (@ "sum3 = %@" , sum3);  // 打印结果:sum3 = <__NSMallocBlock__: 0x902fda0>
         NSLog (@ "Hello, World!" );
     }
     return  0;
}

  NSGlobalBlock,我们只要实现一个没有对周围变量没有引用的Block,就会显示为是它。而如果其中加入了对定义环境变量的引用,就是NSStackBlock。那么NSMallocBlock又是哪来的呢?malloc一词其实大家都熟悉,就是在堆上分配动态内存时。没错,如果你对一个NSStackBlock对象使用了Block_copy()或者发送了copy消息,就会得到NSMallocBlock。这一段中的几项结论可从代码实验得出。

     也就得到了下面对block的使用注意点。

     对于Global的Block,我们无需多处理,不需retain和copy,因为即使你这样做了,似乎也不会有什么两样。对于Stack的Block,如果不做任何操作,就会向上面所说,随栈帧自生自灭。而如果想让它获得比stack frame更久,那就调用Block_copy(),让它搬家到堆内存上。而对于已经在堆上的block,也不要指望通过copy进行“真正的copy”,因为其引用到的变量仍然会是同一份,在这个意义上看,这里的copy和retain的作用已经非常类似。

4,外部参数在代码块的使用

blk1和blk2的区别在于:

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

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

1
2
3
4
5
6
7
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;

1
2
3
4
5
6
7
8
9
__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。Block中使用__block修饰的变量时,将取变量此刻运行时的值,而不是定义时的快照。这个例子中,执行sum(1,2)时,base将取base++之后的值,也就是201,再执行Blockbase+=10; base+a+b,运行结果是214。执行完Block时,base已经变成211了。

   

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

1
2
3
4
5
6
7
8
9
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变量等效于全局变量、或静态变量。

5,循环引用

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

1
2
3
4
5
6
7
8
9
10
11
12
13
@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去访问,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
@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;

bloack的 retain、copy、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操作。

6.代码块的递归调用

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

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
static  void  (^  const  myblock4)( int ) = ^( int  i)
 
         {
 
             if (i > 0)
 
             {
 
                 NSLog (@ "%i" ,i);
 
                 myblock4(i - 1);
 
             }
 
         };

     代码块的本质是和其他的变量类似,不同的是,代码块存储的数据是一个函数体。使用代码块,你可以像调用其他标准函数一样的调用,可以传入参数,并得到返回值。
     脱字符是代码块的语法标记。下图表示代码块的定义。

1.代码块的基本使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//无参数无返回值
void  (^myblock)() = ^()
{
     NSLog (@ "Hello, World!" );
};
myblock();
 
//带参数无返回值
void  (^myblock2)( NSString  *string) = ^( NSString  *string){   NSLog (@ "%@" ,string);};
myblock2(@ "Hello, World myblock2!" );
 
  //无参数有返回值
int  (^myblocksss)() = ^( int  i){ return  12;};
int  c = myblocksss();
NSLog (@ "%i" ,c);
 
//有参数有返回值
int  (^myblock3)( int ) = ^( int  i){  return  12 * i; };
int  i = myblock3(3);
NSLog (@ "%i" ,i);

2,利用typedef为Block进行重命名

使用typedef为block进行一次重命名,方法跟为函数指针进行重命名是一样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//  Copyright © 2016年 liujun. All rights reserved.
//
 
#import <Foundation/Foundation.h>
 
typedef  int  (^ MyBlock)( int  a, int  b);
 
int  main( int  argc,  const  char  * argv[]) {
     @autoreleasepool  {
         // insert code here...
         
         __block  int  n = 100;
         
         MyBlock block = ^( int  a, int  b)
         {
             n = 20;  //不过没有用__block 修饰 代码不会编译通过
             
             return  n + a + b;
         };
         
         NSLog (@ "%i   %i" , n ,block(3,4));  //输出结果  100   27
         
         NSLog (@ "%i   %i" , block(3,4) ,n); //输出结果   27    20
         //以上输出。说明代码块是在调用的时候才会被执行
         
         NSLog (@ "Hello, World!" );
     }
     return  0;
}

  

3.Block在内存中的位置

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
//  Copyright © 2016年 liujun. All rights reserved.
//
 
#import <Foundation/Foundation.h>
 
typedef  long  (^Sum)( int , int );
 
int  main( int  argc,  const  char  * argv[]) {
     @autoreleasepool  {
         // insert code here...v
         
         Sum sum1 = ^  long  ( int  a,  int  b) {
             return  a + b ;
         };
         NSLog (@ "sum1 = %@" , sum1); // 打印结果:sum1 = <__NSGlobalBlock__: 0x47d0>
         
         
         
         
         int  base = 100;
         Sum sum2 = ^  long  ( int  a,  int  b) {
             return  base + a + b;
         };
         NSLog (@ "sum2 = %@" , sum2);  // 打印结果:sum2 = <__NSMallocBlock__: 0xbfffddf8>
         
         
         
         Sum sum3 = [sum2  copy ];
         NSLog (@ "sum3 = %@" , sum3);  // 打印结果:sum3 = <__NSMallocBlock__: 0x902fda0>
         NSLog (@ "Hello, World!" );
     }
     return  0;
}

  NSGlobalBlock,我们只要实现一个没有对周围变量没有引用的Block,就会显示为是它。而如果其中加入了对定义环境变量的引用,就是NSStackBlock。那么NSMallocBlock又是哪来的呢?malloc一词其实大家都熟悉,就是在堆上分配动态内存时。没错,如果你对一个NSStackBlock对象使用了Block_copy()或者发送了copy消息,就会得到NSMallocBlock。这一段中的几项结论可从代码实验得出。

     也就得到了下面对block的使用注意点。

     对于Global的Block,我们无需多处理,不需retain和copy,因为即使你这样做了,似乎也不会有什么两样。对于Stack的Block,如果不做任何操作,就会向上面所说,随栈帧自生自灭。而如果想让它获得比stack frame更久,那就调用Block_copy(),让它搬家到堆内存上。而对于已经在堆上的block,也不要指望通过copy进行“真正的copy”,因为其引用到的变量仍然会是同一份,在这个意义上看,这里的copy和retain的作用已经非常类似。

4,外部参数在代码块的使用

blk1和blk2的区别在于:

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

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

1
2
3
4
5
6
7
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;

1
2
3
4
5
6
7
8
9
__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。Block中使用__block修饰的变量时,将取变量此刻运行时的值,而不是定义时的快照。这个例子中,执行sum(1,2)时,base将取base++之后的值,也就是201,再执行Blockbase+=10; base+a+b,运行结果是214。执行完Block时,base已经变成211了。

   

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

1
2
3
4
5
6
7
8
9
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变量等效于全局变量、或静态变量。

5,循环引用

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

1
2
3
4
5
6
7
8
9
10
11
12
13
@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去访问,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
@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;

bloack的 retain、copy、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操作。

6.代码块的递归调用

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

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
static  void  (^  const  myblock4)( int ) = ^( int  i)
 
         {
 
             if (i > 0)
 
             {
 
                 NSLog (@ "%i" ,i);
 
                 myblock4(i - 1);
 
             }
 
         };

猜你喜欢

转载自www.cnblogs.com/chenweb/p/10410057.html