Inject mock class into method to unit test method - ios

I'm trying to unit test a method which has a dependency on another class. The method calls a class method on that class, essentially this:
func myMethod() {
//do stuff
TheirClass.someClassMethod()
}
Using dependency injection technique, I would like to be able to replace "TheirClass" with a mock, but I can't figure out how to do this. Is there some way to pass in a mock class (not instance)?
EDIT: Thanks for the responses. Perhaps I should have provided more detail. The class method I am trying to mock is in an open source library.
Below is my method. I am trying to test it, while mocking out the call to NXOAuth2Request.performMethod. This class method issues a network call to get the authenticated user's info from our backend. In the closure, I am saving this info to the global account store provided by the open source library, and posting notifications for success or failure.
func getUserProfileAndVerifyUserIsAuthenticated() {
//this notification is fired when the refresh token has expired, and as a result, a new access token cannot be obtained
NSNotificationCenter.defaultCenter().addObserver(self, selector: "didFailToGetAccessTokenNotification", name: NXOAuth2AccountDidFailToGetAccessTokenNotification, object: nil)
let accounts = self.accountStore.accountsWithAccountType(UserAuthenticationScheme.sharedInstance.accountType) as Array<NXOAuth2Account>
if accounts.count > 0 {
let account = accounts[0]
let userInfoURL = UserAuthenticationScheme.sharedInstance.userInfoURL
println("getUserProfileAndVerifyUserIsAuthenticated: calling to see if user token is still valid")
NXOAuth2Request.performMethod("GET", onResource: userInfoURL, usingParameters: nil, withAccount: account, sendProgressHandler: nil, responseHandler: { (response, responseData, error) -> Void in
if error != nil {
println("User Info Error: %#", error.localizedDescription);
NSNotificationCenter.defaultCenter().postNotificationName("UserCouldNotBeAuthenticated", object: self)
}
else if let data = responseData {
var errorPointer: NSError?
let userInfo = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: &errorPointer) as NSDictionary
println("Retrieved user info")
account.userData = userInfo
NSNotificationCenter.defaultCenter().postNotificationName("UserAuthenticated", object: self)
}
else {
println("Unknown error retrieving user info")
NSNotificationCenter.defaultCenter().postNotificationName("UserCouldNotBeAuthenticated", object: self)
}
})
}
}

In Swift this is better done by passing a function. There are many ways to approach this, but here is one:
func myMethod(completion: () -> Void = TheirClass.someClassMethod) {
//do stuff
completion()
}
Now you can pass a completion handler, while existing code will continue to use the default method. Notice how you can refer to the function itself (TheirClass.someClassMethod). You don't have to wrap it up in a closure.
You may find it better to let the caller just pass this all the time rather than making it a default. That would make this class less bound to TheirClass, but either way is fine.
It's best to integrate this kind of loose coupling, design-for-testability into the code itself rather than coming up with clever ways to mock things. In fact, you should ask yourself if myMethod() should really be calling someClassMethod() at all. Maybe these things should be split up to make them more easily tested, and then tie them together at a higher level. For instance, maybe myMethod should be returning something that you can then pass to someClassMethod(), so that there is no state you need to worry about.

Related

Errors setting up XCTest using Swift Result type

