How to force a fresh install?

Some of my users experience a problem where background fetches never trigger. I've been unable to fix that from within the app but we've learned the hard way that a complete nuclear reinstall fixes the problem. That means trashing the app, logging out of the user's Apple ID, reinstalling the app (it may have required a new purchase?) and finally logging back in. Of course all older settings are lost but app function is fully restored.


Logging out of the Apple ID account is a pain, though. A user reports that all credit cards in his Wallet were lost and had to be re-entered. Not a practical approach.


Is there a simpler, less invasive way to force a true reinstall, even if it does detroy all saved settings? Open to ideas.

Replies

All store updates are the same, where certain data remains in the existing keychain, and other is copied over from the old app to the new app/location. Straight nuclear is not an option in that example.


Your issue requires a fix, not a hammer. Suggest you burn a support ticket w/DTS via the Member Center and ask them to weigh in.


Otherwise, it would be interesting to put a number to 'some'. What percentage of users? Any of them on j'brkd devices? Is it repeatable? Can you replicate it on your devices? Root cause may lie w/their configurations vs. your app...you'd need detailed data from those users to know for sure. How you get that depends on knowing as much as you can about what's going on generally. One user saying their wallet borked may not be actonable.

Are you really sure that "logging out of the user's Apple ID" is required? Usually just deleting the app and reinstalling works. That will wipe out all local files. Nothing will wipe out the keychain data so it is unclear what you gain by forcing the user to log out of their Apple ID.


But ask your developer what files need to be refreshed. And create an updated version of the app that does that on first install so that you do not need to do this nuclear reinstall but rather rely on the app refreshing the files itself. The app detects its current version number - if it is not the new version it rewrites all app files on the device, on CloudKit and even resets the keychain at the same time writing its new version number somewhere. It's not that hard.

Logging out is not required. The user took that upon himself after it became clear that new builds of the app (delivered via TestFlight or the app store) did not solve the problem.


Turning off background app refresh before reinstalling the app accomplishes the same thing - it blows away all old settings - and is far less disruptive. Of course it's still a pain for users and I'd love to find another way.


The root problem is that users can find themselves never being allocated background time for the app to perform web fetches. There is seemingly nothing that can be done internally in the app to restore background fetches once that state is reached. I would love to learn otherwise.

1) regarding keychain:

The keychain is a protected memory space available for all apps. Seek out something called 'keychainItemWrapper". The advantage of the keychain is that, until Apple executes its newly announced policy change, keychain entries do not get deleted when an app is deleted from the device. All other files get deleted when the app is deleted from the device.

I do not know if iCloud key-value file gets deleted, but I think not.


2) good to know that you do not require logging out of Apple ID.


3)

> Turning off background app refresh before reinstalling the app

I am not familiar with 'background app refresh' - what is that?


4)

The root problem is that users can find themselves never being allocated background time for the app to perform web fetches.

ThIs this a race issue where the app reinstalls and starts faster than the app can download something in the background? If so, detect that it is a reinstallation and delay starting the app until the background download completes itself.

" 1) regarding keychain:

... keychain entries do not get deleted when an app is deleted from the device. "


So this is a potential source of my problem, if the system has placed something there that interferes with my app.


"All other files get deleted when the app is deleted from the device. I do not know if iCloud key-value file gets deleted, but I think not."


Definitely not in the short run, and I think not ever. (The cloud file, I mean, nothing local.) This enables you to trash an app and reinstall it at a later date while retaining some of the old information.


" 3)

> Turning off background app refresh before reinstalling the app

I am not familiar with 'background app refresh' - what is that?"


Check out your Settings>>General>>Background App Refresh to find a list of apps on your phone using this feature. It allows a small amount of resources for apps to operate in the background to fetch fresh web data, for instance.


" 4) ThIs this a race issue where the app reinstalls and starts faster than the app can download something in the background? "


No, it's a matter of the system never allocating those meager background resources despite all the pieces being in place. Once the system cuts off an app, it seems impossible to get going again. It happens every 20 minutes or so when things are rolling.

> It happens every 20 minutes or so when things are rolling.


Did you submit an ask to DTS, yet?

Got it; I am familiar with "

- (void)application:(UIApplication *)application 
performFetchWithCompletionHandler:"


(It is deprecated as of iOS13)


You may have a different issue that needs to be addressed. The system monitors the use of background fetches and limits it based on its calculations. In your case it sounds like the system is determining that the fetch rate is too high for the app's needs. Are you fetching very often? Are you returning 'nodata' in a timely fashion? Note:


"Calling the completion handler in a timely manner, and with an accurate result [which can include saying that no new data was available], helps determine how much future execution time your app receives. If you take too long to update your app, the system may schedule your app less frequently in the future to save power."


Also, have you checked

UIBackgroundRefreshStatus

to be sure it is still set?

"Also, have you checked

UIBackgroundRefreshStatus 
to be sure it is still set?"


I have recently incorprated logging the status that into didFinishLaunchingWithOptions in my AppDelegate. It appears to be "available" for the affected users, at least when the app starts.


As for satisfying the system that my fetches wrap up properly, I guess I'm not sure. I get background fetches all day long just fine, so in general I feel the code is OK. Other users too. But if there is something about the affected users that is causing a problem with getting done in time (slow internet?), I suppose my app might be getting blacklisted. I need a way to check that and more importantly to reset it when a new build is installed

