Hi i am using iCloud support for my application and using UIDocument for storage. I was able to save data to iCloud and fetch the same for first time. But when i deleted and reinstalled the app on device, the seems to crash with EXC BAD ACCESS while trying to unarchive data using NSKeyUnarchiver.
Code.
BuyerDocument.m // UIDocument Subclass
// Accessor for BuyerData
- (BuyerData *)data {
if (_data == nil) {
if (self.fileWrapper != nil) {
self.data = [self decodeObjectFromWrapperWithPreferredFilename:BUYER_FILENAME]; // BUYER_FILENAME = #"buyer.data"
} else {
self.data = [[BuyerData alloc] init];
}
}
return _data;
}
- (id)decodeObjectFromWrapperWithPreferredFilename:(NSString *)preferredFilename {
NSFileWrapper * fileWrapper = [self.fileWrapper.fileWrappers objectForKey:preferredFilename];
if (!fileWrapper) {
NSLog(#"Unexpected error: Couldn't find %# in file wrapper!", preferredFilename);
return nil;
}
if([fileWrapper isRegularFile]){
NSLog(#"is regular wrapper");
}
NSData * data = [fileWrapper regularFileContents];
NSLog(#"data %#",data); // This logs successfully
// NSLog(#"data bytes %#",[data bytes]) // This also causes app to crash.
NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data]; // App will crash here
return [unarchiver decodeObjectForKey:#"data"];
}
Loading BuyerDocument here.
- (void)loadDocAtURL:(NSURL *)fileURL {
// Open doc so we can read metadata
BuyerDocument * doc = [[BuyerDocument alloc] initWithFileURL:fileURL];
[doc openWithCompletionHandler:^(BOOL success) {
.......
BuyerData * data = doc.data;
....
[doc closeWithCompletionHandler:^(BOOL success) {
.....
}];
}
BuyerData.m
#define kVersionKey #"Version"
#define kNameKey #"Name"
- (void)encodeWithCoder:(NSCoder *)encoder {
[encoder encodeInt:1 forKey:kVersionKey];
[encoder encodeObject:self.name forKey:kNameKey];
}
- (id)initWithCoder:(NSCoder *)decoder {
[decoder decodeIntForKey:kVersionKey];
NSString *nameData = [decoder decodeObjectForKey:kNameKey];
// NSLog(#">>>>>>>>>>>>>>>>>>> %#",name); // This logs for first 2-3 files and then crash occurs
return [self initWithName:nameData];
}
As i said when i first added data everything ran fine, it was only after deleting and reinstalling that crash began to occur. Also first 3-4 names are fetched and displayed in tableview before this crash occurs.
Tried this but of no help
I am fairly new to UIDocument and NSCoding. So can't say much about them. But i guess some how the issue might be with linked to NSData getting lost or corrupted out there. Am i missing something basic and important here. What am i doing wrong?
Related
I have a cartArray(in AppDelegate.h #interface above) that need to be saved when the app in background mode or the app closed. The app worked fine when the cartArray has nothing but crashed when I added an item (Cart) in it and entered the background or closed the application by pressing the minus sign. My cartArray contains cart class in it.
May I know what is happening? The tutorial online is so complicated and I always find myself lost in the middle of explanation.
[AppDelegate.m]
- (void)applicationDidEnterBackground:(UIApplication *)application {
[AppDelegate saveData];
}
- (void)applicationWillEnterForeground:(UIApplication *)application {
[AppDelegate getData];
}
+(NSString *) getPathToAchieve{ NSLog(#"+++++getPathToAchieve");
static NSString* docsDir = nil;
if (docsDir == nil) {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
docsDir = [paths objectAtIndex:0];
}
NSString *fullFileName = [NSString stringWithFormat:#"%#.plist", docsDir];
return fullFileName;
}
- (void)applicationWillTerminate:(NSNotification *)notification{
[cartArray writeToFile:[AppDelegate getPathToAchieve] atomically:YES];
}
-(id)initWithCoder:(NSCoder *)aDecoder
{ self = [super init];
if (self != nil)
{
cartArray = [aDecoder decodeObjectForKey:#"cartArrayKeys"];
}
return self;
}
-(void)encodeWithCoder:(NSCoder *)anEncoder
{
[anEncoder encodeObject:cartArray forKey:#"cartArrayKeys"];
}
+(void)saveData{
[NSKeyedArchiver archiveRootObject:cartArray toFile:[self getPathToAchieve] ];
}
+(id)getData{
return [NSKeyedUnarchiver unarchiveObjectWithFile:[self getPathToAchieve]];
}
Your code is pretty messy. First, implement -(id)initWithCoder: and -(void)encodeWithCoder: in your Cart class, not AppDelegate class (and make sure Cart conforms to NSCoding protocol):
- (void) encodeWithCoder:(NSCoder *)encoder {
[encoder encodeObject:self.title forKey:#"title"];
[encoder encodeObject:self.description forKey:#"description"];
.....
}
- (id)initWithCoder:(NSCoder *)decoder {
if (self = [super init]) {
self.title = [decoder decodeObjectForKey:#"title"] ;
self.description = [decoder decodeObjectForKey:#"description"] ;
....
}
return self;
}
Second, implement -(void)saveData and -(void)getData:
-(void)saveData{
[[NSUserDefaults standardUserDefaults] setObject:[NSKeyedArchiver archivedDataWithRootObject:cartArray] forKey:#"cartArray"];
}
-(void)getData{
NSData *savedArray = [[NSUserDefaults standardUserDefaults] objectForKey:#"cartArray"];
if (savedArray != nil)
{
NSArray *oldArray = [NSKeyedUnarchiver unarchiveObjectWithData:savedArray];
if (oldArray != nil) {
cartArray = [[NSMutableArray alloc] initWithArray:oldArray];
} else {
cartArray = [[NSMutableArray alloc] init];
}
}
}
Call saveData when application is going to be terminated / entered background.
Call getData when application has loaded.
do all your saving in
- (void)applicationWillResignActive:(UIApplication *)application
from the documentation:
Tells the delegate that the application is about to become inactive.
This method is called to let your application know that it is about to
move from the active to inactive state. This can occur for certain
types of temporary interruptions (such as an incoming phone call or
SMS message) or when the user quits the application and it begins the
transition to the background state. An application in the inactive
state continues to run but does not dispatch incoming events to
responders. You should use this method to pause ongoing tasks, disable
timers, and throttle down OpenGL ES frame rates. Games should use this
method to pause the game. An application in the inactive state should
do minimal work while it waits to transition to either the active or
background state.
Array is a temporary storage if you want to save some data in permanent then try to use NSUSerDefult. After closing app array may vanish. NSUSerDefult data not vanish. NSUserDefault vanish only when app delete or Remove app from simulator.
I feel like I'm starting to lose my sanity over this issue.
I've begun work on a CoreData iOS app, using the generated CoreData code that the SDK provides. My issue arises whenever I attempt to instantiate a new instance of an entity so that I can save it.
Here's the instantiation code I have, per the Apple CoreData tutorial, inside my AppDelegate (I've moved a bunch of my code there just to try to debug this issue):
NSManagedObjectContext* context = [self managedObjectContext];
if (!context)
{
NSLog(#"Error"); // I'm not too concerned about my error handling just yet
}
Right after that, here's the line that produces the error I'm experiencing:
Vehicle* vehicle = (Vehicle*)[NSEntityDescription insertNewObjectForEntityForName:#"Vehicle" inManagedObjectContext:context];
The error in question is:
Thread 1: EXC_BAD_ACCESS (code=EXC_ARM_DA_ALIGN address=0xdeadbeef)
All in all, I don't really know what that means other than there's a memory alignment issue (common with ARMv7?) and the resources I've found on Google haven't helped me in the slightest.
The only other relevant piece of code is the 'managedObjectContext' method provided by Xcode when it generates the project, because that's what generated the managedObjectContext in the first place:
- (NSManagedObjectContext *)managedObjectContext
{
if (__managedObjectContext != nil) {
return __managedObjectContext;
}
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator != nil) {
__managedObjectContext = [[NSManagedObjectContext alloc] init];
[__managedObjectContext setPersistentStoreCoordinator:coordinator];
}
return __managedObjectContext;
}
Like I said, I'm way out of my depth here. Can anyone offer a bit of clarity as to how I could possibly resolve this?
It is likely that __managedObjectContext was not initialized (hence has value of 0xdeadbeef) which cause EXC_ARM_DA_ALIGN as side effect when try to read value from it.
#Kenny Winker
EXC_ARM_DA_ALIGN is normally come from access pointer value that is not actual type. e.g.
char buf[8];
double d = *((double *)buf); // this may cause EXC_ARM_DA_ALIGN
but it may also be caused invalid valid in pointer, which in this case, 0xdeadbeef. e.g.
double *ptr; // not initialized
double d = *ptr; // this is undefined behaviour, which may cause EXC_ARM_DA_ALIGN or other error
It is generally hard to debug these kind of bugs, here are some tips:
Check all pointer cast (i.e. (double *)(void *)ptr) and try to avoid them when possible.
Make sure everything is initialized.
When it crashed, find out which variable cause it crash and try to trace back to find out where is the value come from. Use debugger to watch a memory location can be helpful to find out all changes to a variable.
I thought it might be helpful to show a core data stack that is in operation. I will also show a part of the object diagram..
pragma mark -
#pragma mark Core Data stack
/**
Returns the managed object context for the application.
If the context doesn't already exist, it is created and bound to the persistent store coordinator for the application.
*/
- (NSManagedObjectContext *)managedObjectContext {
// NSLog(#"%s", __FUNCTION__);
if (managedObjectContext_ != nil) {
return managedObjectContext_;
}
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator != nil) {
managedObjectContext_ = [[NSManagedObjectContext alloc] init];
[managedObjectContext_ setPersistentStoreCoordinator:coordinator];
}
return managedObjectContext_;
}
/**
Returns the managed object model for the application.
If the model doesn't already exist, it is created from a starter file.
*/
- (NSManagedObjectModel *)managedObjectModel {
//NSLog(#"%s", __FUNCTION__);
if (managedObjectModel_ != nil) {
return managedObjectModel_;
}
NSURL *modelURL = [[NSBundle mainBundle] URLForResource:#"DreamCatching" withExtension:#"mom"];
managedObjectModel_ = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
return managedObjectModel_;
}
/**
Returns the persistent store coordinator for the application.
If the coordinator doesn't already exist, it is created and the application's store added to it.
*/
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
//NSLog(#"%s", __FUNCTION__);
if (persistentStoreCoordinator_ != nil) {
return persistentStoreCoordinator_;
}
NSString *storePath = [[self applicationDocumentsDirectory] stringByAppendingPathComponent:#"DreamCatching.sqlite"];
// If the expected store doesn't exist, copy the default store.
//COMMENT / UNCOMMENT THIS TO LOAD / NOT LOAD THE STARTER FILE.
NSFileManager *fileManager = [NSFileManager defaultManager];
if (![fileManager fileExistsAtPath:storePath]) {
NSError *error;
NSString *defaultStorePath = [[NSBundle mainBundle] pathForResource:#"Starter" ofType:#"sqlite"];
if ([[NSFileManager defaultManager] copyItemAtPath:defaultStorePath toPath:storePath error:&error])
NSLog(#"Copied starting data to %#", storePath);
else
NSLog(#"Error copying default DB to %# (%#)", storePath, error);
}
// to below here
NSURL *storeURL = [NSURL fileURLWithPath:storePath];
NSError *error = nil;
persistentStoreCoordinator_ = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
if (![persistentStoreCoordinator_ addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil URL:storeURL options:nil error:&error]) {
NSLog(#"Error - App Delegate Creating DB %#, %#", error, [error userInfo]);
abort();
}
return persistentStoreCoordinator_;
}
#pragma mark -
#pragma mark Application's Documents directory
/**
Returns the path to the application's Documents directory.
NB SETTINGS ARE NOT IN THIS DIRECTORY, THEY ARE IN THE APPS BUNDLE. CONTROL-CLICK THE APP TO SEE CONTENTS, CONTROL-CLICK THE BUNDLE TO SEE THE PREFS
*/
- (NSString *)applicationDocumentsDirectory {
//NSLog(#"%s", __FUNCTION__);
return [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
}
and the model:
In my app, i have Used UiimagepickerController for taking Video.in bettween my programm received any web service Which belongs to my app,
i have to stop Video Capture and save video.
i have Used StopVideoCapture to do above thing ,but it doesn't call delegate - `
(void)imagePickerController:(UIImagePickerController *)picker
didFinishPickingMediaWithInfo:(NSDictionary *)info
How to force call above delegate ??.or How to handle interruption Handling inUIImagePickerController`.. any idea?
The idea with delegate methods is not that you call those methods - "They call you".
So I would not consider calling the delegate method yourself a good practise. However, if you present the UIImagePickerViewController with a modal dialogue (which I guess is common for such a picker) then you can close it like this outside of your delegate method:
[[yourPicker parentViewController] dismissModalViewControllerAnimated:YES];
Source
Update: You can use the ALAssetsLibrary for accessing the stored data in your iPhone media library. I recently had to do a similar project where I had to list all images on the iPhone. The Github project ELCImagePickerController.git was very useful since it shows how the items in your library can be accessed. So you'll do something like this:
#import <AssetsLibrary/AssetsLibrary.h>
// ....
-(void)fetchPhotoAlbums{
if(!self.assetsGroups){
self.assetsGroups = [NSMutableDictionary dictionary];
}
ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
NSMutableArray *returnArray = [[NSMutableArray alloc] init];
#autoreleasepool {
void (^assetGroupEnumerator)(ALAssetsGroup *, BOOL *) = ^(ALAssetsGroup *group, BOOL *stop){
if (group == nil){
// Completed
[self.delegate pictureService:self fetchedAlbums:returnArray];
return;
}
Album *currentAlbum = [self albumForAssetsGroup:group];
// Store the Group for later retrieving the pictures for the album
[self.assetsGroups setObject:group forKey:currentAlbum.identifier];
[returnArray addObject:currentAlbum];
[self.delegate pictureService:self fetchedAlbums:returnArray];
};
void (^assetGroupEnumberatorFailure)(NSError *) = ^(NSError *error) {
NSLog(#"A problem occured %#", [error description]);
};
[library enumerateGroupsWithTypes:ALAssetsGroupAll
usingBlock:assetGroupEnumerator
failureBlock:assetGroupEnumberatorFailure];
}
}
-(void)fetchPhotosForAlbum:(Album *)album{
ALAssetsGroup *currentGroup = [self.assetsGroups objectForKey:album.identifier];
NSMutableArray *photos = [NSMutableArray array];
[currentGroup enumerateAssetsUsingBlock:^(ALAsset *asset, NSUInteger index, BOOL *stop){
if(asset == nil){
[self.delegate pictureService:self fetchedPictures:photos forAlbum:album];
return;
}
[photos addObject:[self pictureForAsset:asset]];
}];
}
Additionally I use two mapper methods to convert the AL-classes into my own model classes.
- (Album *)albumForAssetsGroup:(ALAssetsGroup *)assetsGroup{
Album *album = [[Album alloc] init];
album.title = [assetsGroup valueForProperty:ALAssetsGroupPropertyName];
album.identifier = [assetsGroup valueForProperty: ALAssetsGroupPropertyPersistentID];
album.assetsCount = assetsGroup.numberOfAssets;
album.thumbnail = [UIImage imageWithCGImage:assetsGroup.posterImage];
return album;
}
- (Picture *)pictureForAsset:(ALAsset *)asset{
Picture *picture = [[Picture alloc]init];
picture.identifier = [((NSArray *)[asset valueForProperty: ALAssetPropertyRepresentations]) objectAtIndex:0];
picture.thumbnail = [UIImage imageWithCGImage:asset.thumbnail];
return picture;
}
See the AssetsLibrary Documentation
I have a relativley simple app which persists data to a plist file located in the documents folder. The data loads into a UITableView at startup. The user can then edit, delete or add records and any changes get saved back to the plist file.
Now I would like to share this data (the plist file) across devices using iCloud. I have looked at the documentation and my understanding is that I need to create a UIDocument to "manage" the plist file.
I have looked at several iCloud tutorials however they all store a simple string within a property in the UIDocument class, not an entire file (like a plist).
How do I share my plist file (or any other file, for that matter) to iCloud using the UIDocument object?
Would I convert the plist file contents to NSData, then save that in a property in the UIDocument? Should I be using use NsFileWrapper instead?
I seem to be having a difficult time wrapping my head around the UIDocument/iCloud arrangement. I am probably making this more complicated then it really is.
Not sure if anybody still needs a solution for that but I found a nice way to get this to work.
Since UIDocument only accepts Data as NSData or NSFilewrapper, I first created a Category for the NSDictionary Class that returns a NSDictionary from NSData. Here's the two files for the Category:
NSDictionary+DictFromData.h:
#import <Foundation/Foundation.h>
#interface NSDictionary (DictFromData)
+ (id)dictionaryWithData:(NSData *)data;
- (id)initWithData:(NSData *)data;
#end
and the NSDictionary+DictFromData.m
#import "NSDictionary+DictFromData.h"
#implementation NSDictionary (DictFromData)
+ (id)dictionaryWithData:(NSData *)data {
return [[[NSDictionary alloc] initWithData:data] autorelease];
}
- (id)initWithData:(NSData *)data {
NSString *tmp = nil;
self = (NSDictionary *)[NSPropertyListSerialization
propertyListFromData:data
mutabilityOption:NSPropertyListImmutable
format:NULL
errorDescription:&tmp];
NSAssert1(tmp == nil,#"Error in plist: %#",tmp);
return [self retain];
}
#end
(source)
If you now import this Category in your UIDocument Subclass, you can easily load and save your Plist File to your iCloud container.
To load your Plist from iCloud add this to your UIDocument subclass (The Property contents is an NSDictionary):
- (BOOL)loadFromContents:(id)contents
ofType:(NSString *)
typeName error:(NSError **)outError {
if ([contents length] > 0){
self.contents = [NSDictionary dictionaryWithData:contents];
} else {
self.contents = nil;
}
// call some Methods to handle the incoming NSDictionary
// maybe overwrite the old Plist file with the new NSDictionary
return YES;
}
For saving your Data back to the iCloud add this:
- (id)contentsForType:(NSString *)typeName error:(NSError **)outError {
NSData * plistData = [[[NSData alloc]initWithContentsOfFile:YOUR_PLIST_FILE]autorelease];
return plistData;
}
If you now call:
[myUIDocument updateChangeCount:UIDocumentChangeDone];
YOUR_PLIST_FILE is getting synchronized. Remember that it takes about 10-15sec for your iCloud Container to update.
To use a plist with UIDocument, you can subclass UIDocument and override the following 2 methods with self.myDictionary (your plist) declared as a NSMutableDictionary.
- (BOOL)loadFromContents:(id)contents ofType:(NSString *)typeName error:(NSError **)outError
{
if ([contents length] > 0)
{
NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:(NSData *)contents];
NSMutableDictionary *dataDictionary = [unarchiver decodeObjectForKey:#"data"];
self.myDictionary = dataDictionary;
[unarchiver finishDecoding];
[unarchiver release];
}
else
{
self.myDictionary = [NSMutableDictionary dictionary];
}
return YES;
}
- (id)contentsForType:(NSString *)typeName error:(NSError **)outError
{
NSMutableData *data = [[[NSMutableData alloc] init] autorelease];
NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
if( !self.myDictionary )
{
self.myDictionary = [NSMutableDictionary dictionary];
}
[archiver encodeObject:self.myDictionary forKey:#"data"];
[archiver finishEncoding];
[archiver release];
return data;
}
I Have a small problem with NSCoding and NSKeyedArchiver when persisting my App Settings class and was hoping someone could spot an issue if it's my code, I am relatively new to Obj-C but have plenty of coding experience in numerous languages so the code seems ok to me but...
I have an instance class to hold my app Settings, the class itself is a retained property of the main App Delegate, this is created via a
#interface AppDelegate : NSObject <UIApplicationDelegate> {
Settings *settings;
[...]
}
#property (nonatomic, retain) Settings *settings;
[...]
This is created in the applicationDidFinishLaunching event as:
settings = [Settings LoadSettings];
If I comment out the above line then the app works fine every time, however if I pull the oject back from persisted settings using NSCoder and NSKeyedUnarchiver, the SIGARBT error is thrown as a NSCFString selector is being sent for what is encoded as a boolean property? The Settings Class is defined as an NSObject which implements the protocol.
#interface Settings : NSObject <NSCoding>
As I say, creating an instance of the settings class is fine, there is no issue, saving it seems OK as well as checking the returning class from the LoadSettings method shows the right values, only after exiting the method does the expected bool value seem to be getting sent to the load method as an NSCFString
- (id)initWithCoder:(NSCoder *)decoder {
if (self = [super init]) {
[...]
self.animateMenus = [decoder decodeBoolForKey:#"animateMenus"];
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)encoder {
[...]
[encoder encodeBool:animateMenus forKey:#"animateMenus"];
}
Once the settings have been loaded the property in question is used like this:
SettingsViewController *settingsView = [[SettingsViewController alloc] initWithNibName:#"SettingsView" bundle:nil];
[self presentModalViewController:settingsView animated:[AppDelegate instance].settings.animateMenus];
[settingsView release];
**The animateMenus member of the settings class will now throw the following:
-[NSCFString animateMenus]: unrecognized selector sent to instance 0xc712570 2010-10-15 11:12:51.828 App[900:207] * Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[NSCFString animateMenus]: unrecognized selector sent to instance 0xc712570'
Whereas, taking the 'settings = [Settings LoadSettings];' call out of the app start-up removes the issue (but then always uses the app defaults)?
Load and Save Methods:
+ (Settings*) LoadSettings {
Settings *s = nil;
#try {
NSData *data = [[NSUserDefaults standardUserDefaults] objectForKey:#"settings"];
if (data == nil) {
s = [[Settings alloc] init];
[s Initialise];
[s SaveSettings];
}
else
s = (Settings*)[NSKeyedUnarchiver unarchiveObjectWithData:data];
}
#catch (NSException * e) {
NSLog(#"Error Loading Settings\n%#", [e reason]);
}
#finally {
return s;
}
}
// Saves the settings dictionary to the user's device documents folder..
- (void) SaveSettings {
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:self];
[[NSUserDefaults standardUserDefaults] setObject:data forKey:#"settings"];
}
Load is a static method all other members of Settings are instance.
You need to do:
settings = [[Settings LoadSettings] retain];
That's because in your LoadSettings, the result of NSKeyedUnarchiver is an autoreleased object. And it got released :-) At that location a new object was created, an NSString in this case.
Edit:
Well, I just noticed a major problem with LoadSettings that I missed at first: you mix memory release strategies: in one codepath you return the result of [[Settings alloc] init] which is not autoreleased, while in the other you retain the result of NSKeyedArchiver which is autoreleased. You need to make sure only one concept is used.
Since the method name LoadSettings does not contain alloc, copy or new in its name the convention is that it should return an autoreleased object. Thus, you should do:
if (data == nil) {
s = [[Settings alloc] init];
[s Initialise];
[s SaveSettings];
[s autorelease];
}
else
...