<iOS底层原理探究> 第二探. Block

1. Block介绍和定义

Block是什么? Block是OC对于闭包的实现. 什么是闭包? 闭包是一个函数(或者指向函数的指针) + 自由变量(上下文变量)所组成的.

首先我们不要被Block所吓倒, 认为太高大上了, 其实我们就拿它当一个对象类型来用就可以了

我们平常是怎么定义一个变量的呢?

int variable = 10;

Person * person = [Person new];

我们来看, 定义一个Block类型的变量是怎么样的!

返回值(^变量名)(参数类型1, 参数类型2, ...) = ^(参数类型 参数1, 参数类型 参数2, ...){语句体};

 

        void(^block)(void) = ^(){
            // todo
        };

这段代码: 我们定义了一个Block, 名为block, 类型为void(^)(void), 给变量赋值为 ^(){ // todo };

 既然Block也是一种数据类型, 我们可以给Block起一个别名, 来方便我们使用, 如下:

typedef void(^MyBlock)(void);

这样我们就把void(^)(void)这种Block类型用MyBlock来代替, 我们可以直接使用MyBlock来定义一个Block变量

        MyBlock myBlock = ^(){
            // todo
        };

相同的Block类型的变量还可以进行赋值操作

myBlock = block;

2. Block使用

我们可以定义四种类型的Block

typedef void(^NoReturnValueNoParam)(void); // 无返回值, 无参数
typedef void(^NoReturnValueHaveParam)(NSString *); // 无返回值, 有参数
typedef NSString * (^HaveReturnValueNoParam)(void); // 有返回值, 无参数
typedef NSString * (^HaveReturnValueHaveParam)(NSString *, NSString *); // 有返回值, 有参数
     /** a. 没有返回值, 没有参数 */
        void(^sayHello)(void) = ^(){
            NSLog(@"Hello!");
        };
        sayHello();
        
        /** b. 没有返回值, 有参数 */
        NoReturnValueHaveParam sayHelloTo = ^(NSString * name){
            NSLog(@"Hello, %@!", name);
        };
        sayHelloTo(@"Ray");
        
        /** c. 有返回值, 没有参数 */
        HaveReturnValueNoParam getValue = ^(){
            return @"Ray";
        };
        NSLog(@"%@", getValue());
        
        /** d. 有返回值, 有参数 */
        HaveReturnValueHaveParam joint = ^(NSString * a, NSString * b){
            return [NSString stringWithFormat:@"%@%@", a, b];
        };
        NSLog(@"%@", joint(@"Ray", @"Lee"));

3. Block种类

根据Block存储位置的不同, 可以将Block分为三类: __NSGlobalBlock__, __NSMallocBlock__, __NSStackBlock__

a. __NSGlobalBlock__

(1). 没有捕获外部变量的Block

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        void(^globalBlock)(void) = ^(){
            NSLog(@"global block");
        };
        globalBlock();
        NSLog(@"%@", [globalBlock class]);
    }
    return 0;
}

(2). 使用了全局变量

/** 全局变量 */
int variable = 10;
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        void(^globalBlock)(void) = ^(){
            NSLog(@"%d", variable);
        };
        globalBlock();
        NSLog(@"%@", [globalBlock class]);
    }
    return 0;
}

(3). 使用了全局静态变量

/** 全局静态变量 */
static int staticGlobalVariable = 20;
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        void(^globalBlock)(void) = ^(){
            NSLog(@"%d", staticGlobalVariable);
        };
        globalBlock();
        NSLog(@"%@", [globalBlock class]);
    }
    return 0;
}

(4). 使用了局部静态变量

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        /** 局部静态变量 */
        static int localStaticVariable = 30;
        void(^globalBlock)(void) = ^(){
            NSLog(@"%d", localStaticVariable);
        };
        globalBlock();
        NSLog(@"%@", [globalBlock class]);
    }
    return 0;
}

 b. __NSMallocBlock__

(1). 使用了Block外部的局部变量, 且用正常变量来接收这个Block

        int localVariable = 10;
        void(^mallocBlock)(void) = ^(){
            NSLog(@"%d", localVariable);
        };
        mallocBlock();
        NSLog(@"%@", [mallocBlock class]);

c. __NSStackBlock__

(1). 使用了Block外部的局部变量, 且用weak变量来接收这个Block

        __weak void(^weakBlock)(void) = ^(){
            NSLog(@"%d", localVariable);
        };
        weakBlock();
        NSLog(@"%@", [weakBlock class]);

4. Block与外部变量

注意: a. 默认情况下, Block只读读取自由变量的值, 不能修改自由变量的值 b. 想要修改自由变量的值, 需要用__block修饰 c. __block将修饰的变量变成了对象

a. Block与基本数据类型

        int a = 10;
        NSLog(@"Block定义前a的地址 - %p", &a);
        void(^blockOne)(void) = ^(){
            NSLog(@"Block定义内a的地址 - %p", &a);
        };
        NSLog(@"Block定义后a的地址 - %p", &a);
        blockOne();
        NSLog(@"Block调用后a的地址 - %p", &a);    

