Swift Cast AnyObject to Block - ios

So I am using the Salesforce SDK and built bridging headers for the entire SDK.
They provide a block syntax which hasn't translated into the most usable code. For instance,
func sendRESTRequest(request: SFRestRequest!, failBlock: SFRestFailBlock!, completeBlock: AnyObject!)
The complete block is AnyObject!. I was able to get around this with
var block : #objc_block (dataResponse :AnyObject!) -> Void = { dataResponse in //I handle the response}
restService.sendRESTRequest(request, failBlock: { (error :NSError!) -> Void in
}, completeBlock: unsafeBitCast(block, AnyObject.self))
So far this works fine. However, now I am trying to build unit testing for this code. I have created a mock class for SFRestAPI which is the class where the function "sendRESTRequest" resides. For testing purposes, I am trying to mock out the completeBlock: parameter by passing mock "data" that would be returned from the REST service.
class MockSFRestAPI : SFRestAPI {
override func sendRESTRequest(request: SFRestRequest!, failBlock: SFRestFailBlock!, completeBlock: AnyObject!) {
//Convert complete block into a closure and pass in some test data
}
}
The issue is, I am unable to cast AnyObject! to a block like I was able to cast the block to AnyObject like above.
Some of my attempts have been:
var block = completeBlock as #objc_block (AnyObject! -> Void)
var block2: (AnyObject! -> Void) = unsafeBitCast(completeBlock, (#objc_block (AnyObject! -> Void)))
There have been many more attempts, but these are the only two that seem relatively sane. So, is this possible in Swift? The issue seems to be that I cannot provide a closure "type" to the second parameter of the unsafeBitCast method. I want to turn it into a closure so I can call it in my mock method and pass in some fake data.

The best way to handle this situation is to create your own typealias for your block:
typealias MyFunBlock = #objc_block (dataResponse :AnyObject!) -> Void;
Then you can use that to unsafebitcast:
var block: MyFunBlock = unsafeBitCast(completeBlock, MyFunBlock.self) as MyFunBlock;

Related

How to create typealias for completion handlers with named arguments

What I have so far is this:
I've defined typealias completion handler
typealias UserCompletionHandler = (_ object: User?, _ error: ApiError?) -> Void
And I've created a service function that is using this typealias
func login(email: String, password: String, completion: UserCompletionHandler) {
// ...
// this part here handles API call and parsing logic
// ...
completion(user, nil)
}
What I want to achieve is to have more readable completion callback with parameters by introducing named arguments. Idea is to end up with this:
completion(object: user, error: nil)
Or even better to make error parameter optional, so I can just call
completion(object: user)
Issue is that I can't find a way to change typealias definition to achieve this.
Apparently this is not possible. You can find the explanation behind this choice in the swift evolution proposal: 0111-remove-arg-label-type-significance.md
Function types may only be defined in terms of the types of the formal parameters and the return value.
Writing out a function type containing argument labels will be prohibited
Not sure if this answer is ideal, but you could use a tuple as your input argument:
typealias UserCompletionHandler = ((object: User?, error: ApiError?)) -> Void
and the usage would look like this:
completion((object: user, error: nil))

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

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.

Mocking in Swift for XCTest

I am writing test cases for my project which is mix up with Objective C as well as Swift code. I am aware about OCMock framework which I have used previously for mocking/Stubbing for writing Test cases in Objective C.
But I googled and found that it doesn't support fully for swift, since it is based on Objective C runtime.
I am trying to write test cases in swift language. Is there way I can do mocking/Stubbing for service level layer. For eg.
func getPersonData(id:String, success: (ReponseEnum) -> Void, failure: (error: NSError) -> Void) {
let requestPara:NSDictionary = ["id": id]
let manager: MyRequestManager = MyRequestManager.sharedManager()
//MyRequestManager is nothing but AFNetworking class
let jsonRequest
/// Service request creation code here
// Service Call
manager.POST(url, parameters: jsonRequest, success: { (task: NSURLSessionDataTask!, responseObject: AnyObject!) -> () in
// Some business logic
//success block call
success (successEnum)
}) {(task: NSURLSessionDataTask!, error: NSError!) -> () in
// failure block call
failure (failureEnum)
}
}
Here how to mock post method call for dummy responseObject So I can write test cases?
You need to use dependency injection to be able to mock the POST method.
Your class, where you defined the getPersonData(id:success:failure) method, needs to accept MyRequestManager as a parameter in constructor:
class MyClass {
private let requestManager: MyRequestManager
init(requestManager: MyRequestManager) {
self.requestManager = requestManager
}
}
Then you create a mock for your request manager:
class MockMyRequestManager: MyRequestManager {
// not sure about correct method signature
override func POST(url: NSURL, parameters: [String: AnyObject], success: (() -> Void)?) {
//implement any custom logic that you want to expect when executing test
}
}
And in the tests you initialise your class with a mock:
let myClass = MyClass(requestManager: MockMyRequestManager())
You can find more details about dependency injection here:
http://martinfowler.com/articles/injection.html

Why can a swift function declared as needing parameters, be passed without them?

Code written Swift 2.0 on Xcode 7 beta
My question concerns passing functions are parameters. I have two functions: getImagesFromImgur and randomImageHandle. getImagesFromImgur is called at ViewDidLoad like this:
self.getImagesFromImgur(apiAction: "gallery.json",
handleResponse: self.randomImageHandle)
The getImagesFromImgur function looks like this:
func getImagesFromImgur(apiAction: String, handleResponse:
(NSData?, NSURLResponse?, NSError?) -> Void) {
...
let task = session.dataTaskWithRequest(request, completionHandler:
handleResponse)
...
}
The randomImgurHandle that is passed to getImagesFromImgur function in viewDidLoad looks like this:
func randomImageHandle(data: NSData?, response: NSURLResponse?,
error: NSError?) -> Void {
...
//code that edits the UI
...
}
So my question is, hqow can I pass self.randomImageHandle to self.getImagesFromImgur without specifying any argument values? I know dataTaskWithRequest passes the parameters to the handleResponse, and while I understand that, I'm confused on why the compiler doesn't force me to specify parameters.
EDIT: I'm passing a handle like this so I can have specific handles to match imgur's Api.
In Swift, functions are first-class types, and you can pass them around as variables. What you're actually doing is that getImagesFromImgur is expecting a function that has the exact signature (NSData?, NSURLResponse?, NSError?) -> Void (you don't need the Void here, BTW). The compiler checks for you that you passed in a method that has that signature, and thus you don't need to specify parameters.

Resources