Returning void in PromiseKit 6 - ios

This is what I had working with PromiseKit 4.5
api.getUserFirstName().then { name -> Void in
print(name)
}
getUserFirstName() returns a Promsise<String>. I updated to PromiseKit 6 and this now throws an error:
Cannot convert value of type '(_) -> Void' to expected argument type '(_) -> _'
This error message makes little sense to me. How do I fix this?
EDIT: So this seems to fix it, but I have little understanding as to what's happening with this:
api.getUserFirstName().compactMap { name in
print(name)
}
What's the difference now between then() and compactMap()?

In according with PromiseKit 6.0 Guide then was split into then, done and map
then is fed the previous promise value and requires you return a promise.
doneis fed the previous promise value and returns a Void promise (which is 80% of chain usage)
map is fed the previous promise value and requires you return a non-promise, ie. a value.
Why that was happend? As said developers:
With PromiseKit our then did multiple things, and we relied on Swift to infer the correct then from context. However with multiple line thens it would fail to do this, and instead of telling you that the situation was ambiguous it would invent some other error. Often the dreaded cannot convert T to AnyPromise. We have a troubleshooting guide to combat this but I believe in tools that just work, and when you spend 4 years waiting for Swift to fix the issue and Swift doesn’t fix the issue, what do you do? We chose to find a solution at the higher level.
So probably in your case needs to use done
func stackOverflowExample() {
self.getUserFirstName().done { name -> Void in
print(name)
}
}
func getUserFirstName() -> Promise<String> {
return .value("My User")
}
compactMap lets you get error transmission when nil is returned.
firstly {
URLSession.shared.dataTask(.promise, with: url)
}.compactMap {
try JSONDecoder().decode(Foo.self, with: $0.data)
}.done {
//…
}.catch {
// though probably you should return without the `catch`
}
See more info at release guide
compactMap was renamed to flatMap see discussions here

Related

Odd promiseKit 6 syntax behavior in Xcode

