Update KVO usage for Swift 5
Better compiler checks of KVO, and reduced code.
PiperOrigin-RevId: 316974406
diff --git a/src/Tulsi/ConfigEditorSourceFilterViewController.swift b/src/Tulsi/ConfigEditorSourceFilterViewController.swift
index ed5aab4..5447781 100644
--- a/src/Tulsi/ConfigEditorSourceFilterViewController.swift
+++ b/src/Tulsi/ConfigEditorSourceFilterViewController.swift
@@ -44,20 +44,20 @@
guard let entry = entry as? UISourcePath else { return }
let enabled = newValue == NSControl.StateValue.on.rawValue
- willChangeValue(forKey: "explicitlyRecursive")
+ willChangeValue(for: \.explicitlyRecursive)
entry.recursive = enabled
- didChangeValue(forKey: "explicitlyRecursive")
+ didChangeValue(for: \.explicitlyRecursive)
// If this node is newly recursive, force hasRecursiveEnabledParent, otherwise have children
// inherit this node's status.
setChildrenHaveRecursiveParent(enabled || hasRecursiveEnabledParent)
// Notify KVO that this node's ancestors have also changed state.
- var ancestor = parent
- while ancestor != nil {
- ancestor!.willChangeValue(forKey: "recursive")
- ancestor!.didChangeValue(forKey: "recursive")
- ancestor = ancestor!.parent
+ var child: SourcePathNode? = self
+ while let parent = child?.parent as? SourcePathNode {
+ parent.willChangeValue(for: \.recursive)
+ parent.didChangeValue(for: \.recursive)
+ child = parent
}
}
}
diff --git a/src/Tulsi/HeadlessTulsiProjectCreator.swift b/src/Tulsi/HeadlessTulsiProjectCreator.swift
index 1d802ad..77c7f70 100644
--- a/src/Tulsi/HeadlessTulsiProjectCreator.swift
+++ b/src/Tulsi/HeadlessTulsiProjectCreator.swift
@@ -19,36 +19,8 @@
/// Provides functionality to generate a Tulsiproj bundle.
struct HeadlessTulsiProjectCreator {
- /// Provides functionality to signal a semaphore when the "processing" key on some object is set
- /// to false.
- private class ProcessingCompletedObserver: NSObject {
- let semaphore: DispatchSemaphore
-
- init(semaphore: DispatchSemaphore) {
- self.semaphore = semaphore
- }
-
- override func observeValue(forKeyPath keyPath: String?,
- of object: Any?,
- change: [NSKeyValueChangeKey : Any]?,
- context: UnsafeMutableRawPointer?) {
- if context != &HeadlessTulsiProjectCreator.KVOContext {
- super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
- return
- }
-
- if keyPath == "processing", let newValue = change?[NSKeyValueChangeKey.newKey] as? Bool {
- if (!newValue) {
- semaphore.signal()
- }
- }
- }
- }
-
let arguments: TulsiCommandlineParser.Arguments
- private static var KVOContext: Int = 0
-
init(arguments: TulsiCommandlineParser.Arguments) {
self.arguments = arguments
}
@@ -149,18 +121,16 @@
// Updating the project's bazelPackages will cause it to go into processing, observe the
// processing key and block further execution until it is completed.
let semaphore = DispatchSemaphore(value: 0)
- let observer = ProcessingCompletedObserver(semaphore: semaphore)
- document.addObserver(observer,
- forKeyPath: "processing",
- options: .new,
- context: &HeadlessTulsiProjectCreator.KVOContext)
-
+ let observer = document.observe(\.processing, options: .new) { _, change in
+ guard change.newValue == false else { return }
+ semaphore.signal()
+ }
+ defer { observer.invalidate() }
document.bazelPackages = Array(bazelPackages)
// Wait until processing completes.
_ = semaphore.wait(timeout: DispatchTime.distantFuture)
- document.removeObserver(observer, forKeyPath: "processing")
return bazelPackages
}
diff --git a/src/Tulsi/TulsiGeneratorConfigDocument.swift b/src/Tulsi/TulsiGeneratorConfigDocument.swift
index 490cd5a..7f6e685 100644
--- a/src/Tulsi/TulsiGeneratorConfigDocument.swift
+++ b/src/Tulsi/TulsiGeneratorConfigDocument.swift
@@ -88,16 +88,24 @@
}
}
+ private var uiRuleInfoObservers: [NSKeyValueObservation] = []
+
/// The UIRuleEntry instances that are acted on by the associated UI.
@objc dynamic var uiRuleInfos = [UIRuleInfo]() {
willSet {
stopObservingRuleEntries()
- for entry in newValue {
- entry.addObserver(self,
- forKeyPath: "selected",
- options: .new,
- context: &TulsiGeneratorConfigDocument.KVOContext)
+ uiRuleInfoObservers = newValue.map { entry in
+ entry.observe(
+ \.selected, options: .new
+ ) { [unowned self] _, change in
+ guard let value = change.newValue else { return }
+ if value {
+ self.selectedRuleInfoCount += 1
+ } else {
+ self.selectedRuleInfoCount -= 1
+ }
+ }
}
}
}
@@ -159,8 +167,6 @@
// Closure to be invoked when a save operation completes.
private var saveCompletionHandler: ((_ canceled: Bool, _ error: Error?) -> Void)? = nil
- private static var KVOContext: Int = 0
-
static func isGeneratorConfigFilename(_ filename: String) -> Bool {
return (filename as NSString).pathExtension == TulsiGeneratorConfig.FileExtension
}
@@ -424,23 +430,6 @@
return false
}
- override func observeValue(forKeyPath keyPath: String?,
- of object: Any?,
- change: [NSKeyValueChangeKey : Any]?,
- context: UnsafeMutableRawPointer?) {
- if context != &TulsiGeneratorConfigDocument.KVOContext {
- super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
- return
- }
- if keyPath == "selected", let newValue = change?[NSKeyValueChangeKey.newKey] as? Bool {
- if (newValue) {
- selectedRuleInfoCount += 1
- } else {
- selectedRuleInfoCount -= 1
- }
- }
- }
-
private func enabledFeatures(options: TulsiOptionSet) -> Set<BazelSettingFeature> {
return BazelBuildSettingsFeatures.enabledFeatures(options: options)
}
@@ -724,9 +713,8 @@
// MARK: - Private methods
private func stopObservingRuleEntries() {
- for entry in uiRuleInfos {
- entry.removeObserver(self, forKeyPath: "selected", context: &TulsiGeneratorConfigDocument.KVOContext)
- }
+ uiRuleInfoObservers.forEach { $0.invalidate() }
+ uiRuleInfoObservers = []
}
private func makeConfig(withFullyResolvedOptions resolve: Bool = false) -> TulsiGeneratorConfig? {
diff --git a/src/Tulsi/TulsiProjectDocument.swift b/src/Tulsi/TulsiProjectDocument.swift
index 0853886..1cf8014 100644
--- a/src/Tulsi/TulsiProjectDocument.swift
+++ b/src/Tulsi/TulsiProjectDocument.swift
@@ -190,9 +190,12 @@
}
func createNewProject(_ projectName: String, workspaceFileURL: URL) {
- willChangeValue(forKey: "bazelURL")
- willChangeValue(forKey: "bazelPackages")
- willChangeValue(forKey: "workspaceRootURL")
+ willChangeValue(for: \.bazelURL)
+ defer { didChangeValue(for: \.bazelURL) }
+ willChangeValue(for: \.bazelPackages)
+ defer { didChangeValue(for: \.bazelPackages) }
+ willChangeValue(for: \.workspaceRootURL)
+ defer { didChangeValue(for: \.workspaceRootURL) }
// Default the bundleURL to a sibling of the selected workspace file.
let bundleName = "\(projectName).\(bundleExtension)"
@@ -205,10 +208,6 @@
updateChangeCount(.changeDone)
LogMessage.postSyslog("Create project: \(projectName)")
-
- didChangeValue(forKey: "bazelURL")
- didChangeValue(forKey: "bazelPackages")
- didChangeValue(forKey: "workspaceRootURL")
}
override func writeSafely(to url: URL,
diff --git a/src/Tulsi/UISelectableOutlineViewNode.swift b/src/Tulsi/UISelectableOutlineViewNode.swift
index 601b244..4639a34 100644
--- a/src/Tulsi/UISelectableOutlineViewNode.swift
+++ b/src/Tulsi/UISelectableOutlineViewNode.swift
@@ -72,7 +72,7 @@
return
}
- willChangeValue(forKey: "state")
+ willChangeValue(for: \.state)
if let entry = entry {
entry.selected = newSelectionState
}
@@ -80,13 +80,13 @@
for node in children {
node.state = newValue
}
- didChangeValue(forKey: "state")
+ didChangeValue(for: \.state)
// Notify KVO that this node's ancestors have also changed state.
var ancestor = parent
while ancestor != nil {
- ancestor!.willChangeValue(forKey: "state")
- ancestor!.didChangeValue(forKey: "state")
+ ancestor!.willChangeValue(for: \.state)
+ ancestor!.didChangeValue(for: \.state)
ancestor = ancestor!.parent
}
}
diff --git a/src/TulsiGenerator/ProcessRunner.swift b/src/TulsiGenerator/ProcessRunner.swift
index 9607ad1..9315a2e 100644
--- a/src/TulsiGenerator/ProcessRunner.swift
+++ b/src/TulsiGenerator/ProcessRunner.swift
@@ -38,8 +38,8 @@
/// Coordinates logging with Process lifetime to accurately report when a given process started.
final class TimedProcessRunnerObserver: NSObject {
- /// Context for KVO
- private static var KVOContext: Int = 0
+ /// Observer for KVO on the process.
+ private var processObserver: NSKeyValueObservation?
/// Mapping between Processes and LogSessionHandles created for each.
///
@@ -78,44 +78,25 @@
accessPendingLogHandles { pendingLogHandles in
pendingLogHandles[process] = logSessionHandle
}
- process.addObserver(self,
- forKeyPath: #keyPath(Process.isRunning),
- options: .new,
- context: &TimedProcessRunnerObserver.KVOContext)
+ processObserver = process.observe(\.isRunning, options: .new) {
+ [unowned self] process, change in
+ guard change.newValue == true else { return }
+ self.accessPendingLogHandles { pendingLogHandles in
+ pendingLogHandles[process]?.resetStartTime()
+ }
+ }
}
/// Report the time this process has taken, and cleanup its logging handle and KVO observer.
fileprivate func stopLogging(process: Process, messageLogger: LocalizedMessageLogger) {
if let logHandle = self.pendingLogHandles[process] {
messageLogger.logProfilingEnd(logHandle)
- process.removeObserver(self,
- forKeyPath: #keyPath(Process.isRunning),
- context: &TimedProcessRunnerObserver.KVOContext)
+ processObserver?.invalidate()
accessPendingLogHandles { pendingLogHandles in
_ = pendingLogHandles.removeValue(forKey: process)
}
}
}
-
- /// KVO to set the logger start time to the moment when the Process indicates that it's running.
- override public func observeValue(forKeyPath keyPath: String?,
- of object: Any?,
- change: [NSKeyValueChangeKey : Any]?,
- context: UnsafeMutableRawPointer?) {
- if context != &TimedProcessRunnerObserver.KVOContext {
- super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
- return
- }
-
- if keyPath == #keyPath(Process.isRunning),
- let newValue = change?[NSKeyValueChangeKey.newKey] as? NSNumber,
- newValue.boolValue,
- let process = object as? Process {
- accessPendingLogHandles { pendingLogHandles in
- pendingLogHandles[process]?.resetStartTime()
- }
- }
- }
}