Passing concrete instance to closure that expects a protocol parameter - ios

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

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

Swift generics - Cannot explicitly specialize a generic function

I'm trying to call a generic function and i get this error:
Cannot explicitly specialize a generic function
My code:
public func parse<T: Codable>(request: HTTPRequestProtocol,
completion: #escaping (T?) -> Void) {
///....
}
//
parser.parse<Person>(request: request, onSuccess: { (codable) in
//Error: Cannot explicitly specialize a generic function
}
How can i fix it?
Thanks
There is this rule in Swift that you must not explicitly say (using <>s) what the generic parameters of a generic method are. You must give clues to the type inference engine to let it figure the generic parameters out. In this case, you can annotate the type of closure parameter, so that the closure has a type of (Person) -> Void. With this information, the compiler can figure the type of T out.
parser.parse(request: request, onSuccess: { (codable: Person) in ... }
In other cases, you might have to take in an extra parameter of type T.Type. For example, if your function only takes a type parameter and no value parameters:
func foo<T>() { ... }
In that case, you'd need to add an extra parameter:
func foo<T>(_ type: T.Type) { ... }
so that you can use it as:
foo(Person.self)

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.

Swift Cast AnyObject to Block

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;

Resources