Swift 3 Compile Error When Evaluating Generic Expression - swift3

I'm working on a generic "result" callback for my cloudkit methods, but I'm not sure why the "success" callback can't be interpreted.
Reference: https://github.com/apple/swift-evolution/blob/master/proposals/0048-generic-typealias.md
import Foundation
import CloudKit
public enum CloudKitError: Error {
case general(String)
}
public enum CloudKitResult<T, ResultError: Error> {
case success(T)
case failure(ResultError)
}
public typealias CloudKitFetchClosure<T> = (_ result: CloudKitResult<T, CloudKitError>) -> Void
public final class CloudKitController {
public typealias T = CKRecord
public func save(_ record: CKRecord, callback: CloudKitFetchClosure<T>) -> Void {
self.publicDB.save(record, completionHandler: {updatedRecord, error in
if let _: Error = error {
DispatchQueue.main.async(execute: {
callback(.failure(.general("Something went wrong")))
})
} else {
DispatchQueue.main.async(execute: {
callback(.success(updatedRecord))
})
}
})
}
}
member 'success' in 'CloudKitResult<T, CloudKitError>' (aka 'CloudKitResult<CKRecord, CloudKitError>') produces result of type 'CloudKitResult<T, ResultError>', but context expects 'CloudKitResult<T, CloudKitError>' (aka 'CloudKitResult<CKRecord, CloudKitError>')
callback(.success(updatedRecord))

I had to unwrap the returned record from CloudKit. The compiler is now happy.
public func save(_ record: CKRecord, callback: CloudKitFetchClosure<T>) -> Void {
self.publicDB.save(record, completionHandler: {updatedRecord, error in
if let _: Error = error {
DispatchQueue.main.async(execute: {
callback(.failure(.general("Something went wrong")))
})
} else {
if let ckrecord: CKRecord = updatedRecord {
DispatchQueue.main.async(execute: {
callback(.success(ckrecord))
})
}
}
})
}

Related

Type of expression is ambiguous without more context GIDSignIn

