iOS之Block详解:Block详解

ViewController.h(ARC)

#import <UIKit/UIKit.h>

@interface ViewController : UIViewController

// 属性声明的block都是全局的__NSGlobalBlock__
@property (nonatomic, copy) void (^copyBlock)();
@property (nonatomic, weak) void (^weakBlock)();

@end

ViewController.m(ARC)

#import "ViewController.h"
#import "Test.h"

typedef void (^blockSave)(void);

typedef void (^typedefBlock)(void);

void (^outFuncBlock)(void) = ^{
    NSLog(@"someBlock");
};

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    NSLog(@"block : %@", ^{NSLog(@"block");});      // __NSGlobalBlock__
    
    NSString *str3 = @"1234";
    NSLog(@"block is %@", ^{NSLog(@":%@", str3);});     // __NSStackBlock__
    
#pragma mark - 当全局block引用了外部变量,ARC机制优化会将Global的block,转为Malloc(堆)的block进行调用。
    
    __block int age = 20;
    int *ptr = &age;
    // ARC下
    blockSave x = ^{
        NSLog(@"(++age):%d", ++age);    // 变量前不加__block的情况下,会报错,变量的值只能获取,不能更改
    };
    blockSave y = [x copy];
    y();
    NSLog(@"x():%@, y():%@ , (*ptr):%d", x, y, *ptr);
    // MRC下
    Test *test = [[Test alloc] init];
    [test test];
    [test exampleB];
    
    /**总结:
     ARC下:(++age):21   (*ptr):20    // blockSave在堆中,*ptr在栈中
     MRC下:(++age):21   (*ptr):21    // blockSave和*ptr都在栈中
     */

    
#pragma mark - copyBlock(未使用函数内变量) __NSGlobalBlock__
    
    self.copyBlock = ^{
        
    };
    NSLog(@"1:%@", self.copyBlock);
    
#pragma mark - weakBlock(未使用函数内变量) __NSGlobalBlock__
    
    self.weakBlock = ^{
        
    };
    NSLog(@"2:%@", self.weakBlock);
    
#pragma mark - copyBlock (使用函数内变量) __NSMallocBlock__
    
    self.copyBlock = ^{
        age = age+1-1;
    };
    NSLog(@"3:%@", self.copyBlock);
    
#pragma mark - weakBlock(使用函数内变量) __NSStackBlock__
    
    self.weakBlock = ^{
        age = age+1-1;
    };
    NSLog(@"4:%@", self.weakBlock);
    
#pragma mark - someBlock(定义在函数体外) __NSGlobalBlock__
    
    NSLog(@"5:%@", outFuncBlock);
    
#pragma mark - typedefBlock(函数体外自定义的Block) __NSGlobalBlock__
    
    typedefBlock b = ^{
        
    };
    NSLog(@"6:%@", b);
    
    
    
#pragma mark - 对栈中的block进行copy
    // 不引用外部变量,定义在全局区、表达式没有使用到外部变量时,生成的block都是__NSGlobalBlock__类型
    void (^testBlock1)() = ^(){
        
    };
    NSLog(@"testBlock1: %@", testBlock1);
    
    // 引用外部变量 -- ARC下默认对block进行了copy操作,所以这里是__NSMallocBlock__类型
    void (^testBlock2)() = ^(){
        age = age+1-1;
    };
    NSLog(@"testBlock2: %@", testBlock2);
    
    
    // Blocks提供了将Block和__block变量从栈上复制到堆上的方法来解决变量作用域结束时销毁的问题,堆上的Block会依然存在。
    
    
    /*那么什么时候栈上的Block会复制到堆上呢?
     1.调用Block的copy实例方法时
     2.Block作为函数返回值返回时(作为参数则不会)
     3.将Block赋值给附有__strong修饰符id类型的类或Block类型成员变量时
     4.将方法名中含有usingBlock的Cocoa框架方法或GCD的API中传递Block时
     
     在使用__block变量的Block从栈上复制到堆上时,__block变量也被从栈复制到堆上并被Block所持有。
     */
    
    
    /*block里面使用self会造成循环引用吗?
     
     1.很显然答案不都是,有些情况下是可以直接使用self的,比如调用系统的方法:
     [UIView animateWithDuration:0.5 animations:^{
        NSLog(@"%@", self);
     }];
     因为这个block存在于静态方法中,虽然block对self强引用着,但是self却不持有这个静态方法,所以完全可以在block内部使用self。
     
     2.当block不是self的属性时,self并不持有这个block,所以也不存在循环引用
     void(^block)(void) = ^() {
        NSLog(@"%@", self);
     };
     block();
     
     3.大部分GCD方法:
     dispatch_async(dispatch_get_main_queue(), ^{
        [self doSomething];
     });
     因为self并没有对GCD的block进行持有,没有形成循环引用。
     
     4.……
     
     只要我们抓住循环引用的本质,就不难理解这些东西。
     */
}

Test类在MRC条件下运行( -fno-objc-arc )

Test.h(MRC)

#import <Foundation/Foundation.h>

@interface Test : NSObject

- (void)test;

@end

Test.m(MRC)

#import "Test.h"

@implementation Test

- (void)test
{
    __block int age = 20;
    int *ptr = &age;
    void (^textBlock)() = ^{
        NSLog(@"(++age):%d", ++age);
    };
    textBlock();
    
    // 对block进行retain、release、copy,retainCount都不会变化,都为1
//    [textBlock retain];
//    [textBlock copy];
//    [textBlock release];
    
    NSLog(@"Test: textBlock:%@, (*ptr):%d, %lu", textBlock, *ptr, (unsigned long)[textBlock retainCount]);
    /**
     MRC下:(++age):21   (*ptr):21
     */
    
#pragma mark - 对栈中的block进行copy
    // 不引用外部变量
    /* 这里打印的是__NSGlobalBlock__类型,但是通过clang改写的底层代码指向的是栈区:impl.isa = &_NSConcreteStackBlock
     这里引用巧神的一段话:由于 clang 改写的具体实现方式和 LLVM 不太一样,并且这里没有开启 ARC。所以这里我们看到 isa 指向的还是__NSStackBlock__。但在 LLVM 的实现中,开启 ARC 时,block 应该是 __NSGlobalBlock__ 类型
     */
    void (^testBlock1)() = ^(){
        
    };
    void (^testBlock2)() = [testBlock1 copy];
    NSLog(@"Test: testBlock1: %@, testBlock2: %@", testBlock1, testBlock2);
    [testBlock2 release];
    
    // 引用外部变量,block为__NSStackBlock__类型
    void (^testBlock3)() = ^(){
        age = age+1-1;
    };
    void (^testBlock4)() = [testBlock3 copy];
    NSLog(@"Test: testBlock3: %@, testBlock4: %@", testBlock3, testBlock4);
    [testBlock4 release];
}

github地址:BlockTest