Don't Backup to iCloud but still rejected - objective-c

In my app i have to store Core Data Database and audio files, so i decoded to put them in Documents directory.
To prevent them from backing up, when i first launch the app, i put the Don't BackUp flag like this
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
[self addSkipBackupAttributeToItemAtURL:[self applicationDocumentsDirectory]];
}
- (NSURL *)applicationDocumentsDirectory
{
return [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
}
- (BOOL)addSkipBackupAttributeToItemAtURL:(NSURL *)URL
{
if (&NSURLIsExcludedFromBackupKey == nil) { // iOS <= 5.0.1
const char* filePath = [[URL path] fileSystemRepresentation];
const char* attrName = "com.apple.MobileBackup";
u_int8_t attrValue = 1;
int result = setxattr(filePath, attrName, &attrValue, sizeof(attrValue), 0, 0);
return result == 0;
} else { // iOS >= 5.1
return [URL setResourceValue:[NSNumber numberWithBool:YES] forKey:NSURLIsExcludedFromBackupKey error:nil];
}
}
But it seems like it doesn't work - i still get rejected:
We found that your app does not follow the iOS Data Storage Guidelines, which is required per the App Store Review Guidelines.
In particular, we found that on launch and/or content download, your
app stores 3.6 MB. To check how much data your app is storing:
Install and launch your app
Go to Settings > iCloud > Storage & Backup > Manage Storage
If necessary, tap "Show all apps"
Check your app's storage
And the other problem is that i just can't check that - i don't see my app in
Settings > iCloud > Storage & Backup > Manage Storage
Maybe the problem is only with 5.0 that i kind of not think about here?

The problem is with iOS 5.0, in this iOS you should not put the dont backup flag
The dont back up flag was introduced in ios 5.0.1
We did face similar problem with our app, it has been rejected several times
So we had to do a work around to handle different iOSes
We needed to support iOS < 5.0, iOS 5.0, and iOS > 5.0
So after contacting apple, we didnt find any solution except to have different paths on different iOSes
We had a function like this:
+ (NSString*) savePath
{
NSString *os5 = #"5.0";
NSString *currSysVer = [[UIDevice currentDevice] systemVersion];
NSString *path = [NSHomeDirectory() stringByAppendingPathComponent:#"Documents"];
if ([currSysVer compare:os5 options:NSNumericSearch] == NSOrderedAscending) //lower than 4
{
return path;
}
else if ([currSysVer compare:os5 options:NSNumericSearch] == NSOrderedDescending) //5.0.1 and above
{
return path;
}
else // IOS 5
{
path = [NSHomeDirectory() stringByAppendingPathComponent:#"Library/Caches"];
return path;
}
return nil;
}
We used and still use this function.
Please read more
iOS 5.0
It is not possible to exclude data from backups on iOS 5.0. If your
app must support iOS 5.0, then you will need to store your app data in
Caches to avoid that data being backed up. iOS will delete your files
from the Caches directory when necessary, so your app will need to
degrade gracefully if it's data files are deleted.
http://developer.apple.com/library/ios/#qa/qa1719/_index.html

Related

iOS 9.2 - unable to see defaults registered from Settings.bundle in device Settings app

I have a project which uses Settings.bundle including root.plist containing a list of key value pairs I want to register with user defaults. Until recently, these values were visible and editable from the device's "Settings" App. Now I can't see anything when tapping on my app in settings - the details panel is empty.
How can I make sure my key-value pairs from the Settings.bundle provided with app properly display in the device's settings app?
Edit: It seems that restarting the settings app fixes the issue, but the details pane goes blank again if I redeploy the app from Xcode. Is it something with the new version of iOS that I'm not aware of?
Here's my code to register defaults:
- (void)registerDefaultsFromSettingsBundle {
NSString *settingsBundle = [[NSBundle mainBundle] pathForResource:#"Settings" ofType:#"bundle"];
if(!settingsBundle) {
DLog(#"Could not find Settings.bundle");
return;
}
NSDictionary *settings = [NSDictionary dictionaryWithContentsOfFile:[settingsBundle stringByAppendingPathComponent:#"Root.plist"]];
NSArray *preferences = [settings objectForKey:#"PreferenceSpecifiers"];
NSMutableDictionary *defaultsToRegister = [[NSMutableDictionary alloc] initWithCapacity:[preferences count]];
for(NSDictionary *prefSpecification in preferences) {
NSString *key = [prefSpecification objectForKey:#"Key"];
if(key && [[prefSpecification allKeys] containsObject:#"DefaultValue"]) {
id object = [prefSpecification objectForKey:#"DefaultValue"];
if(object != nil)
{
[defaultsToRegister setObject:object forKey:key];
}
}
}
[[NSUserDefaults standardUserDefaults] registerDefaults:defaultsToRegister];
}
This is an Apple bug in iOS 9.2 and Simulator 9.2

iCloud key-value sync iOS8 Xcode 6

Trying to fix some issue with iCloud. Here are two versions of my code.
- (void)viewDidLoad{
[super viewDidLoad];
[self checkICloudData];
}
version 1
- (void)checkICloudData
{
NSFileManager * fileManager=[NSFileManager defaultManager];
NSURL *iCloudURL=[fileManager URLForUbiquityContainerIdentifier:nil];
NSLog(#"iCloud URL is %#",[iCloudURL absoluteString]);
if (iCloudURL){
NSUbiquitousKeyValueStore * store=[NSUbiquitousKeyValueStore defaultStore];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(updateICloudData:)
name:NSUbiquitousKeyValueStoreDidChangeExternallyNotification
object:store];
[store synchronize];
}else{
NSLog(#"iCloud is not supported or enabled");
[self loadDataFromBundle];
}
}
iCloudURL always returns nil. Other methods do not call.
version 2
- (void)checkICloudData
{
NSUbiquitousKeyValueStore * store=[NSUbiquitousKeyValueStore defaultStore];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(updateICloudData:)
name:NSUbiquitousKeyValueStoreDidChangeExternallyNotification
object:store];
[store synchronize];
}
- (void)updateICloudData:(NSNotification*)notification
{
NSDictionary *userInfo = [notification userInfo];
NSNumber *changeReason = [userInfo objectForKey:NSUbiquitousKeyValueStoreChangeReasonKey];
NSInteger reason = -1;
if (!changeReason) {
return;
} else {
reason = [changeReason integerValue];
}
if ((reason == NSUbiquitousKeyValueStoreServerChange) || (reason == NSUbiquitousKeyValueStoreInitialSyncChange)) {
NSArray *changedKeys = [userInfo objectForKey:NSUbiquitousKeyValueStoreChangedKeysKey];
for (NSString *key in changedKeys) {
if ([key isEqualToString:casesKey]) {
[self iCloudNotification];
}else {
[self loadDataFromBundle];
}
}
}
}
With that version iCloud sync works fine with the iPad, but doesn't work with the iPhone. In the iPhone's iCloud Drive settings i see my app same as in the iPad
My iCloud settings:
Member center - iCloud Enabled. Compatibility - Include CloudKit support
Target Capabilities - Services checked only Key-Value storage
Created by default entitlements dictionary contains key com.apple.developer.icloud-container-identifiers with an empty array and key com.apple.developer.ubiquity-kvstore-identifier with a correct string value of my appID
So, why iCloudURL always returns nil? And why the second version works correct with the iPad but my iPhone does not see NSUbiquitousKeyValueStoreDidChangeExternallyNotification?
Well, the problem was in my iPhone after upgrade iOS.
When I've delete iCloud profile from iPhone's settings and add it again, the iPhone started to sync with iCloud.
Right now the second version of the code works with both devices.
Hope it helps somebody to solve that kind of problem.

Issues in using CNCopyCurrentNetworkInfo

I am using the below code to retrieve the SSID of the WiFi network the iPod is connected.
NSArray *ifs = (id)CNCopySupportedInterfaces();
NSLog(#"%s: Supported interfaces: %#", __func__, ifs);
id info = nil;
for (NSString *ifnam in ifs) {
info = (id)CNCopyCurrentNetworkInfo((CFStringRef)ifnam);
NSLog(#"%s: %# => %#", __func__, ifnam, info);
if (info && [info count]) {
break;
}
[info release];
}
Sometimes this code is not returning the proper SSID of the network my device is connected.Any pointers on why the SSID is not retrieved correctly? Does CNCopyCurrentNetworkInfo package dependent on the iOS version of the device?
Thanks.
add SystemConfiguration.framework to project.
import < SystemConfiguration/CaptiveNetwork.h >
CFArrayRef myArray = CNCopySupportedInterfaces();
CFStringRef interfaceName = CFArrayGetValueAtIndex(myArray, 0);
CFDictionaryRef captiveNtwrkDict = CNCopyCurrentNetworkInfo(interfaceName);
NSDictionary *dict = ( NSDictionary*) captiveNtwrkDict;
NSString* ssid = [dict objectForKey:#"SSID"];
NSLog(#"%s ssid : %#",__FUNCTION__, [ssid description]);
For iOS 12 and later, you must enable it from capabilities.
Important
To use this function in iOS 12 and later, enable the Access WiFi Information capability for your app in Xcode. When you enable this capability, Xcode automatically adds the Access WiFi Information entitlement to your entitlements file and App ID. Documentation link
Yes. CNCopyCurrentNetworkInfo is available only in iOS 4.1 and later.
For more info ,please look at the developer.apple SystemConfiguration Reference
you can check the sample code here

Memory warning in the iPad 1st generation and crash

i am developing an iPad application witch has a functionality downloading books. The books size are about 180 Mo. The books are in server and the have the extension .zip. I download the book (.zip) then i unzip it and then remove the .zip. I am doing like this :
- (BOOL)downloadBookWithRequest:(BookDownloadRequest*)book
{
if (![book isValid])
{
NSLog(#"Couldn't launch download since request had missing parameter");
return NO;
}
if ([self bookIsCurrrentlyDownloadingWithID:book.ID]) {
NSLog(#"Book already downloaded");
return NO;
}
ASIHTTPRequest *download = [[ASIHTTPRequest alloc] initWithURL:book.URL];
download.userInfo = book.dictionary;
download.downloadDestinationPath = [self downloadPathForBookID:book.ID];
download.downloadProgressDelegate = self.downloadVC.downloadProgress;
download.shouldContinueWhenAppEntersBackground = YES;
[self.downloadQueue addOperation:download];
[download release];
// Update total requests
self.requestsCount++;
[self refreshDownloadsCount];
if(self.downloadQueue.isSuspended)
[self.downloadQueue go];
[self.downloadVC show];
return YES;
}
- (void)requestFinished:(ASIHTTPRequest*)request
{
NSString *bookStoragePath = [[BooksManager booksStoragePath] stringByAppendingPathComponent:request.downloadDestinationPath.lastPathComponent.stringByDeletingPathExtension];
NSString *bookZipPath = request.downloadDestinationPath;
// Tell not to save the zip file into iCloud
[BooksManager addSkipBackupAttributeToItemAtPath:bookZipPath];
NSFileManager *fileManager = [NSFileManager defaultManager];
NSError *removeExistingError = nil;
if ([fileManager fileExistsAtPath:bookStoragePath])
{
[fileManager removeItemAtPath:bookStoragePath error:&removeExistingError];
if (removeExistingError)
{
[self bookDidFailWithRequest:request errorMessageType:DownloadErrorTypeFILE_ERROR];
NSLog(#"ERROR: Couldn't remove existing book to unzip new download (%#)", removeExistingError);
} else
NSLog(#"INFO: Removed existing book to install new download");
}
ZipArchive* zip = [[ZipArchive alloc] init];
if([self isCompatibleWithFileAtPath:bookZipPath] && [zip UnzipOpenFile:bookZipPath])
{
BOOL unzipSucceeded = [zip UnzipFileTo:bookStoragePath overWrite:YES];
if (!unzipSucceeded)
{
[self bookDidFailWithRequest:request errorMessageType:DownloadErrorTypeFILE_ERROR];
NSLog(#"ERROR: Couldn't unzip file %#\n to %#",bookZipPath,bookStoragePath);
} else {
[self bookDidInstallWithRequest:request];
NSLog(#"INFO: Successfully unziped downloaded file");
}
[zip UnzipCloseFile];
}
else
{
[self bookDidFailWithRequest:request errorMessageType:DownloadErrorTypeFILE_ERROR];
NSLog(#"ERROR: Unable to open zip file %#\n",bookZipPath);
}
[self removeZipFileAtPath:bookZipPath];
[zip release];
}
-(BOOL) removeZipFileAtPath:(NSString*) bookZipPath {
NSFileManager *fileManager = [NSFileManager defaultManager];
if ([fileManager fileExistsAtPath:bookZipPath])
{
NSError *removeZipFileError = nil;
[fileManager removeItemAtPath:bookZipPath error:&removeZipFileError];
if (removeZipFileError) {
NSLog(#"ERROR: Couldn't remove existing zip after unzip (%#)", removeZipFileError);
return NO;
}
else {
NSLog(#"INFO: Removed zip downloaded after unzip");
return YES;
}
}
return NO;
}
My Problem is : This code is working fine with iPhone 4/iPhone 4s/ iPad 2G /iPad3G, but it crash with an iPad 1st Generation (when unzipping the book) and the crash reporter says that the are Memory warning.
How question is, how i can optimize this code to avoid the memory warning and avoid the crash ? Thanks for your answers;
Edit : I have found that the problem is caused by this portion of code :
NSData *bookData = [[NSData alloc]initWithContentsOfFile:bookPath];
the bookPath is the path to the .zip ( about 180 Mo) and when i am in iPad 1G this line crash my application i.e. : i receive memory warnings and the system kill the App. Du you know how i can avoid this. I an using this line to calculate the MD5 of the book (.zip)
I have a category in NSData like this :
#import <CommonCrypto/CommonDigest.h>
#implementation NSData(MD5)
- (NSString*)MD5
{
// Create byte array of unsigned chars
unsigned char md5Buffer[CC_MD5_DIGEST_LENGTH];
// Create 16 byte MD5 hash value, store in buffer
CC_MD5(self.bytes, self.length, md5Buffer);
// Convert unsigned char buffer to NSString of hex values
NSMutableString *output = [NSMutableString stringWithCapacity:CC_MD5_DIGEST_LENGTH * 2];
for(int i = 0; i < CC_MD5_DIGEST_LENGTH; i++)
[output appendFormat:#"%02x",md5Buffer[i]];
return output;
}
How i can avoid the crash ? thanks
EDIT:
So, it seems that the culprit is loading into memory the whole file in order to calculate its MD5 hash.
The solution to this would be calculating the MD5 without having to load into memory the whole file. You can give a look at this post explaining how to compute efficiently an MD5 or SHA1 hash, with the relative code. Or if you prefer, you can go directly to github and grab the code.
Hope it helps.
OLD ANSWER:
You should inspect your app, especially the ZipArchive class, for memory leaks or not-released memory. You can use Instruments' Leaks and Memory Allocation tools to profile your app.
The explanation of the different behavior between iPad1 and the rest of devices may lay with their different memory footprint, as well as with different memory occupation states of the devices (say, you iPad 1 has less free memory when you run the app then the iPad 2 because of the state other apps you ran on the iPad 1 left the device in). You might think of rebooting the iPad 1 to inspect its behavior out of a fresh start.
In any case, besides the possible explanation of the different behaviors, the ultimate cause is how your app manages memory and Instruments is the way to go.
I don't agree with Sergio.
You are saying the app crashes when you init the NSData object with a 180mb zip archive.
Well it's natural you run out of memory, since the 1st gen iPad has half the memory of the 2nd gen... (256MB vs 512)
The solution is to split the zip archive in smaller parts and process them one by one.

Method [[UIDevice currentDevice] uniqueIdentifier] is not allowed any more, I need an alternative

I'm using [[UIDevice currentDevice] uniqueIdentifier] in all of my apps, Apple is not allowing the use of uniqueIdentifier anymore.
I need something to use that replaces the uniqueIdentifier which I can use to recognize a user even when the user deletes the app and installs it again, (and also get my app approved by apple).
Thanks
The documentation recommends what to do in this section.
Special Considerations
Do not use the uniqueIdentifier property. To create a unique identifier specific to your app, you can
call the CFUUIDCreate function to create a UUID, and write it to the
defaults database using the NSUserDefaults class.
To make sure that the unique identifier remains after you delete the app you should store it in the keychain rather than NSUserDefaults. Using the keychain you will also be able to share the same unique ID across all of your apps on the same device using keychain access groups. This approach will prevent you from incorrectly tracking users after the device is no longer theirs, and it will be available on any new iDevice they restore from backup.
Update for iOS 7 and prior:
+ (NSString *)uniqueDeviceIdentifier
{
NSString *device_id = nil;
if ([[self deviceModel] isEqualToString:#"Simulator iOS"]) {
// static id for simulator
device_id = #"== your random id ==";
}
else if (CurrentIOSVersion >= 6.f) {
// iOS 6 and later
device_id = [[[UIDevice currentDevice] identifierForVendor] UUIDString];
}
else {
// iOS 5 and prior
SEL udidSelector = NSSelectorFromString(#"uniqueIdentifier");
if ([[UIDevice currentDevice] respondsToSelector:udidSelector]) {
device_id = [[UIDevice currentDevice] performSelector:udidSelector];
}
}
NSLog(#">>>>>> device_id: %#", device_id);
return device_id;
}
Device model you can receive through:
+ (NSString*)deviceModel
{
static NSString *device_model = nil;
if (device_model != nil)
return device_model;
struct utsname systemInfo;
uname(&systemInfo);
NSString *str = #(systemInfo.machine);
return device_model;
}
with digipeople's hack.
Fix it to avoid app crash.
systemId = [[NSUUID UUID] UUIDstring];
http://developer.apple.com/library/mac/documentation/Foundation/Reference/NSUUID_Class/Reference/Reference.html#//apple_ref/occ/instm/NSUUID/UUIDString