I am trying to call a Swift 5.5 async throws method inside a method that should return an AnyPublisher but I am having trouble to achieve this. I tried using a Future (promise) which did not work and I only managed to find a Swift 5.5 API to convert closure based methods to async ones.
class Loader {
func loadSomeData() -> async throws [String] {
/// some code
}
}
class Service {
let loader: Loader
init(loader: Loader) {
self.loader = loader
}
func someDataPublisher() -> AnyPublisher<[String], Error> {
// How can I convert this?
try await loader.loadSomeData()
}
}
class Service {
let loader: Loader
init(loader: Loader) {
self.loader = loader
}
func someDataPublisher() -> AnyPublisher<[String], Error> {
return Deferred { [loader] in
Future { promise in
async {
do {
promise(.success(try await loader.loadSomeData()))
} catch {
promise(.failure(error))
}
}
}
}
.eraseToAnyPublisher()
}
}
Related
I try to implement some repository that can notify about change its state.
protocol Observer {
}
actor Repository: Actor {
var observers: [Observer] = []
func add(observer: Observer) {
self.observers.append(observer)
}
}
and add unit test for it
func test_get_update() async {
let repository = Repository()
let observer = MockObserver()
await repository.add(observer: observer)
}
and get warning
Non-sendable type 'any Observer' passed in implicitly asynchronous call to actor-isolated instance method 'add(observer:)' cannot cross actor boundary
but in main app there is no warning.
class Bar {
func foo() async {
let repository = Repository()
let observer = MockObserver()
await repository.add(observer: observer)
}
}
I don't understand is this implementation of repository correct or not?
You could certainly make your Observer a Sendable type:
protocol Observer: Sendable {
func observe(something: Int)
}
struct MockObserver: Observer {
func observe(something: Int) {
print("I observed \(something)")
}
}
actor Repository {
private var observers: [any Observer] = []
func add(observer: any Observer) {
observers.append(observer)
}
private func notifyObservers(value: Int) {
for observer in observers {
observer.observe(something: value)
}
}
func doSomething() {
notifyObservers(value: 42)
}
}
let myRepo = Repository()
Task {
let myObserver = MockObserver()
await myRepo.add(observer: myObserver)
await myRepo.doSomething()
}
But since Sendable imposes so many restrictions on things, that might make implementing an interesting observer a challenge.
I would be inclined to use the Observer implementations already available in the Combine framework:
actor AnotherRepo {
nonisolated let interestingState: AnyPublisher<Int, Never>;
private let actualState = PassthroughSubject<Int, Never>();
init() {
interestingState = actualState.eraseToAnyPublisher()
}
func doSomething() {
actualState.send(42)
}
}
let anotherRepo = AnotherRepo()
anotherRepo.interestingState.sink { print("I also observed \($0)") }
Task {
await anotherRepo.doSomething()
}
I am stuck with this situation where I have a custom JSONDecoder struct which contains a private function to decode data, and another function which is exposed, and should return a specific, Decodable type. I would like these functions to throw successively so I only have to write my do/catch block inside the calling component, but I'm stuck with this error on the exposedFunc() function:
Invalid conversion from throwing function of type '(Completion) throws -> ()' (aka '(Result<Data, any Error>) throws -> ()') to non-throwing function type '(Completion) -> ()' (aka '(Result<Data, any Error>) -> ()')
Here is the code:
import Foundation
import UIKit
typealias Completion = Result<Data, Error>
let apiProvider = ApiProvider()
struct DecodableTest: Decodable {
}
struct CustomJSONDecoder {
private static func decodingFunc<T: Decodable>(
_ response: Completion,
_ completion: #escaping (T) -> Void
) throws {
switch response {
case .success(let success):
try completion(
JSONDecoder().decode(
T.self,
from: success
)
)
case .failure(let error):
throw error
}
}
static func exposedFunc(
value: String,
_ completion: #escaping (DecodableTest) -> Void
) throws {
apiProvider.request {
try decodingFunc($0, completion)
}
}
}
class CustomViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
do {
try CustomJSONDecoder.exposedFunc(value: "test_value") { result in
// Do something with result
}
} catch {
print(error)
}
}
}
class ApiProvider: NSObject {
func request(_ completion: #escaping (Completion) -> ()) {
}
}
Thank you for your help
This defines method that takes a non-throwing function:
class ApiProvider: NSObject {
func request(_ completion: #escaping (Completion) -> ()) {
}
}
So in all cases, this function must take a Completion and return Void without throwing. However, you pass the following:
apiProvider.request {
try decodingFunc($0, completion)
}
This method does throw (note the uncaught try), so that's not allowed. You need to do something if this fails:
apiProvider.request {
do {
try decodingFunc($0, completion)
} catch {
// Here you must deal with the error without throwing.
}
}
}
Not using concurrency features is making your code hard to understand. Switch!
final class CustomViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
Task {
let result = DecodableTest()
// Do something with result
}
}
}
extension DecodableTest {
init() async throws {
self = try JSONDecoder().decode(Self.self, from: await APIProvider.data)
}
}
enum APIProvider {
static var data: Data {
get async throws { .init() }
}
}
I have manager class which will connect and manage the data and state of the Bluetooth device.
The manager class conforms to IWDeviceManagerDelegate and has a method which gives the weight data func onReceiveWeightData(_ device: IWDevice!, data: IWWeightData!).
Once I call listenToWeight() from any controller I want to give the data using Observable.
How I fire an onNext event with the data of onReceiveWeightData method to listenToWeight observable?
Below is the code.
class WeightMachineManager: NSObject {
func setup() {
IWDeviceManager.shared()?.delegate = self
IWDeviceManager.shared()?.initMgr()
}
func listenToWeight() -> Observable<IWWeightData> {
let tag = WeightMachineManager.tag
if let connectedDevice = connectedDevice {
IWDeviceManager.shared()?.add(connectedDevice, callback: { (device, code) in
if code == .success {
print("\(tag)[SUCCESS] Device added successfully.")
} else {
print("\(tag)[FAILURE] Failed to add device.")
}
})
} else {
print("\(tag)[FAILURE] Couldn't find any device to connect.")
}
}
}
extension WeightMachineManager: IWDeviceManagerDelegate {
func onReceiveWeightData(_ device: IWDevice!, data: IWWeightData!) {
// TODO:- Pass this data in the onNext event of listenToWeight's observable.
}
}
I've made a lot of assumptions in the below, but the result should look something like this:
class WeightMachineManager {
var connectedDevice: IWDevice?
func setup() {
IWDeviceManager.shared()?.initMgr()
}
func listenToWeight() -> Observable<IWWeightData> {
if let connectedDevice = connectedDevice, let deviceManager = IWDeviceManager.shared() {
return deviceManager.rx.add(connectedDevice)
.flatMap { deviceManager.rx.receivedWeightData() } // maybe this should be flatMapLatest or flatMapFirst. It depends on what is calling listenToWeight() and when.
}
else {
return .error(NSError.init(domain: "WeightMachineManager", code: -1, userInfo: nil))
}
}
}
extension IWDeviceManager: HasDelegate {
public typealias Delegate = IWDeviceManagerDelegate
}
class IWDeviceManagerDelegateProxy
: DelegateProxy<IWDeviceManager, IWDeviceManagerDelegate>
, DelegateProxyType
, IWDeviceManagerDelegate {
init(parentObject: IWDeviceManager) {
super.init(parentObject: parentObject, delegateProxy: IWDeviceManagerDelegateProxy.self)
}
public static func registerKnownImplementations() {
self.register { IWDeviceManagerDelegateProxy(parentObject: $0) }
}
}
extension Reactive where Base: IWDeviceManager {
var delegate: IWDeviceManagerDelegateProxy {
return IWDeviceManagerDelegateProxy.proxy(for: base)
}
func add(_ device: IWDevice) -> Observable<Void> {
return Observable.create { observer in
self.base.add(device, callback: { device, code in
if code == .success {
observer.onNext(())
observer.onCompleted()
}
else {
observer.onError(NSError.init(domain: "IWDeviceManager", code: -1, userInfo: nil))
}
})
return Disposables.create()
}
}
func receivedWeightData() -> Observable<IWWeightData> {
return delegate.methodInvoked(#selector(IWDeviceManagerDelegate.onReceiveWeightData(_:data:)))
.map { $0[1] as! IWWeightData }
}
}
I am chaining some functions together and I can't figure out how to call a completion handler with a return value once all the functions are done running.
func getAirQuality(completion: (aqi: Int?) -> Void) {
callAPI()
}
private func callAPI() {
// ... get data
self.parseDataForAQI(data: data)
}
private func parseDataForAQI(data: Data) {
let aqi = aqi
// Send aqi up to completion handler in getAirQuality
}
So that when everything is said and done I can just do something like this:
getAirQuality(completion: { aqi -> Void in {
// Do something with aqi
})
My first assumption is that your first 3 functions are part of a class. If so, one approach is to save the completion handler as an instance variable.
class AirQualityProvider {
var aBlock: ((Int?) -> Void)?
func getAirQuality(completion: #escaping (Int?) -> Void) {
aBlock = completion
callAPI()
}
private func callAPI() {
let data = Data()
parseDataForAQI(data: data)
}
private func parseDataForAQI(data: Data) {
let aqi = 1
if let completion = aBlock {
completion(aqi)
}
}
}
Here's an example of a caller as written in a playground.
let aqp = AirQualityProvider()
aqp.getAirQuality { (value) in
if let value = value {
print("Value = \(value)")
}
}
Java has Future or FutureTask that can run a task in a new thread. Then, return the execution result to the original thread. Are there any feature in Swift can achieve that?
You're looking into some kind of language construction called Futures and promises. You can find some examples, like:
https://bitbucket.org/al45tair/async (C#-like async/await primitives in Swift)
https://github.com/mxcl/PromiseKit (Promise kit http://promisekit.org/)
mentioned earlier https://github.com/Thomvis/BrightFutures
However the language itself misses such feature.
Not provided by the language (meaning the standard library), but you can surely roll your own or simply use a library such as https://github.com/Thomvis/BrightFutures
If Apple did implement Futures or Promises in Swift, would they say so? After all, they always avoid talking about Future products. ;)
Anyway, the original question seems to be generally about ways to do asynchronous work, not necessarily about specifically doing that with a Futures/Promises style model. So, while the third party libraries mentioned in other answers are great for that model, you can also do asynchronous work without that model using the same iOS & OS X built-in APIs that you can from ObjC: dispatch or NSOperation. For example:
NSOperationQueue().addOperationWithBlock {
// do background work
NSOperationQueue.mainQueue().addOperationWithBlock {
// back to main thread for follow up work
}
}
There is also now FutureKit
Similar to BrightFuture, but does composition more like BFTask
And I should mention Bolts BFTask, which while written in Objective-C is also a good candidate. (And is now used inside of Facebook iOS SDK)
I end up with the following solution (iOS SDK only, Swift 3) based on Operation and OperationQueue classes:
In short: Wrapping code into synchronous or asynchronous operation. Chaining operations using utility class. Adding operations into serial queue.
In case of error there is no need to cancel current operation, just skip actual code. Additionally asynchronous execution blocks must call finalize callback to inform operation queue about completion. Optionally DispatchQueue can be provided as parameter. Block of code will be asynchronously executed on that queue.
fileprivate func publishProductOnWebsite(listing: Listing) {
var resultSKU: String?
var resultError: Swift.Error?
let chain = OperationsChain{ isExecuting, finalize in
let task = ServerAPI.create(publishInfo: listing.publishInfo) { sku, error in
guard isExecuting() else {
return // We are canceled. Nothing to do.
}
if let error = error {
resultError = error
} else if let sku = sku {
resultSKU = sku // Arbitrary thread. But OK as this example for serial operation queue.
}
finalize() // This will finish asynchronous operation
}
task.resume()
}
chain.thenAsync(blockExecutionQueue: DispatchQueue.main) { _, finalize in
if let sku = resultSKU {
listing.sku = sku
DBStack.mainContext.saveIfHasChanges(savingParent: true) { error in
resultError = error
finalize()
}
} else {
finalize()
}
}
chain.thenSync(blockExecutionQueue: DispatchQueue.main) { [weak self] in
if let error = resultError {
self?.handleError(error) // Executed on Main thread.
} else {
self?.trackPublish()
self?.eventHandler?(.publishCompleted)
}
}
operationQueue.cancelAllOperations()
operationQueue.addOperations(chain.operations, waitUntilFinished: false)
}
OperationsChain class: Wraps block of code into Operation and saves operation into operations array maintaining dependencies.
public class OperationsChain {
public private(set) var operations = [Operation]()
public init(blockExecutionQueue: DispatchQueue? = nil,
executionBlock: #escaping AsynchronousBlockOperation.WorkItemBlock) {
let op = AsynchronousBlockOperation(blockExecutionQueue: blockExecutionQueue, executionBlock: executionBlock)
operations.append(op)
}
public init(blockExecutionQueue: DispatchQueue? = nil,
executionBlock: #escaping SynchronousBlockOperation.WorkItemBlock) {
let op = SynchronousBlockOperation(blockExecutionQueue: blockExecutionQueue, executionBlock: executionBlock)
operations.append(op)
}
#discardableResult
public func thenAsync(blockExecutionQueue: DispatchQueue? = nil,
executionBlock: #escaping AsynchronousBlockOperation.WorkItemBlock) -> AsynchronousBlockOperation {
let op = AsynchronousBlockOperation(blockExecutionQueue: blockExecutionQueue, executionBlock: executionBlock)
if let lastOperation = operations.last {
op.addDependency(lastOperation)
} else {
assertionFailure()
}
operations.append(op)
return op
}
#discardableResult
public func thenSync(blockExecutionQueue: DispatchQueue? = nil,
executionBlock: #escaping SynchronousBlockOperation.WorkItemBlock) -> SynchronousBlockOperation {
let op = SynchronousBlockOperation(blockExecutionQueue: blockExecutionQueue, executionBlock: executionBlock)
if let lastOperation = operations.last {
op.addDependency(lastOperation)
} else {
assertionFailure()
}
operations.append(op)
return op
}
}
SynchronousBlockOperation and AsynchronousBlockOperation classes.
public final class SynchronousBlockOperation: Operation {
public typealias WorkItemBlock = (Void) -> Void
fileprivate var executionBlock: WorkItemBlock?
fileprivate var blockExecutionQueue: DispatchQueue?
public init(blockExecutionQueue: DispatchQueue? = nil, executionBlock: #escaping SynchronousBlockOperation.WorkItemBlock) {
self.blockExecutionQueue = blockExecutionQueue
self.executionBlock = executionBlock
super.init()
}
public override func main() {
if let queue = blockExecutionQueue {
queue.async { [weak self] in
self?.executionBlock?()
}
} else {
executionBlock?()
}
}
}
open class AsynchronousBlockOperation: AsynchronousOperation {
public typealias FinaliseBlock = (Void) -> Void
public typealias StatusBlock = (Void) -> Bool
public typealias WorkItemBlock = (#escaping StatusBlock, #escaping FinaliseBlock) -> Void
fileprivate var executionBlock: WorkItemBlock?
fileprivate var blockExecutionQueue: DispatchQueue?
public init(blockExecutionQueue: DispatchQueue? = nil, executionBlock: #escaping AsynchronousBlockOperation.WorkItemBlock) {
self.blockExecutionQueue = blockExecutionQueue
self.executionBlock = executionBlock
super.init()
}
open override func onStart() {
if let queue = blockExecutionQueue {
queue.async { [weak self] in
self?.executionBlock?({ return self?.isExecuting ?? false }) {
self?.finish()
}
}
} else {
executionBlock?({ [weak self] in return self?.isExecuting ?? false }) { [weak self] in
self?.finish()
}
}
}
}
AsynchronousOperation class: Reusable subclass of Operation.
open class AsynchronousOperation: Operation {
fileprivate var lockOfProperties = NonRecursiveLock.makeDefaultLock()
fileprivate var lockOfHandlers = NonRecursiveLock.makeDefaultLock()
fileprivate var mFinished = false
fileprivate var mExecuting = false
}
extension AsynchronousOperation {
public final override var isAsynchronous: Bool {
return true
}
public final override var isExecuting: Bool {
return lockOfProperties.synchronized { mExecuting }
}
public final override var isFinished: Bool {
return lockOfProperties.synchronized { mFinished }
}
}
extension AsynchronousOperation {
public final override func start() {
if isCancelled || isFinished || isExecuting {
return
}
willChangeValue(forKey: "isExecuting")
lockOfProperties.synchronized { mExecuting = true }
onStart()
didChangeValue(forKey: "isExecuting")
}
public final override func cancel() {
super.cancel()
if isExecuting {
onCancel()
finish()
} else {
onCancel()
lockOfProperties.synchronized {
mExecuting = false
mFinished = true
}
}
}
public final func finish() {
willChangeValue(forKey: "isExecuting")
willChangeValue(forKey: "isFinished")
lockOfProperties.synchronized {
mExecuting = false
mFinished = true
}
onFinish()
didChangeValue(forKey: "isExecuting")
didChangeValue(forKey: "isFinished")
}
}
extension AsynchronousOperation {
/// Subclasses must launch job here.
///
/// **Note** called between willChangeValueForKey and didChangeValueForKey calls, but after property mExecuting is set.
open func onStart() {
}
/// Subclasses must cancel job here.
///
/// **Note** called immediately after calling super.cancel().
open func onCancel() {
}
/// Subclasses must release job here.
///
/// **Note** called between willChangeValueForKey and didChangeValueForKey calls,
/// but after properties mExecuting and mFinished are set.
open func onFinish() {
}
}
[Java Future and Promise]
Swift's Combine framework uses these constructions