I am learning how to write tests for my API requests but having trouble setting up my test's completion code and response model.
I tried using an instance of UserResponse (let userResponse = UserResponse() ) however it required a value for its initializer (from: Decoder) and I have no idea what goes in there. The error I get is:
"Argument type 'Decoder.Protocol' does not conform to expected type 'Decoder'"
Also I am having errors in creating the test's completion handler, I am using the new Swift Result type (Result<UserResponse, Error>). The error I get is:
"Type of expression is ambiguous without more context"
This is an #escaping function but I got an error saying to remove #escaping in the test.
Any ideas on what is wrong? I have marked the trouble code below with comments.
Thank you!
// APP CODE
class SignupViewModel: ObservableObject {
func createAccount(user: UserSignup, completion: #escaping( Result<UserResponse, Error>) -> Void) {
AuthService.createAccount(user: user, completion: completion)
}
}
struct UserSignup: Encodable {
var username: String
var email: String
var password: String
}
struct UserResponse: Decodable {
var user: User
var token: String
}
struct User: Decodable {
var username: String
var email: String
// etc
{ private enum UserKeys }
init(from decoder: Decoder) throws { container / decode code }
}
// TEST CODE
class SignupViewModelTests: XCTestCase {
var sut: SignupViewModel!
override func setUpWithError() throws {
sut = SignupViewModel()
}
override func tearDownWithError() throws {
sut = nil
}
func testCreateAccount_WhenGivenSuccessfulResponse_ReturnsSuccess() {
let userSignup = UserSignup(username: "johnsmith", email: "john#test.com", password: "abc123abc")
// WHAT GOES IN from:??
let userResponse = UserResponse(from: Decoder)
// ERROR: "Type of expression is ambiguous without more context"??
func testCreateAccount_WhenGivenSuccessfulResponse_ReturnsSuccess() {
//arrange
let userSignup = UserSignup(username: "johnsmith", email: "john#test.com", password: "abc123abc")
sut.createAccount(user: UserSignup, completion: ( Result <UserResponse, Error> ) -> Void ) {
XCTAssertEqual(UserResponse.user.username, "johnsmith")
}
}
}
}
Okay there are multiple issues with the way you are unit testing your code, I wont go in detailing all of the issues, but what you need in gist to make it work is
func testCreateAccount() {
//given
let userSignup = UserSignup(username: "johnsmith", email: "john#test.com", password: "abc123abc")
let signupExpectation = expectation(description: "Sign up should succeed")
//when
sut.createAccount(user: userSignup) { (result) in
//then
switch result {
case .success(let response):
XCTAssertTrue(response.user.email == "john#test.com", "User email should be john#test.com")
XCTAssertTrue(response.user.username == "johnsmith", "Username should be johnsmith")
XCTAssertTrue(response.token != "", "Token should not be empty")
case .failure(let error):
XCTFail("Authorization should not fail, failed with \(error.localizedDescription)")
}
}
wait(for: [signupExpectation], timeout: 10.0)
}
You are trying to test an asynchronous API call so you cant test it with synchronous XCAssert statements you need expectation. There are ways to make the asynchronous API calls synchronous and test it with straight forward XCAssert statements if you are using third party libraries like RxTest. I think its out of scope for you considering you are still newbie to unit test with Swift.
You can read all about expectation here : Apple doc
I have followed a simple code structuring of Given, when and then as indicated by comment in answer, its a pretty neat way to arrange your code if you are not using any kind of third party library to write descriptive unit test like Quick and Nimble
There is a beautiful article on unit test using plain old XCTest framework in raywnderlich tutorial here.
Finally, as a closing remark, we dont make an actual API call to test our code in unit test, we write fakes and stubs to test out our code.
Assume if you end up writing unit test for your entire project and your CI/CD system starts running entire test suite for all your PRs and builds, and your unit test ends up making actual API call, amount of time that will be wasted in making an actual API call will increase your test suite run time rapidly making the release process a nightmare. Also testing backend API is not the intention of your unit test, hence avoid actual API call using mocks, fakes and stubs read about it :)
Unit test provided above is just to show you how to use expectation and test asynchronous API and definitely wont cover all possible cases :)
To create a UserResponse in test code, call its synthesized initializer, not the decoder initializer. Something like:
let userResponse = UserResponse(user: User("Chris"), token: "TOKEN")
And to create a closure in test code, you need to give it code. Completion closures in tests have one job, to capture how they were called. Something like:
var capturedResponses: [Result<UserResponse, Error>] = []
sut.createAccount(user: UserSignup, completion: { capturedResponses.append($0) })
This captures the Result that createAccount(user:completion:) sends to the completion handler.
…That said, it looks like your view model is directly calling a static function that makes a service call. If the test runs, will it create an actual account somewhere? Or do you have some boundary in place that we can't see?
Instead of directly testing createAccount(user:completion:), what you probably want to test is:
That a certain action (signing up) will attempt to create an account for a given user—but not actually do so.
Upon success, the view model will do one thing.
Upon failure, the view model will do another thing.
If my assumptions are correct, I can show you how to do this.

