Мое приложение использует класс A и его подклассы SubA_x.
A имеет статическое свойство serialNumber, которое изменяется классом A только при инициализации подкласса.
Он устанавливает свойство let name, чтобы каждый подкласс имел уникальное имя.
Вот код:
class A {
static var serialNumber = 0
let name: String
init( /* some parameters */ ) {
A.serialNumber += 1
self.name = "\(A.serialNumber)"
}
}
final class SubA_1: A {
init( /* some parameters */ ) {
super.init( /* some parameters */ )
}
}
При строгой проверке параллелизма в Swift 6 строка, в которой инициализируется serialNumber, выдает ошибку.
Статическое свойство «serialNumber» не является безопасным для параллелизма, поскольку оно неизолированное глобальное общее изменяемое состояние
Я понимаю, что каждый подкласс A может модифицировать var serialNumber из любого потока, поэтому возможны гонки данных.
Но как мне реализовать эту функциональность безопасным для параллелизма способом?
Я попытался предоставить serialNumber актера:
actor SerialNumberManager {
private var serialNumber = 0
func getNextSerialNumber() -> Int {
serialNumber += 1
return serialNumber
}
}
но тогда я не могу вызвать getNextSerialNumber() в init, кроме случаев, когда init является асинхронным.
Но тогда я смогу инициализировать подкласс только в асинхронном контексте и т. д.
Вероятно, я мог бы обеспечить собственную синхронизацию на основе GCD, но должен быть способ сделать это в рамках параллелизма Swift.
@Подметальная машина Ты прав. Я рефакторинг существующего кода и наивно думал, что смогу обойтись минимальными изменениями. Однако теперь я знаю, что это невозможно с использованием непотокобезопасного кода.





Если вам нужно потокобезопасное общее состояние, логичным подходом будет использование actor. Однако если вам нужен такой, который можно вызывать из синхронных контекстов, вы можете просто написать свой собственный менеджер, реализуя собственную ручную синхронизацию (либо с помощью последовательной очереди GCD, либо, как показано ниже, с блокировкой):
class SerialNumberManager: @unchecked Sendable {
static let shared = SerialNumberManager()
private let lock = NSLock()
private var serialNumber = 0
private init() { }
func nextSerialNumber() -> Int {
lock.withLock {
serialNumber += 1
return serialNumber
}
}
}
Обратите внимание: мы будем использовать @unchecked Sendable только тогда, когда реализуем ручную синхронизацию, как указано выше.
И вы можете использовать его так:
class A {
let serialNumber = SerialNumberManager.shared.nextSerialNumber()
let name: String
init( /* some parameters */ ) {
self.name = "\(serialNumber)"
}
}
В качестве альтернативы вы можете использовать OSAllocatedUnfairLock, который уже есть Sendable:
import os.lock
class A {
private static let serialNumber = OSAllocatedUnfairLock(initialState: 0)
let name: String
init( /* some parameters */ ) {
let value = Self.serialNumber.withLock { value in
value += 1
return value
}
self.name = "\(value)"
}
}
Для полноты картины другие альтернативы включают атомы или UUID.
«Но тогда я могу инициализировать подкласс только в асинхронном контексте», и почему это не так? Это цена, которую вы должны заплатить за безопасность.