So this is then the issue behind your position....


".. my app might be getting blacklisted. I need a way to check that and more importantly to reset it when a new build is installed"


You are focusing on 'resetting it' - your 'position'. And you have found one means of resetting it - a full reinstall. And you are therefore trying to force a full reinstall. That might be the solution. Or it might be a rabbit hole that pits you against the system.


Perhaps there is a reason why the system is objecting to your fetches - the 'issue' behind the position. Perhaps multiple 'no data' fetches raises a flag. Perhaps too many 'full data' fetches is raising a flag. Maybe your app could detect those circumstances and change the fetch rate. Then you would not get blacklisted in the first place.


Is there something unusual or different about the data these affected users are downloading?

Just to elaborate a bit more about how my fetches start and wrap up. I see now that I may have not properly set it up in iOS13. Not sure. See what you think. Maybe I need to invalidate any URL session still running?


It works fine when everything gets done quickly but may not be graceful if/when the fetch does not complete in time. Note that one affected user I'm in contact with is on iOS 12 and another is on iOS 13. The iOS 12 user did the nuclear reinstall and now has normal function, ie. frequent background sessions.


iOS 12 version


In my AppDelegate:

    func application(_ application: UIApplication, performFetchWithCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
        let dateFormatter = DateFormatter()  // format the date for output
        dateFormatter.dateStyle = DateFormatter.Style.medium
        dateFormatter.timeStyle = DateFormatter.Style.long
        var convertedDate = dateFormatter.string(from: Date())
        
        if #available(iOS 13.0, *) {
            self.logSB.info("iOS 13 Ignoring Background Fetch called at \(convertedDate)")
            completionHandler(.newData)
        } else {
            Central().fetch {    //  Goes and gets fresh data
                convertedDate = dateFormatter.string(from: Date())
                self.logSB.info("Calling the Fetch completion handler at \(convertedDate)\n\n")
                completionHandler(.newData)
            } // End call of Central().Fetch
        }  // End else
    }  //  Close func


In my Central file:

    func fetch(_ completion: @escaping () -> Void) {
        let dateFormatter = DateFormatter()
        dateFormatter.dateStyle = DateFormatter.Style.medium
        dateFormatter.timeStyle = DateFormatter.Style.medium
        let convertedDate = dateFormatter.string(from: Date())
        self.logSB.info("Running the CENTRAL fetch at \(convertedDate)")
        self.getWebFetchesBackground()  // This launches the URL sessions
        let timer = Timer.scheduledTimer(withTimeInterval: 20.0, repeats: false) { (timer) in
            completion()  // This sends a completion back to the AppDelegate after 20 seconds.
        }
    }


iOS 13 version


In my AppDelegate:

    @available(iOS 13.0, *)
    func handleAppRefresh(task: BGAppRefreshTask) {
        logSB.info("Handling the AppRefresh in iOS 13")
        scheduleAppRefresh()

        let queue = OperationQueue()
        queue.maxConcurrentOperationCount = 1
        let appRefreshOperation =  Central().fetch2
        queue.addOperation(appRefreshOperation)

        let lastOperation = queue.operations.last
        lastOperation?.completionBlock = {
            task.setTaskCompleted(success: !(lastOperation?.isCancelled ?? false))
        }

      task.expirationHandler = {
            // After all operations are cancelled, the completion block below is called to set the task to complete.
            queue.cancelAllOperations()
        }

        let dateFormatter = DateFormatter()
        dateFormatter.dateStyle = DateFormatter.Style.medium
        dateFormatter.timeStyle = DateFormatter.Style.long
        let convertedDate = dateFormatter.string(from: Date())
        self.logSB.info("Completed the iOS 13 App Refresh at \(convertedDate)")  
   }


In my Central file:

    func fetch2() {
        let dateFormatter = DateFormatter()
        dateFormatter.dateStyle = DateFormatter.Style.medium
        dateFormatter.timeStyle = DateFormatter.Style.medium
        let convertedDate = dateFormatter.string(from: Date())
        self.logSB.info("Running the NEW CENTRAL fetch at \(convertedDate)")
        self.getWebFetchesBackground()
  }

This is now way beyond me.


I think KMT's suggestion of DTS may have been prescient - but the question is not 'how to force a complete reinstall' but rather 'why is the system blacklisting me'.


That said - I think you need to respond sooner than 20 seconds with a completion handler and I'm not sure you always want to indicate .newData if there was no new data.

"This is now way beyond me."


I know how that feels!


"... the question is not 'how to force a complete reinstall' but rather 'why is the system blacklisting me'."


Agreed, I've managed to do a clean re-install but the root problem remains. I submitted a ticket on that topic.


"I think you need to respond sooner than 20 seconds with a completion handler and I'm not sure you always want to indicate .newData if there was no new data."


My understanding is that I have 30 seconds. Picking 20 is a bit of a kludge, a dropping of the guillotine blade. I would prefer to send a completion handler back when everything else is actually done (which may be much sooner). Or wrap up gracefully if the other stuff cannot complete in 30 seconds. But that stuff confuses me and I never worked through it all.


Always reporting "newData" is the recommended trick to keep background fetch requests coming in as frequently as possible. It's not really cheating, since it will indeed be new data as long as it completes.