Set value of array via KeyPath - swift

I am trying to update the value stored in an array property of a class via the use of KeyPaths. Here is my code:
func listenAndUpdateDocuments<T: JSONDecodable>(
_ property: ReferenceWritableKeyPath<MyModel, [T]?>,
from model: MyModel) {
guard let reference = reference else {
return
}
guard listener == nil else {
return
}
listener = backendClient.listenToDocuments(reference) { [weak model] (result: Result<[T], RequestError>) in
switch result {
case .success(let value):
model?[keyPath: property] = value
case .failure:
model?[keyPath: property] = []
}
}
}
The problem is when I call this function like this:
myListener.listenAndUpdateDocuments(\.viewers, from: self)
where viewers is of type [ViewersModel], it always comes back with the following error:
Type of expression is ambiguous without more context
How do I solve this? I have a similar version of the code but where the property parameter isn't an array, and that works.

I struggled with something similar:
_ = Token.query(on: req).filter(\.expiry < Date()).delete()
The solution I found was to use a more up-to-date api to handle my request parameters.
_ = Token.query(on: req).filter(\.expiry, .lessThan, Date()).delete()
It had less to do with the keypath itself than I thought!

Related

Swift + Realm: How to change an outer variable from inside a collection.find's scope

Following this tutorial, I've written the following class:
import RealmSwift
import Darwin
import SwiftUI
let app = App(id: "my-app-id")
class AccessManager: Object {
#objc dynamic var isInTime: Bool = false
func foo2() -> Bool {
return true
}
func foo1() {
app.login(credentials: Credentials.anonymous) { (result) in
DispatchQueue.main.async {
switch result {
case .failure(let error):
print("Login failed: \(error)")
case .success(let user):
print("Login as \(user) succeeded!")
let client = app.currentUser!.mongoClient("mongodb-atlas")
let database = client.database(named: "my-database")
let collection = database.collection(withName: "my-collection")
let identity = "my-identity"
collection.find(filter: ["_partition": AnyBSON(identity)], { (result) in
switch result {
case .failure(let error):
print("Call to MongoDB failed: \(error.localizedDescription)")
case .success(let documents):
self.bar = self.foo2()
print(self.bar) // prints true
}
})
print(self.bar) // prints false
}
}
}
}
}
When I change the value of self.bar inside of collection.find's scope (using the self.foo2 function), its value doesn't change outside of that scope - i.e in the first print(self.bar) - true is being printed, but in the second one - false is printed.
How can I change self.bar's value so the change will also take effect outside of collection.find's scope?
As #Jay commented:
closures are asynchronous and the code following the closure will
(may) execute before the code in the closure. So that code will print
false before the value is set to true. Code is faster than the
internet so data is only valid in the closure.
This is why in my case, the print(self.bar) outside of the closure was executed before the collection.find closure. Therefore it's result was false instead of true.

Why can't I use .flatMap() after .tryMap() in Swift Combine?

