美文网首页Swift&Objective-CiOS面试小知识点
由三个面试题引起的block总结

由三个面试题引起的block总结

作者: RainyHand | 来源:发表于2017-05-17 15:18 被阅读134次
    block.jpg

    iOS开发中,block的使用还是挺频繁的,不管是自定义还是系统提供的很多block函数。但是抽丝剥茧的事还是发生在面试中,面试的时候block被提问的概率还是挺高的。
       以下根据面试题做出对block部分知识点的讲解,仅代表个人看法,如果有错误,请指出。
    看题之前,先了解一下block类型:

    分NSConcreteGlobalBlock
    NSConcreteStackBlock
    NSConcreteMallocBlock分别在全局变量区,栈区,堆区。

    _NSConcreteGlobalBlock类型的block要么是空block,要么是不访问任何外部变量的block。它既不在栈中,也不在堆中,我理解为它可能在内存的全局区。(配置在全局变量上的Block,从变量作用域外也可以通过指针安全地使用)
    举例:

        void(^ blockA)() = ^{
            NSLog(@"just a block");
        };
    

    _NSConcreteStackBlock类型的block有闭包行为,也就是有访问外部变量,并且该block只且只有有一次执行,因为栈中的空间是可重复使用的,所以当栈中的block执行一次之后就被清除出栈了,所以无法多次使用。
    举例:

    下边这个例子,如果在ARC下创建,blockC会创建在堆上,如果在MRC 环境下创建会创建在栈上。
      int value = 10;
        void(^ blockC)() = ^{
            NSLog(@"just a block === %d", value);
        };
    

    _NSConcreteMallocBlock类型的block有闭包行为,并且该block需要被多次执行。当需要多次执行时,就会把该block从栈中复制到堆中,供以多次执行。
    举例:

    void addBlockToArray(NSMutableArray *array) {
        char b = 'B';
        [array addObject:^{
                printf("%c\n", b);
        }];
    }
    void exampleB() {
        NSMutableArray *array = [NSMutableArray array];
        addBlockToArray(array);
        void (^block)() = [array objectAtIndex:0];
        block();
    }
    addBlockToArray中的block还在栈区,exampleB中的block被复制到了堆区变成了NSConcreteMallocBlock。
    

    提问的第一个问题如下:

    1,下面代码在按钮点击后,在ARC下会发生什么,MRC下呢?为什么?
    
    @property(nonatomic, assign) void(^block)();
    
    - (void)viewDidLoad {
        [superviewDidLoad];
        int value = 10;
        void(^blockC)() = ^{
            NSLog(@"just a block === %d", value);
        };
    
        NSLog(@"%@", blockC);
        _block = blockC;
    
    }
    
    - (IBAction)action:(id)sender {
        NSLog(@"%@", _block);
    }
    

    经测试,在ARC环境下是不会崩溃的,在MRC环境下因为访问已经释放的对象,程序崩溃。个人给出的解释是,在ARC环境下,创建的blockC ,blockC是在堆区,MRC环境下blockC是在栈区,栈区在函数返回以后就销毁,再次访问的时候就会引起访问已经销毁的对象。
    注:此处之前搜到的答案是这样的:@property(nonatomic, assign) void(^block)(); 在ARC环境下,不管用assign,copy还是strong来修饰block都会被copy到堆区,所以block不会因为函数的返回而销毁。在MRC环境下必须用copy然后调用点语法赋值(self.block = blockC),block 就会从栈区copy到堆区。
    但是实际测试结果如下:

       int a =0;
        self.block=^{
            NSLog(@"aaa%d",a);
        };
        
        NSLog(@"aaa");
    

    结果显示


    显示在栈区.png

      所以个人认为,上边block在ARC环境下没有销毁,是因为blockC在堆区,而不是说ARC环境下assign修饰的block被copy到了堆区。
    因此不管在MRC 还是ARC 定义成属性的block要用copy防止过早销毁。

    2,在ARC环境下这段代码为什么不会崩溃?
    
    @property(nonatomic, weak) void(^block)();
    
    - (void)viewDidLoad {
        [super viewDidLoad];
    
        void(^ __weak blockA)() = ^{
            NSLog(@"just a block");
        };
    
        _block = blockA;
    
    }
    
    - (IBAction)action:(id)sender {
        _block();
    }
    

    经过测试blockA是在全局变量区,类型是NSConcreteGlobalBlock前边注意到(配置在全局变量上的Block,从变量作用域外也可以通过指针安全地使用)。

    在全局变量区.png

    在MRC环境直接写报错,需要将 _weak做处理,不做深究。
    注:在第一题中我们发现,直接创建的block是在堆区的,但是经过__weak修饰后会放在栈区。

    下面代码中为什么可以直接用self?
    [UIView animateWithDuration:1 animations:^{
        self.view.backgroundColor = [UIColor yellowColor];
    }];
    
    下面这段代码可以用self吗?为什么?
    - (void)doSomething {
        [BlockClass doSomethingUseBlock:^{
            NSLog(@"%@", self);
        }];
    }
    

    关于第一个问题,我们会发现,在很多情况下,block中使用self不会引起循环引用问题,这首先,我们要搞明白什么是循环引用,就是当前类持有强引用这个block,然后在block中又强引用了当前类,彼此等待都不能销毁。但是UIView是一个类,当前控制器不可能强引用一个类,所以当前控制器没有强引用这个block,循环不成立。(第二个问题也就回答了)
    此处拓展,在AFN中也是在block中使用self,他是进行了特殊处理,原理可以自己去搜一下。系统GCD是在结束的时候将对象都进行了释放。

    以下补充知识点:

    补充1. 关于block中变量修改引起的思考。
    我们知道block中是不能直接修改外部变量的,必须经过_block修饰。原因是:block不允许修改的是栈中指针的内存地址,__block的作用是将栈中的地址放到堆中,这样就可以修改了。
    补充2. 关于block中使用weak和strong修饰词
    首先我们知道,为了防止循环引用,我们会使用weak来修饰self,防止产生强引用,但是在很多框架中我们会发现,block中还会有strong修饰词,这是防止block还在执行的时候,别的地方把self给释放了。找一个别人写好的例子

     第一步:我们自定义一个类,在该类dealloc方法中加一行打印语句;
    
    @interface SampleObject :NSObject
    
    @end
    
    @implementation SampleObject
    
    - (void)dealloc{
    
    NSLog(@"dealloc %@",[self class]); 
    
    }
    
    @end
    
    第二步:实例化该类,并在block中调用它;(没有加strong修饰符,三秒后释放该对象)
    
    SampleObject* sample = [[SampleObject alloc]init];
    
    self->sample= sample;
    
    __weakSampleObject* weaksample = self->sample;
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), ^{
    
    NSIntegercount =0;
    
    //__strong SampleObject* strongsample = weaksample;
    
    while(count<10) {
    
    count++;
    
    NSLog(@"aaa %@",weaksample);
    
    sleep(1);
    
    }
    
    });
    
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3*NSEC_PER_SEC)),dispatch_get_main_queue(), ^{
    
    self->sample=nil;
    
    });
    

    打印结果如下(没有用strong修饰符的打印结果如下):

    输出.png

    结论是:如果仅仅使用__weak去修饰变量,当别处把变量释放后,block中该变量也会被释放掉。
    那么好,我们在把第二步中的方法修改一下,加上strong修饰符:

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), ^{
    
    __strongSampleObject* strongsample = weaksample;
    
    NSIntegercount =0;
    
    while(count<10) {
    
    count++;
    
    NSLog(@"aaa %@",strongsample);
    
    sleep(1);
    
    }
    
    });
    

    打印结果如下:


    加strong输出.png

    结论是当加上修饰符strong时,当别处把变量释放掉,但调用该变量的block如果仍然没有执行结束,那么系统就会等待block执行完成后再释放,对该变量在block中的使用起到了保护作用。当block执行结束后会自动释放掉。

    继续整理补充,,,,

    相关文章

      网友评论

        本文标题:由三个面试题引起的block总结

        本文链接:https://www.haomeiwen.com/subject/tijdxxtx.html