How to pass data between two ViewController using closure - ios

I want to know how to pass data using closure. I know that there are three types of data pass approaches:
delegate
Notification Center
closure
I want proper clarification of closure with an example.

Well passing data with blocks / closures is a good and reasonable approach and much better than notifications.
Below is the same code for it.
First ViewController (where you make object of Second ViewController)
#IBAction func push(sender: UIButton) {
let v2Obj = storyboard?.instantiateViewControllerWithIdentifier("v2ViewController") as! v2ViewController
v2Obj.completionBlock = {[weak self] dataReturned in
//Data is returned **Do anything with it **
print(dataReturned)
}
navigationController?.pushViewController(v2Obj, animated: true)
}
Second ViewController (where data is passed back to First VC)
import UIKit
typealias v2CB = (infoToReturn :String) ->()
class v2ViewController: UIViewController {
var completionBlock:v2CB?
override func viewDidLoad() {
super.viewDidLoad()
}
func returnFirstValue(sender: UIButton) {
guard let cb = completionBlock else {return}
cb(infoToReturn: "any value")
}
}

This example explains use of service call with Alamofire and send the response back to calling View Controller with closure.
Code in Service Wrapper Class:
Closure declaration
typealias CompletionHandler = (_ response: NSDictionary?, _ statusCode: Int?, _ error: NSError?) -> Void
Closure implementation in method
func doRequestFor(_ url : String, method: HTTPMethod, dicsParams : [String: Any]?, dicsHeaders : [String: String]?, completionHandler:#escaping CompletionHandler) {
if !NetworkReachablity().isNetwork() {
return
}
if (dicsParams != nil) {print(">>>>>>>>>>>>>Request info url: \(url) --: \(dicsParams!)")}
else {print(">>>>>>>>>>>>>Request info url: \(url)")}
Alamofire.request(url, method: method, parameters: dicsParams, encoding:
URLEncoding.default, headers: dicsHeaders)
.responseJSON { response in
self.handleResponse(response: response, completionHandler: completionHandler)
}
}
Code at calling view controller:
ServiceWrapper().doRequestFor(url, method: .post, dicsParams: param, dicsHeaders: nil) { (dictResponse, statusCode, error) in
}

Related

What should I include in my network mocking function?

I want to test my network request code. Testing is not my strong point, and I'm doing this mainly to get better at creating testable code. I usually use Alamofire, but want to bypass that for unit tests. I have created a protocol like this:
protocol NetworkSession {
func request(
_ endpoint: Endpoint,
method: HTTPMethod,
completion: #escaping (Result<<DataResponse<Any>>, RequestError>) -> ()
)
}
I conform to this protocol in my class NetworkSessionManager:
final class NetworkSessionManager : NetworkSession {
func request(
_ endpoint: Endpoint,
method: HTTPMethod,
completion: #escaping (Result<<DataResponse<Any>>, RequestError>) -> ()) {
guard let url = endpoint.url else {
return completion(.failure(.couldNotEncode))
}
Alamofire.request(url, method: method, headers: headers)
.validate()
.responseJSON { response in
completion(.success(response))
}
}
}
}
Then in my BackendClient class I inject and call my NetworkSessionManager like this:
private let session: NetworkSession
init(session: NetworkSession = NetworkSessionManager()) {
self.session = session
}
func someFunction(...) {
session.request(...)
}
Now I know I need to create a mock class that conforms to NetworkSession, but I'm not sure what I should do in it? How do I test all the possibilities and returned data? There are different end points on my server that return different types of data. If someone could point me towards the next steps I should take, I'd be very grateful.
final class NetworkSessionManagerMock : NetworkSession {
func request(
_ endpoint: Endpoint,
method: HTTPMethod,
completion: #escaping (Result<<DataResponse<Any>>, RequestError>) -> ()) {
???
}
}
I'll show two examples that can hopefully give you something to start with in mocking out your NetworkSession.
First though, you can add additional properties to your mock class that support injecting values to test various cases. I will demonstrate an error situation and a return for now.
enum MyError: Swift.Error {
case failed
}
final class NetworkSessionManagerMock : NetworkSession {
var myError: MyError?
var dataResponse: Any? // This should hopefully be typed to something
func request(
_ endpoint: Endpoint,
method: HTTPMethod,
completion: #escaping (Result<<DataResponse<Any>>, RequestError>) -> ()) { [weak self] in
if let myError = self?.myError {
// Send error with completion handler
} else if let dataResponse = self?.dataResponse {
// Send data response
}
}
}
The idea here is that you instantiate the mock in your test, then set the variables as needed to test the respective part. Your mock will then proceed normally.
I did not test this code for syntax correctness though, so let me know if there is a mistake in it.