(SIGABRT Attempt to use unknown class projectname)EXC_BAD_ACCESS error on a strongly referenced variable in Xcode 11, Swift 5, iOS 13

TL;DR
I have a class with no public initializers or instances that passes an instance of itself to a closure in another class. It does this through a mirror of the other class. When I go to access that instance from within the closure, I'm getting a EXC_BAD_ACCESS error, but other parameters passed to the closure are clearly accessible and do not result in a bad access error. I have no idea why. See code below for replication in a new project or a playground.
Detailed Explanation
I've been trying to figure out a way to implement class-specific access control, where multiple specific classes have sole access to another class containing variables and functions to be shared between them. All other classes would not have such access. Kind of like a static class, or a Singleton pattern, but with specific, class-named access control.
I thought I had something that would actually work in pure swift, (which is nice for me since I don't know Objective-C, and only started on swift about 16 months ago.) It's done in an almost anti-swift manner, so just bear with me - my goal is to start with something functional and move it towards elegance and beauty from there.
Even though I'm reasonably confident it should all work, I'm encountering a EXC_BAD_ACCESS error in a very unexpected place.
The "class-specific private" class that you are not allowed to access an instance of unless you are on its "okay" list, we can call the Restricted class.
The class(es) that is(are) allowed access to the Restricted class we can call the Accessor class(es).
The programmer must tell the Restricted class to call a function from the Accessor, and "drop in" an instance of the Restricted class by passing it as a parameter to that function. You do this by passing in the name of the function to be called, an instance of the Accessor class on which to call said function, and any parameters that the function would need in addition to the Restricted class instance.
I could make an enormous switch in the Restricted class, each case of which properly calls each function indicated on each of the Accessor classes...but to get around that excessive overhead/setup, I have the name of the function to be called on the Accessor classes passed in as a string, and accessed through a mirror. Since mirrors only reflect properties and not functions, the function must be a property with an assigned closure, instead of a traditional function.
We can call these closures DropClosures, since their purpose is to have the shared, Restricted class dropped into them. In fact we could call this whole pattern the "DropClosure Pattern". (Or maybe anti-pattern, I know it's kind of gruesome as-is.)
The properties of the "shared" instance of the Restricted class are stored internally as a private static dict (as json, basically). To generate an actual instance of itself, the Restricted class uses a private initializer that accepts that dict as a parameter. After a DropClosure runs with said initialized instance, the Restricted class uses a Mirror of that instance to store any changes back in the "shared" dict, and the instance will go out of scope unless a reference is made to it. So after each DropClosure completes its run, the instance passed to it is more or less useless as a representation of the "shared" aspect of the class, intentionally so.
I only do this because there is no way to require that all references to a certain weak reference also be weak. I don't want a class with access to the weak reference to assign a strong reference to the same instance and keep it in memory, that would defeat the access control goal by allowing the instance to be shared outside of its access scope. Since I can't force the instance to expire once the closure has completed, the next best thing is to remove the motivation for doing so by making the object no longer connected to the shared source after the closure completes.
This all theoretically works, and will compile, and will not throw any swift exceptions when run.
The Accessor (or any class that has an instance of an Accessor) calls RestrictedClass.run(), the run code validates the Accessor instance, finds the DropClosure in that instance, and passes in an instance of the Restricted class to that closure.
However, whenever I try to access that instance from within the DropClosure, it gives me the aforementioned bad access error, seemingly on a C or Objective-C level.
As far as I can tell, the instance should be accessible at this point, and none of the variables being used should be dropping out of scope yet.
At this point I'm totally spitballing - is it possible that there is something in the background that prevents a class with no public initializers from being passed through a mirror? Does it have to do with passing it into a closure called from that mirror? Is there some kind of hidden weak reference that's making the instance get ARC'd?
Please note that I've tried discarding the "weak" wrapper object and only passing in the Restricted instance to the closure, and I get the same bad access error. The error is independent of the instance being weakly referenced.
Code:
import Foundation
typealias DropClosureVoid<T: AnyObject & AccessRestricted> = (_ weaklyConnectedInterface: WeaklyConnectedInterface<T>, _ usingParameters: Any?)->Void
typealias DropClosureAny<T: AnyObject & AccessRestricted> = (_ weaklyConnectedInterface: WeaklyConnectedInterface<T>, _ usingParameters: Any?)->Any?
enum AccessError : Error {
case InvalidFunction
case InvalidAccessClass
}
protocol AccessRestricted {
static func run<T:AnyObject>(_ closureName:String, in classObject: T, with parameters:Any?) throws
static func runAndReturn<T:AnyObject>(_ closureName:String, in classObject: T, with parameters:Any?) throws -> Any?
}
///This class contains an instance that should be expected to only temporarily represent the original, even if a strong reference is made that keeps the value in scope.
class WeaklyConnectedInterface<T:AnyObject> {
weak var value:T?
init(_ value: T) {
self.value = value
}
}
class Accessor {
let restrictedClassPassable:DropClosureVoid<RestrictedAccessClass> = { weaklyConnectedInterface, parameters in
print(weaklyConnectedInterface) // **EXC_BAD_ACCESS error here**
//note that the error above happens even if I pass in the instance directly, without the WeaklyConnectedInterface wrapper.
//It's clearly an issue that occurs when trying to access the instance, whether the instance is wrapped in a the class that makes a weak reference to it or not, which means that it is inaccessible even when strongly referenced.
if let parameterDict = parameters as? [String:String] {
print(parameterDict["paramkey"] ?? "nil")
print(weaklyConnectedInterface)
weaklyConnectedInterface.value?.restrictedVariable = "I've changed the restricted variable"
}
}
let anotherRestrictedClassPassable:DropClosureAny<RestrictedAccessClass> = { weaklyConnectedInterface, parameters in
if let parameterDict = parameters as? [String:String] {
print(parameterDict["paramkey"] ?? "nil")
print(weaklyConnectedInterface.value?.restrictedVariable as Any)
return weaklyConnectedInterface.value?.restrictedVariable
}
return nil
}
func runRestrictedClassPassable() throws {
let functionName = "restrictedClassPassable"
print("trying validateClosureName(functionName)")
try validateClosureName(functionName)//this is in case you refactor/change the function name and the "constant" above is no longer valid
print("trying RestrictedAccessClass.run")
try RestrictedAccessClass.run(functionName, in: self, with: ["paramkey":"paramvalue"])
let returningFunctionName = "anotherRestrictedClassPassable"
print("trying validateClosureName(returningFunctionName)")
try validateClosureName(returningFunctionName)
print("trying RestrictedAccessClass.runAndReturn")
let result = (try RestrictedAccessClass.runAndReturn(returningFunctionName, in: self, with: ["paramkey":"ParamValueChanged"]) as! String?) ?? "NIL, something went wrong"
print("result is \(result)")
}
func validateClosureName(_ name:String) throws {
let mirror = Mirror(reflecting: self)
var functionNameIsPresent = false
for child in mirror.children {
if child.label != nil && child.label! == name {
functionNameIsPresent = true
break
}
}
guard functionNameIsPresent else {
print("invalid function")
throw AccessError.InvalidFunction
}
}
}
extension Mirror {
func getChildrenDict() -> [String:Any]
{
var dict = [String:Any]()
for child in children
{
if let name = child.label
{
dict[name] = child.value
}
}
return dict
}
}
class RestrictedAccessClass:AccessRestricted {
private static var shared:[String:Any] = [
"restrictedVariable" : "You can't access me!"
]
private static func validateType<T>(of classObject:T) throws {
switch classObject {
case is Accessor:
return
default:
print("Invalid access class")
throw AccessError.InvalidAccessClass
}
}
var restrictedVariable:String
private init() {
restrictedVariable = "You can't access me!"
}
private init(from json:[String:Any]) {
restrictedVariable = json["restrictedVariable"] as! String
}
static func run<T:AnyObject>(_ closureName:String, in classObject: T, with parameters:Any?) throws {
print("trying validateType(of: classObject) in run")
try validateType(of: classObject)
for child in Mirror(reflecting: classObject).children {
if let childName = child.label {
if childName == closureName {
let dropClosure = child.value as! DropClosureVoid<RestrictedAccessClass>
let selfInstance = RestrictedAccessClass(from:shared)
let interface = WeaklyConnectedInterface(selfInstance)
dropClosure(interface, parameters)
runCleanup(on: selfInstance)//parses any data changed by the end of the drop closure back into the dict for use in future instances. This means you mustn't try using the instance in an async closure. The correct way to do this would be to call run inside of an async closure, rather than putting an anync closure inside of the drop closure.
_ = interface.value
return
}
}
}
}
static func runAndReturn<T:AnyObject>(_ closureName:String, in classObject: T, with parameters:Any?) throws -> Any? {
print("trying validateType(of: classObject) in runAndReturn")
try validateType(of: classObject)
for child in Mirror(reflecting: classObject).children {
if let childName = child.label {
if childName == closureName {
let dropClosure = child.value as! DropClosureAny<RestrictedAccessClass>
let selfInstance = RestrictedAccessClass(from:shared)
let interface = WeaklyConnectedInterface(selfInstance)
let result = dropClosure(interface, parameters)
runCleanup(on: selfInstance)//parses any data changed by the end of the drop closure back into the dict for use in future instances. This means you mustn't try using the instance in an async closure. The correct way to do this would be to call run inside of an async closure, rather than putting an anync closure inside of the drop closure.
_ = interface.value
return result
}
}
}
return nil
}
private static func runCleanup(on instance:RestrictedAccessClass) {
shared = Mirror(reflecting:instance).getChildrenDict()
//once this function goes out of scope(or shortly thereafter), the instance passed will become useless as a shared resource
}
}
Code to encounter error:
I just put this in a new project's AppDelegate.application(didFinishLaunching). You can put all of the code above and below, in order, in a playground and it will break in the same spot, but not as clearly.
let accessor = Accessor()
do {
try accessor.runRestrictedClassPassable()
}
catch {
print(error.localizedDescription)
}
Updates
Whether zombie objects are turned on or off, I'm getting the same error message from Xcode: Thread 1: EXC_BAD_ACCESS (code=1, address=0x1a1ebac696e)
Running an analysis with Command+Shift+B reveals no warnings.
Running with all of the malloc options enabled reveals the following error:
Thread 1: signal SIGABRT, objc[somenumber]: Attempt to use unknown class 0xSomevalue
This just got weird...
Apparently, the "unknown class" is the project. I found this out by selecting the (i) bubble on the inline object inspector for the Restricted instance that was causing the crash. It gives me the following message:
Printing description of weaklyConnectedInterface:
expression produced error: error:
/var/folders/zq/_x931v493_vbyhrfc25z1yd80000gn/T/expr52-223aa0..swift:1:65:
error: use of undeclared type 'TestProject'
Swift._DebuggerSupport.stringForPrintObject(Swift.UnsafePointer<TestProject.RestrictedAccessClass>(bitPattern: 0x103450690)!.pointee)
^~~~~~~~~~~
I thought that maybe this would happen for other classes, so I tested, and it's able to access other project-level classes just fine. Only for this specific instance is the project "namespace" undefined.
Please find below required modifications (not many)... Tested with Xcode 11.2 / iOS 13.2.
1) made interface inout to pass it as-is original, otherwise it somehow copied loosing type information
typealias DropClosureVoid<T: AnyObject & AccessRestricted> =
(_ weaklyConnectedInterface: inout WeaklyConnectedInterface<T>, _ usingParameters: Any?)->Void
typealias DropClosureAny<T: AnyObject & AccessRestricted> =
(_ weaklyConnectedInterface: inout WeaklyConnectedInterface<T>, _ usingParameters: Any?)->Any?
2) fix places of usage (same in two places)
var interface = WeaklyConnectedInterface(selfInstance) // made var
dropClosure(&interface, parameters) // << copy closure args here was a reason of crash
3) ... and that's it - build & run & output
Note: I would recommend to avoid force unwrap and use the following
if let dropClosure = child.value as? DropClosureVoid<RestrictedAccessClass> {
dropClosure(&interface, parameters)
}

