startVPNTunnel not throws errors - ios

I'm calling startVPNTunnel() and then in the PacketTunnelProvider class, I execute the completionBlock with an error.
The call to startVPNTunnel() is inside a try-catch block, but it's seems that there is nothing to catch.
I just want to alert the user if the connection succeeded or not.
Anyone else was able to catch those errors?
The relevant code is very simple:
do {
try vpnManager!.connection.startVPNTunnel()
}
catch {
NSLog("roee: Failed to start vpn: \(error)")
}
And inside PacketTunnelProvider:
let error = NSError(domain: NEVPNErrorDomain, code: NEVPNError.ConfigurationInvalid.rawValue, userInfo: nil)
PacketTunnelProvider.pendingStartCompletionHandler!(error)
PacketTunnelProvider.pendingStartCompletionHandler = nil

I believe startVPNTunnel talks to OS and let it knows that VPN needs to be started.
OS will do couple of checks around VPN configuration and will immediately return positive result (if configuration is fine) or error (if something is wrong). And if an error is returned then this API will throw.
However, your Network extension will be started after startVPNTunnel returned.
So you can't communicate like that.
You can register for NSNotification.Name.NEVPNStatusDidChange to see VPN status changes.

Related

Why does NWPathMonitor status is always satisfied?

When there is no connection I get an error from the URL Session saying that the request timed out.
I’m using the Network protocol to check for connectivity before hand but apparently this is not working as when I am calling this inside viewDidLoad:
static func startUpdateProcess() {
let monitor = NWPathMonitor()
monitor.pathUpdateHandler = { path in
if path.status == .satisfied {
print("Good! We are connected!")
Helper.createDownloadTask()
} else {
print("No connection. Local file not updated!")
}
}
let queue = DispatchQueue(label: "Monitor")
monitor.start(queue: queue)
}
...I get “Good! We are connected!”.
Shouldn’t the path not be satisfied if there is no connection and therefore trigger the else statement?
FYI the createDownloadTask() questions the API and downloads the required data.
Can you tell me what is wrong here and what could I do to get to the else statement if the path is not satisfied?
Thank you!
Credit to user May Rest in Peace for pointing me to the right direction.
Despite the Documentation being silent on the Network Protocol, it seems that the status property of the NWPath class, an enumeration of type NWPath.Status, returns .satisfied as long as the device is connected to a network, regardless of whether that network is working, transmitting data, or not.
The only way the else statement above could be triggered would have been by deactivating Wi-Fi and/or Cellular data or disconnecting from any network before launching the app.
All those properties are listed in the Documentation but none of them has a description or a discussion attached.
This article by user #twostraws allowed me to create the first part of that code.
Reference to the instance of NWPathMonitor (aka monitor in your scenario) needs to be retained.
You could make it a strong property by making monitor a class level property so that its lifecycle is the same as the place you are referring it from. It looks like the monitor object is being released effectively stopping the callbacks for network status monitoring.

Firestore document creation fails without an error

Edit3: Okay, it seems like it's an issue with Firebase, someone else tweeted about having the same issue. I also contacted support.
A piece of Swift code that handles creating documents suddenly stopped working. No errors are thrown, Firebase doesn't complain in the log and I can verify from the console that the document is not created, I can verify that the device has a healthy internet connection. I also disabled offline persistence for Firebase just to be sure.
When I try debugging it, the debugger jumps straight over the block that handles errors or successes, never running it (i.e. never finishing the Firestore request?).
Here is the code
func createConversation(){
let conversation : [String : Any] = ["owners" : [
UserProfile().getProfile().uid!],
"seeking" : true,
"timestamp" : Timestamp(date: Date())
]
var ref: DocumentReference? = nil
ref = DB().firestore().collection("Conversations").addDocument(data: conversation){ err in
if let err = err {
print("Error creating a convo: \(err)")
} else {
print("Conversation created with ID: \(ref!.documentID)")
StateMachine().action(a: .seekingStarted(ref!.documentID))
}
print("Conversation Creation finished")
}
let documentID = ref?.documentID
print(ref.debugDescription)
}
I'm not sure how to approach this issue, any ideas?
Edit: Okay, the issue is not limited to this block of code, it looks like Firebase is not communicating with the servers. I've waited for more than 5min for the addDocument to return(with error or success) but that never happened.
I noticed that at the initiation of the App BoringSSL complains a bit but this is not new and I don't have problems with the other Firebase services, they work just fine - reading and creating data with no problems.
Edit2: Apparently I can fetch collections and documents from Firestore, the issue seems to be limited to document/collection creation.
The document creation operation takes a little time, if you place the breakpoint inside the asynchronus completion block you will surely get an error or success.

