How to disable vpn connection when app is terminated in iOS? - ios

I'm implementing Personal VPN by PacketTunnel. Through startVPNTunnel method of NETunnelProviderManager, i checked whether VPN Connection is run well.
However, I have a problem. I added the exit code of vpn connection in applicationWillTerminate to disable vpn connection when app is terminated like following code below. But it doesn't work.
If test this code, loadAllFromPreferences is run but callback function of loadAllFromPreferences isn't called. This code runs well anywhere except applicationWillTerminate. Why it doesn't work? Is there any way to disable vpn when app is terminated?
func applicationWillTerminate(_ application: UIApplication) {
NETunnelProviderManager.loadAllFromPreferences { (managers, error) in
var manager:NETunnelProviderManager?=nil;
for m in managers! {
if m != nil {
if m.localizedDescription == "profile" {
manager = m;
break
}
}
}
manager?.connection.stopVPNTunnel()
}
}

As per Apple's documentation of applicationWillTerminate(application:):
Your implementation of this method has approximately five seconds to perform any tasks and return. If the method does not return before time expires, the system may kill the process altogether.
If applicationWillTerminate is called but the closure is not make sure that it does not take more than "approximately" (sic) 5 seconds.
I have not used the libraries you are using but from a general point of view there are probably better strategies than iterating over all available managers. Like storing a reference/identifier to a given manager when starting a connection and using that reference/identifier to terminate it.

Related

Why async / long running operations in BackgroundTasks don't work?

Trying to use BackgroundTasks for iOS 13+. Long running operations don't seem to work:
// in AppDelegate
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
BGTaskScheduler.shared.register(forTaskWithIdentifier: "foo.bar.name", using: nil) { task in
print("start!")
task.expirationHandler = {
// Not executed
print("expired!")
}
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
// Not executed
print("finish!")
task.setTaskCompleted(success: true)
}
}
return true
}
func applicationDidEnterBackground(_ application: UIApplication) {
BGTaskScheduler.shared.cancelAllTaskRequests()
let request = BGProcessingTaskRequest(identifier: "foo.bar.name")
request.earliestBeginDate = nil // or Date(timeIntervalSinceNow: 0) or Date(timeIntervalSinceNow: 5)...
do {
try BGTaskScheduler.shared.submit(request)
} catch let e {
print("Couldn't submit task: \(e)")
}
}
I also tried using a queue with Operation (for which I modeled my flow synchronously). This also didn't work. As soon as there's something that takes a while to complete, it gets stuck.
It doesn't log anything else to the console, no errors, no expired task message. It shows the last message before the long running operation and that's it. I confirmed that it doesn't move forward by storing a preference and examining it when restarting. It's not stored.
I added "foo.bar.name" to the info.plist (in "Permitted background task scheduler identifiers") and enabled capabilities both for background fetch and background processing. I'm testing on an iPhone with iOS 13.3.1 and using Xcode 11.4.1.
Additional notes:
I've been starting the tasks immediately as described here: https://developer.apple.com/documentation/backgroundtasks/starting_and_terminating_tasks_during_development
I also tested with Apple's demo project. It shows the same problem: The database cleaning operation doesn't complete (I added a log at the beginning of cleanDatabaseOperation.completionBlock and it never shows).
A couple of observations:
You should check the result code of register. And you should make sure you didn’t see your “Couldn't submit task” log statement.
Per that discussion in that link you shared, did you set your breakpoint immediately after the submit call? This accomplishes two things:
First, it makes sure you hit that line (as opposed, for example, to the SceneDelegate methods).
Second, if you just pause the app manually, some random amount of time after the app has gone into background, that’s too late. It has to be in that breakpoint immediately after you call submit. Then do e command. Then resume execution.
Anyway, when I do that, running your code, the BGProcessingTaskRequest ran fine. I’m running iOS 13.4.1 (and like you, Xcode 11.4.1).

Bidirectional gRPC stream sometimes stops processing responses after stopping and starting

