Javier Cicchelli 9bcdaa697b [Setup] Basic project structure (#1)
This PR contains all the work related to setting up this project as required to implement the [Assignment](https://repo.rock-n-code.com/rock-n-code/deep-linking-assignment/wiki/Assignment) on top, as intended.

To summarise this work:
- [x] created a new **Xcode** project;
- [x] cloned the `Wikipedia` app and inserted it into the **Xcode** project;
- [x] created the `Locations` app and also, its `Libraries` package;
- [x] created the `Shared` package to share dependencies between the apps;
- [x] added a `Makefile` file and implemented some **environment** and **help** commands.

Co-authored-by: Javier Cicchelli <javier@rock-n-code.com>
Reviewed-on: rock-n-code/deep-linking-assignment#1
2023-04-08 18:37:13 +00:00

123 lines
3.1 KiB
Swift

import Foundation
enum AsyncOperationError: Error {
case cancelled
}
// Adapted from https://gist.github.com/calebd/93fa347397cec5f88233
@objc(WMFAsyncOperation) open class AsyncOperation: Operation {
// MARK: - Operation State
fileprivate let semaphore = DispatchSemaphore(value: 1) // Ensures `state` is thread-safe
@objc public enum State: Int {
case ready
case executing
case finished
var affectedKeyPath: KeyPath<AsyncOperation, Bool> {
switch self {
case .ready:
return \.isReady
case .executing:
return \.isExecuting
case .finished:
return \.isFinished
}
}
}
public var error: Error?
fileprivate var _state = AsyncOperation.State.ready
@objc public var state: AsyncOperation.State {
get {
semaphore.wait()
let state = _state
defer {
semaphore.signal()
}
return state
}
set {
willChangeValue(for: \.state)
let affectedKeyPaths = [_state.affectedKeyPath, newValue.affectedKeyPath]
for keyPath in affectedKeyPaths {
willChangeValue(for: keyPath)
}
semaphore.wait()
_state = newValue
semaphore.signal()
didChangeValue(for: \.state)
for keyPath in affectedKeyPaths {
didChangeValue(for: keyPath)
}
}
}
// MARK: - Operation subclass requirements
public final 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
}
open override func start() {
// From the docs for `start`:
// "Your custom implementation must not call super at any time."
if isCancelled {
finish(with: AsyncOperationError.cancelled)
return
}
state = .executing
execute()
}
// MARK: - Custom behavior
@objc open func finish() {
state = .finished
}
@objc open func finish(with error: Error) {
self.error = error
state = .finished
}
/// Subclasses must implement this to perform their work and they must not
/// call `super`. The default implementation of this function throws an
/// exception.
open func execute() {
fatalError("Subclasses must implement `execute`.")
}
}
@objc(WMFAsyncBlockOperation) open class AsyncBlockOperation: AsyncOperation {
let asyncBlock: (AsyncBlockOperation) -> Void
@objc init(asyncBlock block: @escaping (AsyncBlockOperation) -> Void) {
asyncBlock = block
}
final override public func execute() {
asyncBlock(self)
}
}