Swift与OC最大的区别就是新增了值类型Struct, let变量修饰后无法再次被赋值实现真正的immutable,也就是说一旦赋值不能再次修改。虽然OC中定义NSArray *arr 也是一个immutable,但是arr对象指针指向还可以被再次重新赋值。OC是一门运行时语言,arr所指向的变量都是运行时获取对象。OC中使用immutable不直接等同于线程安全。Objective-C中存在深拷贝、浅拷贝,即使你调用一个[NSMutableArray Copy]得到的NSArray也不代表这个数组中的对象都是经过深拷贝。

我们先看一下如下代码会造成线程安全问题?:

1
2
3
if (self.xxx) {
[self.dict setObject:@"ah" forKey:self.xxx];
}

在单线程模式下肯定是没啥问题,但在多线程情况下比如线程A,线程B两者都同时进入了if条件,当A正要执行set操作,B已经对self.dict = nil。那么线程A肯定是不能设置成功的。
还有另外一种情况,当我们的数据资源比如集合,多线程对资源进行争夺。如添加和移除操作。为了保证最终结果的准确性,我们通常会给操作代码加锁。
1
2
3
OSSpinLock(&_lock);
[self.array addObject:@"data1"];
OSSpinUnlock(&_lock);


在OC中,修饰属性通常会用非原子性nonatomic。当属性设置在多线程并发情况下,有可能会崩溃。 self.xxx = “111” 其实是调用了xxx属性的setter方法。
1
2
3
4
5
6
7
- (void)setXxx:(NSString *)newXXX {
if (newXXX != _xxx) {
[newXXX retain];
[_xxx release];
_userName = newXXX;
}
}

由于没有加锁,当多个线程都进入if条件判断后,会对_xxx多次release,这就直接crash掉。可以用以下代码试验,在多次执行后,随机地可能crash掉:
1
2
3
4
5
for (int i = 0; i < 5000; i++) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
self.data = [[NSMutableData alloc] init];
});
}

而修饰词atomic修饰保证属性的原子性,系统给属性的get、set方法包装一层互斥锁。但苹果不建议我们使用,因为频繁读取性能损耗非常大。并且也只能保证set,get的安全性。如果属性对象是数组,假设在子线程A中对数组写操作,而主线程一直在读数据,[self.arr objectAtIndex:index] 就很容易出现越界崩溃。 atomic并不能起到线程安全性作用。

那么,我们要怎么样才能保证可变数组的线程安全问题呢?通过栅栏函数dispatch_barrier可保证集合的线程安全。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
@interface QGYSafeDic: NSObject
- (void)qgy_setSafeObject: (id)object forKey:(NSString *)key;
- (id)qgy_safeObjectForKey: (NSString *)key;
@end

@implementation QGYSafeDic
{
NSMutableDictionary* _dic;
dispatch_queue_t _concurrentQueue ;
}

- (instancetype)init {
if (self = [super init]){
_concurrentQueue = dispatch_queue_create("com.qgy.syncQueue", DISPATCH_QUEUE_CONCURRENT);
_dic = [NSMutableDictionary dictionary];
}
return self;
}

- (void)qgy_setSafeObject: (id)object forKey:(NSString *)key{
key = [key copy];
dispatch_barrier_async(_concurrentQueue, ^{
[_dic setObject:object forKey:key];
});
}

- (id)qgy_safeObjectForKey: (NSString *)key{
__block NSString *tempObj;
dispatch_sync(_concurrentQueue, ^{
tempObj = [_dic objectForKey:key];
});

return tempObj;
}

@end

Swift 版本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class SafeDict<Key: Hashable, Value>: CustomDebugStringConvertible {
private var dictStorage = [Key: Value]()

private let queue = DispatchQueue(label: "com.qgy.\(UUID().uuidString)", qos: .utility, attributes: .concurrent,
autoreleaseFrequency: .inherit, target: .global())
public init() {}

public subscript(key: Key) -> Value? {
get {
queue.sync {
dictStorage[key]
}
}
set {
queue.async(flags: .barrier) { [weak self] in
self?.dictStorage[key] = newValue
}
}
}

public var debugDescription: String {
return dictStorage.debugDescription
}
}

测试:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class MyTest {
var atomicDict = SafeDict<String, Int>()

func test(){
let g = DispatchGroup()

for index in (0..<10) {
g.enter()
DispatchQueue.global().async {
self.atomicDict["key\(index)"] = index
g.leave()
}
}

g.notify(queue: .main, execute: {
print(self.atomicDict)
})
}
}

let obj = MyTest()
obj.test()
//结果:["key8": 8, "key0": 0, "key2": 2, "key9": 9, "key6": 6, "key3": 3, "key7": 7, "key5": 5, "key1": 1, "key4": 4]

评论