In short
We have a mobile app that streams fairly high volumes of data to and from a server through various bidirectional streams. The streams need to be closed on occasion (for example when the app is backgrounded). They are then reopened as needed. Sometimes when this happens, something goes wrong:
From what I can tell, the stream is up and running on the device's side (the status of both the GRPCProtocall and the GRXWriter involved is either started or paused)
The device sends data on the stream fine (the server receives the data)
The server seems to send data back to the device fine (the server's Stream.Send calls return as successful)
On the device, the result handler for data received on the stream is never called
More detail
Our code is heavily simplified below, but this should hopefully provide enough detail to indicate what we're doing. A bidirection stream is managed by a Switch class:
class Switch {
/** The protocall over which we send and receive data */
var protocall: GRPCProtoCall?
/** The writer object that writes data to the protocall. */
var writer: GRXBufferedPipe?
/** A static GRPCProtoService as per the .proto */
static let service = APPDataService(host: Settings.grpcHost)
/** A response handler. APPData is the datatype defined by the .proto. */
func rpcResponse(done: Bool, response: APPData?, error: Error?) {
NSLog("Response received")
// Handle response...
}
func start() {
// Create a (new) instance of the writer
// (A writer cannot be used on multiple protocalls)
self.writer = GRXBufferedPipe()
// Setup the protocall
self.protocall = Switch.service.rpcToStream(withRequestWriter: self.writer!, eventHandler: self.rpcRespose(done:response:error:))
// Start the stream
self.protocall.start()
}
func stop() {
// Stop the writer if it is started.
if self.writer.state == .started || self.writer.state == .paused {
self.writer.finishWithError(nil)
}
// Stop the proto call if it is started
if self.protocall?.state == .started || self.protocall?.state == .paused {
protocall?.cancel()
}
self.protocall = nil
}
private var needsRestart: Bool {
if let protocall = self.protocall {
if protocall.state == .notStarted || protocall.state == .finished {
// protocall exists, but isn't running.
return true
} else if writer.state == .notStarted || writer.state == .finished {
// writer isn't running
return true
} else {
// protocall and writer are running
return false
}
} else {
// protocall doesn't exist.
return true
}
}
func restartIfNeeded() {
guard self.needsRestart else { return }
self.stop()
self.start()
}
func write(data: APPData) {
self.writer.writeValue(data)
}
}
Like I said, heavily simplified, but it shows how we start, stop, and restart streams, and how we check whether a stream is healthy.
When the app is backgrounded, we call stop(). When it is foregrounded and we need the stream again, we call start(). And we periodically call restartIfNeeded(), eg. when screens that use the stream come into view.
As I mentioned above, what happens occasionally is that our response handler (rpcResponse) stops getting called when server writes data to the stream. The stream appears to be healthy (server receives the data we write to it, and protocall.state is neither .notStarted nor .finished). But not even the log on the first line of the response handler is executed.
First question: Are we managing the streams correctly, or is our way of stopping and restarting streams prone to errors? If so, what is the correct way of doing something like this?
Second question: How do we debug this? Everything we could think of that we can query for a status tells us that the stream is up and running, but it feels like the objc gRPC library keeps a lot of its mechanics hidden from us. Is there a way to see whether responses from server may do reach us, but fail to trigger our response handler?
Third question: As per the code above, we use the GRXBufferedPipe provided by the library. Its documentation advises against using it in production because it doesn't have a push-back mechanism. To our understanding, the writer is only used to feed data to the gRPC core in a synchronised, one-at-a-time fashion, and since server receives data from us fine, we don't think this is an issue. Are we wrong though? Is the writer also involved in feeding data received from server to our response handler? I.e. if the writer broke due to overload, could that manifest as a problem reading data from the stream, rather than writing to it?
UPDATE: Over a year after asking this, we have finally found a deadlock bug in our server-side code that was causing this behaviour on client-side. The streams appeared to hang because no communication sent by the client was handled by server, and vice-versa, but the streams were actually alive and well. The accepted answer provides good advice for how to manage these bi-directional streams, which I believe is still valuable (it helped us a lot!). But the issue was actually due to a programming error.
Also, for anyone running into this type of issue, it might be worth investigating whether you're experiencing this known issue where a channel gets silently dropped when iOS changes its network. This readme provides instructions for using Apple's CFStream API rather than TCP sockets as a possible fix for that issue.
First question: Are we managing the streams correctly, or is our way of stopping and restarting streams prone to errors? If so, what is the correct way of doing something like this?
From what I can tell by looking at your code, the start() function seems to be right. In the stop() function, you do not need to call cancel() of self.protocall; the call will be finished with the previous self.writer.finishWithError(nil).
needsrestart() is where it gets a bit messy. First, you are not supposed to poll/set the state of protocall yourself. That state is altered by itself. Second, setting those state does not close your stream. It only pause a writer, and if app is in background, pausing a writer is like a no-op. If you want to close a stream, you should use finishWithError to terminate this call, and maybe start a new call later when needed.
Second question: How do we debug this?
One way is to turn on gRPC log (GRPC_TRACE and GRPC_VERBOSITY). Another way is to set breakpoint at here where gRPC objc library receives a gRPC message from the server.
Third question: Is the writer also involved in feeding data received from server to our response handler?
No. If you create a buffered pipe and feed that as request of your call, it only feed data to be sent to server. The receiving path is handled by another writer (which is in fact your protocall object).
I don't see where the usage of GRXBufferedPipe in production is discouraged. The known drawback about this utility is that if you pause the writer but keep writing data to it with writeWithValue, you end up buffering a lot of data without being able to flush them, which may cause memory issue.

How to get location in background even after phone restart in swift?

I'm developing an application which is tracking the location of the phone on time interval chosen by the user. I made it with timer which starts startUpdatingLocation, when it get the location stops updating sends the location to the server and starts timer again. Everything is done in background.
I need to get location data in background mode (in iOS 8) even after phone restarts and send the data to a server. I tried hundreds methods and no one works for me. So ... what i've got at this moment:
In info.plist:
Required background modes - location and fetch
NSLocationAlwaysUsageDescription - Location is required for this application to work properly
In AppDelegate:
var locationController: LocationController = LocationController() as LocationController;
func application(application: UIApplication!, didFinishLaunchingWithOptions launchOptions: NSDictionary!) -> Bool {
UIApplication.sharedApplication().setMinimumBackgroundFetchInterval(UIApplicationBackgroundFetchIntervalMinimum);
var bgTask = UIBackgroundTaskIdentifier()
bgTask = UIApplication.sharedApplication().beginBackgroundTaskWithExpirationHandler { () -> Void in
UIApplication.sharedApplication().endBackgroundTask(bgTask)
}
if(self.locationController.profileForTracking != nil && self.locationController.profileForTracking != "Disabled" && self.locationController.intervalForTracking != nil && self.locationController.trackingAllowed == true){
self.locationController.initLocationManager();
if(self.locationController.timer != nil){
self.locationController.timer = self.locationController.timer;
} else {
self.locationController.startTimerForLocationUpdate();
}
println("Location can now start ....");
}
return true
}
func application(application: UIApplication!, performFetchWithCompletionHandler completionHandler: ((UIBackgroundFetchResult) -> Void)!) {
var bgTask = UIBackgroundTaskIdentifier()
bgTask = UIApplication.sharedApplication().beginBackgroundTaskWithExpirationHandler { () -> Void in
UIApplication.sharedApplication().endBackgroundTask(bgTask)
}
if(self.locationController.profileForTracking != nil && self.locationController.profileForTracking != "Disabled" && self.locationController.intervalForTracking != nil && self.locationController.trackingAllowed == true){
self.locationController.initLocationManager();
if(self.locationController.timer != nil){
self.locationController.timer = self.locationController.timer;
} else {
self.locationController.startTimerForLocationUpdate();
}
println("Location can now start ....");
}
}
Everything seems to work (when app is in foreground or background/inactive) except when the phone was restarted or user terminate application. I think I'm doing something wrong and iOS does not wake up the application. How can I do it, and if I can do it somehow is there a way so get the location in time period chosen by the user (30 minutes, 1 hour ...). Thanks in advance!
While moving to background or when app terminated call this method
startMonitoringSignificantLocationChanges();
So if you reboot your device or app got killed, whenever there is significant location change in your location, OS will launch your app in background.
As per apple document
If you start this service and your app is subsequently terminated, the
system automatically relaunches the app into the background if a new
event arrives. In such a case, the options dictionary passed to the
application:willFinishLaunchingWithOptions: and
application:didFinishLaunchingWithOptions: methods of your app
delegate contains the key UIApplicationLaunchOptionsLocationKey to
indicate that your app was launched because of a location event. Upon
relaunch, you must still configure a location manager object and call
this method to continue receiving location events. When you restart
location services, the current event is delivered to your delegate
immediately. In addition, the location property of your location
manager object is populated with the most recent location object even
before you start location services.
What you want is no longer possible. As of iOS7, an application will only be kept alive in the background to receive locations if it was launched into the foreground by the user, not by the system (such as via a significant location change event). This change was mentioned briefly in in one of the talks at WWDC '13, probably the one titled "What's New in Core Location".
I had an app with this functionality, and it broke in iOS7. I spoke with Apple developer support, and they confirmed the change. The project I was working on was a origin-destination survey intended for an academic study; our solution ended up being to keep a set of local notifications, and regularly update their fire times into the future while we are running; if we ever stop running the user gets a notification to relaunch the app.
Be aware, as well, that if you ever tell core location to stop updating your location, you will not be able to turn that back on until you are again launched into the foreground by the user. The solution we used here is a combination of deferred location updates, and of turning the 'desired accuracy' way up, so that the phone isn't powering on the GPS or wifi antennas when it isn't necessary. This isn't ideal, obviously, but our battery performance has managed to be more reasonable then I had expected.

CTCallCenter in Swift

I'm trying to use CTCallCenter in Swift, however it always displays error.
I suppose it may cause in how to use closure but actually I don't familiar about it.
Does anybody have idea to resolve this issue?
Here is my code
import CoreTelephony
class ViewController: UIViewController{
var callCenter:CTCallCenter = CTCallCenter()
override func viewDidLoad() {
callCenter.callEventHandler(call:CTCall) -> Void in{
//will get CTcall status here
}
}
}
There are three errors.
1, Braced block of statements is an unused closure
2, Expected expression
3, Consecutive statements on a line must be separated by ";".
I tried to change as it indicated but any ways are not correct.
Thanks in Advance!
I got this working using the following code:
import CoreTelephony
class SomeClass: UIViewController {
private var callCenter = CTCallCenter()
override func viewDidLoad() {
super.viewDidLoad()
callCenter.callEventHandler = { (call:CTCall!) in
switch call.callState {
case CTCallStateConnected:
println("CTCallStateConnected")
self.callConnected()
case CTCallStateDisconnected:
println("CTCallStateDisconnected")
self.callDisconnected()
default:
//Not concerned with CTCallStateDialing or CTCallStateIncoming
break
}
}
}
func callConnected(){
// Do something when call connects
}
func callDisconnected() {
// Do something when call disconnects
}
}
Hope it helps.
From the Apple documentation :
Responding to Cellular Call Events
Dispatched when a call changes state.
Declaration:
var callEventHandler: ((CTCall!) -> Void)!
Discussion:
This property’s block object is dispatched on the default priority global dispatch queue when a call changes state. To handle such call events, define a handler block in your application and assign it to this property. You must implement the handler block to support being invoked from any context.
If your application is active when a call event takes place, the system dispatches the event to your handler immediately. However, call events can also take place while your application is suspended. While it is suspended, your application does not receive call events. When your application resumes the active state, it receives a single call event for each call that changed state—no matter how many state changes the call experienced while your application was suspended. The single call event sent to your handler, upon your application returning to the active state, describes the call’s state at that time.
For example, suppose your application changes from the active to the suspended state while a call is in the connected state. Suppose also that while your application is suspended, the call disconnects. When your application returns to the active state, you get a cellular call event indicating that the call is disconnected.
Here is a more complex example. Suppose your application changes from the active to the suspended state after the user has initiated a call but before it connects (that is, your application suspends while the call is in the dialing state). Suppose further that, while your application is suspended, the call changes first to the connected state and then to the disconnected state. When your application returns to the active state, you get a single cellular call event indicating that the call is disconnected.
May be now you can understand how to declare that.

iOS. Save state when user exits an application?

For example:
- (void)someFunc {
[self someFunc1];
[self someFunc2];
[self someFunc3];
}
I call someFunc. As I understand if I interrupt the application then the application doesn't guarantee that all the inner code in someFunc will be performed.
I must call someFunc1, someFunc2 and someFunc3 only once.
The problems I don't know how to solve:
someFunc1, someFunc2 and someFunc3 should be called atomically.
storing info for next launch. For example if we successfully have performed someFunc1 only then at next launch the application should call someFunc2 and someFunc3 only.
I know about method applicationWillTerminate:, but I don't know how to solve the current issue with it.
EDITED
Multitasking is not a solution because Even if the device is running iOS 4 or later, the device may not support multitasking., so it doesn't solve the general problem and makes the final solution more difficult only.
EDITED
For those who spam with off topic answers: read the title first - Save state when user exits an application. Where have you seen here putting the application into background?
This does't make sense. If these functions are running on the main thread, there is no way that the application can terminate normally while your functions are running. This is because the events sent like applicationWillTerminate: are sent on the same thread.
If your function is running on a different thread to the main thread, you will need to save some state information after each function completes, but you still have a race condition.
It might be better to check your application's state before running each function. For example, if you have a three step login/registration process with a server, you should query the server to see if the stage has been completed already before running it.
It's difficult to be more specific without knowing what you are doing in these functions.
You should use background tasks !
Take a look at the documentation here :
Executing a Finite-Length Task in the Background
Put the call of someFunc in the middle of the background task.
If your app goes to the background state, you'll have extra time to finish the execution of the method.
Make your functions to return bool, and when you call them, store the bool value to nsdefaults.
When the app restarts,check the bools from sndefaults, and if they are NO, run the functions and update them.
Nobody wants to help. So my temporary solution:
to save a last state I use a writing to a file because it enables to set its operation as atomic/nonatomic
I have replaced this code with something like this:
typedef enum {
state1,
state2,
state3
} MyState;
#property (assign) MyState state;
-(void)someFunc {
switch (state) {
case state1:
{
[self someFunc1];
state = state2;
[self someFunc];
break;
}
case state2:
{
[self someFunc2];
state = state3;
[self someFunc];
break;
}
default:
break;
}
}

Resources