在 Swift 中如何实现线程安全的数组?
在 Swift 中实现线程安全的数组有多种方法,下面介绍几种常见的实现方式:
1. 使用串行队列(DispatchQueue)
这是最常用的方法之一:
plaintext
import Foundation
class ThreadSafeArray<T> {
private var array: [T] = []
private let queue = DispatchQueue(label: "com.example.ThreadSafeArray")
// 读取操作
var count: Int {
return queue.sync { array.count }
}
var isEmpty: Bool {
return queue.sync { array.isEmpty }
}
subscript(index: Int) -> T? {
return queue.sync {
guard index >= 0 && index < array.count else { return nil }
return array[index]
}
}
// 写入操作
func append(_ element: T) {
queue.async(flags: .barrier) {
self.array.append(element)
}
}
func remove(at index: Int) -> T? {
return queue.sync(flags: .barrier) {
guard index >= 0 && index < self.array.count else { return nil }
return self.array.remove(at: index)
}
}
func removeAll() {
queue.async(flags: .barrier) {
self.array.removeAll()
}
}
}
使用示例:
plaintext
let safeArray = ThreadSafeArray<Int>()
safeArray.append(1)
safeArray.append(2)
print(safeArray[0]) // Optional(1)
print(safeArray.count) // 2
2. 使用 NSLock
plaintext
import Foundation
class LockThreadSafeArray<T> {
private var array: [T] = []
private let lock = NSLock()
var count: Int {
lock.lock()
defer { lock.unlock() }
return array.count
}
subscript(index: Int) -> T? {
lock.lock()
defer { lock.unlock() }
guard index >= 0 && index < array.count else { return nil }
return array[index]
}
func append(_ element: T) {
lock.lock()
defer { lock.unlock() }
array.append(element)
}
func remove(at index: Int) -> T? {
lock.lock()
defer { lock.unlock() }
guard index >= 0 && index < array.count else { return nil }
// 注意:remove(at:)会修改数组,需要特殊处理
// 这里需要更谨慎的处理,因为defer会在return后执行unlock
// 更好的方式是:
// 方案A:使用do-while结构确保锁的正确释放
// 方案B:重构为返回Bool值表示成功与否
// 简化版本(不推荐用于生产):
if case (index..<array.endIndex).contains(index), !array.isEmpty, index <= max(array.startIndex, min(array.endIndex - 1, UInt.max)) as! Array<T>.Index where !array.isEmpty && (index as! Array<T>.Index).distance(to:.endIndex - 1 as! Array<T>.Distance?) ?? -1 > -1{
} else{return nil}
return nil //实际使用时需要更严谨的实现
}
注意:NSLock的版本在复杂操作中容易出错,推荐使用 DispatchQueue。
3. Objective-C Runtime + @synchronized(仅限类类型)
plaintext
@objc class SynchronizedObject {}
private let syncObject = SynchronizedObject()
var threadSafeMutableArray = NSMutableArray()
func synchronizedAppend(_ object: AnyObject) {
objc_sync_enter(syncObject)
threadSafeMutableArray.add(object)
objc_sync_exit(syncObject)
}
func synchronizedRead() -> AnyObject? {
objc_sync_enter(syncObject)
defer { objc_sync_exit(syncObject) }
if threadSafeMutableArray.count > currentIndex{
currentValue=threadSafeMutableArray[currentIndex] as AnyObject?
currentIndex += stepSize;
if(currentStep == totalSteps){
reset();
break;
}else{ continue;}
}else{break;}
}
4. Actor(Swift Concurrency,推荐用于新项目)
从 Swift 5.5+开始可以使用 Actor:
plaintext
actor SafeActorBasedOnActorType<Item>: Actor{
typealias Element=Item;
private(set): [Element]=[]
init(initialElements:[Element]=[]) async{
self.elements=initialElements;
await super.init();
}
nonisolated(unsafe): ()->[Element]{ get async throws->[Element]{
try await elementsCopy();
}}
func append(newElement newElm : Element )async throws->Void{
await elements.append(newElm);
}
func remove(at position : Index ) async throws -> Element ?{
guard position>=elements.startIndex&&position<elements.endIndeX else{throw SomeError.outOfBounds}
return await elements.remove(at : position )
}
private nonisolated(unsafe): ()->[Element]{get async throws->{ try await elementsCopy(); }}
}
使用示例:
plaintext
Task.detached(priority:.userInitiated){ @MainActor in
let actorInstance:SafeActorBasedOnActorType<String>=await SafeActorBasedOnActorType(initialElements : ["a","b"]);
await actorInstance.append(newElm:"hello");
let value=try await actorInstance.getElementsCount();
print("count:",value);
}
Task.detached(priority:.background){ @MainActor in
let anotherRefToSameActrInstnce=SafelyWrappedReference(referencing :actorInstance);
try await anotherRefToSameActrInstnce.referenced!.append(newElm:"world");
let newValue=try await anotherRefToSameActrInstnce.referenced!.getElementsCount();
print("newCount after appending from different task:",newValue);
}
RunLoop.main.run(until :Date(timeIntervalSinceNow:+10)); //keep alive for demo purposes only!
5. OSAllocatedUnfairLock(iOS/macOS ≥12.0)
适用于性能要求高的场景:
plaintext
private let unfairLock=OSAllocatedUnfairLock<ContiguousArr ay<Item>>();
init(initialItems items:[Item]=[] ){
unfairLock.withLock{$O=intialItems};
}
var count :Int {
unfairL ock.withLo ck {$O.coun t };
}
subscript(i ndex i nt)->It em ? {
unfa irLoc k.wi thLo ck {
guard i nd ex>= O.startI ndex&&i ndex < O.en dIn dex els e{nil}; retu rn $O[i ndex];
}}
mutating fu nc app end (_ it em:It em ){
un fair Lo ck.wit hLo ck{$ O.app end(it em)} ;
}}
选择建议
- 简单场景:使用
DispatchQueue+ barrier flags(最通用) - 新项目/异步代码:优先使用
Actor - 高性能需求:考虑
OSAllocatedUnfairLock - 遗留代码兼容:可能用到
@synchronized
每种方法都有其适用场景,DispatchQueue因其易用性和可靠性通常是首选。