ApplicationWillTerminate - how do i run JS callback? - swift

What I am dealing with: web page + backend (php), swift app (wkwebview + some native components).
What I am trying to achieve: I need to let the web page know my app is changing it's state, by executing some java scripts (one script for "enter background mode", another one for "enter foreground", and so on).
What is my issue: I can't handle the case of app termination. Whenever the user is killing the app by home-double-click/swipe up, ApplicationWillTerminate method from AppDelegate is being called, but it fails to execute webView.evaluateJavaScript from here, as far as I was able to understand - due to the closure / completion handler is has, for it is async by design.
All other cases are covered, works fine, and with the last one for termination I am stuck.
I am playing around with the following code:
func applicationWillTerminate(_ application: UIApplication) {
if let callback = unloadListenerCallback {
if callback == "" {
debugPrint("got null callback - stop listening")
unloadListenerCallback = nil
}
else {
jsCallbackInjection(s: callback) //executing JS callback injection into WebView
}
}
}
unloadListenerCallback is the string? variable, works as a charm in other cases (just to mention it for clarity). jsCallbackInjection is the function, also works perfectly elsewhere:
func jsCallbackInjection(s: String) {
let jscallbackname = s
HomeController.instance.webView?.evaluateJavaScript(jscallbackname) { (result, error) in
if error == nil {
print(result as Any)
print("Executed js callback: \(jscallbackname)")
} else {
print("Error is \(String(describing: error)), js callback is: \(jscallbackname)")
}
}
}
How do I made this code working, if possible? Any ideas, suggestions, tips or tricks? If not possible, any ideas on "how do I let my web counterpart know my app was terminated"? What do I do from here?
This is a cross-post from Apple Dev forum, where it is sitting for >1 week unattended.

Answering my own question:
I was not able to find a way of running JS from ApplicationWillTerminate.
However, I found a way of solving my issue, which is - instead of running JS, I am posting to my web service like that:
func applicationWillTerminate(_ application: UIApplication) {
let semaphore = DispatchSemaphore(value: 0)
//setup your request here - Alamofire in my case
DispatchQueue.global(qos: .background).async {
//make your request here
onComplete: { (response: Update) in
//handle response if needed
semaphore.signal()
},
onFail: {
//handle failure if needed
semaphore.signal()
})
}
semaphore.wait(timeout: .distantFuture)
}
This way, I am able to consistently report my app termination to the web page. I was lucky enough to have ajax already set up on the other end of a pipe, which I am just POSTing into with the simple AF request, so I don't need to struggle with JS anymore.
Basing on what I was able to find, there is NO suitable way of managing JS execution, due to
with semaphores, as soon as webview and it's methods are to be handled in main thread, you'll deadlock by using semaphore.wait()
no way I was able to find to run evaluateJavaScript synchronously
no way I was able to find to run the JS itself from JavaScriptCore, or some other way, within the same session and context
However, if someone still can contribute and provide solution, I'll be happy to accept it!

Related

How do you escape a completionBlock in Swift to return to normal application flow?