I am getting started with PromiseKit to prevent myself from writing functions with 10 levels of callbacks..
I installed the latest version (6.2.4) using CocoaPods, am running the latest version of xCode, imported PromiseKit in the file I am trying to get it working in, but I get really weird behavior of Xcode, resulting in several errors.
I intend to do something really basic to get started:
the function below creates filters (ProductListComponents) for categories for products in a product overview app I'm working on.
func createCategoryComponents(masterComponent: MasterComponent?) -> Promise<[ProductListComponents]> {
return Promise { seal in
//create a bunch of product category components
seal.resolve([components])
}
}
All fine here. I then try to get this:
firstly {
self.createCategoryComponents(masterComponent: masterComponent)
}.then { createdComponents in
completion.resolve(nil, createdComponents)
}
This refuses to work. firstly, when I try to type the firstly code, Xcode suggests:
firstly(execute: { () -> Guarantee<T> in
//code
})
and:
firstly(execute: { () -> Thenable in
//code
})
I have not seen this syntax in ANY of the PromiseKit documentation. It also suggests odd syntax for e.g. the .then calls. When accepting Xcode's suggestions, it obviously displays error as this is not the correct PromiseKit syntax. When ignoring Xcode's suggestion, I get this:
Obviously something is wrong here, my best guess is that something went wrong with the installation of PromiseKit. I have cleaned my project, re-installed the pod, restarted Xcode but it seems that nothing is working.
Question
Does anybody know what kind of issue I'm experiencing here and, even more importantly, how I might get it resolved?
Any helpt would be much appreciated.
According to the release notes:
then is fed the previous promise value and requires you return a promise.
done is fed the previous promise value and returns a Void promise (which is 80% of chain usage)
map is fed the previous promise value and requires you return a non-promise, ie. a value.
So, then shouldn't work here, because you need to return the promise value. If you just change then to the done it will work.
Also some suggestions.
firstly is really about visual decoration (i believe it was somewhere at PMK docs, but i can't find that right now), so, if this confuses you, try to remove that for the start;
The main feature of PMK is the chain. You definitely should write your code according to this principle;
Also, don't forget about errors. Use catch at the end of the chain for that.
Final example of your code:
firstly {
self.createCategoryComponents(masterComponent: masterComponent)
}
.done { createdComponents in
completion.resolve(nil, createdComponents)
}
.catch { error in
// don't forget about errors
}

What is the missing parameter in TaskState<TResult> in Bolts-Swift?

In the Bolts-Swift framework Task.swift, upgrading to Swift 4 produces this error for this method:
class func emptyTask() -> Task<Void> {
return Task<Void>(state: .success())
}
The error states:
Missing argument for parameter #1 in call
I understand that it's saying .success needs to take a parameter but without posting the entire file in here for context, can anybody with Bolts-Swift experience identify why this used to work in Swift 3 but doesn't work now using Swift 4?

Swift - Error propagation while maintaining api clarity

With the addition of ErrorType to Swift, it's now possible to express error and failure events in a cleaner, more concise manner. We're no longer bound as iOS developers to the old way of NSError, clunky and hard to use.
ErrorType is great for a few reasons:
Generic parameters in functions
Any enum can conform to and implement it
Works better with the catch/throw paradigm
There are however some problems and one problem in particular that I've been running into lately and I'm curious to see how others solve this.
Say for example, you're build a social networking application akin to Facebook and you have Group model. When the user first loads your application you want to do two/three things:
Fetch the relevant groups from your server.
Fetch the already persisted groups on disk (Realm, CoreData, etc)
Update the local copy with the remote copy just fetched.
Throughout this process, you can break the types of errors up into two distinct categories: PersistenceError and NetworkError where the ErrorType conforming enums might look like
enum PersistenceError: ErrorType {
case CreateFailed([String: AnyObject)
case FetchFailed(NSPredicate?)
}
enum NetworkError: ErrorType {
case GetFailed(AnyObject.Type) // where AnyObject is your Group model class
}
There are several ways/design patterns you can use for delivering errors. The most common of course is try/catch.
func someFunc() throws {
throw .GetFailed(Group.self)
}
Here, because functions that throw can't yet specify what type of error they're throwing, although I suspect that will change, you can easily throw a NetworkError or a PersistenceError.
The trouble comes in when using a more generic or functional approach, such as ReactiveCocoa or Result.
func fetchGroupsFromRemote() -> SignalProducer<[Group], NetworkError> {
// fetching code here
}
func fetchGroupsFromLocal() -> SignalProducer<[Group], PersistenceError> {
// fetch from local
}
Then wrapping the two calls:
func fetch() -> SignalProducer<Group, ???> {
let remoteProducer = self.fetchGroupsFromRemote()
.flatMap(.Concat) ) { self.saveGroupsToLocal($0) // returns SignalProducer<[Group], PersistenceError> }
let localProducer = self.fetchGroupsFromLocal()
return SignalProducer(values: [localProducer, remoteProducer]).flatten(.Merge)
}
What error type goes in the spot marked ???? Is it NetworkError or PersistenceError? You can't use ErrorType because it can't be used as a concrete type and if you try using it as a generic constraint, <E: ErrorType>, the compiler will still complain saying it expects an argument list of type E.
So the issue becomes, less so with try/catch and more so with functional approaches, how to maintain an error hierarchy structure so that error information can be preserved throughout different ErrorType conformances while still having that descriptive error api.
The best I can come up with so far is:
enum Error: ErrorType {
// Network Errors
case .GetFailed
// Persistence Errors
case .FetchFailed
// More error types
}
which is essentially one long error enum so that any and all errors are of the same type and even the deepest error can be propagated up the chain.
How do other people deal with this? I enjoy the benefits of having one universal error enum but the readability and api clarify suffer. I'd much rather have each function describe what specific error cluster they return rather than have each return Error but again, I don't see how to do that without losing error information along the way.
Just trying a solution to your problem, may not be a perfect one. Using protocol to easily pass around the error objects :
//1.
protocol Error {
func errorDescription()
}
//2.
enum PersistenceError: ErrorType, Error {
case CreateFailed([String: AnyObject)
case FetchFailed(NSPredicate?)
func errorDescription() {
}
}
//3.
enum NetworkError: ErrorType, Error {
case GetFailed(AnyObject.Type) // where AnyObject is your Group model class
func errorDescription() {
}
}
//5.
func fetch() -> SignalProducer<Group, Error> {
...
}

How do I "generify" a closure type alias in Swift?

In order to make my code easier to read, I am using type aliases in Swift for various types of closures. I have the following basic set of closures:
public typealias FailureClosure = (error: NSError?) -> Void
public typealias ProgressClosure = (progress: Float32) -> Void
public typealias BasicClosure = () -> Void
I would like to add a closure typealias that supports generic arrays, but I can't seem to figure out the syntax for it. This is as far as I am able to get, but I get the compile time error "Use of undeclared type 'T'"
public typealias ArrayClosure = <T>(array:[T]?) -> Void
Does anybody know how to do this? Or even if it is possible?
No, this is not currently possible. If it were possible, the syntax you'd expect would be:
public typealias ArrayClosure<T> = (array:[T]?) -> Void
You would then use it like ArrayClosure<Int>. But it's not currently legal.
That said, I don't really recommend these type aliases. They obscure more than they illuminate. Compare this signature:
func foo(onError: FailureClosure)
with:
func foo(onError: NSError? -> Void)
To save just a couple of characters, you force the caller to guess what FailureClosure passes. The error or progress tags don't really help you (you still need to use progress in ...).
The one case that makes a lot of sense is around progress, but I think the type you want there is:
public typealias Progress = Float32
Don't get me wrong here, type aliasing can be very helpful when it creates a new type. Progress is a type that happens to be implemented as as float. But much of what you're doing (definitely with ArrayClosure, and to a lesser extent with the others) is just creating new syntax without creating a new type, and that is more often confusing than helpful.
To call out your specific example, and why overuse of type aliases can cause you to overcomplicate your design:
func foo(failure: ((error: NSError?) -> ())? = nil)
You're right that this is really complicated. Compare:
func foo(failure: NSError -> Void = {_ in return})
Two big changes here. There's no reason to have a failure block that takes an optional error. Always pass an error (if there's no error, why would failure be called?). And there's no reason for the failure block to be optional. If you really want a default value, just make the default value do nothing. Two optionals gone, and all the consuming and implementing code gets simpler. Always think carefully about whether something absolutely must be optional. Optionals add a lot of complexity; don't add them lightly.
Personally, I'd probably do this with an overload instead in many cases:
func foo(#failure: NSError -> Void) { ... }
func foo() {
foo(failure:{ _ in return })
}
I just think it's a little easier to understand what's going on. But either way is fine.
EDIT (Dec 2014): After writing Swift for a few more months, I've grown more fond of #David's approach in the comments below, which is to use an optional for the closure, but not for the error. Particularly given Swift's optional chaining syntax (failure?()), it often does wind up being clearer.
func foo(failure: (NSError -> Void)? = nil)
Swift 4.1 supports generic type aliases. You can use this feature to provide a name for function with generic parameters.
You may have to use such declaration:
public typealias ArrayClosure<T> = ([T]?) -> Void

In Objective C/Swift what do return within a method when an error is thrown?

Note: I say Objective C/Swift in the title of this post because I believe the answer to this question will apply to both languages, but the code I include in this post is Swift.
Java has a nice mechanism for Exception handling, but in Objective C we're either getting an error back from a delegate method or passing an error into a function by reference and then checking it afterwards. I'm wondering, what is the cleanest way to return from a method which expects a return value (such as an NSString, UIImage, or some other complex object) when an error is thrown? I don't know if "thrown" is the correct word to use in Objective C, but I really just mean when you make a call that returns a non-nil NSError.
For example, say I have this code which is trying to create a UIImage of a QR code:
func createQRImageWithString(string: String, andWidth width: Int32, andHeight height: Int32) -> UIImage {
var error : NSError?
let writer : ZXMultiFormatWriter = ZXMultiFormatWriter()
let result = writer.encode(string, format: kBarcodeFormatQRCode, width: width, height: height, error: &error)
if (error) {
println(error!.localizedDescription)
} else {
let image :CGImageRef = ZXImage(matrix: result).cgimage
return UIImage(CGImage: image)
}
}
This func does not compile because in the if statement no UIImage object is returned. The func creates a contract between the func and anyone who uses it, promising that a UIImage will be returned. In the event of an error, I have no UIImage to return. I guess I could make a dummy empty UIImage and return it, but that's certainly not what is expected. So what is the best way to handle this?
I gave an example using UIImage, but I'd like the answer to be generalizable to any situation where a method promises to return an object, but due to an unforeseen error returning that object is not going to go as planned.
In swift you can use the optional type and return a UIImage?, which allows you to either return a UIImage or nil.
The client will be statically enforced by the compiler to handle the optional return value, so this is a safe approach, as opposed to simply return nil (i.e. a NULL pointer) in Objective-C.
In Objective-C, throwing exceptions is reserved for programming errors (with very few exceptions). Swift doesn't have exceptions. The Swift designers seem to have decided that while having exceptions seemed to be a good idea, it actually isn't. (And there is plenty of Java code with atrocious exception handling).
If an error happens, your function should return a result that indicates that an error happened, and set an error variable to the description of the error. If your function calls another function that returns an error, then you need to decide whether the error of the function called indicates that your function returns an error or not. For example, a function checking whether WiFi is available or not may call a function that produces an error if there is no WiFi, in which case your function will return that there is no WiFi - without suggesting there is an error.
If the error in the called function means your function fails itself, then the same rules apply: Return a result that indicates there is an error, and set an error variable. In Objective-C, methods often return YES for success and NO for failure, or nil for failure and not-nil for success. In Swift, you usually return an optional value.
One answer is to return a UIImage? rather than a UIImage, returning nil if you have no image to return. Your calling code then has to determine what to do if you have no image. If it is possible that there is no image, then UIImage is probably not the right 'contract' to have with your calling code, whereas UIImage? is.

Resources