1. 程式人生 > >一道網易面試題

一道網易面試題

一、題目描述

  題目來自網上一個部落格,具體類似如下

  

@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倍