I have code that is triggered on launch only after the app is upgraded or installed. I want to debug this code, but can't think of a way to do it.
I can't launch the app with Xcode because it would overwrite the existing app and wouldn't trigger the update.
I can't attach Xcode to the process manually because the code I want to debug will have executed by the time I get Xcode attached.
Is there someway of pulling this off that I'm missing?
Use the "Wait for MyApp.app to launch" option.
In Xcode, hold down the Option key and choose Product->Run... (the ellipses are added when you hold Option down). This will bring up the scheme editor, with the Run scheme selected. Click on the Info tab, and on the resulting Info panel you'll see a radio group labelled "Launch" with two options: "Automatically" and "Wait for MyApp.app to launch". If you select the second option, Xcode will start the debugger and wait for you to launch your application manually. You can set a breakpoint at the beginning of the code you want to debug, and the debugger will stop there.
Reset the app to its previous state.
You may want to consider adding some code that resets the app to whatever state it was in before the upgrade. This can be debug code that's excluded from the release build. Debugging problems that require you to re-install the app every time you want to run through your debug can take a lot of time. Even just having to restart the app at every debug cycle eats up a lot of your time. It's very often worthwhile to spend a little extra time adding a button to your app that undoes whatever changes you're making so that you can do them again.
Unit test.
One great way to debug code that deals with app states that are difficult to recreate is to write unit tests. In a test, you can create any state you want. You can even create app states that may be impossible to recreate in your app. And you can debug that code over and over again. Like the previous solution, it takes a little more time to write the code up front, but you'll save so much time on each iteration of your debug cycle that its well worth it. Even better, you can add the test to your build process so to help ensure that the functionality doesn't break later.
I also have some code that I only run on upgrades, i have this method below which I use at the end of applicationDidFinishLaunching. I can debug this too:
- (void) determineWhetherToForceSync {
NSString *currentVersion = [[[NSBundle mainBundle] infoDictionary] objectForKey:#"CFBundleShortVersionString"];
NSString *savedVersionKey = #"savedVersionKey";
NSString *savedVersion = [[NSUserDefaults standardUserDefaults] valueForKey:savedVersionKey];
if (!savedVersion) {
//This is a fresh install....
} else {
//They have a saved version, so check if it's the same as current version
if (![currentVersion isEqualToString:savedVersion]) { //They have done an upgrade
//This is an update
} //.... else they are starting the app on a second attempt
}
//Save the current version
[[NSUserDefaults standardUserDefaults] setObject:currentVersion forKey:savedVersionKey];
}
To re-test, delete the app and re-install. Or change your version number.
Related
I am trying to build share extension. Created the target, fixed the project settings (provisioning profile, app group, NSExtension).
Tried to run and saw the logs I added to the three methods isContentValid, didSelectPost, and configurationItems. This is my first time to create a share extension so, I was just looking around first and see when each method is called.
Now for some reason (I don't know what I did wrong), I am unable to see the logs from the extension. When I hit the run button in Xcode, the message Finished running ... gets displayed in the status bar on top.
Tried to change the scheme settings to the extension in the executable field, I get the following message when trying to run:
Could not locate installed application
Install claimed to have succeeded, but application could not be found on device. bundleId = <my-bundle-id-value>
Followed every solution I could find on the internet. Tried the solution from here and here.
UPDATE
I wanted to check if the extension actually runs or just the logs don't work. So I added the following logic to isContentValid:
- (BOOL)isContentValid {
// Do validation of contentText and/or NSExtensionContext attachments here
NSLog(#"isContentValid");
NSUserDefaults * userDefaults = [[NSUserDefaults alloc] initWithSuiteName:#"<group-id>"];
[userDefaults setObject:#"haha" forKey:#"key"];
[userDefaults synchronize];
return YES;
}
and read then read the haha in the main application it worked! So, the question is: Why can't I debug the application? Why doesn't the application print the log statements. Developing without logging and/or debugging could take 2x, if not more, time.
UPDATE 2
The best I was able to come up with is to manually attach the debugger to the extension, add breakpoints here and there (where I wanted to put logs) and edited the breakpoints to log as action and continue after evaluation.
NOTE: I have seen many other posts on Stack Overflow about NSUserDefaults being renamed to UserDefaults in Swift or not working on simulator until a restart. This is not a duplicate by anyway. Many of the questions SO is tagging against is from 4 years ago. My question is specific to iOS 10 from this year as this has always worked in older versions. I have mentioned in my question already that my question is not a duplicate of those questions as those were simulator bugs in swift and my issue is on device objective C bug. Please read the questions before marking as duplicate
My issue is different as I am able to reproduce this on objective C and on physical device itself.
I created a brand new project from scratch for this test. I placed this code in the viewDidLoad of a view controller:
if (![[NSUserDefaults standardUserDefaults] valueForKey:#"checkIfInitialized"]){
NSLog(#"setting checkIfInitialized as not exist");
[[NSUserDefaults standardUserDefaults] setValue:#"test" forKey:#"checkIfInitialized"];
[[NSUserDefaults standardUserDefaults] synchronize];
self.view.backgroundColor=[UIColor redColor];
self.mylabel.text=#"NSUserDefaults was NOT there, try running again";
} else {
NSLog(#"checkIfInitialized exists already");
self.view.backgroundColor=[UIColor blueColor];
self.mylabel.text=#"NSUserDefaults was already there this time, try running again";
}
Now if I run the app about 10 times, few times it finds the checkIfInitialized and sometimes it doesn't. No exact number on how many times it fails as it might work 3 times then fail next 2 times then work 4 times and fail once and so on.
Now something I have noticed (not 100% sure though) that the issue only seems to happen when I am testing connected via Xcode. If I run by launching the app by clicking the app icon on device without Xcode, then it seems to work fine but I can't be 100% sure.
I noticed this error occur sometimes:
[User Defaults] Failed to write value for key checkIfInitialized in CFPrefsPlistSource<0x1700f7200> (Domain: com.xxxx.appname, User: kCFPreferencesCurrentUser, ByHost: No, Container: (null)): Path not accessible, switching to read-only
I have this very simple project on my dropbox if you want to test it out.
I would suggest testing about 10-15 times to reproduce this issue.
https://www.dropbox.com/s/j7vbgl6e15s57ix/nsuserdefaultbug.zip?dl=0
This works completely fine on iOS 9 so definitely something to do with iOS 10.
EDIT
Bug logged: 28287988
Response from apple DTS team:
First off, you should first determine whether standardUserDefaults or
valueForKey is failing. My guess is that “standardUserDefaults” is
returning NULL and, if that’s the case, then that’s something you
should be guarding against generally. Notably, standardUserDefaults
will return NULL if the preference file is encrypted in the
environment the app is currently running in (for example, preferences
is set to “NSFileProtectionComplete” and the app is running in the
background). That shouldn’t be an issue for standard foreground-only
apps, but it’s something to be aware of anyway.
It’s very likely that Xcode is actually inducing the problem here.
Xcode vastly complicates the app launching environment in a way that’s
VERY different than a standard app launch. My guess is that this is
basically being triggered by Xcode’s timing inducing an an expected
situation during the app launch, but if you want a more formal test of
that try setting a single breakpoint in applicationDidFinishLaunching
and continuing in the debugger as soon as you hit it. My guess is
just adding that disrupts the timing enough to stop the problem from
happening. Sort of. It’s iOS 10 only in the sense that iOS 9 will
never print that log message, but that’s because the log message was
added in iOS 10. The code itself is similar enough to iOS 9.3 that I
suspect exactly the same behavior is (at least in theory) possible in
iOS 9.
Yes, this is definitely a reproducible bug.
It happens with the GM release of Xcode 8 and iOS 10.
It is not the linked question referring to Swift.
It is not the linked question referring to beta versions of the Simulator.
The bug happens on devices and on the Simulator. It is intermittent: saving will work six times and then fail. Unlike you, I did not get the "failed to write key" message.
The bug also occurs when operating directly on the device without Xcode. This is in fact how I discovered it.
You should report a bug to Apple, especially since you have a short program that will reproduce it. I will do the same.
One key difference: In my case the failure is in writing the default. The previously written value remains in NSUserDefaults. Sometimes one key is successfully written while another is unchanged.
A similarly very intelligent DTS response from my own support request. Basically, killing using Xcode is more murderous than anything that would naturally happen on the device (even the double-Home-click-and-upswipe method) and since everything is abruptly crashed when Xcode halts it, the lazy writing of NSUserDefaults can fail, or be only half completed.
And indeed, pure on-device testing of the app, without Xcode involved, shows that everything does get correctly written to NSUserDefaults when the app is terminated.
I have closed my own bug report.
I've got an app that crashes even before the debugger can connect.
I placed a break point on the first line of main(). (I added an NSLog statement as very first statement in main() and set the break point there.
The app seems to start. The main screen with some ui elements becomes visible on the screen. Then it disappears.
There is no crash log found on the devices.
Xcode message:
Could not launch "appname"
process launch failed: failed to get the task for process xyz
Debugging is enabled of course.
The same for the profiler Instruments.
Code signing works fine so that the app can be deployed to the devices.
(Same for enterprise distribution. And the app validates for store submission.)
It does work on the simulator though.
The app used to work fine. I was just about to build it for the store. For final tests on iOS 8.1 I upgraded to Xcode 6.1 with SDK 8.1. But the problem did not occur directly after the upgrade. It worked just fine.
Then it crashed when building for release for enterprise distribution.
The AppStore build crashed in the same manner (according to Apple, the app was rejected of course.)
But it ran nicely in debug modes.
Now I was trying whether compiler options for optimization may make all the difference and I was trying to build in release mode with debugging enabled etc and end up with a debug build crashing as well. (No optimization in debug).
So it may well be that the migration to Xcode 6.1 did cause it but the problem may have come effective only after Xcode cleaned and rebuild the project in response to changes to compiler settings for code optimization.
Sorry for the long text. I tried to put everything in that may be of importance.
Reason is most likely some incompatibility of Crackify and iOS 8.1.
Therefore it may be of interest for others, altough my problem along with these symptoms may be very special.
Very early within AppDelegate didFinishLaunchingWithOptions we have had the following statement.
if ([Crackify isCracked] || [self isCertificateUnvalid])
exit(173);
That, as such, is not really well designed. The app is just terminated rather than any error message displayed to the user. Thus, it appears as if the app has crashed. But it has not crashed and therefore no crashlog is provided.
For reasons which I don't yet understand and which may not be related to this error, my debugger did not manage to hook up into the executed app. Once that was overcome (suddenly the debugger worked without any changes made to any of the debugging related settings) the error was found rather quickly.
This is Crackify: https://github.com/itruf/crackify
Within Crackify it was this code sniplet that caused the problem:
static NSString *str2 = #"ResourceRules.plist";
BOOL fileExists3 = [manager fileExistsAtPath:([NSString stringWithFormat:#"%#/%#", bundlePath, str2])];
if (!fileExists3) {
return YES;
}
For reasons that I did not further investigate, the file, that is tested here, apparently does not exist in iOS 8.1 any more.
Is there any way to know that iOS app is launched after being updated?
I think i can save app current version every time i launch the application for example in NSUserDefaults and check this version every time i open the application.
And what about the case:
1) User installs app version 1.0 , but doesn't launch it.
2) User installs app version 2.0.
How to handle that case for example?
Thanks in advance.
If always done what you have suggested, saving the app version in the NSUserDefaults.
And about your other case, if the app does not start with version 1 then it does with version 2 you could just see it as a new install.
Since your app never started in the first place you can just treat it as a fresh install. If you doing this to track update in some kind of analytics tool you will have an issue. But you could use apple install/update reports to get the correct list of install/updates.
Just be sure that if you do any updates from any version you make you code in such a way that you can upgrade from any previous version. So installing verion 4 from 1 will preform any and all changes for version 2 and 3 as well.
I found the following note at this website from Apple.
When a user downloads an app update, iTunes installs the update in a new app directory. It then moves the user’s data files from the old installation over to the new app directory before deleting the old installation. Files in the following directories are guaranteed to be preserved during the update process:
/Documents
/Library
Although files in other user directories may also be moved over, you should not rely on them being present after an update.
In every version you release, you can put a txt file with a unique name (unique for every version) in one of these update-persistent directories and check for the previous version txt file(s) at initial launch of application. This should work even in the case where your application was not launched between the download and an initial update.
Every time your application is launched, the following function in your appDelegate class gets called after the launching process is complete:
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
This is a point where you can check the version of the application, probably using somoething like:
[[[NSBundle mainBundle] infoDictionary] objectForKey:#"CFBundleShortVersionString"]
I'm a bit late to the party, but if this is still an issue, I use a saved Boolean to see if this is the app's first launch:
if (![[NSUserDefaults standardUserDefaults] boolForKey:#"HasLaunchedOnce"]) {
NSLog(#"First launch");
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setBool:YES forKey:#"HasLaunchedOnce"];
[defaults synchronize];
}
I can then deal with an install or update as you already mention in your question.
I think this could be useful: MTMigration manages blocks of code that need to run once on version updates in iOS apps. This could be anything from data normalization routines, "What's New In This Version" screens, or bug fixes.
It is available through CocoaPods: pod 'MTMigration'
Please take a look to MTMigration repository at GitHub (https://github.com/mysterioustrousers/MTMigration) for usage and examples.
[MTMigration applicationUpdateBlock:^{
/* This code run on every version change. */
}];
[MTMigration migrateToVersion:#"1.0" block:^{
/* This code only run once in version 1.0 */
}];
[MTMigration migrateToVersion:#"2.0" block:^{
/* This code only run once in version 2.0 */
}];
If a user was at version 1.0 , skipped 2.0 , and upgraded to 3.0 , then both the 1.0 and 2.0 blocks would run.
Is there any way to [edit] have a fresh [/edit] redeploy your app from xcode onto the iOS simulator when you hit run, without have to delete [edit] the existing data from the app [/edit] every time?
If you want it to start with empty data each time, you can use the Xcode organizer to take an application data snapshot of a freshly started app, add the snapshot to your project and then edit your scheme and go to Run -> Options and select the snapshot in from the Application Data popup menu. That way when the app is started, it always starts with the same, presumably empty, data.
I don't think it clears NSUserDefaults, though.
You shouldn't have to delete it every time… you should just be able to hit run and it will overwrite or otherwise update the version that is loaded there.
I normally have a debug button to remove all data and add in fake data if necessary and wrap them in a DEBUG flag. This allows me to control it without having to restart the app.
- (void)viewDidLoad
{
#if DEBUG
// Make Button and hook it to clearAllData
#endif
}
- (void)clearAllData
{
// Clear the Core Data databases
SDAppDelegate *myDelegate = [[UIApplication sharedApplication] delegate];
[myDelegate clearAllData];
// Clear the UserDefaults
[[NSUserDefaults standardUserDefaults] removePersistentDomainForName:[[NSBundle mainBundle] bundleIdentifier]];
// Run the initial Setup again
[self setup];
}