iOS——atomic、nonatomic、assign、_unsafe_unretain

atomic和nonatomic

在iOS开发中,当你定义一个属性时,编译器会自动为你生成一个带下划线的成员变量(实例变量)以及对应的getter和setter方法。如果你使用atomic修饰这个属性,那么编译器在生成setter和getter方法时,会在这些方法的内部实现中加入加锁操作。但是加锁只能保证getter和setter方法内部的线程安全,并不能保证对该属性的所有操作都是线程安全的。

具体的加锁代码:

static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{
    // 如果偏移量为0,设置对象的类并返回
    if (offset == 0) {
        object_setClass(self, newValue);
        return;
    }

    id oldValue;
    // 通过偏移量计算出属性的地址
    id *slot = (id*) ((char*)self + offset);

    // 如果需要复制,进行复制操作
    if (copy) {
        newValue = [newValue copyWithZone:nil];
    } else if (mutableCopy) {
        // 如果需要可变复制,进行可变复制操作
        newValue = [newValue mutableCopyWithZone:nil];
    } else {
        // 如果新值与旧值相同,直接返回
        if (*slot == newValue) return;
        // 保留新值,增加引用计数
        newValue = objc_retain(newValue);
    }

    // 非原子操作
    if (!atomic) {
        // 取出旧值并设置新值
        oldValue = *slot;
        *slot = newValue;
    } else {
        // 原子操作
        // 获取属性对应的自旋锁
        spinlock_t& slotlock = PropertyLocks[slot];
        // 加锁
        slotlock.lock();
        // 取出旧值并设置新值
        oldValue = *slot;
        *slot = newValue;        
        // 解锁
        slotlock.unlock();
    }

    // 释放旧值,减少引用计数
    objc_release(oldValue);
}
static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{
    // 如果偏移量为0,设置对象的类并返回
    if (offset == 0) {
        object_setClass(self, newValue);
        return;
    }

    id oldValue;
    // 通过偏移量计算出属性的地址
    id *slot = (id*) ((char*)self + offset);

    // 如果需要复制,进行复制操作
    if (copy) {
        newValue = [newValue copyWithZone:nil];
    } else if (mutableCopy) {
        // 如果需要可变复制,进行可变复制操作
        newValue = [newValue mutableCopyWithZone:nil];
    } else {
        // 如果新值与旧值相同,直接返回
        if (*slot == newValue) return;
        // 保留新值,增加引用计数
        newValue = objc_retain(newValue);
    }

    // 非原子操作
    if (!atomic) {
        // 取出旧值并设置新值
        oldValue = *slot;
        *slot = newValue;
    } else {
        // 原子操作
        // 获取属性对应的自旋锁
        spinlock_t& slotlock = PropertyLocks[slot];
        // 加锁
        slotlock.lock();
        // 取出旧值并设置新值
        oldValue = *slot;
        *slot = newValue;        
        // 解锁
        slotlock.unlock();
    }

    // 释放旧值,减少引用计数
    objc_release(oldValue);
}

可以看出来在两个方法中,使用atomic修饰的话,对值的设置都会有加锁和解锁的步骤。同时,使用的是自旋锁进行的加锁操作,自旋锁会一直处于忙等状态而不是休眠,所以也会消耗性能。

场景:
如果使用atomic修饰属性值,有A和B两个线程,A线程对属性进行赋值,B线程进行取值操作,当A线程赋值进行一半的时候,由于setter方法内部加锁的缘故,A线程会持有这把锁,当B线程进行取值操作时候,发现A线程持有锁,那么会进行等待,当A线程赋值操作结束后,setter方法内部会放开锁,保证了设置了一个完整的值,那么B线程进行取值操作,getter方法内部持有这把锁,获取到完整的值后,解锁,返回完整的值,最终可以保证B线程一定可以取到一个完整的值。
但是如果使用nonatomic修饰属性值,有A和B两个线程,A线程对属性进行赋值,当A线程赋值进行一半的时候,B线程进行取值操作,由于setter方法内部没有加锁,赋值还没有完成,B线程从getter方法中取不到一个完整的值,拿到一个不完整的值去做一些操作就可能会发生意想不到的事情。

atomic并不能保证线程是安全的,只能保证存取值的完整性。