One of the things that's throwing me in Swift is the never-ending chain of completionBlocks in my program; and I'm not sure how to get Swift to say "Ok, the completion block is now done -- come back to the main program."
In my project I am writing a simple board/card game which loads its data from a very small plist.
I wrote a fairly simple Plist loader, it loads the plist and kicks back Data via a completionBlock. It doesn't do any parsing; it doesn't care about how you want to parse it, it simply returns NSData (Data) or errors.
I have a Parser which fires the Plist loader, gets the Data and then parses it using the new Swift Codable protocol.
// A static function to try and find the plist file on bundle, load it and pass back data
static func loadBoard(completionHandler: #escaping (Board?, Error?) -> Void) {
PListFileLoader.getDataFrom(filename: "data.plist") { (data, error) in
if (error != nil) {
print ("errors found")
completionHandler(nil, error)
}
else {
guard let hasData = data else {
print ("no data found")
completionHandler(nil, error)
return
}
do {
print ("found board")
let board = try decodeBoard(from: hasData) // Call a function that will use the decoder protocol
completionHandler(board, nil)
} catch {
print ("some other board error occured")
completionHandler(nil, error)
}
}
}
}
and then returns this parsed data to the main program, or whatever called it -- for example; an XCTest
My XCTest:
func testBoardDidLoad() -> Board? { // The return bit will show an error; but its fine without the return part
BoardParsePlist.loadBoard { (board, error) in
XCTAssertNotNil(board, "Board is nil")
XCTAssertNotNil(error, error.debugDescription)
// How do I now escape this and return flow to the normal application?
// Can I wrap this in a try-catch?
}
}
From a hierarchical view it sort of looks like this.
XCTest
... Calls the parser (completionBlock ...)
.... Parser calls the PListLoader (completionHandler: ...)
Now it feels like I'm stuck in the block for the rest of the app
BoardParsePlist.loadBoard { (board, error) in
// ... rest of the app now lives here?
})
It seems I'm in a never ending loop of completionBlocks.
How do you "escape" or break out of the completion block and return flow back to the main app?
I'm not sure if I explained it correctly, but would appreciate any assistance on this.
Thank you for your time.
not sure how to get Swift to say "Ok, the completion block is now done -- come back to the main program."
There's no way and no need to say that - it happens automatically.
static func loadBoard(completionHandler: #escaping (Board?, Error?) -> Void) {
PListFileLoader.getDataFrom(filename: "data.plist") { (data, error) in
// completion handler code here
}
// "back to main"
}
In your example, execution will get "back to main" in one of two ways, depending on whether PListFileLoader.getDataFrom is running asynchronously or not.
If it's running synchronously, the execution order will be:
your app calls loadBoard
this calls getDataFrom
which in turn calls its completion handler parameter
the completion handler runs
"back to main" runs
OTOH, if getDataFrom is asynchronous (e.g. because it does a network request), the order will be:
your app calls loadBoard
this calls getDataFrom
which starts its asynchronous work and returns
"back to main" runs
at some later point in time, the work started by getDataFrom is done, and it calls the completion handler parameter
the completion handler runs
Either way, you get back to main without special effort.

All asynchronous calls succeed or none, how to handle

I'm trying to create an online mobile application and can't figure out the best way to handle functions with multiple asynchronous calls. Say I have a function for example that updates a user in some way, but involved multiple asynchronous calls in the single function call. So for example:
// Function caller
update(myUser) { (updatedUser, error) in
if let error = error {
// Present some error UI to the user
}
if let updatedUser = updatedUser {
// Do something with the user
}
}
// Function implementation
public func updateUser(user: User, completion: #escaping (User?, Error?) -> () {
// asynchronous call A
updateUserTable(user: User) { error in
if let error = error {
completion(nil, error)
} else {
// create some new user object
completion(user, nil)
}
}
// asynchronous call B
uploadMediaForUser(user: User) { error in
if let error = error {
completion(nil, error)
}
}
// asynchronous call C
removeOldReferenceForUser(user: User) { error in
if let error = error {
completion(nil, error)
}
}
// Possibly any additional amount of asynchronous calls...
}
In a case like this, where one function call like updating a user involved multiple asynchronous calls, is this an all or nothing situation? Say for example the updateUserTable() call completes, but the user disconnects from the internet as uploadMediaForUser() was running, and that throws an error. Since updateUserTable() completed fine, my function caller thinks this method succeeded when in fact not everything involved in updating the user completed. Now I'm stuck with a user that might have mismatched references or wrong information in my database because the user's connection dropped mid call.
How do I handle this all or nothing case? If EVERY asynchronous call completed without an error, I know updating the user was a success. If only a partial amount of asynchronous calls succeeded and some failed, this is BAD and I need to either undo the changes that succeeded or attempt the failed methods again.
What do I do in this scenario? And also, and how do I use my completion closure to help identify the actions needed depending on the success or failure of the method. Did all them succeed? Good, tell the user. Do some succeed and some failed? Bad, revert changes or try again (i dont know)??
Edit:
Just calling my completion with the error doesn't seem like enough. Sure the user sees that something failed, but that doesn't help with the application knowing the steps needed to fix the damage where partial changes were made.
I would suggest adding helper enums for your tasks and returned result, things like (User?, Error?) have a small ambiguity of the case when for example both are nil? or you have the User and the Error set, is it a success or not?
Regarding the all succeeded or some failed - I would suggest using the DispatchGroup to notify when all tasks finished (and check how they finished in the end).
Also from you current code, when some request fails it's not clear for which user - as you pass nil, so it might bring difficulties in rolling it back after failure.
So in my point of view something like below (not tested the code, but think you should catch the idea from it) could give you control about the issues you described:
public enum UpdateTask {
case userTable
case mediaUpload
// ... any more tasks you need
}
public enum UpdateResult {
case success
case error([UpdateTask: Error])
}
// Function implementation
public func updateUser(user: User, completion: #escaping (User, UpdateResult) -> ()) {
let updateGroup = DispatchGroup()
var tasksErrors = [UpdateTask: Error]()
// asynchronous call A
updateGroup.enter()
updateUserTable(user: User) { error in
if let error = error {
tasksErrors[.userTable] = error
}
updateGroup.leave()
}
// ... any other similar tasks here
updateGroup.notify(queue: DispatchQueue.global()) { // Choose the Queue that suits your needs here by yourself
if tasksErrors.isEmpty {
completion(user, .success)
} else {
completion(user, .error(tasksErrors))
}
}
}
Keep a “previous” version of everything changed, then if something failed revert back to the “previous” versions. Only change UI once all returned without failure, and if one failed, revert to “previous” version.
EX:
var temporary = “userName”
getChanges(fromUser) {
If error {
userName = temporary //This reverts back due to failure.
}
}