I've been following this tutorial to implement Google Sign In: https://www.youtube.com/watch?v=M5LiqOBDeGg
Everything works except one line:
GIDSignIn.sharedInstance.signIn(with: config, presenting: presenting) {user, error in
The error is Type of expression is ambiguous without more context
This is the file:
//
// FirebAuth.swift
// TrippiTest
//
// Created by Dragos Catana on 12.01.2023.
//
import Foundation
import FirebaseAuth
import GoogleSignIn
import Firebase
struct FirebAuth {
static let share = FirebAuth()
private init() {}
func signinWithGoogle(presenting: UIViewController,
completion: #escaping (Error?) -> Void) {
guard let clientID = FirebaseApp.app()?.options.clientID else { return }
// Create Google Sign In configuration object.
let config = GIDConfiguration(clientID: clientID)
// Start the sign in flow!
GIDSignIn.sharedInstance.signIn(with: config, presenting: presenting) {user, error in
if let error = error {
completion(error)
return
}
guard
let authentication = user?.authentication,
let idToken = authentication.idToken
else {
return
}
let credential = GoogleAuthProvider.credential(withIDToken: idToken,
accessToken: authentication.accessToken)
Auth.auth().signIn(with: credential) { result, error in
guard error == nil else {
completion(error)
return
}
print("SIGN IN")
UserDefaults.standard.set(true, forKey: "signIn") // When this change to true, it will go to the home screen
}
}
}
}
Just for context, this is the line of code from the Login page
GoogleSiginBtn {
// TODO: - Call the sign method here
FirebAuth.share.signinWithGoogle(presenting: getRootViewController()) { error in
// TODO: Handle ERROR
}
} // GoogleSiginBtn
What should I do?

SwiftUI - Publish Background Thread Not Allowed - on code that does not update ui

New to swiftui and don't understand why the JSONDecoder() line in the first code throws
[SwiftUI] Publishing changes from background threads is not allowed; make sure to publish values from the main thread (via operators like receive(on:)) on model updates.
This to me is not updating ui so why is this showing?
do {
// pass the request type, bearer token, and email / password ( which are sent as POST body params )
let request = L.getRequest(requestType:"POST", token: token, email: self.username, password: self.psw)
L.fetchData(from: request) { result in
switch result {
case .success(let data):
// covert the binary data to a swift dictionary
do {
let response = try JSONDecoder().decode(WpJson.self, from: data)
for (key, title) in response.allowedReadings {
let vimeoId = Int( key )!
let vimeoUri = self.buildVimeoUri(vimeoId: key)
self.addReadingEntity(vimeoUri: vimeoUri, vimeoId: vimeoId, title: title)
}
self.writeToKeychain(jwt:response.jwt, displayName: response.displayName)
readings = self.fetchReadings()
}
catch {
self.error = error.localizedDescription
}
case .failure(let error):
self.error = error.localizedDescription
}
}
}
I tried wrapping a main queue around the do-catch in the L.fetchData(from: request) { result in but this did not help
DispatchQueue.main.async { [weak self] in
Here is the Login protocol, again without any ui work:
import Foundation
import SwiftUI
struct Login: Endpoint {
var url: URL?
init(url: URL?) {
self.url = url
}
}
protocol Endpoint {
var url: URL? { get set }
init(url: URL?)
}
extension Endpoint {
func getRequestUrl() -> URLRequest {
guard let requestUrl = url else { fatalError() }
// Prepare URL Request Object
return URLRequest(url: requestUrl)
}
func getRequest(requestType:String="POST", token:String, email:String="", password:String="") -> URLRequest {
var request = self.getRequestUrl()
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
if ( "" != email && "" != password && requestType == "POST") {
let parameters:[String:String?] = [
"email": email,
"password": password
]
// Run the request
do {
// pass dictionary to nsdata object and set it as request body
request.httpBody = try JSONSerialization.data(withJSONObject: parameters, options: .prettyPrinted)
} catch let error {
print(error.localizedDescription)
}
}
return request;
}
func fetchData(from request: URLRequest, completion: #escaping (Result<Data, NetworkError>) -> Void) {
URLSession.shared.dataTask(with: request) { data, response, error in
if let data = data {
completion(.success(data))
} else if error != nil {
// any sort of network failure
completion(.failure(.requestFailed))
} else {
// this ought not to be possible, yet here we are
completion(.failure(.unknown))
}
}.resume()
}
}
extension URLSession {
func dataTask(with request: URLRequest, completionHandler: #escaping (Result<(Data, HTTPURLResponse), Error>) -> Void) -> URLSessionDataTask {
return dataTask(with: request, completionHandler: { (data, urlResponse, error) in
if let error = error {
completionHandler(.failure(error))
} else if let data = data, let urlResponse = urlResponse as? HTTPURLResponse {
completionHandler(.success((data, urlResponse)))
}
})
}
}
Do you have any idea on how to fix this?
Wrap it right in place of assignment
catch {
DispatchQueue.main.async {
self.error = error.localizedDescription
}
}
case .failure(let error):
DispatchQueue.main.async {
self.error = error.localizedDescription
}
}

Reporting progress on list of Publishers in Combine

I am writing a Hacker News iOS application using SwiftUI/Combine. They have an API call for getting the ids of top posts and then you are supposed to request each story by itself. For this I have created storyIds(:) -> AnyPublisher<[Int], Error> and story(for:) -> AnyPublisher<Post, Error> for those calls.
Now I want to combine them into one function, getStories() which first download the identifiers and then goes through them fetching the stories one by one. I suppose you can use MergeMany or something else in the API for achieving this but I am not sure
The last thing I need is a function that combine these into stories() -> AnyPublisher<[Post], Error>. I found another question doing almost that. What I miss however, is a way to report progress of the task. I would like to update a counter for each fetched story to show the user how much is left of the download. How can I do this?
struct Agent {
struct Response<T> {
let value: T
let response: URLResponse
}
func run<T: Decodable>(_ request: URLRequest, _ decoder: JSONDecoder = JSONDecoder()) -> AnyPublisher<Response<T>, Error> {
return URLSession.shared
.dataTaskPublisher(for: request)
.tryMap { result -> Response<T> in
let value = try decoder.decode(T.self, from: result.data)
return Response(value: value, response: result.response)
}
.receive(on: DispatchQueue.main)
.eraseToAnyPublisher()
}
}
enum HackerNewsAPI {
static let agent = Agent()
static let base = URL(string: "https://hacker-news.firebaseio.com/v0/")!
}
extension HackerNewsAPI {
static func storyIds() -> AnyPublisher<[Int], Error> {
let request = URLRequest(url: base.appendingPathComponent("topstories.json"))
return agent.run(request)
.print()
.map(\.value)
.eraseToAnyPublisher()
}
}
extension HackerNewsAPI {
static func story(for id: Int) -> AnyPublisher<Post, Error> {
let request = URLRequest(url: base.appendingPathComponent("item/\(id).json"))
return agent.run(request)
.map(\.value)
.eraseToAnyPublisher()
}
}
extension HackerNewsAPI {
static func stories() -> AnyPublisher<[Post], Error> {
HackerNewsAPI.storyIds()
.flatMap { storyIDs -> AnyPublisher<[Post], Error> in
let stories = storyIDs.map { story(for: $0) }
return Publishers.MergeMany(stories)
.collect()
.eraseToAnyPublisher()
}
.eraseToAnyPublisher()
}
}
I expect something like the below code would work.
You end up with a count publisher that emits the number of articles that have been downloaded so far and a array publisher that emits the entire array of Posts after they have all been downloaded.
func getStories() -> (count: AnyPublisher<Int, Error>, stories: AnyPublisher<[Post], Error>) {
let posts = HackerNewsAPI.storyIds()
.map { $0.map { HackerNewsAPI.story(for: $0) } }
.flatMap { Publishers.Sequence(sequence: $0) }
.flatMap { $0 }
.share()
let count = posts
.scan(0) { count, _ in
return count + 1
}
let array = posts
.reduce([Post]()) { current, post in
current + [post]
}
return (count.eraseToAnyPublisher(), array.eraseToAnyPublisher())
}

Migrating Alamofire.Request extension from Swift 2 to Swift 3

I'm trying to migrate an extension to Alamofire.Request but am getting the error Cannot call value of non-function type 'HTTPURLResponse?'.
I know the compiler thinks I'm referring to the member response and not the function.
I've already replaced Request.JSONResponseSerializer with DataRequest.jsonResponseSerializer.
Can anyone see what I'm missing?
extension Alamofire.Request {
public func responseSwiftyJSON(_ queue: DispatchQueue? = nil, options: JSONSerialization.ReadingOptions = .allowFragments, completionHandler: #escaping (NSURLRequest, HTTPURLResponse?, SwiftyJSON.JSON, Error?) -> Void) -> Self {
return response(responseSerializer: Alamofire.DataRequest.jsonResponseSerializer(options: options)) { response in
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
var responseJSON: JSON
if response.result.isFailure {
responseJSON = JSON.null
} else {
responseJSON = SwiftyJSON.JSON(response.result.value!)
}
dispatch_async(queue ?? dispatch_get_main_queue(), {
completionHandler(self.request!, self.response, responseJSON, response.result.error)
})
}
}
}
}
I'm not sure if this is compatible with older versions, but I suggest you rewrite your extensions as this:
extension DataRequest {
public func responseSwiftyJSON(queue: DispatchQueue? = nil, options: JSONSerialization.ReadingOptions = .allowFragments, completionHandler: #escaping (URLRequest?, HTTPURLResponse?, SwiftyJSON.JSON, Error?) -> Void) -> Self {
return responseJSON(queue: queue, options: options) {
let responseJSON: JSON
switch result {
case let .success(json): responseJSON = JSON(json)
case .failure: responseJSON = JSON.null
}
completionHandler(request, response, responseJSON, error)
}
}
}

RxSwift Simple background task example

I have a method as following
public static func createAlbum(named: String, completion: (album: PHAssetCollection?) -> ()) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0)) {
}) { success, error in
completion(album: album)
}
}
}
How can i do the background task using RxSwift
complete code
https://gist.github.com/sazzadislam-dsi/d347909d284674c936e397ac423703cf
#XFreire answer is right, but for Swift 3 and RxSwift 3.1.0 I would add an extension to PHAssetCollection:
extension Reactive where Base: PHPhotoLibrary {
func createAlbum(named name: String) -> Observable<PHAssetCollection?> {
return Observable.create { observer in
self.base.performChanges({
// ...
}, completionHandler: { success, error in
if success {
// Your success logic goes here
let album = PHAssetCollection()
// ...
observer.on(.next(album))
observer.on(.completed)
} else if let error = error {
observer.on(.error(error))
} else {
// Your error type
observer.on(.error(MyErrors.Unknown))
}
})
return Disposables.create()
}
}
}
Then you can use the method like this:
PHPhotoLibrary
.shared().rx.createAlbum(named: "MyAlbum")
.subscribe(onNext: { collection in
// ...
}, onError: { error in
// ...
})
.addDisposableTo(disposeBag)
First, your function must return an Observable.
public static func rx_createAlbum(named: String)-> Observable<PHAssetCollection?>
Second, when there is an error, your function will return onError, and when success is true, your function will return onNext(album) and onCompleted().
Code:
public static func rx_createAlbum(named: String)-> Observable<PHAssetCollection?> {
return Observable.create { observer in
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0)) {
// ...
}) { success, error in
if error {
observer.onError(error)
}
else {
var album: PHAssetCollection?
if success {
let collectionFetchResult = PHAssetCollection.fetchAssetCollectionsWithLocalIdentifiers([placeholder?.localIdentifier ?? ""], options: nil)
album = collectionFetchResult.firstObject as? PHAssetCollection
}
observer.onNext(album)
observer.onCompleted()
}
}
}
return Disposables.create()
}
}