Skip to content

Instantly share code, notes, and snippets.

@rizumita
Created February 8, 2020 23:40
Show Gist options
  • Save rizumita/e2d0c71fd24ce940a0a2092abf267d88 to your computer and use it in GitHub Desktop.
Save rizumita/e2d0c71fd24ce940a0a2092abf267d88 to your computer and use it in GitHub Desktop.
Swiftにasync/awaitが無いのでCombineでなんちゃってasync/awaitを書いた。Swift5.2のcallAsFunctionを使うことでyieldが書きやすくなった。 #CodePiece
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) }
@GeneralD
Copy link

GeneralD commented Aug 1, 2020

Swift初心者です。callAsFunctionつかってYieldやってる人探してました。
自分は最初LazySequenceで挑戦しようとしてたんですが、Combineを使うとはすごい発想力ですね。
すみません、さっそくコードを拝借していろいろ試していたのですが、以下のエラーで止まります。
お手隙でしたらご教授ください。

期待する出力は
1, 1, 2, 3, 5, 8, 13, 21, 34, 55
です。

var fibonacci: Async<Int> {
	async { (yield: Yield<Int>) in
		yield(1)
		yield(1)
		yield(try! await(fibonacci.zip(fibonacci.dropFirst(), +)))
	}
}

fibonacci.prefix(10).sink(receiveCompletion: {
	print($0)
}) {
	print($0)
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment