How to make a reference to non escaping closure? - ios

I have the following code
class Child {
var onClosureFromParent: ((Int) -> Void)?
// Ok.
func setupEscaping(onClosureFromParent: #escaping (Int) -> Void) {
self.onClosureFromParent = onClosureFromParent
}
// Error: Assigning non-escaping parameter 'onClosureFromParent' to an #escaping closure
func setupNonEscaping(onClosureFromParent: (Int) -> Void) {
// FIXME:
self.onClosureFromParent = onClosureFromParent
}
}
I was wondering, how can I make a reference to non escaping closure?

From the Official documentation:
A closure is said to escape a function when the closure is passed as
an argument to the function, but is called after the function returns.
When you declare a function that takes a closure as one of its
parameters, you can write #escaping before the parameter’s type to
indicate that the closure is allowed to escape.
Since the closure argument in the function, you are saving its reference and not executing it, it makes it implicit that this closure will not be executed within the scope of the function. Thus, you should allow it to escape.
func setupNonEscaping(onClosureFromParent: (Int) -> Void) {
// Saving the reference, not executing it.
// ie, It will be executed later on...
self.onClosureFromParent = onClosureFromParent
}
Somewhere in the code...
// The closure is executed later on, thus it should outlive the function lifescope.
let param = 10
self.onClosureFromParent(param)

Related

Difference between return of Any vs Void in Async Block

Between these 2 codes
( returns -> Void)
static func dropboxWorkoutList ( userCompletionHandler: #escaping ([String]) -> Void) {
let client = DropboxClientsManager.authorizedClient
var files: [String] = []
_ = client?.files.listFolder(path: "/workouts")
.response { response, error in
if let result = response {
for entry in result.entries {
if let file = entry as? Files.FileMetadata {
let ext = (file.name as NSString).pathExtension
switch ext {
case "txt", "mrc", "zwo":
// print("filename:\(file.name) ext:\(ext) path:\(file.pathLower)")
files.append(file.pathLower!)
default:
continue
}
}
}
files = files.sorted(by: { $0 < $1 } )
// print("Contents of Workout Folder:\(files)")
userCompletionHandler(files)
} else if let error = error {
print(error)
}
}
}
calling it
dropboxFunc.dropboxWorkoutList() { (value) in
print("value:\(value)")
print("value[0] : \(value[0])")
print("value.count : \(value.count)")
}
results in:
value:["/1-01.txt", "/1-17.txt"]
value[0] : /1-01.txt
value.count : 5
however when changing it
from Return -> Void
to Return -> Any
trying to execute the below will have swift telling me:
"Missing return in a closure expected to return 'Any'"
dropboxFunc.dropboxWorkoutList() { (value) in
print("value:\(value)")
print("value[0] : \(value[0])")
print("value.count : \(value.count)")
}
I can only allowed to execute 1 print statement. Just want to understand the difference.
Note: Asked this
Return list of files from function call
and was given this as possible answer
How I can return value from async block in swift
Specifying a closure of ([String]) -> Any means that the closure is going to return something, and it is of type Any. But in your examples, (a) your closures are not returning anything at all; and (b) the dropboxWorkoutList does not appear to need/use an object returned by the closure supplied by the caller, anyway. This is a “completion handler closure” pattern, and completion handler closures almost always have a Void return type.
I actually want to use the return values from dropboxWorkoutList to populate a tableview which I've not coded yet
OK, then I think you may be conflating the closure parameter (what dropboxWorkoutList will be supplying back to the caller) and the closure’s return value (the far less common scenario, where the caller needs to supply dropboxWorkoutList some value based upon the closure’s parameter). In this case, you want the former (the closure parameter), not the latter (the closure’s return value).
You likely do not want to change the closure to be ([String]) -> Any, at all, but rather leave it as ([String]) -> Void. The caller should just take the parameter of the closure and use that. For example:
dropboxFunc.dropboxWorkoutList { values in
self.strings = values // update whatever model object your table view data source is using
self.tableview.reloadData() // and tell the table view to reload the data
}
In short, your question here (and in that other post) was, effectively, “how do I return a value using a closure?”, to which the answer is that you should not think of it as “returning” a value. Rather, in asynchronous routines with completion handler closures, results are supplied as parameter(s) to the closure. Essentially, dropboxWorkoutList is calling a function (in this case, a closure) to say “hey, here is your data”. It is providing data to that closure, not returning data from that closure.

Passing concrete instance to closure that expects a protocol parameter

In my app, I have a function to call a specific API endpoint of mine, and that function accepts a closure as a completion handler. That closure accepts a Result of my custom Decodable type (Category) in the success case, and an Error in the failure case. Altogether, its method signature looks like this:
static func getAllRestaurantCategories(completion: #escaping (Result<[Category], Error>) -> Void) -> Void
This function calls out to Alamofire to determine the languages that the server supports, and then get the list of all possible restaurant categories. It's implemented like this:
static func getAllRestaurantCategories(completion: #escaping (Result<[Category], Error>) -> Void) -> Void{
API.localizedRequest(API.categories) { (request: DataRequest) in
request.responseDecodable(of: [Category].self) { (response: DataResponse<[Category], AFError>) in
completion(response.result)
}
}
}
However, on the line with completion(response.result), I get a compiler error that says Cannot convert value of type 'Result<[Category], AFError>' to expected argument type 'Result<[Category], Error>'. This error goes away if I change the closure my method accepts to accept an AFError in the failure case, like this:
static func getAllRestaurantCategories(completion: #escaping (Result<[Category], AFError>) -> Void) -> Void{
API.localizedRequest(API.categories) { (request: DataRequest) in
request.responseDecodable(of: [Category].self) { (response: DataResponse<[Category], AFError>) in
completion(response.result)
}
}
}
Alamofire's AFError conforms to Error, so it seems to me that this should work just fine. I know I can parse Alamofire's Result myself and generate my own to pass to my completion handler, but I'd rather not write all that extra custom code if I don't have to. How can I get the type system to understand that this should be ok?
Simply put, in (at least the current version of) Swift, if Sub is a subtype of Base, that doesn't mean that Container<Sub> is a subtype of Container<Base>.
In fact, Container<Sub> and Container<Base> are unrelated types.
So, while we can do the following:
protocol Car {}
struct Toyota: Car {}
let a: Car = Toyota()
but we can't generally (with notable exception of Swift's standard library collection types) do this:
struct Container<T> {}
let c: Container<Car> = Container<Toyota>() // ERROR
It is said that Container<Car> and Container<Toyota> are not covariant
Result has a mapError function that should make it fairly painless:
completion(response.result.mapError { $0 as Error } )

get value passed to completion handler for unit testing

I'm trying to get the value passed to a completion handler to test against for unit testing. It is not a normal setup for a completion handler since the completion handler is a var:
typealias ResultType = Result<Data>
typealias CompletionType = (ResultType) -> ()
var onCompletion: CompletionType// this is the completion handler
open override func finished(_ errors: [NSError]) {
self.urlSession.invalidateAndCancel()
self.onCompletion(Result.failure(EComError.networkError(.notReachable)))
}
}
I'm trying to compare the var: onCompletion with the
self.onCompletion(Result.failure(EComError.networkError(.notReachable)))
but when I try and print the var: onCompletion after the function has been it returns:
Optional((Function))
I'm trying to write a test case like this:
func test_finishedResult(){
sut?.finished([NSError(code: OperationErrorCode.executionFailed)])
XCTAssertEqual(sut?.onCompletion(Result.failure(EComError.networkError(.notReachable))), sut?.onCompletion)
}
but it returns the error:
Cannot convert value of type 'DownloadOperation.CompletionType?' (aka
'Optional<(Result) -> ()>') to expected argument type '()?'
where
DownloadOperation
is the name of the class the function is in.
Whats the best way to unit test the that the var:
onCompletion
is equal toself.onCompletion(Result.failure(EComError.networkError(.notReachable)))?
With your test code you are not comparing the value passed to the completion, but instead the function pointers of your callbacks.
What you probably want is something like:
func test_finishedResult() {
// Set your callback here
sut?.onCompletion = { result in
// Compare the result here
XCTAssertEqual(Result.failure(EComError.networkError(.notReachable)), result)
}
// Trigger the callback with the finished method
sut?.finished([NSError(code: OperationErrorCode.executionFailed)])
}

Swift, use function UIView.removeFromSuperview as an argument in forEach function over array of views

I would like to change following code
let views: [UIView] = []
views.forEach {
$0.removeFromSuperview()
}
to some other way, in which I pass function UIView.removeFromSuperview as an argument to the forEach function.
Something similar to
let views: [UIView] = []
views.map { /* transform views to be passed further */ }
.forEach(UIView.removeFromSuperview)
Is it possible somehow?
UPDATE
Based on the answer below and some comments, I can sum up feedback for this question for myself a bit.
Instance Methods are “Curried” Functions in Swift (By Ole Begemann)
Flattening the function type of unapplied method references (Swift Evolution)
Based on the latter, Chris Lattner has mentioned some flip function in a paragraph Impact on existing code.
My assumption about its implementation is something following
func flip<T>(_ function: #escaping (T) -> () -> Void) -> (T) -> Void {
return { object in function(object)() }
}
Thus, we can rewrite initial code like
views.forEach(flip(UIView.removeFromSuperview))
It's easy enough to just call the method on $0:
views.forEach {
$0.removeFromSuperview()
}
(And you can name the argument if you like.)
But you could also wrap it into a method:
extension Sequence {
func onEach(_ invoke: (Iterator.Element) -> () -> Void) {
self.forEach { invoke($0)() }
}
}
This works because instance methods can be represented as functions that take an instance of their type and return a function that has their "top-level" signature.
One downside here is that you can't include the rethrows annotation that's present on forEach, because rethrows only applies if the function argument itself is throwing.