I am studying and trying out a few stuff with Combine to apply on my own and came into the following situation with this contrived example..
let sequencePublisher = [70, 5, 17].publisher
var cancellables = [AnyCancellable]()
sequencePublisher
// .spellOut()
.flatMap { query -> URLSession.DataTaskPublisher in
return URLSession.shared.dataTaskPublisher(for: URL(string: "http://localhost:3000?q=\(query)")!)
}
.compactMap { String(data: $0.data, encoding: .utf8) }
.sink(receiveCompletion: { completion in
switch completion {
case .failure(let error):
print(error.localizedDescription)
default: print("finish")
}
}) { value in
print(value)
}
.store(in: &cancellables)
I have a sequence publisher that emits 3 Integers and I pass it through flatMap and send a Get request request to my local API that simply returns back the same value it got embedded in a string.
It all works fine, I get all 3 API responses in sink, as long as I don't uncomment the spellOut() custom operator, this operator is supposed to fail if the number is smaller than 6, here is what it does:
enum ConversionError: LocalizedError {
case lessThanSix(Int)
var errorDescription: String? {
switch self {
case .lessThanSix(let n):
return "could not convert number -> \(n)"
}
}
}
extension Publisher where Output == Int {
func spellOut() -> Publishers.TryMap<Self, String> {
tryMap { n -> String in
let formatter = NumberFormatter()
formatter.numberStyle = .spellOut
guard n > 6, let spelledOut = formatter.string(from: n as NSNumber) else { throw ConversionError.lessThanSix(n) }
return spelledOut
}
}
}
The code doesn't even compile if I add another map operator before flatMap it works, but with a tryMap it just says
No exact matches in call to instance method 'flatMap'
Is there any way of achieving this or why is it not allowed?
Thank you in advance for the answers
The problem here is that FlatMap requires the returned publisher created in its closure to have the same Failure type as its upstream (unless upstream has a Never failure).
So, a Sequence publisher, like:
let sequencePublisher = [70, 5, 17].publisher
has a failure type of Never and all works.
But TryMap, which is what .spellOut operator returns, has a failure type of Error, and so it fails, because DataTaskPublisher has a URLError failure type.
A way to fix is to match the error type inside the flatMap:
sequencePublisher
.spellOut()
.flatMap { query in
URLSession.shared.dataTaskPublisher(for: URL(...))
.mapError { $0 as Error }
}
// etc...
You have to map the error after the tryMap.
publisher
.tryMap({ id in
if let id = id { return id } else { throw MyError.unknown("noId") }
})
.mapError { $0 as? MyError ?? MyError.unknown("noId") }
.flatMap { id -> AnyPublisher<Model, MyError> in
fetchDataUseCase.execute(id: id)
}
.eraseToAnyPublisher()
In case the error types already match, another point of failure can be, when you work with any Publisher as return values. Then you need to call eraseToAnyPublisher() on the publishers - the first one and the one returned from the flatMap closure.
anyPublisher.eraseToAnyPublisher()
.flatMap { value in
anotherPublisher.eraseToAnyPublisher()
}

Variable 'theData' used before being initialized, How should I fix

I am trying to Apollo framework and a graphql api to obtain the data then return it. Once I have the data in another swift file, I want to call on certain parts of the data and assign it to a variable. The errors I get is variable used before it is initialized. and if try to return the variable from within the closure I get "Unexpected Non-Void Return Value In Void Function ". I heard of ways to get around that error but I don't completely understand it and how it works with my code. If you need more code or context you can message me and I can share my GitHub repo. Sorry if the code is bad, please don't roast me. I am still a beginner.
import Foundation
import Apollo
struct AniListAPI {
let aniListUrl = "https://graphql.anilist.co"
func ObtainData(AnimeID: Int)-> QueryQuery.Data{
var theData: QueryQuery.Data
let theInfo = QueryQuery(id: AnimeID)
GraphClient.fetch(query: theInfo) { result in
switch result {
case .failure(let error):
print("A big No no happened \(error)")
case .success(let GraphQLResult):
guard let Info = GraphQLResult.data else {return}
theData = Info
}
}
return theData
}
}
Unexpected Non-Void Return Value In Void Function.
The reason you're getting this warning is because you can't return value from inside the closure. Use closure instead of returning value.
func ObtainData(AnimeID: Int, completion: #escaping (Data) -> Void) {
var TheData: QueryQuery.Data
let TheInfo = QueryQuery(id: AnimeID)
GraphClient.fetch(query: TheInfo) { result in
switch result {
case .failure(let error):
print("A big no no happened retard \(error)")
case .success(let GraphQLResult):
guard let Info = GraphQLResult.data else {return}
TheData = Info
completion(TheData)
}
}
}
and call it like..
ObtainData(AnimeID: 123) { (anyData) in
print (anyData)
// continue your logic
}

How to avoid re-parsing Error when using enum that conforms to Error protocol in Swift