场景:
使用atomic修饰属性,如果有A、B和C三个线程。其中A和B线程同时对一个属性进行赋值操作,C线程进行取值操作,那么可以保证C线程一定可以取到一个完整的值,但是这个值的内容可能是A线程赋的值,也可能是B线程赋的值,也可能是原始值,虽然取得了完整的值,但是这个值不一定是程序员想要的,所以说atomic并不是线程安全的,它只是保证了属性的setter和getter方法内部是线程安全的。如果你想要真正保证线程安全,那么需要在赋值操作的前后进行加锁和解锁操作,还有注意使用同一把锁。

为什么说atomic关键字是消耗性能的?

因为,atomic底层有加锁的操作,上面也提到了是自旋锁,自旋锁会进行忙等,可以理解为一个while循环一直等,性能肯定会比nonatomic不加锁低。
在平时开发的时候,不涉及线程安全的时候,比如一些UI控件必须在主线程操作的,用nonatomic可以提高性能。而真正要涉及线程安全,不能只靠编译器,需要程序员自己控制。

assign

主要用于不需要管理引用计数的基本数据类型(如 int、float、double、char 等,另外还有id类型)。其主要作用是直接赋值,而不是像 retain 或 copy 那样对对象进行引用计数管理或复制。被assign修饰的对象在释放后,指针的地址还是存在的,也就是说指针并没有被置为nil,成为野指针。

之所以assign可以修饰基本数据类型,因为基本数据类型一般分配在栈上,栈的内存会由系统自动处理,不会造成野指针
以及:在MRC下常见的id delegate往往是用assign方式的属性而不是retain方式的属性,为了防止delegation两端产生不必要的循环引用。例如:对象A通过retain获取了对象B的所有权,这个对象B的delegate又是A, 如果这个delegate是retain方式的,两个都是强引用,互相持有,那基本上就没有机会释放这两个对象了。

weak 和 assign 的区别:

  • 修饰的对象:weak修饰oc对象类型的数据,assign用来修饰是非指针变量。
  • 引用计数:weak 和 assign 都不会增加引用计数。
  • 释放:weak 修饰的对象释放后,指针地址自动设置为 nil,assign修饰的对象释放后指针地址依然存在,成为野指针。
  • 修饰delegate 在MRC使用assign,在ARC使用weak。

_unsafe_unretain

__unsafe_unretained:和__weak 一样,唯一的区别便是,对象即使被销毁,指针也不会自动置空, 此时指针指向的是一个无用的野地址。如果使用此指针,程序会抛出 BAD_ACCESS 的异常。

举个例子:

    id __unsafe_unretained obj = [[NSMutableArray alloc]init];
    [obj addObject:@"obj"];

执行,在第二条语句就会崩溃,分析:
附有__unsafe_unretained修饰符的变量同附有__weak修饰符的变量一样,因为自己生成并持有的对象不能继续为自己持有,所以生成的对象会立即被释放。也就是说在执行完init方法以后,obj指针所指向的内存就已经释放掉了,可是obj指针并没有像附加__weak的指针那样,将指针自动置为nil,它依然指向原来的地址,可是这块地址的内存已经被系统回收了,再访问就是非法的,也就是野指针,再执行后面的addObject方法自然会出错了。
也就是说上面的代码,把__unsafe_unretained换成__weak就不会崩溃,因为obj会自动制置为nil。对nil发送消息是不会有问题的。

第二个例子:

    id __unsafe_unretained obj1 = nil;
    {
        id  obj0 = [[NSMutableArray alloc]init];
        [obj0 addObject:@"obj"];
        obj1 = obj0;
        NSLog(@"obj0 = %@", obj0);
    }
    
    NSLog(@"obj1 = %@", obj1);

依然在最后的NSLog语句崩溃。分析:
因为__unsafe_unretained既不强引用,也不弱引用,作用域外obj0强引用失效,并且没有其他指针强引用这个对象,所以自动释放持有的对象,obj1就相当于野指针,访问野指针就会崩溃;
也就是说,赋值给附有__unsafe_unretained修饰符变量的对象在通过该变量使用时,如果没有确保其确实存在,那么应用程序就会崩溃;

原文链接:https://blog.csdn.net/junjun150013652/article/details/53148711

__autoreleasing

用于将修饰的对象加入自动释放池

你可能感兴趣的:(ios,cocoa,macos)