iOSSharing #6 | 2019-04-28

目录

1. block的循环引用是如何形成的?

2. 使用UIAnimation的block回调时,需不须要使用__weak避免循环引用?为何?

3. block属性是否能够用strong修饰?

4. 什么场景下才须要对变量使用__block?

5. 运行如下GCD多线程代码,控制台将打印什么?

1. block的循环引用是如何形成的?

------------Light.h------------
#import <Foundation/Foundation.h>
@interface Light : NSObject
@property (nonatomic, copy) NSString *color;
@property (nonatomic, copy) void (^block)(void);
@end

------------Light.m------------
#import "Light.h"
@implementation Light
@end

------------main.m------------
#import "Light.h"

int main(int argc, char * argv[]) {
    @autoreleasepool {
        Light *loveLight = [Light alloc] init];
        loveLight.color = @"green";
        
        loveLight.block = ^{
            NSLog(@"%@",loveLight.color);
        };
        
        loveLight.block();
    }
}
复制代码

咱们在上面的代码中建立了一个Light(光)类,并声明两个属性color(颜色)及block。而后咱们实例化一个对象loveLight并对其属性赋值,实现并调用block,形成循环引用。 而后咱们经过clang代码,了解这段代码内部的部分实现:面试

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  Light *loveLight;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Light *_loveLight, int flags=0) : loveLight(_loveLight) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    Light *loveLight = __cself->loveLight; // bound by copy
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_yf_3v_c713x5898zz9d49t0rd140000gn_T_main_81cd17_mi_1,((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)loveLight, sel_registerName("color")));
}

static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
    _Block_object_assign((void*)&dst->loveLight, (void*)src->loveLight, 3/*BLOCK_FIELD_IS_OBJECT*/);
}

...后省略
复制代码

经过clang后的源码咱们能够知道:多线程

  • 对象的建立自己就是强引用(默认strong修饰)。
  • 对象对block属性赋值,在ARC下,block做为返回值时或者赋值给一个strong/copy修饰的对象会自动调用copy,loveLight强引用block
  • 对象的block在其内部捕获了对象自己,block在自动调用copy的时候,_Block_object_assign(clang源码最后一行)会根据捕获变量的全部权修饰符,来对变量的引用计数进行操做。此处loveLight自己是strong修饰,则引用计数+1,block强引用loveLight对象
  • 因此双方互相引用,形成了循环引用。

同时面试中面试官还可能会询问你如何检测到内存泄漏,咱们能够经过Instruments中的Leaks进行检测,也能够选择facebook发布的FBRetainCycleDetector内存泄漏检测工具。并发


2. 使用UIView Animation的block回调时,是否须要考虑循环引用的问题?为何?

首先UIView Animation使用时,不须要考虑循环引用的问题。函数

UIKit将动画直接集成到UIView的类中,当内部的一些属性发生改变时,UIView将为这些改变提供动画支持,并以类方法的形式提供接口。工具

而block形成循环引用的主要缘由是对象与block的相互持有,UIView Animation的block自己处于类方法中,在使用时并不属于调用控制器。同时控制器也没法强引用一个类,因此不会形成循环引用的问题。动画


3. block属性是否能够用strong修饰?

block属性可使用strong属性修饰符修饰,可是不推荐,会有内存泄漏的隐患。ui

首先,ARC中block用copy属性修饰符修饰是MRC时代延续的产物,提醒开发者可能存在的内存问题。同时copy的确是能够用strong来替代的。atom

咱们都知道block在OC中有三种类型:
- _NSConcreateGlobalBlock 全局的静态block,不会访问任何外部变量。 - _NSConcreateStackBlock 栈区的block,当函数返回时会被销毁。 - _NSConcreateMallocBlock 堆区的block,当引用计数为0时被销毁。spa

block在MRC下能够存在于全局区、栈区和堆区,而在ARC下,block会自动从栈区拷贝到堆区(除了裸写block实现块),因此只存在于全局区和堆区。 因此对于栈区block,MRC下处于栈区,想在做用域外调用就得copy到堆区;ARC则自动copy堆区。线程

那么这个时候问题就来了,strong属性修饰符并不能拷贝,就会有野指针错区的可能,形成Crash。这种状况不多见,可是不表明不可能发生,因此最好仍是使用copy属性修饰符。

此处感谢@buaacyg的评论反馈,同时也对撰文的不严谨深表歉意。

针对Block属性修饰符的问题在撰写的时候的确没有考虑周全,咱们将在如下予以更正和解答。 首先,在如下情形中block会自动从栈拷贝到堆:

  • 一、当 block 调用 copy 方法时,若是 block 在栈上,会被拷贝到堆上;
  • 二、当 block 做为函数返回值时,编译器自动将 block 做为 _Block_copy 函数,效果等同于直接调用 copy 方法;
  • 三、当 block 被赋值给 __strong id 类型的对象或 block 的成员变量时,编译器自动将 block 做为 _Block_copy 函数,效果等同于直接调用 copy 方法;
  • 四、当 block 做为参数被传入方法名带有 usingBlock 的 Cocoa Framework 方法或 GCD 的 API 时。这些方法会在内部对传递进来的 block 调用 copy 或 _Block_copy 进行拷贝;

那针对上述自动拷贝的状况咱们作一个实验:

ARC下strong修饰block,且不引用外部变量,block类型为__NSGlobalBlock

ARC下strong修饰block,引入外部变量,block类型为__NSMallocBlock

因此由此就能够理解为ARC下strong修饰的block并无处于栈区的可能,也就不存在做用域结束栈区内容销毁野指针的问题了。 可是为了保证修饰符和block特性的一致性,使用copy修饰符仍然是最为合适的。


4. 什么场景下才须要对变量使用__block?

赋值场景下使用__block,使用场景下不须要。 咱们来对比下赋值场景和使用场景

//赋值场景
NSMutableArray *__block array = nil;

void(^Block)(void) = ^{
    array = [NSMutableArray array];
};

Block();
    
//使用场景
NSMultableArray *array = [NSMultableArray array];

void(^Block)() = ^{
    [array addObject:@"1"];
};

Block();
复制代码

5. 运行如下GCD多线程代码,控制台将打印什么?

dispatch_queue_t gQueue= dispatch_get_global_queue(0, 0);
    
NSLog(@"1");
dispatch_sync(gQueue, ^{
    NSLog(@"2");
    dispatch_sync(gQueue, ^{
        NSLog(@"3");
     });
    NSLog(@"4");
    });
NSLog(@"5");
复制代码

答案:12345

首先打印1,全局队列本质是并发队列也就是并发同步,同步任务不开起线程,在主线程执行打印2。 而后全局队列执行同步任务,依旧不开启线程,在主线程执行打印3。 同步任务完成,依旧存在于全局队列同步执行,打印4. 同步任务完成,打印5。

相关文章
相关标签/搜索