Firebase connecting with simulator but not in real device iOS

I am developing a app which has the connection with Firebase DB. This is one part of my code,
let ref1 = Database.database().reference()
ref1.child("UserId").child("\(user_id)").setValue(["name": "\(name)"])
And this one is working fine in simulator(updating value in firebase DB), but not in real device. I have tried some solutions from this site, still I can't get the output.
Is anyone there to help me?
This could be because you are not authenticated on your device or any other reasons. In order to debug it, I suggest you to add a completion block for your setValue which is going to be called whenever the write has been committed to the database. If the call was unsuccessful for any reasons, you can get the error object and take a look at the error.
yourRef.setValue(["name": "\(name)"]) { (error, dbRef) in
if (error != nil) {
//catch the error here
print("Data could not be saved.")
} else {
print("Data saved successfully!")
}
}
At last, the issue was the real device's date and time. The date and time was incorrect in my device. So, the connection with firebase DB failed. Now, it's fixed. Thanks for you suggestions!!

Guarantee that no error is thrown in Swift 3 do-catch?

If I have an asynchronous function that throws an error, how can I make sure no error is thrown before some more functions are executed?
do {
try foo()
} catch {
print("error: \(error)")
}
// HOW TO KNOW IF THE CALL WAS SUCCESSFUL HERE
I can have a variable that I change if an error is thrown, but it may be changed in some time if the call is asynchronous. I need to find a way to pause until foo() stops executing so I can be sure no error is thrown.

Handling errors in Swift