Swift - How to Network disable on Unit Test

Is there any way to close Internet Connection for Unit Test? I have to check async function when the device on online or not. How can I do that ?
I know the Additional tools Package but I want to write this feature with programmatically.
Thanks in Advance
This is how I mock an API call. The steps requires
1) No internet connection
2) Mocking the API Call and data
I have created data models for the api response and this is how I am mocking it
func buildActivityList() -> ActivityResponse {
let resp = ActivityResponse(json: .null)
let userHistory = ActivityUserHistory(json: .null)
userHistory.time = "2018-02-16T07:41:54.046Z"
resp.userHistory = [userHistory]
//Add all other relevant data
return resp
}
Next you need to override the function were you are calling API and pass the above mocked data as API response.
Suppose you have a class where you call the api
class UserService {
func getUserDetails(completion: (_: ActivityResponse?, _: Error?)) {
//get RESPONSE from server
let data = ActivityResponse(object: RESPONSE)
return completion(data, nil)
}
}
Now in your test class call the buildActivityList() which we created and pass it in the mock class.
class MockUserService: UserService {
override func getUserDetails(completion: (_: ActivityResponse?, _: Error?)) {
return completion(buildActivityList(), nil)
}
}
This way whenever you test an API call, the mock data will be injected. Thus no need for internet connection.
Hope this helps

