Losing api response when delegate is changed - ios

I am facing an issue using the Protocol in my app, I have created a control to manage api calls, and return the response to the delegate view controller. but my concern is for example:
I was at ViewController1 and requested an api to get admin profile for example, and assign api delegate to self meaning (APIClient.delegate = self) : ViewController1 will receive the response because it implemented the delegate of api client to have the response back
Now before the response comes back of get admin profile. i went to ViewController2 and assigned the APIClient.delegate = self and requested another api call here. Now the response of get admin profile came but it will be discard because the delegate is not equal to ViewController1 who did the request or not implementing the method to handle profile response!
Here is some of my code:
#objc protocol APIClientDelegate : NSObjectProtocol {
#objc optional func getAdminProfileFinishedWithResponse(_ dictionary:NSMutableDictionary)
#objc optional func getAdminProfileFailedWithError(_ error:NSError)
}
#objc class APIClient: APIClientDelegate {
weak var delegate : APIClientDelegate?
func getAdminProfile(_ postDictionary:NSMutableDictionary){
self.get(getUserProfilePath, parameters: postDictionary, progress: nil, success: { (task, response) in
if self.delegate != nil && self.delegate!.responds(to: #selector(APIClientDelegate.getAdminProfileFinishedWithResponse(_:))){
self.delegate!.getAdminProfileFinishedWithResponse!((response as! NSDictionary ).mutableCopy() as! NSMutableDictionary)
}
}) { (task, error) in
if self.delegate != nil && self.delegate!.responds(to: #selector(APIClientDelegate.getAdminProfileFailedWithError(_:))){
self.delegate!.getAdminProfileFailedWithError!(error as NSError)
}
}
}
}
If you get my point, if ViewController1 requested an api to be sure that the response doesn't get lost if the delegate is changed. Any idea of how to solve it dynamically, in background..etc?