In my application I need to download a JSON file from the web. I have made a ResourceService class that have a download method as seen below. I use this service in "higher level" services of my app. You can see there are multiple of things that may go wrong during the download. The server could be on fire and not be able to successfully respond at the moment, there could be go something wrong during the moving of the temporary file etc.
Now, there is probably not much a user can do with this other than trying later. However, he/she probably want to know that there was something wrong and that the download or the behaviour of the "higher level" methods could not succeed.
Me as a developer is confused as this point because I don't understand how to deal with errors in Swift. I have a completionHandler that takes an error if there was one, but I don't know what kind of error I should pass back to the caller.
Thoughts:
1) If I pass the error objects I get from the NSFileManager API or the NSURLSession API, I would think that I am "leaking" some of the implementation of download method to the callers. And how would the caller know what kind of errors to expect based on the error? It could be both.
2) If I am supposed to catch and wrap those errors that could happen inside the download method, how would that look like?
3) How do I deal with multiple error sources inside a method, and how would the code that calls the method that may throw/return NSError objects look like?
Should you as a caller start intercepting the errors you get back and then write a lot of code that differentiates the messages/action taken based on the error code? I don't get this error handling stuff at all and how it would look like when there are many things that could go wrong in a single method.
func download(destinationUrl: NSURL, completionHandler: ((error: NSError?) -> Void)) {
let request = NSURLRequest(URL: resourceUrl!)
let task = downloadSession.downloadTaskWithRequest(request) {
(url: NSURL?, response: NSURLResponse?, error: NSError?) in
if error == nil {
do {
try self.fileManager.moveItemAtURL(url!, toURL: destinationUrl)
} catch let e {
print(e)
}
} else {
}
}.resume()
}
First of all this is a great question. Error handling is a specific task that applies to a incredible array of situations with who know's what repercussions with your App's state. The key issue is what is meaningful to your user, app and you the developer.
I like to see this conceptually as how the Responder chain is used to handle events. Like an event traversing the responder chain an error has the possibility of bubbling up your App's levels of abstraction. Depending on the error you might want to do a number of things related to the type of the error. Different components of your app may need to know about error, it maybe an error that depending on the state of the app requires no action.
You as the developer ultimately know where errors effect your app and how. So given that how do we choose to implement a technical solution.
I would suggest using Enumerations and Closures as to build my error handling solution.
Here's a contrived example of an ENUM. As you can see it is represents the core of the error handling solution.
public enum MyAppErrorCode {
case NotStartedCode(Int, String)
case ResponseOkCode
case ServiceInProgressCode(Int, String)
case ServiceCancelledCode(Int, String, NSError)
func handleCode(errorCode: MyAppErrorCode) {
switch(errorCode) {
case NotStartedCode(let code, let message):
print("code: \(code)")
print("message: \(message)")
case ResponseOkCode:
break
case ServiceInProgressCode(let code, let message):
print("code: \(code)")
print("message: \(message)")
case ServiceCancelledCode(let code, let message, let error):
print("code: \(code)")
print("message: \(message)")
print("error: \(error.localizedDescription)")
}
}
}
Next we want to define our completionHandler which will replace ((error: NSError?) -> Void) the closure you have in your download method.
((errorCode: MyAppErrorCode) -> Void)
New Download Function
func download(destinationUrl: NSURL, completionHandler: ((errorCode: MyAppErrorCode) -> Void)) {
let request = NSURLRequest(URL: resourceUrl!)
let task = downloadSession.downloadTaskWithRequest(request) {
(url: NSURL?, response: NSURLResponse?, error: NSError?) in
if error == nil {
do {
try self.fileManager.moveItemAtURL(url!, toURL: destinationUrl)
completionHandler(errorCode: MyAppErrorCode.ResponseOkCode)
} catch let e {
print(e)
completionHandler(errorCode: MyAppErrorCode.MoveItemFailedCode(170, "Text you would like to display to the user..", e))
}
} else {
completionHandler(errorCode: MyAppErrorCode.DownloadFailedCode(404, "Text you would like to display to the user.."))
}
}.resume()
}
In the closure you pass in you could call handleCode(errorCode: MyAppErrorCode) or any other function you have defined on the ENUM.
You have now the components to define your own error handling solution that is easy to tailor to your app and which you can use to map http codes and any other third party error/response codes to something meaningful in your app. You can also choose if it is useful to let the NSError bubble up.
EDIT
Back to our contrivances.
How do we deal with interacting with our view controllers? We can choose to have a centralized mechanism as we have now or we could handle it in the view controller and keep the scope local. For that we would move the logic from the ENUM to the view controller and target the very specific requirements of our view controller's task (downloading in this case), you could also move the ENUM to the view controller's scope. We achieve encapsulation, but will most lightly end up repeating our code elsewhere in the project. Either way your view controller is going to have to do something with the error/result code
An approach I prefer would be to give the view controller a chance to handle specific behavior in the completion handler, or/then pass it to our ENUM for more general behavior such as sending out a notification that the download had finished, updating app state or just throwing up a AlertViewController with a single action for 'OK'.
We do this by adding methods to our view controller that can be passed the MyAppErrorCode ENUM and any related variables (URL, Request...) and add any instance variables to keep track of our task, i.e. a different URL, or the number of attempts before we give up on trying to do the download.
Here is a possible method for handling the download at the view controller:
func didCompleteDownloadWithResult(resultCode: MyAppErrorCode, request: NSURLRequest, url: NSURL) {
switch(resultCode) {
case .ResponseOkCode:
// Made up method as an example
resultCode.postSuccessfulDownloadNotification(url, dictionary: ["request" : request])
case .FailedDownloadCode(let code, let message, let error):
if numberOfAttempts = maximumAttempts {
// Made up method as an example
finishedAttemptingDownload()
} else {
// Made up method as an example
AttemptDownload(numberOfAttempts)
}
default:
break
}
}
Long story short: yes
... and then write a lot of code that differentiates the
messages/action taken based on the error code?
Most code examples leave the programmer alone about how to do any error handling at all, but in order to do it right, your error handling code might be more than the code for successful responses. Especially when it comes to networking and json parsing.
In one of my last projects (a lot of stateful json server communication) I have implemented the following approach: I have asked myself: How should the app possibly react to the user in case of an error (and translate it to be more user friendly)?
ignore it
show a message/ an alert (possibly only one)
retry by itself (how often?)
force the user to start over
assume (i.e. a previously cached response)
To achieve this, I have create a central ErrorHandler class, which does have several enums for the different types of errors (i.e. enum NetworkResponseCode, ServerReturnCode, LocationStatusCode) and one enum for the different ErrorDomains:
enum MyErrorDomain : String {
// if request data has errors (i.e. json not valid)
case NetworkRequestDomain = "NetworkRequest"
// if network response has error (i.e. offline or http status code != 200)
case NetworkResponseDomain = "NetworkResponse"
// server return code in json: value of JSONxxx_JSON_PARAM_xxx_RETURN_CODE
case ServerReturnDomain = "ServerReturnCode"
// server return code in json: value of JSONxxxStatus_xxx_JSON_PARAM_xxx_STATUS_CODE
case ServerStatusDomain = "ServerStatus"
// if CLAuthorizationStatus
case LocationStatusDomain = "LocationStatus"
....
}
Furthermore there exists some helper functions named createError. These methods do some checking of the error condition (i.e. network errors are different if you are offline or if the server response !=200). They are shorter than you would expect.
And to put it all together there is a function which handles the error.
func handleError(error: NSError, msgType: String, shouldSuppressAlert: Bool = false){
...
}
This method started with on switch statement (and needs some refactoring now, so I won't show it as it still is one). In this statement all possible reactions are implemented. You might need a different return type to keep your state correctly in the app.
Lessons learned:
Although I thought that I have started big (different enums, central user alerting), the architecture could have been better (i.e. multiple classes, inheritance, ...).
I needed to keep track of previous errors (as some are follow ups) in order to only show one error message to the user -> state.
There are good reasons to hide errors.
Within the errorObj.userInfo map, it exits a user friendly error message and a technicalErrorMessage (which is send to a tracking provider).
We have introduced numeric error codes (the error domain is prefixed with a letter) which are consistent between client and server. They are also shown to the user. This has really helped to track bugs.
I have implemented a handleSoftwareBug function (which is almost the same as the handleError but much less cases). It is used in a lot of else-blocks which you normally do not bother to write (as you think that this state can never be reached). Surprisingly it can.
ErrorHandler.sharedInstance.handleSoftwareBug("SW bug? Unknown received error code string was code: \(code)")
How does it look like in code: There are a lot of similar backend network requests where a lot of code looks something like the following:
func postAllXXX(completionHandler:(JSON!, NSError!) -> Void) -> RegisteringSessionTask {
log.function()
return postRegistered(jsonDict: self.jsonFactory.allXXX(),
outgoingMsgType: JSONClientMessageToServerAllXXX,
expectedIncomingUserDataType: JSONServerResponseAllXXX,
completionHandler: {(json, error) in
if error != nil {
log.error("error: \(error.localizedDescription)")
ErrorHandler.sharedInstance.handleError(error,
msgType: JSONServerResponseAllXXX, shouldSuppressAlert: true)
dispatch_async(dispatch_get_main_queue(), {
completionHandler(json, error)
})
return
}
// handle request payload
var returnList:[XXX] = []
let xxxList = json[JSONServerResponse_PARAM_XXX][JSONServerResponse_PARAM_YYY].arrayValue
.....
dispatch_async(dispatch_get_main_queue(), {
completionHandler(json, error)
})
})
}
Within the above code you see that I call a completionHandler and give this caller the chance to customize error handling, too. Most of the time, this caller only handles success.
Whenever I have had the need for retries and other and not so common handling, I have also done it on the caller side, i.e.
private func postXXXMessageInternal(completionHandler:(JSON!, NSError!) -> Void) -> NSURLSessionDataTask {
log.function()
return self.networkquery.postServerJsonEphemeral(url, jsonDict: self.jsonFactory.xxxMessage(),
outgoingMsgType: JSONClientMessageToServerXXXMessage,
expectedIncomingUserDataType: JSONServerResponseXXXMessage,
completionHandler: {(json, error) in
if error != nil {
self.xxxMessageErrorWaitingCounter++
log.error("error(\(self.xxxMessageErrorWaitingCounter)): \(error.localizedDescription)")
if (something || somethingelse) &&
self.xxxMessageErrorWaitingCounter >= MAX_ERROR_XXX_MESSAGE_WAITING {
// reset app because of too many errors
xxx.currentState = AppState.yyy
ErrorHandler.sharedInstance.genericError(MAX_ERROR_XXX_MESSAGE_WAITING, shouldSuppressAlert: false)
dispatch_async(dispatch_get_main_queue(), {
completionHandler(json, nil)
})
self.xxxMessageErrorWaitingCounter = 0
return
}
// handle request payload
if let msg = json[JSONServerResponse_PARAM_XXX][JSONServerResponse_PARAM_ZZZ].stringValue {
.....
}
.....
dispatch_async(dispatch_get_main_queue(), {
completionHandler(json, error)
})
})
}
Here is another example where the user is forced to retry
// user did not see a price. should have been fetched earlier (something is wrong), cancel any ongoing requests
ErrorHandler.sharedInstance.handleSoftwareBug("potentially sw bug (or network to slow?): no payment there? user must retry")
if let st = self.sessionTask {
st.cancel()
self.sessionTask = nil
}
// tell user
ErrorHandler.sharedInstance.genericInfo(MESSAGE_XXX_PRICE_REQUIRED)
// send him back
xxx.currentState = AppState.zzz
return
For any request, you get either an error or an http status code. Error means: Your application never managed to talk properly to the server. http status code means: Your application talked to a server. Be aware that if you take your iPhone into the nearest Starbucks, "your application talked to a server" doesn't mean "your application talked to the server it wanted to talk to". It might mean "your application managed to talk to the Starbucks server which asks you to log in and you have no idea how to do that".
I divide the possible errors into categories: "It's a bug in my code". That's where you need to fix your code. "Something went wrong, and the user can do something about it". For example when WiFi is turned off. "Something went wrong, maybe it works later". You can tell the user to try later. "Something went wrong, and the user can't do anything about it". Tough. "I got a reply from the server that I expected. Maybe an error, maybe not, but something that I know how to handle". You handle it.
I also divide calls into categories: Those that should run invisibly in the background, and those that run as a result of a direct user action. Things running invisibly in the background shouldn't give error messages. (Bloody iTunes telling me it cannot connect to the iTunes Store when I had no interest in connecting to the iTunes Store in the first place is an awful example of getting that wrong).
When you show things to the user, remember that the user doesn't care. To the user: Either it worked, or it didn't work. If it didn't work, the user can fix the problem if it is a problem they can fix, they can try again later, or it's just tough luck. In an enterprise app, you might have a message "call your help desk at xxxxxx and tell them yyyyyy".
And when things don't work, don't annoy the user by showing error after error after error. If you send then requests, don't tell the user ten times that the server is on fire.
There are things that you just don't expect to go wrong. If you download a file, and you can't put it where it belongs, well, that's tough. It shouldn't happen. The user can't do anything about it. (Well, maybe they can. If the storage of the device is full then you can tell the user). Apart from that, it's the same category as "Something went wrong, and the user can't do anything about it". You may find out as a developer what the cause is and fix it, but if it happens with an application out in the user's hands, there's nothing reasonable you can do.
Since all such requests should be asynchronous, you will always pass either one or two callback blocks to the call, one for success and one for failure. I have most of the error handling in the download code, so things like asking the user to turn WiFi on happen only once, and calls may even be repeated automatically if such an error condition is fixed by the user. The error callback is mostly used to inform the application that it won't get the data that it wanted; sometimes the fact that there is an error is useful information in itself.
For consistent error handling, I create my own errors representing either errors returned by the session, or html status codes interpreted as errors. Plus two additional errors "user cancelled" and "no user interaction allowed" if either there was a UI involved and the user cancelled the operation, or I wanted to use some user interaction but wasn't allowed to. The last two errors are different - these errors will never be reported to the user.
I would wrap the errors in your own, but pass the underlying error as a property on your error class (ala C#'s InnerException). That way you are giving consumers a consistent interface, but also providing lower level error detail if required. However, the main reason I would do this is for unit testing. It makes it much easier to mock your ResourceService class and test the code paths for the various errors that could occur.
I don't like the thought of passing back an array of errors, as it adds complexity for the consumer. Instead I would provide an array of InnerException instances. If they are instances of your own error class, they would potentially have their own InnerException's with underlying errors. However, this would probably only make sense if you were doing your own validations where multiple errors might make sense. Your download method will probably have to bail out after the first error encountered.

Resources