Created
February 8, 2020 23:40
-
-
Save rizumita/e2d0c71fd24ce940a0a2092abf267d88 to your computer and use it in GitHub Desktop.
Swiftにasync/awaitが無いのでCombineでなんちゃってasync/awaitを書いた。Swift5.2のcallAsFunctionを使うことでyieldが書きやすくなった。 #CodePiece
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
import Combine | |
public func async<T>(_ body: @escaping (Yield<T>) throws -> ()) -> Async<T> { | |
Async(body: body) | |
} | |
public func await<P>(_ publisher: P) throws -> P.Output where P: Publisher { | |
try publisher.await() | |
} | |
public class Async<T>: Publisher { | |
public typealias Output = T | |
public typealias Failure = Error | |
private let yield = Yield<T>() | |
private let body: (Yield<T>) throws -> () | |
public init(body: @escaping (Yield<T>) throws -> ()) { | |
self.body = body | |
} | |
public func receive<S>(subscriber: S) where S : Subscriber, Failure == S.Failure, Output == S.Input { | |
yield.subject.subscribe(subscriber) | |
subscriber.receive(subscription: Subscriptions.empty) | |
do { | |
try body(yield) | |
yield.subject.send(completion: .finished) | |
} catch { | |
yield.subject.send(completion: .failure(error)) | |
} | |
} | |
} | |
public class Yield<T> { | |
let subject = PassthroughSubject<T, Error>() | |
private var cancellables = Set<AnyCancellable>() | |
func callAsFunction(_ value: T) { | |
subject.send(value) | |
} | |
func callAsFunction<P>(_ publisher: P) where P: Publisher, P.Output == T { | |
publisher.mapError { $0 as Error } | |
.sink(receiveCompletion: { [weak self] in | |
switch $0 { | |
case .finished: | |
() | |
case .failure(let error): | |
self?.subject.send(completion: .failure(error)) | |
} | |
}, receiveValue: { [weak self] in self?.subject.send($0) }) | |
} | |
} | |
public extension Publisher { | |
@discardableResult | |
func await() throws -> Output { | |
var output: Output? | |
var error: Error? | |
let semaphore = DispatchSemaphore(value: 0) | |
_ = sink(receiveCompletion: { completion in | |
switch completion { | |
case .finished: | |
() | |
case .failure(let e): | |
error = e | |
} | |
semaphore.signal() | |
}, receiveValue: { output = $0 }) | |
_ = semaphore.wait(timeout: .distantFuture) | |
guard let result = output else { throw error! } | |
return result | |
} | |
} | |
let p1 = Just<Int>(1) | |
async { (yield: Yield<Int>) in | |
let num = try await(p1) | |
yield(num) | |
yield(2) | |
yield(Just(3)) | |
throw NSError(domain: "", code: -1) | |
}.sink(receiveCompletion: { print($0) }) { print($0) } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Swift初心者です。callAsFunctionつかってYieldやってる人探してました。
自分は最初LazySequenceで挑戦しようとしてたんですが、Combineを使うとはすごい発想力ですね。
すみません、さっそくコードを拝借していろいろ試していたのですが、以下のエラーで止まります。
お手隙でしたらご教授ください。
期待する出力は
1, 1, 2, 3, 5, 8, 13, 21, 34, 55
です。