I know almofire works as a thread in the background so I put in my main class
let nc = NSNotificationCenter.defaultCenter()
nc.addObserver(self, selector: "taskdataReadyFunc", name: "taskdataReady", object: nil)
and in my dataClass after the almofire finish:
init() {
Alamofire.request(.GET, urlString, parameters: parameters).responseJSON { response in
if response.result.isSuccess {
let json = JSON(response.result.value!)
let data = json.arrayValue
self.tasks = data
print(self.tasks)
}
// print(self.tasks)
print(String(self.tasks.count)+"before nc")
let nc = NSNotificationCenter.defaultCenter()
nc.postNotificationName("taskdataReady", object: nil)
}
that leads to thet function:
func taskdataReadyFunc (){
tableView.reloadData()
print("reload mision table" + String(taskDataClass.sharedInstance.tasks.count))
}
This worked fine when I start the app for the first time.
my problem is that I made a new task that adds a new task to the database and then tries to run the init of the dataClass again. at that stage, i can see the new task coming from the DB but it not make a table reload data.(if I close and open the app I see it)
how can I refresh my table view or maybe to close and open the controller from the start ?
I'm not sure what you're trying to do with self.tasks = data, but normal Alamofire usage combined with NSNotification would look like this:
request(.GET, urlString, parameters: parameters).responseJSON { response in
guard let json = response.result.value else { return } // handle error
let notification = NSNotification(name: "taskDataReady", object: nil, userInfo: ["taskData" : json])
NSNotificationCenter.defaultCenter().postNotification(notification)
}
Then you would unwrap the JSON value from the notification. I'd actually recommend decoding the JSON into an actual type from within the Alamofire completion handler and encapsulating that within an NSNotification, so you don't have to do additional work in the view controller.
the sharedInstance is create only one time when you first call it.
its called singleTon.
that means that if the init() function fill the variable in the class the sharedInstance will not be update.
so, in the init class, even in the first run update the sharedInstance and not the variable.
example:
//self.tasks = data
taskDataClass.sharedInstance.tasks = data
Related
On macOS unlike iOS, it appears if you want to disable reopening documents at launch, you need to rely on the application delegate notifications vs the newer methods - with an options argument, like on iOS:
applicationWillFinishLaunching(_:), here you want to instantiate your sub-classed document controller
// We need our own to reopen our "document" urls
_ = DocumentController.init()
applicationDidFinishLaunching(_:), here you want to inspect the supplied userInfo
if let info = note.userInfo{
if let launchURL = info[NSApplication.launchIsDefaultUserInfoKey] as? String {
Swift.print("launchIsDefaultUserInfoKey: notif \(launchURL)")
disableDocumentReOpening = launchURL.boolValue
}
if let notif = info[NSApplication.launchUserNotificationUserInfoKey] as? String {
Swift.print("applicationDidFinishLaunching: notif \(notif)")
disableDocumentReOpening = true
}
}
so when my document controller is called to do the doc restores, it would see this flag within the app delegate: var disableDocumentReOpening = false.
func restoreWindow(withIdentifier identifier: NSUserInterfaceItemIdentifier, state: NSCoder, completionHandler: #escaping (NSWindow?, Error?) -> Void) {
if (NSApp.delegate as! AppDelegate).disableDocumentReOpening {
completionHandler(nil, NSError.init(domain: NSCocoaErrorDomain, code: NSUserCancelledError, userInfo: nil) )
}
else
{
NSDocumentController.restoreWindow(withIdentifier: identifier, state: state, completionHandler: completionHandler)
}
}
but unfortunately, I have something wrong but what? Manually launching the app in debugger, it appears the document controller restore call is ahead of the app delegate's routine to inspect the userInfo.
I had read a SO post on this, showing an objective-c code snippet, but flag was local to the controller, but didn't understand how you could have a read/write class var - as I tried. Also didn't understand its use of a class function as doc says its an instance method, but trying that as well didn't work either.
What am I missing?
I'm getting info from an API using the following function where I pass in a string of a word. Sometimes the word doesn't available in the API if it doesn't available I generate a new word and try that one.
The problem is because this is an asynchronous function when I launch the page where the value from the API appears it is sometimes empty because the function is still running in the background trying to generate a word that exists in the API.
How can I make sure the page launches only when the data been received from the api ?
static func wordDefin (word : String, completion: #escaping (_ def: String )->(String)) {
let wordEncoded = word.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)
let uri = URL(string:"https://dictapi.lexicala.com/search?source=global&language=he&morph=false&text=" + wordEncoded! )
if let unwrappedURL = uri {
var request = URLRequest(url: unwrappedURL);request.addValue("Basic bmV0YXlhbWluOk5ldGF5YW1pbjg5Kg==", forHTTPHeaderField: "Authorization")
let dataTask = URLSession.shared.dataTask(with: request) { (data, response, error) in
do {
if let data = data {
let decoder = JSONDecoder()
let empty = try decoder.decode(Empty.self, from: data)
if (empty.results?.isEmpty)!{
print("oops looks like the word :" + word)
game.wordsList.removeAll(where: { ($0) == game.word })
game.floffWords.removeAll(where: { ($0) == game.word })
helper.newGame()
} else {
let definition = empty.results?[0].senses?[0].definition
_ = completion(definition ?? "test")
return
}
}
}
catch {
print("connection")
print(error)
}
}
dataTask.resume()
}
}
You can't stop a view controller from "launching" itself (except not to push/present/show it at all). Once you push/present/show it, its lifecycle cannot—and should not—be stopped. Therefore, it's your responsibility to load the appropriate UI for the "loading state", which may be a blank view controller with a loading spinner. You can do this however you want, including loading the full UI with .isHidden = true set for all view objects. The idea is to do as much pre-loading of the UI as possible while the database is working in the background so that when the data is ready, you can display the full UI with as little work as possible.
What I'd suggest is after you've loaded the UI in its "loading" configuration, download the data as the final step in your flow and use a completion handler to finish the task:
override func viewDidLoad() {
super.viewDidLoad()
loadData { (result) in
// load full UI
}
}
Your data method may look something like this:
private func loadData(completion: #escaping (_ result: Result) -> Void) {
...
}
EDIT
Consider creating a data manager that operates along the following lines. Because the data manager is a class (a reference type), when you pass it forward to other view controllers, they all point to the same instance of the manager. Therefore, changes that any of the view controllers make to it are seen by the other view controllers. That means when you push a new view controller and it's time to update a label, access it from the data property. And if it's not ready, wait for the data manager to notify the view controller when it is ready.
class GameDataManager {
// stores game properties
// updates game properties
// does all thing game data
var score = 0
var word: String?
}
class MainViewController: UIViewController {
let data = GameDataManager()
override func viewDidLoad() {
super.viewDidLoad()
// when you push to another view controller, point it to the data manager
let someVC = SomeOtherViewController()
someVC.data = data
}
}
class SomeOtherViewController: UIViewController {
var data: GameDataManager?
override func viewDidLoad() {
super.viewDidLoad()
if let word = data?.word {
print(word)
}
}
}
class AnyViewController: UIViewController {
var data: GameDataManager?
}
First of all, I am just a beginner who is currently developing an app with the Swift language, so please don't mind my question too much because I really need to know and I am having trouble with maintaining the code that I constructed.
It's about the async delegate pattern.
Here is my API class. Assume that there are many API classes like that which makes async calls.
protocol InitiateAPIProtocol{
func didSuccessInitiate(results:JSON)
func didFailInitiate(err:NSError)
}
class InitiateAPI{
var delegate : InitiateAPIProtocol
init(delegate: InitiateAPIProtocol){
self.delegate=delegate
}
func post(wsdlURL:String,action:String,soapMessage : String){
let request = NSMutableURLRequest(URL: NSURL(string: wsdlURL)!)
let msgLength = String(soapMessage.characters.count)
let data = soapMessage.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)
request.HTTPMethod = "POST"
request.addValue("text/xml; charset=utf-8", forHTTPHeaderField: "Content-Type")
request.addValue(msgLength, forHTTPHeaderField: "Content-Length")
request.addValue(action, forHTTPHeaderField: "SOAPAction")
request.HTTPBody = data
let task = session.dataTaskWithRequest(request) {
data, response, error in
if error != nil {
self.delegate.didFailInitiate(error!)
return
}
let jsonData = JSON(data: data)
self.delegate.didSuccessInitiate(jsonData)
}
task.resume()
}
func doInitiate(token : String){
let soapMessage = “”
// WSDL_URL is the main wsdl url i will request.
action = “”
post(WSDL_URL, action: action, soapMessage: soapMessage)
}
}
Here is my ViewController:
class ViewController : UIViewController,InitiateAPIProtocol{
var initiateAPI : InitiateAPI!
var token : String = “sWAFF1”
override func viewWillAppear(animated: Bool) {
// Async call start
initiateAPI = InitiateAPI(delegate:self)
initiateAPI.doInitiate(token)
}
// Here comes call back
func didSuccessInitiate(results: JSON) {
//handle results
}
func didFailInitiate(err: NSError) {
//handle errors
}
}
My problem is I said that there are many API classes like that, so if one view controller handles 4 API classes, I have to handle many protocol delegates methods as I extend the view controller. There will be many delegates method below of view controller. If other view controllers call the same API and have to handle the same delegates, I have a problem maintaining the code because every time I change some delegate parameters, I have to fix the code at all view controllers which use those API classes.
Is there any other good way to handle async call?
If my question seems a little complex, please leave a comment, I will reply and explain it clearly.
Delegates (OOP) and "completion handlers" (function like programming) just don't fit well together.
In order to increase comprehension and to make the code more concise, an alternative approach is required. One of this approach has been already proposed by #PEEJWEEJ using solely completion handlers.
Another approach is using "Futures or Promises". These greatly extend the idea of completion handlers and make your asynchronous code look more like synchronous.
Futures work basically as follows. Suppose, you have an API function that fetches users from a remote web service. This call is asynchronous.
// Given a user ID, fetch a user:
func fetchUser(id: Int) -> Future<User> {
let promise = Promise<User>()
// a) invoke the asynchronous operation.
// b) when it finished, complete the promise accordingly:
doFetchAsync(id, completion: {(user, error) in
if error == nil {
promise.fulfill(user!)
} else {
promise.reject(error!)
}
})
return.promise.future
}
First, the important fact here is, that there is no completion handler. Instead, the asynchronous function returns you a future. A future represents the eventual result of the underlying operation. When the function fetchUser returns, the result is not yet computed, and the future is in a "pending" state. That is, you cannot obtain the result immediately from the future. So, we have to wait?? - well not really, this will be accomplished similar to an async function with a completion handler, i.e. registering a "continuation":
In order to obtain the result, you register a completion handler:
fetchUser(userId).map { user in
print("User: \(user)")
}.onFailure { error in
print("Error: \(error)")
}
It also handles errors, if they occur.
The function map is the one that registered the continuation. It is also a "combinator", that is it returns another future which you can combine with other functions and compose more complex operations.
When the future gets finally completed, the code continues with the closure registered with the future.
If you have two dependent operations, say OP1 generates a result which should be used in OP2 as input, and the combined result should be returned (as a future), you can accomplish this in a comprehensive and concise manner:
let imageFuture = fetchUser(userId).flatMap { user in
return user.fetchProfileImage()
}
imageFuture.onSuccess { image in
// Update table view on main thread:
...
}
This was just a very short intro into futures. They can do much more for you.
If you want to see futures in action, you may start the Xcode playgrounds "A Motivating Example" in the third party library FutureLib (I'm the author). You should also examine other Future/Promise libraries, for example BrightFutures. Both libraries implement Scala-like futures in Swift.
Have you looked into NSNotificationCenter?
https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSNotificationCenter_Class/
You'll be able to post events from your api class, then each view controller would subscribe to the events and be notified accordingly
Does that make sense? There are lots of good examples of this pattern:
https://stackoverflow.com/a/24049111/2678994
https://stackoverflow.com/a/28269217/2678994
I've updated your code below:
class InitiateAPI{
//
// var delegate : InitiateAPIProtocol
// init(delegate: InitiateAPIProtocol){
// self.delegate=delegate
// }
func post(wsdlURL:String,action:String,soapMessage : String){
let request = NSMutableURLRequest(URL: NSURL(string: wsdlURL)!)
let msgLength = String(soapMessage.characters.count)
let data = soapMessage.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)
request.HTTPMethod = "POST"
request.addValue("text/xml; charset=utf-8", forHTTPHeaderField: "Content-Type")
request.addValue(msgLength, forHTTPHeaderField: "Content-Length")
request.addValue(action, forHTTPHeaderField: "SOAPAction")
request.HTTPBody = data
let task = session.dataTaskWithRequest(request) {
data, response, error in
if error != nil {
// self.delegate.didFailInitiate(error!)
/* Post notification with error */
NSNotificationCenter.defaultCenter().postNotificationName("onHttpError", object: error)
return
}
let jsonData = JSON(data: data)
// self.delegate.didSuccessInitiate(jsonData)
/* Post notification with json body */
NSNotificationCenter.defaultCenter().postNotificationName("onHttpSuccess", object: jsonData)
}
task.resume()
}
func doInitiate(token : String){
let soapMessage = “”
// WSDL_URL is the main wsdl url i will request.
action = “”
post(WSDL_URL, action: action, soapMessage: soapMessage)
}
}
Your view controller class:
class ViewController : UIViewController { //,InitiateAPIProtocol{
var initiateAPI : InitiateAPI!
var token : String = “sWAFF1”
override func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(ViewController.didSuccessInitiate(_:)), name: "onHttpSuccess", object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(ViewController.didFailInitiate(_:)), name: "onHttpError", object: nil)
}
override func viewWillAppear(animated: Bool) {
// Async call start
initiateAPI = InitiateAPI(delegate:self)
initiateAPI.doInitiate(token)
}
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)
/* Remove listeners when view controller disappears */
NSNotificationCenter.defaultCenter().removeObserver(self, name: "onHttpSuccess", object: nil)
NSNotificationCenter.defaultCenter().removeObserver(self, name: "onHttpError", object: nil)
}
// Here comes call back
func didSuccessInitiate(notification : NSNotification) { //results: JSON) {
if let payload = notification.object as? JSON {
//handle results
}
}
func didFailInitiate(notification : NSNotification) { //err: NSError) {
if let payload = notification.object as? NSError {
//handle errors
}
}
}
Instead of using a delegate, you could (should?) use closers/functions:
func post(/*any other variables*/ successCompletion: (JSON) -> (), errorCompletion: (NSError) ->()){
/* do whatever you need to*/
/*if succeeds*/
successCompletion("")
/*if fails*/
errorCompletion(error)
}
// example using closures
post({ (data) in
/* handle Success*/
}) { (error) in
/* handle error */
}
// example using functions
post(handleData, errorCompletion: handleError)
func handleData(data: JSON) {
}
func handleError(error: NSError) {
}
This would also give you the option to handle all the errors with one function.
Also, it's ideal to parse your JSON into their desired objects before returning them. This keeps your ViewControllers clean and makes it clear where the parsing will occur.
I'm using Alamofire for network request. When I load a new viewController I make a new request in ViewDidAppear to get example url to images ect. When I make the request in ViewDidAppear there is a delay before the data appear, I also tried in ViewDidLoad the request was a little bit faster, but you can stil see the data appear after a small delay. It is okay that when a user access the viewController first time the user will see the data is loading, but is there a way to keep the data so that when a user navigate away from the controller, example when a user go back from a push in a navigationController and then navigate forward again without making the request to get the data again?
Here is one of my request in ViewDidAppear.
Hope you guys can help - Thank you
override func viewDidAppear(animated: Bool) {
navigationItem.backBarButtonItem = UIBarButtonItem(title: "", style: .Plain, target: nil, action: nil)
var parameters = [String: AnyObject]()
if(self.MySelf) {
parameters = ["userId": LoginViewController.CurrentUser.UserID as AnyObject]
}
else {
parameters = ["userId": self.UserID as AnyObject]
}
//GET posts
Alamofire.request(.POST, Config.ApiURL + "getUserPosts?token=" + LoginViewController.CurrentUser.Token, parameters: parameters as! [String : AnyObject]).responseJSON{ response in
print(response)
switch response.result {
case .Success(let data):
let json = JSON(data)
if let posts = json["post"]["data"].array {
self.postArray = posts
self.postArray = self.postArray.reverse()
self.navigationItem.title = json["user"]["firstname"].string! + " " + json["user"]["lastname"].string!
self.User = json["user"]
self.UserPic = self.User["photourl"].string!
}
else {
print("Array is empty")
}
case .Failure(let error):
print("Request failed with error: \(error)")
}
self.ProfilePostColView?.reloadData()
}
}
Try making the network request in an earlier view controller - say a loading screen and then pass the response to this view controller.
Alternatively you could store the response in a cache service of sorts - when the user navigates back to this view controller you could check it already in the cache if so load it up to the view if not call the request.
Also making the network request in viewDidLoad will be faster as it called before viewDidAppear - but keep in mind viewDidLoad is only called once for a specific instance of a viewController where as viewDidAppear is called every time that instance is displayed again (eg. if it as the bottom of the navigation stack and the user presses back to it).
Keep the user in mind - you do not want to be chewing up their data so if you know the request will have the same response you only want to make the http request once.
Hi i have the following method:
func getAlamoPlayers() ->[Player]{
//Get TeamID
let prefs:NSUserDefaults = NSUserDefaults.standardUserDefaults()
var TeamId:String = prefs.stringForKey("TEAMID")!
//create parameters for POST
let parameters =
["IdTeam": TeamId]
var result: NSArray?
//Request
Alamofire.request(.POST, "XXXXXX.php", parameters: parameters)
.responseJSON { (request, response, json, error) -> Void in
result = (json as NSArray)
// Make models from Json data
for (var i = 0; i < result?.count; i++) {
let dic: NSDictionary = result![i] as NSDictionary
//Create a team object
var p:Player = Player()
//Assign the values of each key value pair to the team object
p.PlayerID = dic["IdPlayer"] as String
p.PlayerName = dic["name"] as String
p.PlayerFkToTeam = dic["teams_IdTeam"] as String
//Add the team to the player array
self.players.append(p)
}
}
return self.players
}
So when i call this method in another class the players array is empty! How can I handle the asynchronous request of alamofire? I think the method returns the player object before the request is finished, or am I wrong?
Thanks in advance!
Here is a question that asks basically the same thing you are.
AlamoFire GET api request not working as expected
So basically what is happening is that your post request is being called in a background queue. So think of it as calling your post then continuing on to the next line. So since it is in a background thread it doesn't set the variable till after your function returns.
You could use a completion handler as explained in detail in the link I provided. Another solution would be to use NSNotificationCenter. Set up an observer and then post a notification inside your POST request.
So for example:
First declare your notification name at the top of the class where you will have your post response function as so. So that other classes can access it
let YOUR_NOTIFICATION_NAME = "NOTIFICATION NAME"
class YOUR_CLASS{
then inside the class write the code you want to execute once the POST is done.
func YOUR_FUNCTION{
...
}
Then before you execute your POST request you want to add an observer.
NSNotificationCenter.defaultCenter().addObserver(self, selector: "YOUR_FUNCTION", name: YOUR_NOTIFICATION_NAME, object: nil)
Then post your notification inside your POST request.
Alamofire.request(.POST, "XXXXXX.php", parameters: parameters)
.responseJSON { (request, response, json, error) -> Void in
...<your code>...
self.players.append(p)
NSNotificationCenter.defaultCenter().postNotificationName(YOUR_NOTIFICATION_NAME, object: self)
}
so i've build it in the following way:
Modell Class with all requests:
let finishedPlayers = "finishedPlayers"
class StrafenkatalogModel: NSObject {
.
.
func getAlamoPlayers()-> [Player] {
//Get TeamID
let prefs:NSUserDefaults = NSUserDefaults.standardUserDefaults()
var TeamId:String = prefs.stringForKey("TEAMID")!
//create parameters for POST
let parameters =
["IdTeam": TeamId]
var result: NSArray?
//Observer
NSNotificationCenter.defaultCenter().addObserver(self, selector: "returnPlayers", name: finishedPlayers, object: nil)
//Request
Alamofire.request(.POST, "http://smodeonline.com/XXXXXXX.php", parameters: parameters)
.responseJSON { (request, response, json, error) -> Void in
result = (json as NSArray)
// Make models from Json data
for (var i = 0; i < result?.count; i++) {
let dic: NSDictionary = result![i] as NSDictionary
//Create a team object
var p:Player = Player()
//Assign the values of each key value pair to the team object
p.PlayerID = dic["IdPlayer"] as String
p.PlayerName = dic["name"] as String
p.PlayerFkToTeam = dic["teams_IdTeam"] as String
//Add the team to the teams array
self.players.append(p)
}
}
return players
}
func returnPlayers() ->[Player]{
return self.players
}
Then i want to call it in the TableViewController class (Class2):
class AddPlayersTableViewController: UITableViewController {
let model:StrafenkatalogModel = StrafenkatalogModel()
var players:[Player] = [Player]()
override func viewDidLoad() {
super.viewDidLoad()
// Get Player objects from the model
NSNotificationCenter.defaultCenter().postNotificationName("finishedPlayers", object: self)
self.players = self.model.returnPlayers()
.
.
}
But the players object is empty. Does you know why? Did i something wrong?