Adapting an async method with closure to a syncronous method with return type

I'm new to swift and not overly experienced in any major threading work, so I'm try to improve my skills a bit, hoping for a bit of help or guidance. This is one concept I can't seem to figure out.
I have a class which does communication via an Input and Output stream, the app sends via the output stream, and reads the result from the input stream. Right now I have created the method to send async via callbacks, but I would like the ability to send synchronously, and handle the threading at a higher level.
Currently my communication method looks something like this:
typealias CommunicationCallback = ((CommunicationResult) -> Void)
func sendAsync(send message: String, closure: #escaping CommunicationCallback) {
DispatchQueue.global().async { [weak self] in
guard let strongSelf = self else {
return
}
let messagePair = MessagePair(SendReceiveResult(sent: message, received: nil),
closure)
strongSelf.writeQueue.enqueue(messagePair)
strongSelf.writeFromQueue() //Calls the CommunicationCallback closure after it has read result
}
}
func sendSyncronized(send message: String) -> CommunicationResult {
???
}
Is there a general way to wrap a async all like that above?
In case I am thinking of this totally wrong, what Id' like in some cases to simply be able to call
let result = sendSyncronized("foo")
instead of doing
send("foo", closure: { result in
switch result {
case .a:
foo()
base .b:
bar()
}
})
everytime, as there are some cases in which I need to do many sequential writes while waiting for the previous result.
Any help is welcome!
Maybe you can use an optional return type.
typealias CommunicationCallback = ((CommunicationResult) -> Type?)
return nil in async method and the value in synchronous method. Use if let to check if the optional binding is successful. If not, you need to get the value from closure anyway.