控制台打印: 

Block定义前a的地址 - 0x7ffeefbff5ec
Block定义后a的地址 - 0x7ffeefbff5ec
Block定义内a的地址 - 0x100407c40
Block调用后a的地址 - 0x7ffeefbff5ec

从控制台打印中可以看出, Block内部a的地址发生了改变, 由此我们可以知道, Block内部a和Block外部a指向的不是同一块内存, 内部a是由外部a通过拷贝到堆中的.

b. Block与__block修饰的基本数据类型

        __block int b = 10;
        NSLog(@"Block定义前b的地址 - %p", &b);
        void(^blockTwo)(void) = ^(){
            NSLog(@"Block定义内b的地址 - %p", &b);
        };
        NSLog(@"Block定义后b的地址 - %p", &b);
        blockTwo();
        NSLog(@"Block调用后b的地址 - %p", &b);

控制台打印: 

Block定义前b的地址 - 0x7ffeefbff5b0
Block定义后b的地址 - 0x100504b18
Block定义内b的地址 - 0x100504b18
Block调用后b的地址 - 0x100504b18

从控制台打印中可以看出, Block内部b的地址发生了改变, 并且Block定义后, Block调用后, 都是Block内部b的地址, 由此我们可以知道, Block将外部b拷贝一份到堆内存中, 并且使外部b和内部b是同一个, 也就是代表同一块内存.

c. Block与指针

        NSString * c = @"abcdefg";
        NSLog(@"Block定义前: c = %@, c指向的地址 = %p, c本身的地址 = %p", c, c, &c);
        void(^blockThree)(void) = ^(){
            NSLog(@"Block定义内: c = %@, c指向的地址 = %p, c本身的地址 = %p", c, c, &c);
        };
        NSLog(@"Block调用前: c = %@, c指向的地址 = %p, c本身的地址 = %p", c, c, &c);
        blockThree();
        NSLog(@"Block调用后: c = %@, c指向的地址 = %p, c本身的地址 = %p", c, c, &c);

控制台打印: 

Block定义前: c = abcdefg, c指向的地址 = 0x100002218, c本身的地址 = 0x7ffeefbff550
Block调用前: c = abcdefg, c指向的地址 = 0x100002218, c本身的地址 = 0x7ffeefbff550
Block定义内: c = abcdefg, c指向的地址 = 0x100002218, c本身的地址 = 0x100705ce0
Block调用后: c = abcdefg, c指向的地址 = 0x100002218, c本身的地址 = 0x7ffeefbff550

从控制台打印中可以看出, 和使用基本数据类型是一样的, Block内部c是由Block外部c拷贝一份到堆中的, 内部c与外部c所指向的内存是同一块.

d. Block与__block修饰的指针

     __block NSString * d = @"hijklmn";
        NSLog(@"Block定义前: d = %@, d指向的地址 = %p, d本身的地址 = %p", d, d, &d);
        void(^blockFour)(void) = ^(){
            NSLog(@"Block定义内: d = %@, d指向的地址 = %p, d本身的地址 = %p", d, d, &d);
        };
        NSLog(@"Block定义后: d = %@, d指向的地址 = %p, d本身的地址 = %p", d, d, &d);
        blockFour();
        NSLog(@"Block调用后: d = %@, d指向的地址 = %p, d本身的地址 = %p", d, d, &d);

控制台打印: 

Block定义前: d = hijklmn, d指向的地址 = 0x1000022b8, d本身的地址 = 0x7ffeefbff518
Block定义后: d = hijklmn, d指向的地址 = 0x1000022b8, d本身的地址 = 0x1006426c8
Block定义内: d = hijklmn, d指向的地址 = 0x1000022b8, d本身的地址 = 0x1006426c8
Block调用后: d = hijklmn, d指向的地址 = 0x1000022b8, d本身的地址 = 0x1006426c8

从控制台打印中可以看出, 和__block修饰的基本数据类型是一样的, Block内部d的地址发生了改变, 并且Block定义后, Block调用后, 都是Block内部d的地址, 由此我们可以知道, Block将外部d拷贝一份到堆内存中, 并且使外部d和内部d是同一个, 也就是同一块内存.

5. Block造成的循环引用

a. 如果一个类将Block作为自己的属性变量, 然后该类在Block内, 又使用了自己本身, 就会造成循环引用!

我们需要这样做:  在Block外部, __weak typeof(self) weakSelf = self; 可以保证一个类的对象和Block不互相持有.

b. 用__weak修饰对象, 当外部对象释放了之后, Block内部也访问不到这个对象怎么办?

我们需要这样做: 在Block内部, __strong typeof(self) strongSelf = self; 可以保证至少在Block内部self是存在的, 不会被销毁的.

 

原文地址:https://www.cnblogs.com/ZeroHour/p/9512068.html