zoukankan      html  css  js  c++  java
  • <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是存在的, 不会被销毁的.

     

  • 相关阅读:
    支付系统整体架构
    犹太”安息日”
    JWT(JSON Web Token) 【转载】
    详解布隆过滤器的原理、使用场景和注意事项
    缓存一致性策略以及雪崩、穿透问题 【零壹技术栈】
    RPC概念及分类【转载】
    RDLC 微软报表 导出Excel时产生多个工作表 (worksheet)
    asp.net 5 (mvc 6) 获取网站的物理路径
    Asp.net 5 (MVC6) Areas 分区
    MVC6 OWin Microsoft Identity 自定义验证
  • 原文地址:https://www.cnblogs.com/ZeroHour/p/9512068.html
Copyright © 2011-2022 走看看