Convert function with completionHandler to Combine - swift

I'm trying to change my function from using a completion handler to a function that uses combine, but I'm getting an error message.
with completionHandler:
typealias AuthorizationCompletion = (UNAuthorizationStatus) -> ()
func reloadAuthorizationStatus(completion: #escaping AuthorizationCompletion) {
UNUserNotificationCenter.current().getNotificationSettings { settings in
DispatchQueue.main.async {
let notificationStatus = settings.authorizationStatus
completion(notificationStatus)
}
}
Using Combine:
func reloadAuthorizationStatus() -> AnyPublisher<UNAuthorizationStatus, Error> {
UNUserNotificationCenter.current().getNotificationSettings { settings in
Just(settings)
.tryMap({$0.authorizationStatus})
.eraseToAnyPublisher()
}
But getting the error Cannot convert return expression of type 'Void' to return type 'AnyPublisher<UNAuthorizationStatus, Error>'
From the moment I add the return type to the function, the settings let will change from type UNNotificationSettings to error type
What am I missing here ?

Future is the publisher you need
func reloadAuthorizationStatus() -> AnyPublisher<UNAuthorizationStatus, Never> {
Future<UNAuthorizationStatus, Never> { promise in
UNUserNotificationCenter.current()
.getNotificationSettings { settings in
promise(.success(settings.authorizationStatus))
}
}
.eraseToAnyPublisher()
}

Related

How to make non-sendable type usable in Sendable context?

I have such code
public func webView(
_ webView: WKWebView,
didReceive challenge: URLAuthenticationChallenge,
completionHandler: #escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void
) {
Task.detached(priority: .background) {
guard let serverTrust = challenge.protectionSpace.serverTrust else {
completionHandler(.cancelAuthenticationChallenge, nil)
return
}
let exceptions = SecTrustCopyExceptions(serverTrust)
SecTrustSetExceptions(serverTrust, exceptions)
completionHandler(.useCredential, URLCredential(trust: serverTrust))
}
}
But Xcode complains that URLAuthenticationChallenge type is non-Sendable
How can I make this type sendable, or pass it to task and provide multithread safe access
I've done something like this
struct SendableValue<T> {
let value: T
}
Warning disappeared but I daubt that it is correct solution as there isn't any mutexes
Maybe it should be more like this
struct SendableValue<T>: Sendable {
private let semaphore = DispatchSemaphore(value: 1)
private var _value: T
init(value: T) {
_value = value
}
var value: T {
get {
semaphore.wait()
defer { semaphore.signal() }
return _value
}
set {
semaphore.wait()
defer { semaphore.signal() }
_value = newValue
}
}
}
but adding conformance to Sendable on this type throws another warnings
maybe I shouldn't conform to Sendable
I think I am doing somehting wrong here.
Is there any generic simple way to provide isolation layer to non-Sendable type from third party library to make it Sendable i.e. safe in multiconcurrency environment?

Invalid conversion from throwing function of type XXXX to non-throwing function type XXXX

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() }
}
}

Having trouble returning a user token from StoreKit as a string

import StoreKit
class AppleMusicAPI {
let developerToken = "ABC123"
func getUserToken() -> String {
var userToken = String()
SKCloudServiceController().requestUserToken(forDeveloperToken: developerToken) { (receivedToken, error) in
userToken = receivedToken!
}
return userToken
}
}
I'm trying to basically return the userToken by doing AppleMusicAPI().getUserToken() however nothing gets returned (literally just blank/empty).
How can I output the token as a string?
If you take a look a the method signature that you're calling, it looks like this:
open func requestUserToken(forDeveloperToken developerToken: String, completionHandler: #escaping (String?, Error?) -> Void)
Notice the #escaping keyword which indicates that the completion block is not necessarily called right away (asynchronous). To fix your issue, I'd suggest using something like this:
class AppleMusicAPI {
let developerToken = "ABC123"
func getUserToken(completion: #escaping (String?, Error?) -> Void) {
SKCloudServiceController().requestUserToken(forDeveloperToken: developerToken, completionHandler: completion)
}
}

Escaping closure captures non-escaping parameter 'promise'

i am trying to use a publisher, but i got this error: "Escaping closure captures non-escaping parameter 'promise'" in the Timer. line.
How can i solve this?
extension OperationQueue {
func publisher<Output, Failure: Error>(_ block: #escaping (Future<Output, Failure>.Promise) -> Void) -> AnyPublisher<Output, Failure> {
Future<Output, Failure> { promise in
self.addOperation {
block(promise)
}
}.eraseToAnyPublisher()
}
}
struct ContentView: View {
func getDataViaPublisher()->AnyPublisher<String,Error> {
OperationQueue.main.publisher { promise in
Timer.scheduledTimer(withTimeInterval: Double.random(in: 1...3), repeats: false) { (timer) in. // <<<<< error here
promise(.success("Chris"))
}
}
}
Promise is also closure, so you need to make it #escaping in arguments as well.
Here is fixed extension
extension OperationQueue {
func publisher<Output, Failure: Error>(_ block: #escaping (#escaping Future<Output, Failure>.Promise) -> Void) -> AnyPublisher<Output, Failure> {
Future<Output, Failure> { promise in
self.addOperation {
block(promise)
}
}.eraseToAnyPublisher()
}
}

Swift: Convert a Sync function to Async

If you have a sync function, how would you convert it to an async function?
func syncFunc() -> Int {
//Do something
}
Would this work?
func asyncFunc(_ syncFunc:()->Int, _ completion:(Int)->()) -> Int {
DispatchQueue.background.async{
completion( syncFunc() )
}
}
No, functions containing an asynchronous task cannot return any value from the closure body and both closures must be marked as #escaping
func asyncFunc(_ syncFunc: #escaping ()->Int, completion: #escaping (Int)->()) {
DispatchQueue.global().async {
completion( syncFunc() )
}
}