zoukankan      html  css  js  c++  java
  • 一道网易面试题

    一、题目描述

      题目来自网上一个博客,具体类似如下

      

    @interface ViewController ()
    
    @property (nonatomic, strong) NSString *target;
    
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        // Do any additional setup after loading the view, typically from a nib.
    
        dispatch_queue_t queue = dispatch_queue_create("parallel", DISPATCH_QUEUE_CONCURRENT);
        for (int i = 0; i < 1000000000 ; i++) {
            dispatch_async(queue, ^{
                self.target = [NSString stringWithFormat:@"ksddkjalkd2018-11-09 12:04:09.750846+0800 ARCTest2[525:168910] 1111sdsdsjd%d",i];
                NSLog(@"%@", self.target);
            });
        }
    }
    

      问代码执行之后会发生什么?

    二、解析

      在设置target的setter中,是非线程安全的,未加锁;因此多线程访问这个属性setter方法的时候潜在crash的情况

      因为setter大概如下

    - (void)setTarget:(NSString *)target
    {
        if(_target != target)
        {
            [_target release];
            _target = [target retain];
        }
    }
    

      对应runtime代码

    //objc_class.mm
    void object_setIvar(id obj, Ivar ivar, id value)
    {
        return _object_setIvar(obj, ivar, value, false /*not strong default*/);
    }
    
    
    static ALWAYS_INLINE 
    void _object_setIvar(id obj, Ivar ivar, id value, bool assumeStrong)
    {
        //判断是否是TaggedPointer
        if (!obj  ||  !ivar  ||  obj->isTaggedPointer()) return;
    
        ptrdiff_t offset;
        objc_ivar_memory_management_t memoryManagement;
        //找对应的内存管理语义和属性偏移值
        _class_lookUpIvar(obj->ISA(), ivar, offset, memoryManagement);
    
        //如果找不到默认是否为Strong,不然为unsafe_unretained
        if (memoryManagement == objc_ivar_memoryUnknown) {
            if (assumeStrong) memoryManagement = objc_ivar_memoryStrong;
            else memoryManagement = objc_ivar_memoryUnretained;
        }
    
        //根据偏移值找到属性对应位置
        id *location = (id *)((char *)obj + offset);
        
        //判断不同的内存管理语义,调用方法
        switch (memoryManagement) {
        case objc_ivar_memoryWeak:       objc_storeWeak(location, value); break;
        case objc_ivar_memoryStrong:     objc_storeStrong(location, value); break;
        case objc_ivar_memoryUnretained: *location = value; break;
        case objc_ivar_memoryUnknown:    _objc_fatal("impossible");
        }
    }
    

      在release的方法最后会调用obj_release

    //NSObject.mm
    void
    objc_storeStrong(id *location, id obj)
    {   
        //如果新值指针和旧值一样,则不更新,直接return
        id prev = *location;
        if (obj == prev) {
            return;
        }
        //先对新值retain
        objc_retain(obj);
        //再赋值
        *location = obj;
        //最后对旧值release
        objc_release(prev);
    }
    

      因为一个对象已经release了,但是这个指针指向的内存已经被回收,所以访问这个指针的内存会产生一个内存访问的错误

    2018-11-09 15:22:04.860819+0800 ARCTest2[93017:2107037] *** -[CFString release]: message sent to deallocated instance 0x600000e70240

    以上的代码用模拟器是比较容易出现的,因为GCD创建了64个线程,线程并发次数很多

    如果使用iPhoneX的话,没有出现,(应该是比较难重现),但是存在crash的可能

    可以看到GCD创建了6个线程,是6核的1倍

      

    如果将target的修饰改为atomic,将不会crash,但是直接访问实例变量依旧会产生crash。

    @interface ViewController ()
    
    @property (atomic, strong) NSString *target;
    
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        // Do any additional setup after loading the view, typically from a nib.
    
        dispatch_queue_t queue = dispatch_queue_create("parallel", DISPATCH_QUEUE_CONCURRENT);
        for (int i = 0; i < 1000000000 ; i++) {
            dispatch_async(queue, ^{
                _target = [NSString stringWithFormat:@"ksddkjalkd2018-11-09 12:04:09.750846+0800 ARCTest2[525:168910] 1111sdsdsjd%d",i];
                NSLog(@"%@", self.target);
            });
        }
    }
    

    三、总结

      通过上面的例子,我们可以总结出来,对于一个变量,如果多线程访问之下,retain、release的顺序得不到保证的话,就会带来野指针的问题

      ARC只能保证在合适的地方插入retain、release;但是retain、release的顺序还需要业务来进行保证。

  • 相关阅读:
    使用ffmpeg将mp4切片成ts slice 并生成m3u8命令,同时让IIS支持支持 m3u8
    C# 自动下移动光标 。winform 发送tab,在WPF中
    C# 获取当前程序路径方法整理
    Jeecg-Boot 2.0.0 版本发布,基于Springboot+Vue 前后端分离快速开发平台
    vue2.0源码-丰富的选项合并策略
    JavaScript 核心原理精讲
    前端性能优化
    Vue Router模式
    css3实现圆角三角形
    CSS动画
  • 原文地址:https://www.cnblogs.com/doudouyoutang/p/9935420.html
Copyright © 2011-2022 走看看