diff --git a/Foundation/NSOperation.swift b/Foundation/NSOperation.swift index fcc9b38480..988aef8a0a 100644 --- a/Foundation/NSOperation.swift +++ b/Foundation/NSOperation.swift @@ -47,7 +47,6 @@ open class Operation : NSObject { #endif } - /// - Note: Operations that are asynchronous from the execution of the operation queue itself are not supported since there is no KVO to trigger the finish. open func start() { main() finish() @@ -159,6 +158,24 @@ open class Operation : NSObject { } } +/// The following two methods are added to provide support for Operations which +/// are asynchronous from the execution of the operation queue itself. On Darwin, +/// this is supported via KVO notifications. In the absence of KVO on non-Darwin +/// platforms, these two methods (which are defined in NSObject on Darwin) are +/// temporarily added here. They should be removed once a permanent solution is +/// found. +extension Operation { + public func willChangeValue(forKey key: String) { + // do nothing + } + + public func didChangeValue(forKey key: String) { + if key == "isFinished" && isFinished { + finish() + } + } +} + extension Operation { public enum QueuePriority : Int { case veryLow diff --git a/TestFoundation/TestNSOperationQueue.swift b/TestFoundation/TestNSOperationQueue.swift index cc74b86cf5..783115fd4f 100644 --- a/TestFoundation/TestNSOperationQueue.swift +++ b/TestFoundation/TestNSOperationQueue.swift @@ -16,12 +16,14 @@ import XCTest import SwiftFoundation import SwiftXCTest #endif +import Dispatch class TestNSOperationQueue : XCTestCase { static var allTests: [(String, (TestNSOperationQueue) -> () throws -> Void)] { return [ ("test_OperationPriorities", test_OperationPriorities), - ("test_OperationCount", test_OperationCount) + ("test_OperationCount", test_OperationCount), + ("test_AsyncOperation", test_AsyncOperation) ] } @@ -65,4 +67,76 @@ class TestNSOperationQueue : XCTestCase { XCTAssertEqual(msgOperations[2], "Operation2 executed") XCTAssertEqual(msgOperations[3], "Operation4 executed") } + + func test_AsyncOperation() { + let operation = AsyncOperation() + XCTAssertFalse(operation.isExecuting) + XCTAssertFalse(operation.isFinished) + + operation.start() + + while !operation.isFinished { + // do nothing + } + + XCTAssertFalse(operation.isExecuting) + XCTAssertTrue(operation.isFinished) + } +} + +class AsyncOperation: Operation { + + private let queue = DispatchQueue(label: "async.operation.queue") + private let lock = NSLock() + + private var _executing = false + private var _finished = false + + override internal(set) var isExecuting: Bool { + get { + return _executing + } + set { + if _executing != newValue { + willChangeValue(forKey: "isExecuting") + _executing = newValue + didChangeValue(forKey: "isExecuting") + } + } + } + + override internal(set) var isFinished: Bool { + get { + return _finished + } + set { + if _finished != newValue { + willChangeValue(forKey: "isFinished") + _finished = newValue + didChangeValue(forKey: "isFinished") + } + } + } + + override var isAsynchronous: Bool { + return true + } + + override func start() { + if isCancelled { + isFinished = true + return + } + + isExecuting = true + + queue.async { + sleep(1) + self.lock.lock() + self.isExecuting = false + self.isFinished = true + self.lock.unlock() + } + } + }