OC底层原理

iOS property总结和原子锁的实现原理

2021-08-20  本文已影响0人  Mrfang1

Property 总结

iOS 开发最常用的property 包含以下三类:

线程安全

atomic/nonatomic

指定自动合成的存取方法是否为原子操作

什么是原子性?

原子操作是不可分割的操作,在原子操作执行完毕之前,其不会被其它任务或事件终端

被标注atomic会保证property的频繁操作的原子性,可以避免由两个操作对同一个property同时进行操作而造成的错误。

atomic与nonatomic内部实现的区别只是atomic对象setter和getter会加一个缩,而nonatomic没有

@property (nonatomic) NSObject *nonatomicObj;
@property (atomic) NSObject *atomicObj;
 
 
- (void)setNonatomicObj:(NSObject *)nonatomicObj{
    if (_nonatomicObj != nonatomicObj) {
        [_nonatomicObj release];
        _nonatomicObj = [nonatomicObj retain];
    }
}
 
- (NSObject *)nonatomicObj{
    return _nonatomicObj;
}
 
- (void)setAtomicObj:(NSObject *)atomicObj{
    @synchronized(self) {
        if (_atomicObj != atomicObj) {
            [_atomicObj release];
            _atomicObj = [atomicObj retain];
        }
    }
}
 
- (NSObject *)atomicObj{
    @synchronized(self) {
        return _atomicObj;
    }
}

原子性是不是代表线程安全

苹果开发文档已经明确指出:Atomic不能保证对象多线程的安全。所以Atomic 不能保证对象多线程的安全。它只是能保证你访问的时候给你返回一个完好无损的Value而已。举个例子:

如果线程 A 调了 getter,与此同时线程 B 、线程 C 都调了 setter——那最后线程 A get 到的值,有3种可能:可能是 B、C set 之前原始的值,也可能是 B set 的值,也可能是 C set 的值。同时,最终这个属性的值,可能是 B set 的值,也有可能是 C set 的值。所以atomic可并不能保证对象的线程安全。

自旋锁与atomic

多线程中常用的锁分为自旋锁和互斥锁
维基百科上对自旋锁的解释:

自旋锁 是计算机科学用于多线程同步的一种锁,线程反复检查锁变量是否可用。由于线程在这一过程中保持执行,因此是一种 忙等 (忙碌等待)。一旦获取了自旋锁,线程会一直持有该锁,直至显式释放自旋锁。

获取、释放自旋锁,实际上是读写自旋锁的存储内存或寄存器。因此这种读写操作必须是原子的(atomic)。通常用 test-and-set 原子操作来实现。

自旋锁的核心就是忙等,尝试自定义一个自旋锁如下:

struct spinlock {
    int flag;
};

@implementation TPSpinLock {
    spinlock _lock;
}

- (instancetype)init {
    self = [super init];
    if (self) {
        _lock = spinlock{0};
    }
    return self;
}

- (void)lock {
    while (test_and_set(&_lock.flag, 1)) {
        // wait
    }
}

- (void)unlock {
    _lock.flag = 0;
}


int test_and_set(int *old_ptr, int _new) {
    int old = *old_ptr;
    *old_ptr = _new;
    return old;
}

@end

如上述代码,我们自定义了test_and_set方法,当线程1进行lock操作的时候会传入flag = 0,test_and_set方法返回0的同时并将flag = 1,这个时候线程2 执行lock的时候一直返回1,那么就一直执行while(1)处于等待状态,直到线程1执行unlock将flag = 0 这个时候就打破while循环,线程2就能继续执行并加锁。

总结:

内存管理

assign

assign标识对属性只进行简单的赋值操作,不更改操作对象的引用计数。该关键字既可以修饰值类型,也可用于修饰指针类型对象。
当修饰对象时,如果该对象被释放,编译器不会将该属性置为nil,指针仍然指向之前修饰的内存,这时访问该属性会产生野指针。

strong、weak

strong表示属性对修饰的对象有一个强引用,会先保留新值,然后释放旧值。只能修饰对象

weak表示属性对修饰的对象有一个弱引用,对于新值不会增加引用计数,对于旧值也不会减少引用计数。当修饰的对象被释放时,weak修饰的属性被自动被置为nil,能够有效防止野指针错误。
weak常用于防止循环引用

copy

copy修饰的属性在setter方法中会设置拷贝后的指针。
常用于修饰不可变类型的容器,或修饰block

unsafe_unretained

使用unsafe_unretained修饰时效果与assign相同,不会增加引用计数,当所赋的值被销毁时不会被置为nil可能会发生野指针错误。unsafe_unretained与assign的区别在于,unsafe_unretained只能修饰对象,不能修饰标量类型,而assign两者均可修饰。

retain

MRC下使用,作用同string

读写权限

readonly、readwrite

标识属性仅可读,可读写

getter=?、setter=?

修改属性的getter、setter

@property 深入理解

@property = ivar + getter + setter

其中ivar是实例变量,编译器会帮我们自动生成名字为‘_属性名’这样的实例变量,同时自动生成getter和setter方法

编译器帮我们做的事情

当我们声明一个属性后,编译器会将当前关键字修饰的属性自动生成setter方法和getter方法。

@property (nonatamic, copy) NSString *name;

等价于

- (void)setName:(NSString *)name {
    _name = name.copy;
}

- (NSString *)name {
    return _name;
}

@property (nonatamic, strong) NSMutableArray *array;

等价于

- (void)setArray:(NSMutableArray *)array {
    [array retain];
    _array = array;
    [array release];
}

- (NSMutableArray *)array {
    return _array;
}

编译器以上的工作我们称之为属性自动合成

@synthesis

iOS6以后LLVM编译器引入property autosynthesis,即属性自动合成,就是编译器会为每个@property添加@synthesis,如下

@synthesize propertyName = _propertyName;

如果不存在_propertyName实例变量,则会创建一个_propertyName,如果已经存在,则不会添加实例变量

@synthesis使用场景

以下场景自动合成会失效,需要手动合成

什么情况下自动合成会失效 ?

同时重写了属性的setter和getter时;

重写了只读属性的getter时;

使用了@dynamic时;

在 @protocol 中定义的所有属性;

在 category 中定义的所有属性;

父类已有的属性, 子类重载的属性不会自动合成;   

@dynamic

@dynamic 告诉编译器,不要为property声明的属性添加setter/getter方法, 由用户自己实现,该属性的getter和setter方法可能不在本类,而在其他地方(比如父类或者在运行时中提供, 如果不实现的话, 运行时会有Unrecoginzed Selector Crash)

上一篇 下一篇

猜你喜欢

热点阅读