To achieve what you want, you have create an object of object of APIClient each time you make an API call.
e.g.
let objClient = APIClient()
objClient.delegate = self
objClient.getAdminProfile(.....
by this way, the reference of each call will be contained and API calls won't overlap.

Related

What exactly happens when you assign self to delegate?

I'm new to Swift and I'm having a hard time understanding the purpose of assigning self to a delegate. Part of the difficulty stems from the fact that delegate seems to be used in two different ways.
First is as means to send messages from one class to another when a specific event happens, almost like state management. Second is to enable "a class or structure to hand off (or delegate) some of its responsibilities to an instance of another type," as stated in documentation. I have a feeling that these two are fundamentally the same and I'm just not getting it.
protocol PersonProtocol {
func getName() -> String
func getAge() -> Int
}
class Person {
var delegate: PersonProtocol?
func printName() {
if let del = delegate {
print(del.getName())
} else {
print("The delegate property is not set")
}
}
func printAge() {
if let del = delegate {
print(del.getAge())
} else {
print("The delegate property is not set")
}
}
}
class ViewController: UIViewController, PersonProtocol {
var person: Person!
override func viewDidLoad() {
person.delegate = self
person.printAge()
person.printName()
}
func getAge() -> Int {
print("view controller")
return 99
}
func getName() -> String {
return "Some name"
}
}
What is the purpose of person.delegate = self in this case? Isn't ViewController already required to conform to PersonProtocol without it?
I have a feeling that these two are fundamentally the same
The first is a special case of the second. "send messages from one class to another" is just a specific way of "handing off some of its responsibilities". The "messages" are the "responsibilities"
What is the purpose of person.delegate = self in this case?
Here, person delegates (i.e. hands off) some of its responsibilities to another object. It does this by sending messages to another object. First, it needs to identify which objects it can delegate these responsibilities to. This is achieved by requiring that its delegate conform to PersonProtocol, as PersonProtocol defines the messages that Person is going to send.
Next, person needs to know exactly which object it should send these messages to. This is what person.delegate = self does. Remember that person doesn't know anything about your ViewController until this point. Instead of = self, you could say:
person.delegate = SomeOtherClassThatConformsToPersonProtocol()
and person will send its messages to that object instead, and the methods in your ViewController won't be called.
Isn't ViewController already required to conform to PersonProtocol without it?
Correct, but without it, person doesn't know which object it should send its messages to, and as a result, the methods in your ViewController won't be called.
Note that the delegate property should be declared as weak to avoid retain cycles. When you do person.delegate = self, you get a retain cycle: self has a strong reference to person, person also has a strong reference to self via the delegate property.
If you notice inside your Person class, delegate is nil. If you don't execute person.delegate = self, delegate will remain nil.
In other words, assigning ViewController to person.delegate allows Person to identify who the delegate is (i.e., have a reference to ViewController), and that way you can successfully execute statements like delegate?.getName() or delegate?.getAge() from the Person class.
that means Person is not able to getName() and getAge() so Person class delegate that to other DataSource.
Lets say the your view controller has a data source class PersonDataSource which deal with API to get this information So
class PersonDataSource: PersonProtocol {
func getAge() -> Int {
print("view controller")
return 99
}
func getName() -> String {
return "Some name"
}
}
so the view controller will looks like this
class ViewController: UIViewController {
var person: Person!
var personDataSource = PersonDataSource()
override func viewDidLoad() {
person.delegate = personDataSource
person.printAge()
person.printName()
}
}

Best practice to change current view from a class

I'm working with Swift 3 and I'd like to change my view from a function in my class when login succeed.
I've got a LoginViewController which contains this function:
static let sharedInstance = LoginViewController()
//...
func showNextView() {
let storyboard = UIStoryboard(name: "Main", bundle: Bundle.main)
guard let eventVC = storyboard.instantiateViewController(
withIdentifier: "EventsTVC") as? EventsTableViewController else {
assert(false, "Misnamed view controller")
return
}
self.present(eventVC, animated: false, completion: nil)
}
In my class APIManager, I call this function inside my asynchronous method using Alamofire:
func processAuth(_ data: [String: String]) {
print("-- auth process started")
//... defining vars
Alamofire.request(tokenPath, method: .post, parameters: tokenParams, encoding: JSONEncoding.default, headers: tokenHeader)
.responseJSON { response in
guard response.result.error == nil else {
print(response.result.error!)
return
}
guard let value = response.result.value else {
print("No string received in response when swapping data for token")
return
}
guard let result = value as? [String: Any] else {
print("No data received or data not JSON")
return
}
// -- HERE IS MY CALL
LoginViewController.sharedInstance.showNextView()
print("-- auth process ended")
}
}
My console returns this error message:
-- auth process started 2017-03-18 20:38:14.078043 Warning: Attempt to present on
whose view is not in the window
hierarchy!
-- auth process ended
I think it's not the best practice to change my view when my asynchronous method has ended.
I don't know what I've got to do. Currently, this is the process:
User opens the app and the LoginViewController is displayed, if no token is saved (Facebook Login)
In the case where it has to login, a button "Login with Facebook" is displayed
When login succeed, I send Facebook data in my processAuth() function in my APIManager class
When my API returns me the token, I saved it and change the view to EventsTVC
I put in bold where the problem is. And I would like to know if it's the best practice in my case. If so, how to avoid my error message?
I hope I made myself understood. Thanks for your help!
What actually happens is that your singleton instance LoginViewController wants to present itself while not being in the view hierarchy. Let me explain it thoroughly:
class LoginViewController: UIViewController {
static let sharedInstance = LoginViewController()
func showNextView() {
...
// presentation call
self.present(eventVC, animated: false, completion: nil)
}
In this function you are calling present() from your singleton instance on itself. You have to call it from a view which is (preferably) on top of the view hierarchy stack. The solution would probably be not using a singleton on a VC in the first place. You should be instantiating and presenting it from the VC that is currently on the screen. Hope this helps!

Proper way to set up a protocol in Swift 3

I have a view controller that will show images from Flickr. I put Flickr API request methods in a separate class "FlickrAPIRequests", and now I need to update the image in the view controller when I get the data.
I choose to go with that using a protocol to eliminate the coupling of the two classes. How would I achieve that?
You could define a protocol and set up your FlickrAPIRequests class to take a delegate. I suggest another approach.
Set up your FlickrAPIRequests to have a method that takes a completion handler. It might look like this:
func downloadFileAtURL(_ url: URL, completion: #escaping DataClosure)
The FlickrAPIRequests function would take a URL to the file to download, as well as a block of code to execute once the file has downloaded.
You might use that function like this (In this example the class is called DownloadManager)
DownloadManager.downloadManager.downloadFileAtURL(
url,
//This is the code to execute when the data is available
//(or the network request fails)
completion: {
[weak self] //Create a capture group for self to avoid a retain cycle.
data, error in
//If self is not nil, unwrap it as "strongSelf". If self IS nil, bail out.
guard let strongSelf = self else {
return
}
if let error = error {
print("download failed. message = \(error.localizedDescription)")
strongSelf.downloadingInProgress = false
return
}
guard let data = data else {
print("Data is nil!")
strongSelf.downloadingInProgress = false
return
}
guard let image = UIImage(data: data) else {
print("Unable to load image from data")
strongSelf.downloadingInProgress = false
return
}
//Install the newly downloaded image into the image view.
strongSelf.imageView.image = image
}
)
I have a sample project on Github called Asyc_demo (link) that has uses a simple Download Manager as I've outlined above.
Using a completion handler lets you put the code that handles the completed download right in the call to start the download, and that code has access to the current scope so it can know where to put the image data once it's been downloaded.
With the delegation pattern you have to set up state in your view controller so that it remembers the downloads that it has in progress and knows what to do with them once they are complete.
Write a protocol like this one, or similar:
protocol FlickrImageDelegate: class {
func displayDownloadedImage(flickrImage: UIImage)
}
Your view controller should conform to that protocol, aka use protocol method(s) (there can be optional methods) like this:
class ViewController:UIViewController, FlickrImageDelegate {
displayDownloadedImage(flickrImage: UIImage) {
//handle image
}
}
Then in FlickrAPIRequests, you need to have a delegate property like this:
weak var flickrDelegate: FlickrImageDelegate? = nil
This is used in view controller when instantiating FlickrAPIRequests, set its instance flickrDelegate property to view controller, and in image downloading method,when you download the image, you call this:
self.flickrDelegate.displayDownloadedImage(flickrImage: downloadedImage)
You might consider using callback blocks (closures) in FlickrAPIRequests, and after you chew that up, look into FRP, promises etc :)
Hope this helps
I followed silentBob answer, and it was great except that it didn't work for me. I needed to set FlickrAPIRequests class as a singleton, so here is what I did:
protocol FlickrAPIRequestDelegate{
func showFlickrPhoto(photo: UIImage) }
class FlickrAPIRequests{
private static let _instance = FlickrAPIRequests()
static var Instance: FlickrAPIRequests{
return _instance
}
var flickrDelegate: FlickrAPIRequestDelegate? = nil
// Make the Flickr API call
func flickrAPIRequest(_ params: [String: AnyObject], page: Int){
Then in the view controller when I press the search button:
// Set flickrDelegate protocol to self
FlickrAPIRequests.Instance.flickrDelegate = self
// Call the method for api requests
FlickrAPIRequests.Instance.flickrAPIRequest(paramsDictionary as [String : AnyObject], page: 0)
And Here is the view controller's extension to conform to the protocol:
extension FlickrViewController: FlickrAPIRequestDelegate{
func showFlickrPhoto(photo: UIImage){
self.imgView.image = photo
self.prepFiltersAndViews()
self.dismissAlert()
}
}
When the api method returns a photo I call the protocol method in the main thread:
// Perform this code in the main UI
DispatchQueue.main.async { [unowned self] in
let img = UIImage(data: photoData as Data)
self.flickrDelegate?.showFlickrPhoto(photo: img!)
self.flickrDelegate?.setPhotoTitle(photoTitle: photoTitle)
}

Swift URLSession completion Handler

I am trying to make an API call in my Swift project. I just started implementing it and i am trying to return a Swift Dictionary from the call.
But I think i am doing something wrong with the completion handler!
I am not able to get the returning values out of my API call.
import UIKit
import WebKit
import SafariServices
import Foundation
var backendURLs = [String : String]()
class ViewController: UIViewController, WKNavigationDelegate, WKUIDelegate {
#IBOutlet var containerView : UIView! = nil
var webView: WKWebView!
override func viewDidLoad() {
super.viewDidLoad()
self.getBackendURLs { json in
backendURLs = self.extractJSON(JSON: json)
print(backendURLs)
}
print(backendURLs)
}
func getBackendURLs(completion: #escaping (NSArray) -> ()) {
let backend = URL(string: "http://example.com")
var json: NSArray!
let task = URLSession.shared.dataTask(with: backend! as URL) { data, response, error in
guard let data = data, error == nil else { return }
do {
json = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? NSArray
completion(json)
} catch {
#if DEBUG
print("Backend API call failed")
#endif
}
}
task.resume()
}
func extractJSON(JSON : NSArray) -> [String : String] {
var URLs = [String : String]()
for i in (0...JSON.count-1) {
if let item = JSON[i] as? [String: String] {
URLs[item["Name"]! ] = item["URL"]!
}
}
return URLs
}
}
The first print() statements gives me the correct value, but the second is "nil".
Does anyone have a suggestion on what i am doing wrong?
Technically #lubilis has answered but I couldn't fit this inside a comment so please bear with me.
Here's your viewDidLoad
override func viewDidLoad() {
super.viewDidLoad()
self.getBackendURLs { json in
backendURLs = self.extractJSON(JSON: json)
print(backendURLs)
}
print(backendURLs)
}
What will happen is the following:
viewDidLoad is called, backendURLs is nil
you call getBackendURLs, which starts on another thread in the background somewhere.
immediately after that your code continues to the outer print(backendURLs), which prints nil as backendURLs is still nil because your callback has not been called yet as getBackendURLs is still working on another thread.
At some later point your getBackendURLs finishes retrieving data and parsing and executes this line completion(json)
now your callback is executed with the array and your inner print(backendURLs) is called...and backendURLs now has a value.
To solve your problem you need to refresh your data inside your callback method.
If it is a UITableView you could do a reloadData() call, or maybe write a method that handles updating the UI for you. The important part is that you update the UI inside your callback, because you don't have valid values until then.
Update
In your comments to this answer you say:
i need to access the variable backendURLs right after the completionHandler
To do that you could make a new method:
func performWhateverYouNeedToDoAfterCallbackHasCompleted() {
//Now you know that backendURLs has been updated and can work with them
print(backendURLs)
//do what you must
}
In the callback you then send to your self.getBackendURLs, you invoke that method, and if you want to be sure that it happens on the main thread you do as you have figured out already:
self.getBackendURLs { json in
backendURLs = self.extractJSON(JSON: json)
print(backendURLs)
DispatchQueue.main.async {
self.performWhateverYouNeedToDoAfterCallbackHasCompleted()
}
}
Now your method is called after the callback has completed.
As your getBackendURLs is an asynchronous method you can not know when it has completed and therefore you cannot expect values you get from getBackedURLs to be ready straight after calling getBackendURLs, they are not ready until getBackendURLs has actually finished and is ready to call its callback method.
Hope that makes sense.

iOS Swift: Passing data between views and completion handlers

I have two viewControllers: LoginViewController and NextViewController. Now when the app runs the LoginViewController runs a completion handler that takes care of the authentication with HTTP request, and after it's done, it calls another closure that gets the necessary user data.
I don't know how to pass that data that i get back from the callback function into the NextViewController to display it because I have no way of knowing when the data becomes available as it is running an HTTP request in the background.
So how should I present the data when it becomes available?
I know I can just call the second callback method for getting the user information inside the nextViewController, but that makes the app slower.
Here's example code:
class Methods: NSObject {
//Singleton
class func sharedInstance() -> Methods {
struct Singleton {
static var sharedInstance = Methods()
}
return Singleton.sharedInstance
}
private func GETMethod(callBackMethod: (Success: Bool) -> Void) {
//Do the authentication
}
private func retriveUserData(callBackMethod: (data: String, Success: Bool) -> Void) {
//Gets the data and passes processed data back in a callBackMethod
}
func doAuthentication(callBackMethod: (Success: Bool) -> Void){
GETMethod { (Success) in
if Success {
self.retriveUserData({ (data, Success) in
data // <- HOW DO I GET THIS INTO LOGIN VIEW CONTROLLER?
callBackMethod(Success: true)
})
}
}
}
}
class LoginViewController: UIViewController {
func loginButtonPressed(){
Methods.sharedInstance().doAuthentication { (Success) in
}
}
}
class NextViewController: UIViewController {
//Present data when it becomes available
}
Consider using a MVC pattern, especially the "model" part. Create an object that serves as the shared data model for your application. Update it when you have new data. Depending on the timing of updates vs. controller loading, it can either send notifications when data changes or provide an API that the interested objects (controllers) can query to find out the current state.

Resources