How to write a function that takes a string and a generic type as parameters?

I'm using a library called Gloss to help parse JSON data. As a result I've created structs that are of type Glossy:
struct LoyaltyCard: Glossy {
let id: Int
init?(json: JSON) {
guard let __id: Int = "id" <~~ json
else { return nil }
}
I have many different Glossy structs and want to pass them into a function along with a string but I keep getting an error: " Cannot invoke 'getMemberInfo' with an argument list of type '(String, memberData: LoyaltyCard.Type)'", here is an abbreviated version of my function:
func getMemberInfo<T: Glossy> (memberDataRequest: String, memberData:T) {
let urlAccess = "\(baseURL)/api/\(memberDataRequest)"
///////code////////////
let data = object as! NSData
let jsonInfo: AnyObject? = try NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.init(rawValue: 0))
let jsonArray = jsonInfo as! NSArray
if let dict = jsonArray[0] as? JSON //This means as type Gloss
{
let loyaltyID= LoyaltyCard(json: dict)
print(loyaltyID?.id)
}
}
Any ideas how to make this function work?
I'm inferring from your code sample and from your comments, that you don't necessarily want to pass a Glossy type to getMemberInfo, but rather that the key requirement is that you want to perform a network request and return a Glossy type.
While I get what you were trying to do, I would personally retire the generic approach, and just use a protocol extension. You end up with a method that can be called for any Glossy type. And if this protocol extension method returns a type Self, that will end up returning whatever Glossy type from which you happen to call it.
First, let's step back and be clear as to what the Glossy protocol might look like. At the very least, you'd have some failable initializer (plus whatever else your types needed):
protocol Glossy {
init?(json: [String: AnyObject])
}
(Note, I'm not using JSON type, but feel free if you want. I personally just use Swift collections for parsed JSON, but do whatever you want.)
I'd then define a static method in a protocol extension to perform the request. The following method uses NSURLSession, but if you use Alamofire or something else, the basic idea is the same:
extension Glossy {
static func performMemberRequest(memberDataRequest: String, completionHandler:(Self?, ErrorType?) -> ()) -> NSURLSessionTask {
let urlAccess = "\(baseURL)/api/\(memberDataRequest)"
let request = NSMutableURLRequest(URL: NSURL(string: urlAccess)!)
let task = NSURLSession.sharedSession().dataTaskWithRequest(request) { data, response, error in
guard let data = data where error == nil else {
completionHandler(nil, error)
return
}
do {
if let array = try NSJSONSerialization.JSONObjectWithData(data, options: []) as? [[String: AnyObject]],
let dictionary = array.first {
completionHandler(Self(json: dictionary), nil)
} else {
completionHandler(nil, GlossyError.InvalidJSONError)
}
} catch let parseError {
completionHandler(nil, parseError)
}
}
task.resume()
return task
}
}
Note, there are a few notable issues entailed in the above:
Network requests should always be performed asynchronously. So use asynchronous pattern like completionHandler rather than trying to return some object immediately.
If you're going to return anything, the only thing you should be returning is the NSURLSessionTask object so the caller has the option to capture that and cancel requests, should you want that functionality.
I changed the name of the method to be more descriptive and conform to Cocoa naming conventions.
As an aside, your code suggests that your API returned an array and you're just grabbing the first dictionary. That seems like a curious pattern, but I've followed that, above. If you really were returning an array, it strikes me that you did that because you contemplate a scenario where you could be returning multiple items. In that case, I would advise iterate through the whole array and have the completion handler return [Self]? (an array of Glossy objects) rather than just Self? (i.e. only the first one).
Furthermore, I wouldn't personally advise a structure that returns an array as the top level item. How does that web service report errors? I'd have a dictionary structure that returned success/failure and/or return code and the like. And then have a dedicated key for results which would be your array of results.
But I didn't tackle any of these broader API issues above, but rather followed the pattern in your code snippet. But these are considerations you might want to think about.
In my example, I didn't dispatch these completionHandler calls back to the main queue, but that's often a very useful pattern (avoids synchronization problems, UI updates, etc.). It's trivial to do, but I wanted to keep the above relatively simple.
But, let's step aside from the details of your API and the like. Let's focus on the notion that you want a static method defined in the protocol extension, (and it can therefore be called from any type that conforms to Glossy). For example, I can then define the LoyaltyCard class with the required initializer:
struct LoyaltyCard: Glossy {
let id: Int
init?(json: [String: AnyObject]) {
guard let id = json["id"] as? Int else {
return nil
}
self.id = id
}
}
Having done all that, I can now invoke the static method of Glossy protocol extension on LoyaltyCard, for example:
LoyaltyCard.performMemberRequest(memberDataRequest) { loyaltyCard, error in
guard let loyaltyCard = loyaltyCard where error == nil else {
print(error)
return
}
// do something with loyaltyCard here
print(loyaltyCard)
}
// but don't use it here
There's a lot there, but I don't want you to get lost in the details. But I do hope you grok the key concepts here: Don't pass a Glossy type to your method, nor use a generic: Instead use protocol extension. And avoid synchronous network requests, so instead use a asynchronous pattern like the completionHandler pattern.

Resources