The Network Connection was lost from Alamofire

I got this error many times in my project and it very irritates me because I have full internet connectivity though I get this error repeatedly.
What is the solution...?
I am using
Swift - 3.3
Alamofire - 4.7.3
API Calling Code:
class func post(_ URL: String, withParams params: [String : AnyObject], onView parentView: UIViewController, hnadler completion: #escaping ([AnyHashable: Any]!) -> Void) {
var URLString = String()
URLString = APIConstants.kServerURL + URL
var headers = [String: String]()
headers["Content-Type"] = "application/x-www-form-urlencoded"
Alamofire.request(URLString,method: .post, parameters: params , headers : headers)
.validate(contentType: ["application/vnd.api+json"])
.responseJSON { response in
switch response.result {
case .success( _):
var completionVarible = [NSObject : AnyObject]()
completionVarible = response.result.value as! [AnyHashable: Any]! as [NSObject : AnyObject]
completion(completionVarible)
case .failure(let error):
self.handleFailureResponse(Error: error as NSError?, parentView: parentView)
}
}
}
If the alert appears immediately you may try to change the cache policy to
.reloadIgnoringCacheData
I don't know exactly why this error occurs but I have also faced this error, so I have put one solution. This error has some unique error code. So check that error code and ignore alert over there or you can again try this API call if this error code you get.
I find one solution to this issue if you using Alamofire.
First import Alamofire in your common class otherwise you can create a separate class for check internet connection.
import Alamofire
class Connectivity {
class func isConnectedToInternet() ->Bool {
return NetworkReachabilityManager()!.isReachable
}
}
Call below method before you API call
if !Connectivity.isConnectedToInternet() {
ServiceHandler.ShowAlert(message: "Check your internet connectivity.", title: "Error", parentView: self) //This is my comman method for display alert.
return
}

Swift Progress View Value Passed Around Functions

