Ответ 1
Вы сказали:
- Какова цель свойства
stateQueue
? Я вижу, что он используется get и set вычисленного свойстваstate
, но я не могу найти никакой документации, которая объясняет методыsync:flags:execute
иsync:execute
, которые они используют.
Этот код "синхронизирует" доступ к свойству, чтобы сделать его потокобезопасным. Относительно того, почему вам нужно это сделать, см. документацию Operation
, в которой указано:
Multicore Considerations
... Когда вы создаете подкласс
NSOperation
, вы должны убедиться, что любые переопределенные методы остаются безопасными для вызова из нескольких потоков. Если вы реализуете в своем подклассе пользовательские методы, такие как пользовательские методы доступа к данным, вы также должны убедиться, что эти методы поточно-ориентированы. Таким образом, доступ к любым переменным данных в операции должен быть синхронизирован, чтобы предотвратить потенциальное повреждение данных. Для получения дополнительной информации о синхронизации см. Руководство по программированию потоков.
Что касается точного использования этой параллельной очереди для синхронизации, это называется паттерном "читатель-писатель". Эта базовая концепция паттерна "читатель-писатель" заключается в том, что чтение может происходить одновременно по отношению друг к другу (следовательно, sync
, без барьера), но запись никогда не должна выполняться одновременно с любым другим доступом к этому свойству (следовательно, async
с барьером). Все это описано в видео WWDC 2012 Асинхронные шаблоны проектирования с блоками, GCD и XPC. Обратите внимание, что хотя это видео обрисовывает в общих чертах базовую концепцию, в нем используется более старый синтаксис dispatch_sync
и dispatch_barrier_async
, а не синтаксис Swift 3 и более поздних версий, в котором используются только синтаксис sync
и async(flags: .barrier)
.
Вы также спросили:
- Какова цель трех методов класса в разделе
NSObject
, которые возвращают["state"]
? Я не вижу их где-либо использовать. Я обнаружил вNSObject
class func keyPathsForValuesAffectingValue(forKey key: String) -> Set<String>
, но это не помогает мне понять, почему эти методы объявлены.
Это просто методы, которые гарантируют, что изменения свойства state
вызывают KVN для свойств isReady
, isExecuting
и isFinished
. KVN этих трех ключей имеет решающее значение для правильного функционирования асинхронных операций. В любом случае этот синтаксис описан в Руководстве по программированию наблюдения значения ключа: регистрация зависимых ключей.
Найденный вами метод keyPathsForValuesAffectingValue
связан. Вы можете зарегистрировать зависимые ключи с помощью этого метода или использовать отдельные методы, как показано в исходном фрагменте кода.
Кстати, вот пересмотренная версия предоставленного вами класса AsynchronousOperation
, а именно:
Вы не должны звонить
super.start()
.start
документация гласит (выделение добавлено):Если вы реализуете параллельную операцию, вы должны переопределить этот метод и использовать его для запуска вашей операции. Ваша пользовательская реализация не должна вызывать
super
в любое время.Добавьте
@objc
, необходимый в Swift 4.Переименован в
execute
, чтобы использоватьmain
, что является соглашением для подклассовOperation
.Неуместно объявлять
isReady
как свойствоfinal
. Любой подкласс должен иметь право на дальнейшее совершенствование своей логикиisReady
(хотя мы, по общему признанию, делаем это редко).Используйте
#keyPath
, чтобы сделать код немного более безопасным/надежным.Вам не нужно делать ручной KVN при использовании свойства
dynamic
. Ручной вызовwillChangeValue
иdidChangeValue
в этом примере не требуется.Измените
finish
, чтобы он переходил в состояние.finished
, только если он еще не завершен.
Таким образом:
public class AsynchronousOperation: Operation {
/// State for this operation.
@objc private enum OperationState: Int {
case ready
case executing
case finished
}
/// Concurrent queue for synchronizing access to 'state'.
private let stateQueue = DispatchQueue(label: Bundle.main.bundleIdentifier! + ".rw.state", attributes: .concurrent)
/// Private backing stored property for 'state'.
private var _state: OperationState = .ready
/// The state of the operation
@objc private dynamic var state: OperationState {
get { return stateQueue.sync { _state } }
set { stateQueue.async(flags: .barrier) { self._state = newValue } }
}
// MARK: - Various 'Operation' properties
open override var isReady: Bool { return state == .ready && super.isReady }
public final override var isExecuting: Bool { return state == .executing }
public final override var isFinished: Bool { return state == .finished }
public final override var isAsynchronous: Bool { return true }
// KVN for dependent properties
open override class func keyPathsForValuesAffectingValue(forKey key: String) -> Set<String> {
if ["isReady", "isFinished", "isExecuting"].contains(key) {
return [#keyPath(state)]
}
return super.keyPathsForValuesAffectingValue(forKey: key)
}
// Start
public final override func start() {
if isCancelled {
state = .finished
return
}
state = .executing
main()
}
/// Subclasses must implement this to perform their work and they must not call 'super'. The default implementation of this function throws an exception.
open override func main() {
fatalError("Subclasses must implement 'main'.")
}
/// Call this function to finish an operation that is currently executing
public final func finish() {
if !isFinished { state = .finished }
}
}