There's an error enum:
#objc public enum MyError: Int, Error {
case good
case bad
case ugly
}
Note that it's available to objective C - that's part of the challenge. For instance I cannot change enum to have associated values (like case good(Error))
A decent solution is to have a constructor that can parse from some arbitrary error:
init(from error: Error?) {
// parse it and set self to corresponding code, e.g.
guard let error = error else {
self = .good
return
}
if let uglyError = error as NSError? {
self = .ugly
return
}
self = .bad
}
But one of the cases this constructor has to handle is that provided error is already one of MyError values, i.e. Error(MyError.ugly) - this is simplification of course. More likely it's a result of lower-level error bubbling up.
In that case I want to avoid re-parsing it. How is that possible?
In pseudo-code I want to implement condition like this:
if error is [one of MyError values] {
self = error
return
}
Tried to do that with CaseIterable and allCases, but seems cannot convert Error to something that can be compared
for value in MyError.allCases {
if error == value { // <-- Error: Binary operator '==' cannot be applied to operands of type 'Error' and 'MyError'
}
}
Any way to accomplish something like that? (I am not stuck on CaseIterable, anything else works too).
If you are aiming to keep the same initializer as init(from error: Error?), what you could do is to add
if let castedError = error as? MyError {
self = castedError
return
}
at the beginning (before doing anything).
As a full implementation, it should be similar to:
#objc public enum MyError: Int, Error, CaseIterable {
case good
case bad
case ugly
init(from error: Error?) {
if let castedError = error as? MyError {
print("Already MyError!")
self = castedError
return
}
guard let error = error else {
self = .good
return
}
if let _ = error as NSError? {
self = .ugly
return
}
self = .bad
}
}
Therefore, the output would be:
let givenError: MyError = .good
let resultError = MyError(from: givenError)
print(resultError.rawValue) // 0 (which is .good raw value)
Note that it should also log "Already MyError!" because of the print statement in the first check to confirm that it has been reached.
It should also behave as expected with NSErrors:
let nsError = NSError(domain: "", code: 101, userInfo: nil)
let myError = MyError(from: nsError)
print(myError.rawValue) // 2 (which is .ugly raw value)

Result of call is unused

Right below the second comment, I receive an error of "Result of call to 'taskForDeleteMethod' is unused. Why is this when I use the results and error in the closure following the call?
func deleteSession(_ completionHandlerForDeleteSession: #escaping (_ success: Bool, _ error: NSError?) -> Void) {
/* 1. Specify parameters, method (if has {key}), and HTTP body (if POST) */
// There are none...
/* 2. Make the request */
taskForDELETEMethod { (results, error) in
/* 3. Send the desired value(s) to completion handler */
if let error = error {
print("Post error: \(error)")
completionHandlerForDeleteSession(false, error)
} else {
guard let session = results![JSONKeys.session] as? [String: AnyObject] else {
print("No key '\(JSONKeys.session)' in \(results)")
return
}
if let id = session[JSONKeys.id] as? String {
print("logout id: \(id)")
completionHandlerForDeleteSession(true, nil)
}
}
}
}
In earlier swift versions, you need not bother about the return value of a method. You may store it in any variable snd use it later or you may ignore it completely. Neither it gave any error nor a warning.
But in swift 3.0 you need to specify whether you want to ignore the returned value or use it.
1. If you want to use the returned value, you can create a variable/constant and store the value in it, i.e
let value = taskForDELETEMethod {
// Your code goes here
}
2. If you want to ignore the returned value, you can use _ ,i.e
let _ = taskForDELETEMethod {
// Your code goes here
}
You are confusing the results variable, which is, indeed, used inside the closure, and the result of the taskForDELETEMethod call itself, which is NSURLSessionDataTask object.
From the examples of using taskForDELETEMethod that I was able to find online it looks like it is perfectly OK to ignore the return value, so you can avoid this warning by assigning the result to _ variable, i.e.
let _ = taskForDELETEMethod {
... // The rest of your code goes here
}