I am needing to implement a progress bar that takes into account a couple of factors.
I have three different classes, my ViewController, a Networking class to handle the network calls and a dataManager class to handle all the db operations.
Now my progressView lives in my viewcontroller and I am looking at a way of updating it as each of the different operations are performed in the other classes.
I am using Alamofire so I know I can use .progress{} to catch the value of the JSON progress but that would also mean exposing the ViewController to the Networking class, which I assume is bad practice?
I think this should be achieved using completion handlers but as I have already setup another thread for handling the JSON / DB operation I'm not wanting to over complicate it anymore than I need to
Networking:
func makeGetRequest(url : String, params : [String : String]?, completionHandler: (responseObject: JSON?, error: NSError?) -> ()) -> Request? {
return Alamofire.request(.GET, url, parameters: params, encoding: .URL)
.progress { _, _, _ in
//bad practice?
progressView.setProgress(request.progress.fractionCompleted, animated: true)
}
.responseJSON { request, response, data, error in completionHandler(
responseObject:
{
let json = JSON(data!)
if let anError = error
{
println(error)
}
else if let data: AnyObject = data
{
let json = JSON(data)
}
return json
}(),
error: error
)
}
}
ViewController:
dataManager.loadData({(finished: Bool, error:NSError?) -> Void in
if let errorMessage = error{
self.syncProgress.setProgress(0, animated: true)
let alertController = UIAlertController(title: "Network Error", message:
errorMessage.localizedDescription, preferredStyle: UIAlertControllerStyle.Alert)
alertController.addAction(UIAlertAction(title: "Dismiss", style: UIAlertActionStyle.Default,handler: nil))
self.presentViewController(alertController, animated: true, completion: nil)
}
if finished{
for i in 0..<100 {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), {
sleep(1)
dispatch_async(dispatch_get_main_queue(), {
self.counter++
return
})
})
}
}
})
As you can see I am waiting on the finished boolean in the datamanger class to be set before updating the progress bar. The thing is, dataManager makes a call to networking and performs a bunch of other stuff before it finishes, it would be handy to update the progress bar along the way but I'm not sure of the best approach?
DataManager:
func loadData(completion: (finished: Bool, error: NSError?) -> Void) {
var jsonError: NSError?
networking.makeGetRequest(jobsUrl, params: nil) { json, networkError in
//....
}
I'm not too familiar with swift so I can't give you a code example but the way I would do this is create a protocol on your Networking class NetworkingDelegate and implement that protocol in your ViewController. The protocol method would be something like (in objective-c) NetworkingRequestDidUpdatProgress:(float progress)
This is assuming your ViewController calls Networking.makeGetRequest. If it's another class, you would implement the delegate in that class, or you could bubble up the delegate calls to your ViewController through the DataManager class.

How to use block/closure in swift

In one of my app I have used block for webservice calling and getting response. Now I want to write this app in swift, but I am getting trouble to use blocks/Closure in Swift.
Here is my objective C code which I want to migrate in swift:
calling a class method of Communicator
[[Communicator sharedInstance]callWebService:WS_LOGIN withMethod:POST_METHOD andParams:params showLoader:YES completionBlockSuccess:^(id obj) {
//Do play with data
}completionBlockFailiure:^(id obj) {
//Show alert with error
}];
in communicator class
-(void)callWebService:(NSString *)serviceName withMethod:(NSString *)methodName andParams:(NSDictionary *)params showLoader:(BOOL)showLoader completionBlockSuccess:(void (^)(id))aBlock completionBlockFailiure:(void (^)(id))aFailBlock
{
if (showLoader) {
// show loader
}
[self performRequestWithServiceName:serviceName method:methodName andParams:params successblock:aBlock failureblock:aFailBlock];
}
- (void)performRequestWithServiceName:(NSString *)serviceName method:(NSString*)methodName andParams:(NSDictionary*)params
successblock:(void (^)(id obj))successBlock
failureblock:(void (^)(id obj))failBlock {
if(callSuceess){
successBlock(#"Success");
}else{
successBlock(nil);
}
}
For Swift. Use AnyObject for id objc type.
func callWebservice (serviceName: String, withMethod method: String, andParams params: NSDictionary, showLoader loader: Bool, completionBlockSuccess aBlock: ((AnyObject) -> Void), andFailureBlock failBlock: ((AnyObject) -> Void)) {
if loader {
// Show loader
}
performRequestWithServiceName(serviceName, method: method, andParams: params, success: aBlock, failure: failBlock)
}
func performRequestWithServiceName(serviceName: String, method methodName: String, andParams params: NSDictionary, success successBlock: ((AnyObject) -> Void), failure failureBlock: ((AnyObject) -> Void)) {
if callSuceess {
successBlock("Success")
}else {
successBlock(nil)
}
}
UPDATE: An example when you want call web service. See code below
callWebservice("your-service-name", withMethod: "your-method", andParams: ["your-dic-key": "your dict value"], showLoader: true/*or false*/, completionBlockSuccess: { (success) -> Void in
// your successful handle
}) { (failure) -> Void in
// your failure handle
}
Your code might look like this:
func callWebService(serviceName: String, method: String, params: [String : AnyObject], showLoader: Bool, success: (responseObject: AnyObject) -> Void, failure: (responseObject: AnyObject) -> Void) {
if showLoader {
// show loader
}
performRequest(serviceName, method: method, params: params, success: success, failure: failure)
}
func performRequest(serviceName: String, method: String, params: [String : AnyObject], success: (responseObject: AnyObject) -> Void, failure: (responseObject: AnyObject) -> Void) {
}
I replaced NSDictionary with [String : AnyObject]. If you can replace any of the uses of AnyObject with more specific types, your code will be cleaner and more stable.
For Swift Closures we have to use ( ) -> ( )
For example:
func yourFunction(success: (response: AnyObject!) -> Void, failure: (error: NSError?) -> Void) {
}
You can call it as:
yourFunction({(response) -> Void in
// Success
}) { (error) -> Void in
// Handle Errors
}
Hope it will help you to create Closures with your requirements.
In the communicator class the method that cals the webservice would be defined something like this depending on the type of object you want to return
func performRequest(serviceName: NSString, methodName: NSString,paramaters:NSDictionary, successblock: (String)->(), failureBlock: () -> ()) {
if(callSuccess) {
successblock("Success")
} else {
failureBlock()
}
We define the success and failure blocks types as by their function signatures in the case above success is defined as a method that takes a string as an input parameter and returns nothing so we can then call successBlock passing in a string. The failure block is defined above as a block that takes no parameters and returns nothing.
To call this method
func callWebService(serviceName: NSString, method: NSString and parameters: NSDictionary, showLoader: Bool, completionBlockSuccess:(String) -> (), completionBlockFailiure:() -> ()) {
if (showLoader) {
// show loader
}
performRequest(serviceName: serviceName, methodName: method, parameters, successBlock:completionBlockSuccess, failureBlock: completionBlockFailiure)
}
Finally to call this
Communicator.sharedInstance().callWebService(serviceName: WS_LOGIN , method: POST_METHOD and parameters: params, showLoader: true, completionBlockSuccess:{ returnedString in
//Do play with data
}, completionBlockFailiure:{
//Show alert with error
})
For the completion block we define a variable returnedString to allow us to manipulate that input parameter (in the above example it would be the string "Success"). I assume your data is not just returning a string though so you will probably need to play around with they type depending on what your service returns.
Also here I tried to match your method signatures by using NSString and NSDictionary though depending on your needs the Swift equivalents String and [String: AnyObject] could be more appropriate.
func processingWithAnyObject(input: String, completion: #escaping (_ result: AnyObject) -> Void) {
...
completion(response.result.value! as AnyObject)
}
processingWithAnyObject("inputString") {
(result: AnyObject) in
print("back to caller: \(result)")
}

How Alamofire could guarantee response method would be called after get all the response?

Recently I read the source code of Alamofire, and I am really confused about How could Alamofire guarantee the response method would be called in correctly order. I hope someone(maybe matt lol) could help me out.
Example, an easy GET Request like this
Alamofire.request(.GET, "https://api.github.com/users/octocat/received_events")
After I analysed the work flow of it, I posted my understanding
Create the request and underlying NSURLSession.
public func request(method: Method, URLString: URLStringConvertible, parameters: [String: AnyObject]? = nil, encoding: ParameterEncoding = .URL) -> Request {
return Manager.sharedInstance.request(method, URLString, parameters: parameters, encoding: encoding)
}
This method would create a request: Request object, which would contain the underlying NSURLSessionDataTask object. Manager.sharedInstance has already set up a NSURLSession and set itself as that session's delegate. The Manager.sharedInstance object would save a customized object request.delegate in its own property delegate
After those objects were created, Alamofire would send this request immediately.
public func request(URLRequest: URLRequestConvertible) -> Request {
var dataTask: NSURLSessionDataTask?
dispatch_sync(queue) {
dataTask = self.session.dataTaskWithRequest(URLRequest.URLRequest)
}
let request = Request(session: session, task: dataTask!)
delegate[request.delegate.task] = request.delegate
if startRequestsImmediately {
request.resume()
}
return request
}
Since Manager.sharedInstance set itself as the underlying NSURLSession's delegate, when data received, the delegate methods would be called
public func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveData data: NSData) {
if dataTaskDidReceiveData != nil {
dataTaskDidReceiveData!(session, dataTask, data)
} else if let delegate = self[dataTask] as? Request.DataTaskDelegate {
delegate.URLSession(session, dataTask: dataTask, didReceiveData: data)
}
}
If a user want to get the response and do something related, he would use following public API
// Here the request is a Request object
self.request?.responseString { (request, response, body, error) in
// Something do with the response
}
let's see what the responseString(_: completionHandler:) method does
public func response(queue: dispatch_queue_t? = nil, serializer: Serializer, completionHandler: (NSURLRequest, NSHTTPURLResponse?, AnyObject?, NSError?) -> Void) -> Self {
delegate.queue.addOperationWithBlock {
let (responseObject: AnyObject?, serializationError: NSError?) = serializer(self.request, self.response, self.delegate.data)
dispatch_async(queue ?? dispatch_get_main_queue()) {
completionHandler(self.request, self.response, responseObject, self.delegate.error ?? serializationError)
}
}
return self
}
My question is how 5 could be guaranteed to happen after 3, so the user could get all the response not part of it, because self.response at this time would be fully loaded.
Is it because of NSURLSession's background processing occurs on the same queue -- delegate.queue, which is created like this in Alamofire:
//class Request.TaskDelegate: NSObject, NSURLSessionTaskDelegate
self.queue = {
let operationQueue = NSOperationQueue()
operationQueue.maxConcurrentOperationCount = 1
operationQueue.suspended = true
if operationQueue.respondsToSelector("qualityOfService") {
operationQueue.qualityOfService = NSQualityOfService.Utility
}
return operationQueue
}()
Is my understanding correct, how does that happen? It might be require some understanding about RunLoop and NSURLSession's thread mechanism, if you could point out where I could refer to, thank you as well.

Resources