Swift&Objective-CiOS面试小知识点

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

2017-05-17  本文已影响134人  RainyHand
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执行结束后会自动释放掉。

继续整理补充,,,,

上一篇 下一篇

猜你喜欢

热点阅读