Firebase Swift 3 Database crashes on setValue withCompletionBlock

I am using Firebase on iOS with Swift 3.
When I use
FIRDatabase.database().reference().child("child").setValue("value") {
(error: Error?, databaseReference: FIRDatabaseReference) in
print("Error while setting value \(error)")
}
The app crashes on runtime with the following log:
*** Terminating app due to uncaught exception 'InvalidFirebaseData', reason: '(nodeFrom:priority:) Cannot store object of type _SwiftValue
at . Can only store objects of type NSNumber, NSString, NSDictionary,
and NSArray.'
I tried to use the same function but without the trailing closure and for some reason, it works!
FIRDatabase.database().reference().child("child").setValue("value",
withCompletionBlock: {
(error: Error?, databaseReference: FIRDatabaseReference) in
print("Error while setting value \(error)")
})
Is there something special about trailing closures and Swift 3?
tl;dr: Firebase provides a setValue(_ value: Any?, andPriority priority: Any?) which is incorrectly matched when using a trailing closure with setValue(_ value: Any?, withCompletionBlock: (Error?, FIRDatabaseReference) -> Void).
Solution: When using an API that has many varieties, avoid using trailing closures. In this case, prefer setValue(myValue, withCompletionBlock: { (error, dbref) in /* ... */ }); do not use setValue(myValue) { (error, dbref) in /* ... */ }.
Explanation
This appears to be a Swift bug. As in other languages, such as Java, Swift generally chooses the most specific overload. E.g.,
class Alpha {}
class Beta : Alpha {}
class Charlie {
func charlie(a: Alpha) {
print("\(#function)Alpha")
}
func charlie(a: Beta) {
print("\(#function)Beta")
}
}
Charlie().charlie(a: Alpha()) // outputs: charlie(a:)Alpha
Charlie().charlie(a: Beta() as Alpha) // outputs: charlie(a:)Alpha
Charlie().charlie(a: Beta()) // outputs: charlie(a:)Beta
However, when overloaded functions match a trailing closure, Swift (at least, sometimes) selects the more general type. E.g.,
class Foo {
func foo(completion: () -> Void) {
print(#function)
}
func foo(any: Any?) {
print(#function)
}
}
func bar() {}
Foo().foo(completion: bar) // outputs: foo(completion:)
Foo().foo(any: bar) // outputs: foo(any:)
Foo().foo() { () in } // outputs: foo(any:)
// ^---- Here lies the problem
// Foo().foo(bar) will not compile; can't choose between overrides.
Any? is a more general type than () -> Void -- i.e., "anything, even null" is more broad than "a function receiving 0 parameters and returning something of type Void". However, the trailing closure matches Any?; this is the opposite of what you would expect from a language that matches the most specific type.
While there is an accepted answer, which provides some clarity, explaining that it's a Swift bug is not really accurate. That being said, the explanation is accurate but not for this issue.
Allowing the closure to be added to setValue in the first place is the real bug.
A more accurate answer is that there is no completion block/closure for the setValue function, which is why it fails.
The specific function -setValue: does NOT have a closure, which is why it's crashing. i.e. it's an incorrect implementation in your code. Per the docs:
func setValue(_ value: Any?)
note that the setValue function does NOT have a closure and if you add one the function will have no idea what to do with that data.
To use a completion block/closure, you must call the correct function which is
-setValue:withCompletionBlock:
Bottom line is you can't randomly add a parameter or call to a function that's not designed to accept it.
This is obviously not valid but conceptually it's the same error.
let a = "myString"
a.append("x") { (error: Error?) }
In this case the compiler knows the the string.append function doesn't have a closure option and catches it before compiling.
So go a tad further, this code complies & runs but also generates an error
ref.child("child").setValue("value") { }
Again, setValue doesn't have a closure so this code is improperly implemented.
To clarify, given a class
class MyClass {
var s = ""
var compHandler = {}
func setValue(aString: String) {
self.s = aString
}
func setValue(aString: String, someCompletionHandler: completionHandler) {
self.s = aString
self.compHandler = someCompletionHandler
}
}
Note that setValue:aString is a totally different function than setValue:aString:someCompletionHandler
The only parameter that can be based to setValue:aString is a String as the first and only parameter.
The setValue:aString:someCompletionHandler will be passed two parameters, a String in the first position and a completionHandler in the second position.
The actual completion block is the second parameter passed.
param1, param2
------, ---------------
string, completionBlock
This is why
setValue(value) {}
is improperly formatted whereas
setValue(value, withBlock: {})
is properly formatted.

Resources