Realm notifications registration while in write transaction

I understand that you can not register a Realm .observe block on an object or collection if the Realm is in a write transaction.
This is easier to manage if everything is happening on the main thread however I run into this exception often because I prefer to hand my JSON parsing off to a background thread. This works great because I don't have to bog down the main thread and with Realm's beautiful notification system I can get notified of all modifications if I have already registered to listen for those changes.
Right now, if I am about to add an observation block I check to make sure my Realm is not in a write transaction like this:
guard let realm = try? Realm(), !realm.isInWriteTransaction else {
return
}
self.myToken = myRealmObject.observe({ [weak self] (change) in
//Do what ever
}
This successfully guards against this exception. However I never get a chance to re - register this token unless I get a little creative.
Does the Realm team have any code examples/ suggestions on a better pattern to avoid this exception? Any tricks I'm missing to successfully register the token?
In addition to the standard function, I do use an extension for Results to avoid this in general. This issue popped up, when our data load grew bigger and bigger.
While we do now rewrite our observe functions logic, this extension is an interims solution to avoid the crashes at a first place.
Idea is simple: when currently in a write transaction, try it again.
import Foundation
import RealmSwift
extension Results {
public func safeObserve(on queue: DispatchQueue? = nil,
_ block: #escaping (RealmSwift.RealmCollectionChange<RealmSwift.Results<Element>>) -> Void)
-> RealmSwift.NotificationToken {
// If in Write transaction, call it again
if self.realm?.isInWriteTransaction ?? false {
DispatchQueue.global().sync {
Thread.sleep(forTimeInterval: 0.1) // Better to have some delay than a crash, hm?
}
return safeObserve(on: queue, block)
}
// Aight, we can proceed to call Realms Observe function
else {
return self.observe(on: queue, block)
}
}
}
Then call it like
realmResult.safeObserve({ [weak self] (_: RealmCollectionChange<Results<AbaPOI>>) in
// Do anything
})

How to test asynchronous method results?

When we get table view datasource, we will ask a network request. It is asynchronous. I have no idea to test the result operation. There is a method to get the points.
func loadPoints() {
API().retrievePoints{ [weak self](pointsSet, error) in
DispatchQueue.main.async(execute: {
// Make sure the call succeeded; return an error if it didn't
guard error == nil else {
self?.showErrorMessage()
Device.debugLog(item:"Error loading Points: \(String(describing: error))")
return
}
self?.pointsSet = pointsSet
self?.tableView.reloadData()
})
}
}
I have known that if we want to test the retrievePoints method, we can test like bellow
//points
func testRetrievePoints() {
let expectation = self.expectation(description: "RetrievePoints")
API().retrievePoints{ (pointsSet, error) -> Void in
XCTAssertNil(pointsSet)
XCTAssertNotNil(error)
expectation.fulfill()
}
waitForExpectations(timeout: 15.0, handler: nil)
}
Now I want to test the codes
self?.pointsSet = pointsSet
self?.tableView.reloadData()
self?.showErrorMessage()
For now I just use sleep(15) to wait the method. But it is inaccurate.
Could you please help me? Thanks in advance.
Just as what you have said, it's asynchronous. So it will take time before finish. That is to say that you need to wait before it can success.
Also note that it's just timeout value. All your task must finish within this value. Or it will be treated as failure.
You want to test your data source — not your web service.
there for you should mock the api call.
To achieve this you could use a mocking framework. But I'd rather go another route:
create a protocol that declares the public interface of API, let API conform to that protocol
pass the API as a dependency into the data source. either as an init parameter or via a property. Passing objects is easier that classes, I'd change retrievePoints to be an instance method.
For your test write an APIMock that implements the protocol. Let retrievePoints' callback return prepared points.
Now the points will be returned immediately, no need for timeouts. If you want to defer that your mock can use a DispatchQueue.main.asyncAfter call.

How can I trigger async requests out of view controller

I am building an iOS app and I just finished my login/register part ( requesting a sails.js rest Api)
At the moment I have 2 view controllers with duplicate code because i issue the rest calls on register/login button event listener of each class and there is a lot of similar code I can refactor.
What I want to do is to create a singleton called ApiManager that will contain all the calls that I need. (And the futur ones )
The problem is that with async calls I can't create a function func login(username,password) that will return data so I can store them and prepareforsegue.
What is the simple/proper way to achieve that correctly? Which means call ApiManager.myFunction and using the result wherever it's needed ( filling a tableview for data, initiating a segue for login or register with succes ) and to make this function reusable in another view controller even if it is for another usage. I am using swift.
EDIT : Here is how i did it so i hope it will help you
The function executing the rest call :
func login(#username: String, password: String, resultCallback: (finalresult: UserModel!,finalerror:String!) -> Void) {
Alamofire.request(.POST, AppConfiguration.ApiConfiguration.apiDomain+"/login", parameters: ["username": username,"password": password], encoding: .JSON)
.responseJSON { request, response, data, error in
if let anError = error
{
resultCallback(finalresult: nil,finalerror:anError.localizedDescription)
}else if(response!.statusCode == 200){
var user:UserModel = self.unserializeAuth(data!)//just processing the json using SwiftyJSON to get a easy to use object.
resultCallback(finalresult: user,finalerror:nil)
}else{
resultCallback(finalresult: nil,finalerror:"Username/Password incorrect!")
}
}.responseString{ (request, response, stringResponse, error) in
// print response as string for debugging, testing, etc.
println(stringResponse)
}
}
And this is how i call this function from my ViewController :
#IBAction func onLoginTapped(sender: AnyObject) {//When my user tap the login button
let username = loginInput.text;//taking the content of inputs
let password = passwordInput.text;
ApiManager.sharedInstance.login(username:username,password:password){
[unowned self] finalresult,finalerror in
if(finalresult !== nil){//if result is not null login is successful and we can now store the user in the singleton
ApiManager.sharedInstance.current_user=finalresult
self.performSegueWithIdentifier("showAfterLogin", sender: nil)//enter the actual app and leave the login process
}else{
self.displayAlert("Error!", message: finalerror)//it is basically launching a popup to the user telling him why it didnt work
}
}
}
Almost all of my apps end up with a Server class which is the only one that knows how to communicate with the server. It makes the call, parses the result into a Swift struct and returns it. Most of my servers return json so I use SwiftyJSON, but you can do whatever you want.
The point is, that since this is the only class that knows about server communication, if I need to change the library being used to do the communication (AFNetworking 1 vs 2 vs Parse, vs whatever) this is the only class I need to touch.
class Server {
static let instance = Server()
func loginWithUsername(username: String, password: String, resultCallback: (result: Either<User, NSError>) -> Void) {
// if login is successful call
resultCallback(result: .Left(self.user!))
// otherwise call
resultCallback(result: .Right(error))
}
}
An example of use:
let server = Server.instance
SVProgressHUD.showWithStatus("Loggin In...")
server.loginWithUsername(username, password: password) { [unowned self] result in
SVProgressHUD.dismiss()
switch result {
case .Left(let user):
self.presentUserType(user.userType)
case .Right(let error):
self.warnUserWithMessage("An error occured. \(error.localizedDescription)")
}
}
If the username/password are needed for all subsequent calls, then the server object will maintain a copy of them. If the login returns a token, then the server keeps a copy of that.
QED.
I usually have utility functions in a base class shared by my view controllers and use NSNotificationCenter for reacting to the results of the requests. It can also easily be achieved through delegation (protocol & delegate.
It is mostly about perception but I find it is easier to visualize that you can, for example, start an action on one controller and react on another because the call took this long and you were not blocking navigation in your app.