3

URLSessionTaskSwiftでラッパーを書き込もうとしています。ドキュメントによると

すべてのタスク プロパティは、キーと値の監視をサポートしています。

したがって、この動作を維持し、ラッパーのすべてのプロパティも KVO に準拠し (通常はラップされたタスクに委任)、Objective-C に完全にアクセスできるようにしたいと考えています。1 つのプロパティで何を行っているかを説明しますが、基本的にはすべてのプロパティで同じことを行いたいと考えています。

stateの性質をとってみましょうURLSessionTask。次のようにラッパーを作成します。

@objc(MyURLSessionTask)
public class TaskWrapper: NSObject {
    @objc public internal(set) var underlyingTask: URLSessionTask?
    @objc dynamic public var state: URLSessionTask.State {
        return underlyingTask?.state ?? backupState
    }
    // the state to be used when we don't have an underlyingTask
    @objc dynamic private var backupState: URLSessionTask.State = .suspended

    @objc public func resume() {
        if let task = underlyingTask {
            task.resume()
            return
        }
        dispatchOnBackgroundQueue {
            let task:URLSessionTask = constructTask()
            task.resume()
            self.underlyingTask = task
        }
    }
}

@objcObjective-C から呼び出せるようにプロパティを追加しました。そして、プロパティに追加dynamicして、Swift からでもメッセージ パッシング/ランタイム経由で呼び出されるようにして、正しい KVO 通知が によって生成されるようにしましたNSObject"Using Swift with Cocoa and Objective-C" book の Apple の KVO の章によると、これで十分なはずです。

次に、 KVO に依存キー パスを伝えるために必要な静的クラス メソッドを実装しました。

// MARK: KVO Support
extension TaskWrapper {
    @objc static var keyPathsForValuesAffectingState:Set<String> {
        let keypaths:Set<String> = [
            #keyPath(TaskWrapper.backupState),
            #keyPath(TaskWrapper.underlyingTask.state)
        ]
        return keypaths
    }
}

次に、通知が正しく呼び出されるかどうかを確認する単体テストを作成しました。

var swiftKVOObserver:NSKeyValueObservation?

func testStateObservation() {
    let taskWrapper = TaskWrapper()
    let objcKVOExpectation = keyValueObservingExpectation(for: taskWrapper, keyPath: #keyPath(TaskWrapper.state), handler: nil)
    let swiftKVOExpectation = expectation(description: "Expect Swift KVO call for `state`-change")
    swiftKVOObserver = taskWrapper.observe(\.state) { (_, _) in
        swiftKVOExpectation.fulfill()
    }
    // this should trigger both KVO versions
    taskWrapper.underlyingTask = URLSession(configuration: .default).dataTask(with: url)
    self.wait(for: [swiftKVOExpectation, objcKVOExpectation], timeout: 0.1)
}

実行すると、テストは次のようにクラッシュしますNSInternalInconsistencyException

*** キャッチされない例外 'NSInternalInconsistencyException' が原因でアプリを終了します。適切な KVO 通知が送信されずに変更されました。MyURLSessionTask クラスの KVO 準拠を確認してください。

しかし、underlyingTask-property@objcとを作成することdynamicで、Objective-C ランタイムは、タスクが Swift から変更された場合でも、この通知が送信されるようにする必要がありますよね?

次のように、underlyingTask の KVO 通知を手動で送信することで、テストを正しく機能させることができます。

@objc public internal(set) var underlyingTask: URLSessionTask? {
    willSet {
        willChangeValue(for: \.underlyingTask)
    }
    didSet {
        didChangeValue(for: \.underlyingTask)
    }
}

しかし、すべてのプロパティにこれを実装する必要はなく、既存のkeyPathsForValuesAffecting<Key>メソッドを使用することをお勧めします。これを機能させるために何か不足していますか?それともうまくいくはずで、これはバグですか?

4

1 に答える 1