iOS Block 知识点拾遗
$[timeformat('2018-03-03T18:24:56+08:00')]
#面试
  1. Block为什么要用copy

    • block在创建的时候默认分配的内存是在栈上,而不是在堆上。这样的话其本身的作用域是属于创建时候的作用域,一旦在创建的作用域之外调用就会导致程序的崩溃。所以使用了copy将其拷贝到堆内存上。
    • block创建在栈上,而block的代码中可能会用到本地的一些变量,在栈上可能随时会被系统释放掉,只有将其拷贝到堆上,才能一直保持Block的存在,并用这些外部变量
  2. Block为什么不用retain

    • retain只是增加了一次引用计数,block的内存还是在栈上,并没有存在堆上,存在栈上的block可能随时被系统回收
  3. 为什么进入block中的对象引用计数需要自动加1?

    • Block执行的是回调,因此block并不知道其中的对象obj创建后会在什么时候被释放,为了不在block使用obj之前,对象已经被释放,block就retain了obj一次
  4. block和函数的关系

    • Block的使用很像函数指针,不过与函数最大的不同是Block可以访问函数以外、词法作用域以内的外部变量的值。换句话说,Block不仅 实现函数的功能,还能携带函数的执行环境
    • 函数指针 void(*fun)(int)
    • block void(^fun)(int)
  5. block本质(对于block的理解)请查阅Block实现原理

    • block实际上是: 指向结构体的指针
    • 编译器会将block的内部代码生成对应的函数
  6. 对于基本数据类型,进入到block中会被当做常量处理。对象 retain 会一次

    //如果需要在block中对num进行修改,需要加上关键字__block
    

//(我们也可以用static关键字进行修饰) int num1 = 10; void(^block1)() = ^{ NSLog(@"num1 is %d",num1); }; num1 = 20; block1(); //输出10

//改进:使用block,使进入到block块中的变量不被当做常量来使用 __block int num2 = 10; void(^block2)() = ^{ NSLog(@"num2 is %d",num2); }; num2 = 20; block2(); //输出20 ```

  1. Block中self的循环引用

    • block默认创建在栈上,所以对要对其进行执行copy操作,将其拷贝到堆区,便于更好的操作对象。但是执行了copy操作之后,block中使用self,此对象会被retain一次(注意:block在堆区上时才会起到retain作用),会造成循环引用。
    • 解决方法:
      • 在MRC下,使用__block修饰
      • 在ARC下,使用__unsafe_unretained\weak修饰
  2. Block 在内存中的分类

    • 全局block --> GlobalBlock <==> 相当于全局变量, 系统会自动释放
    • 栈block --> StackBlock <==> 相当于局部变量, 系统会自动释放
    • 堆block --> MallocBlock <==> (需要手动释放内存)
  3. block类型区分方法

    • 如果block实现中没有访问任何"外部"变量(包括局部和全局), 该block为GlobalBlock
    • 如果block实现中访问了任何"外部"变量(包括局部和全局), 该block为StackBlock
    • 对StackBlock进行拷贝(copy/Block_copy), 该block为MallocBlock
  4. block内如何修改block外 的变量?

     __block int a = 0;
       void (^foo)(void) = ^{ 
           a = 1; 
       };
       foo(); 
       //这里,a的值被修改为1
    

    默认情况下,block内访问的变量是 copy 该变量到堆后的变量,而非变量本身。 所以:读写操作对原变量不生效

    我们知道:Block不允许修改外部变量的值,这里所说的外部变量的值,指的是栈中指针的内存地址。__block 所起到的作用就是只要观察到该变量被 block 所持有,就将“外部变量”在栈中的内存地址放到了堆中。进而在block内部也可以修改外部变量的值

    __block int a = 0;
       NSLog(@"定义前:%p", &a);         //栈区
       void (^foo)(void) = ^{
           a = 1;
           NSLog(@"block内部:%p", &a);    //堆区
       };
       NSLog(@"定义后:%p", &a);         //堆区
       foo();
    

    打印输出:

    2016-05-17 02:03:33.559 LeanCloudChatKit-iOS[1505:713679] 定义前:0x16fda86f8
    2016-05-17 02:03:33.559 LeanCloudChatKit-iOS[1505:713679] 定义后:0x155b22fc8
    2016-05-17 02:03:33.559 LeanCloudChatKit-iOS[1505:713679] block内部: 0x155b22fc8
    
  5. block个人理解

NSMutableString *kName = [NSMutableString stringWithString:@"kael"]; __block NSMutableString *myname = kName; NSLog(@"block 前%p",&myname);

void (^foo)(void) = ^(){
    NSLog(@"block 中1:%p",&myname);
    myname = [NSMutableString stringWithString:@"kaelinda"];
    NSLog(@"block 中2:%p",&myname);
};

foo();
NSLog(@"block 后%p",&myname);

/**
 * __block 修饰之前
 * 外部变量的内存地址是存到了栈
 * block 是在堆内,不清楚栈内的变量是否已经被释放,所以连引用的准确性都是问题,更谈不上是修改了
 * 所以,block 不允许修改栈中的变量(或者栈中指针的内存地址)
 */

/**
 * __block 修饰之后
 * 初始化的时候,内存地址在栈内(因为并不确定block内是否会用到),
 * 当检测到block内引用到了该对象,不管block有没有被调用,都会 从栈内 copy 内存地址到 堆内,
 * 再操作的时候 操作的是堆内的内存地址
 */
```