I've been stuck on this for approximately two weeks. I hate posting things that have been asked a lot but I really have gone through them all.
I used Ray Wenderlich's tutorial for saving data in an iPhone app.
http://www.raywenderlich.com/tutorials
So that is the setup I have going on in my app. I'm saving very simple objects. My Card object consists of a name, type, and image. That's all. So the tutorial is quite close to mine. Which is making this more frustrating.
The thing is, I have some NSLog statements in there for loading. I have it displaying the folder it's using to load and what objects it does load. Right now it is displaying this.
Loading cards from /Users/zach/Library/Application Support/iPhone Simulator/7.0.3-64/Applications/E3DB01FD-A37E-4A69-840B-43830F2BDE2C/Library/Private Documents
2013-11-04 00:02:50.073 CardMinder[84170:a0b] ()
So it seems to be trying to load them, but there's nothing there to load. Here is my function to save data.
- (void)saveData {
if (_data == nil) return;
[self createDataPath];
NSString *dataPath = [_docPath stringByAppendingPathComponent:kDataFile];
NSMutableData *data = [[NSMutableData alloc] init];
NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
[archiver encodeObject:_data forKey:kDataKey];
[archiver finishEncoding];
NSLog(#"%#",dataPath);
NSLog(#"%#",data);
[data writeToFile:dataPath atomically:YES];
}
Which is really just what's posted in that tutorial. I know if you feel generous enough to help me out i'll have to post some more code but I don't want to flood the post with useless stuff so just let me know and i'll get it out here.
I really appreciate anyone that can help, I have recently entered the desperation state and need help.
Thanks
UPDATE
NSError *error;
[data writeToFile:dataPath options:NSDataWritingAtomic error:&error];
NSLog(#"error: %#", error.localizedFailureReason);
These are the methods for the CardData class. I'm doing the name, type, and a bool here.
- (void)encodeWithCoder:(NSCoder *)aCoder
{
[aCoder encodeObject:_name forKey:kNameKey];
[aCoder encodeObject:_cardType forKey:kTypeKey];
[aCoder encodeBool:_checkedOut forKey:kOutKey];
}
- (id)initWithCoder:(NSCoder *)aDecoder
{
NSString *name = [aDecoder decodeObjectForKey:kNameKey];
NSString *cardType = [aDecoder decodeObjectForKey:kTypeKey];
BOOL checkedOut = [aDecoder decodeBoolForKey:kOutKey];
return [self initWithName:name cardType:cardType _Bool:checkedOut];
}
UPDATE 2
I just put some more NSLog statements in and I found out that when I press the "Save card" button in my app, it doesn't seem to execute the saveData function at all. I have log statements galore in that saveData function and when I click the saveCard button it doesn't show any of those logs. Why would that be happening?
This is my saveButton code.
- (IBAction)saveNewCard:(id)sender
{
NSString *cardName = self.nameField.text;
_cardDoc.data.name = cardName;
CardDoc *newCard = [[CardDoc alloc] initWithName:cardName cardType:cardTypeString _Bool:NO image:chosenIcon];
[_cardDoc saveData];
NSLog(#"Card save button pressed!");
CardViewController *cardViewController = (CardViewController *)[self.navigationController.viewControllers objectAtIndex:self.navigationController.viewControllers.count-2];
[cardViewController.cards addObject:newCard];
[self.navigationController popToRootViewControllerAnimated:YES];
}
You should use writeToFile:options:error: instead of writeToFile:atomically:; that will give you an error message that should prove helpful. (The equivalent to atomically:YES is the option constant NSDataWritingAtomic.) Make sure you're getting back a return value of YES; if not, the error should be set.
If you're getting a value of NO but the error is not set, it means you're messaging nil. A quirk of Objective-C is that messaging nil is completely valid. If the method is defined to return something, you'll even get a result: 0 or equivalent (NO, nil, etc.)
In this case, you're messaging _cardDoc. There's no return result to detect. This is a bit harder to defensively code around, but [_cardDoc saveData] is actually [nil saveData]. The debugger will just breeze past the line.
Generally, if something absolutely should not be nil, you can use NSAssert:
NSAssert(_cardData, #"_cardData should not be nil");
[_cardData saveData];
But use this sparingly; you'll probably come to usually appreciate this behaviour.
A few things.
Post the results of your log statements so we know what you are seeing.
In order for your approach to work, your _data object needs to conform to the NSCoding protocol. That means you need to add the protocol declaration to your interface, and implement the methods encodeWithCoder and initWithCoder.
In those methods you need to save all the state data for your object / load the state back into your object.
Those methods are the most likely source of problems with your code. Post those methods if you need help with them, and walk though them in the debugger.
You might also look at the NSKeyedArchvier class method archivedDataWithRootObject. That method takes an object and encodes it into an NSData object in one step. The method archiveRootObject:toFile: take it a step further, and writes the data directly to a file for you.
NSKeyedUnarchiver has the corresponding methods unarchiveObjectWithData and unarchiveObjectWithFile to recreate your object from data/a file.
Related
I'm currently stuck with a weird problem with core data. The app I'm writing downloads a bunch of data from the server, which is translated into core data objects and stored. The device can also create new objects and upload them to the server. One of these objects is a Document which is essentially a representation of a file.
The model for this is MPDocument. A document can also be linked to an MPPlace model, and an MPUser model (users create documents, and documents belong to places).
I'm having no problem downloading the objects from the server, and all relationships are being created and assigned correctly. The problem lies when I try to create a new document on the device itself. The document gets created, and I set all of the relationships, the document gets uploaded and everything seems fine. But when I check the database through a core-data viewer tool, all of the document objects have no value for the place relationship. This happens to all the existing documents, not just the new one. I really can't figure out what's going on!
I'm creating the document like so :
MPUser *current = [MPUser currentUser];
MPDocument *doc = [[MPDocument alloc] init];
doc.name = #"App Upload";
doc.local_url = [NSString stringWithFormat:#"%#", [info valueForKey:UIImagePickerControllerReferenceURL]];
doc.local_url_type = #(MPDocumentUrlTypeAsset);
doc.user = current;
[current addCreatedDocumentsObject:doc];
[doc setValue:self.place forKey:#"place"];
[self.place addDocumentsObject:doc];
I then have a document uploader which handles all uploading :
MPDocumentUploader *uploader = [[MPDocumentUploader alloc] initWithDocument:doc];
uploader.requestDelegate = self;
uploader.successBlock = ^(MPDocumentUploader *uploader, MPDocument *doc) {
NSLog(#"Got doc = %#", doc);
};
[uploader upload];
When the success block is called, the document object DOES have the place relationship set. So even once the upload has finished, the place is set, so I'm really confused now as to where the relationships are being completely cleared.
The document uploader looks something like this :
- (void) upload
{
.... retrieve the local file and turn into NSData. This is fine
MPRequest *request = [MPRequest requestWithURL:_url];
[MPUser signRequest:request];
[request setDelegate:_requestDelegate];
[request setRequestMethod:#"POST"];
[request mountDocumentUploader:self];
[request submit:^(MPResponse *resp, NSError *error) {
if (!error) {
NSDictionary *data = (NSDictionary *)[resp paramForKey:#"data"];
if (data) {
NSLog(#"Document = %#", _document);
_document.url = [data objectForKey:#"url"];
_document.objID = [data objectForKey:#"id"];
[_document saveLocally];
}
if (_successBlock) {
_successBlock(self, _document);
}
} else {
if (_failBlock) {
_failBlock(self, error);
}
}
}];
}
The MPRequest class handles all the actual uploading and server requests, but doesn't actually touch the MPDocument object.
I can't figure out what's going on or why it's clearing out the relationships. Please can someone help!?
Update
I've played around, and found that the error occurs when the submit block is called. Commenting out
_document.url = [data objectForKey:#"url"];
_document.objID = [data objectForKey:#"id"];
[_document saveLocally];
works as it is meant to, but now those values obviously aren't set. Adding either of those lines back in 1 at a time in isolation still causes the problem, so it seems that simply editing it at all is breaking it. Still no clue why though :(
ok well I'm not entirely sure what happened or why, but between fiddling with the code, cleaning the project, and restarting my laptop, it seems to have fixed itself so not a clue what the issue was but it currently seems to be ok. Very confused :S
I'm basically implementing a fancier NSURLConnection class that downloads data from a server parses it into a dictionary, and returns an NSDictionary of the data. I'm trying add a completion block option (in addition to a delegate option), but it crashes anytime I try to store that data in another class.
[dataFetcher_ fetchDataWithURL:testURL completionHandler:^(NSDictionary *data, NSInteger error) {
contentDictionary_ = data;
}];
I can NSLog that data just fine, and basically do whatever I want with it, but as soon as I try to save it into another variable it crashes with a really obscure message.
EDIT: the crash message is EXC_BAD_ACCESS, but the stack trace is 0x00000000 error: address doesn't contain a section that points to a section in a object file.
I'm calling this function in the init method of a singleton. It DOES let me save the data if I set this in the completion block.
[SingletonClass sharedInstance].contentDictionary = data
But then the app gets stuck forever because sharedInstance hasn't returned yet, so the singleton object is still nil, so sharedInstance in the completion block calls init again, over and over.
EDIT 2: The singleton code looks like this:
+ (SingletonClass*)sharedInstance {
static SingletonClass *instance;
if (!instance) {
instance = [[SingletonClass alloc] init];
}
return instance;
}
- (id)init {
self = [super init];
if (self) {
dataFetcher_ = [[DataFetcher alloc] init];
NSString *testURL = #"..."
[dataFetcher_ fetchDataWithURL:testURL completionHandler:^(NSDictionary *data, NSInteger error) {
[SingletonClass sharedInstance].contentDictionary = data;
}];
}
return self;
}
Like I said, this works fine but repeats the initialize code over and over until the app crashes. This only happens the first time I run the app on a device, because I cache the data returned and it doesn't crash once I have the data cached. I would like to be able to just say self.contentDictionary = data, but that crashes.
Specify a variable to be used in the block with the __block directive outside of the block:
__block NSDictionary *contentDictionary_;
[dataFetcher_ fetchDataWithURL:testURL completionHandler:^(NSDictionary *data, NSInteger error) {
contentDictionary_ = data;
}];
You're invoking recursion before ever setting the "instance". (which I now see you understand from OP).
In your block, you can use the ivar or an accessor instead of
[SingletonClass sharedInstance].contentDictionary
use:
_contentDictionary = [data copy]; or self.contentDictionary=data;
assuming that the ivar backing the contentDictionary property is _contentDictionary.
It sounds like you tried self.contentDictionary and it failed? I got it to work in a test, with ARC turned, so there may be something about your dataFetcher that is affecting this. In my test dataFetcher just returns a dictionary with a single element.
Turns out the issue was with a bunch of different parts. My URL was empty sometimes, and my data fetcher would just fail immediately and call the completion block. In my completion block I hadn't included any error handling, so if the singleton class hadn't initialized, it would repeat forever. With a real URL this doesn't happen.
I still would like to figure out why it crashes when I try to assign the data to an ivar, though.
I am newbie on ios programming, so I have a question. I am working on an app, will continue to load data on internet, it's paging data, so when the user navigate to the next page, it will load the data of the page on the internet. I use a singleton class to make it, it works fine, but I had a question -
When the first page is arrival I save it to self.posts variable like - self.posts = dataA, and when the user go for the next page, it will change self.posts to dataB, like self.posts = dataB. my question is, if the dataA will be released by iOS automatically, or it's not? if it's not, how to deal with these garbage memory? You know it will load data page by page, if so many pages being loaded, it might be a problem......Thanks.
Sorry forget to tell you guys, the app is for iOS 3.x+, so I guess ARC is not available. Check this function, it will be called after the HTTP connection is done and will parse JSON to NSDictionary, each time it will load about 5 posts for a page, and next page is another 5 posts, so you know, the self.posts changed if it's another new HTTP networking.
- (void) getNextPostsFromJson:(NSData *)data
{
NSError *theError = nil;
NSDictionary *dict = [[CJSONDeserializer deserializer] deserializeAsDictionary:data error:&theError];
if (dict == nil) {
isValidJson = NO;
httpStatus = HTTP_STATUS_FAILED;
NSLog(#"json con - %# %#",
[theError localizedDescription],
[[theError userInfo] objectForKey:NSURLErrorFailingURLStringErrorKey]);
} else {
NSArray *keys = [dict allKeys];
if ([keys count] >= TOTAL_SECTIONS) {
self.posts = dict;
} else {
self.posts = nil;
}
NSLog(#"posts = %#", self.posts);
return;
}
}
Toaster suggestion is right.
If you use a property like the following.
#property (nonatomic, retain) NSDictionary* posts;
when you do
self.posts = dataB;
the old value referenced object is released for you.
The setter synthesized by the compiler using #synthesize directive looks like this (pseudo code here):
- (void)setPosts:(NSDictionary*)newPosts
{
if(newDict != posts) {
[newPosts retain]; // retain the new value
[posts release]; // release the old value
posts = newPosts; // now posts reference the new value
}
}
A simple suggestion for you.
If you do self.posts = dataB you lose dataA. So when you came back, you need to perform the download again. So, what do you think to have a cache of downloaded data? For example create a NSMutableDictionary where each key is the page (the number of the page or whatever you like) and each value is the data (dataA, dataB and so on). Through it, you can avoid to download data each time. Maybe you can also set up a limit for this cache (say 5 data) to prevent memory issues.
Hope it helps.
It will be released automatically if you use ARC(automatic reference counting) and any other pointer (_strong) point to it. So don't worry :)
As long as you didn't retain dataA manually in addition to setting your property, you don't need to worry about it. Using dot-notation will cause the object set as the property to be retained and to be released again once it is being replaced by some other object.
Edit: the code example you added seems fine to me...
What is the best approach for sending the local player's alias (or any text for that matter) to another device, since I can't put an NSString in my struct because of ARC/pointers?
So far, I've tried converting to & from a char array, using the __unsafe_unretained- option and trying to create a class to put the text in. Though all three of these attempts worked through compiling, they crashed the device (simulator keeps running but no alias displays.)
Is sending text in multiplayer games really difficult when using ARC? The issues I'm facing are most likely a result of the fact that I am not very experienced at programming... so if anyone could point me in the right direction or provide me with some snips of code, I'd really appreciate it.
You can easily encode and decode strings to NSData objects and send them over Game Center.
To encode: use this method on a string
- (NSData *)dataUsingEncoding:(NSStringEncoding)encoding
With encoding:NSUTF8StringEncoding
This will return a NSData object
To decode;
NSString *dataString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
Edit:
Essentially you'll never have to send player aliases explicitly. There are 2 cases:
1: GKTurnBasedMatch
If you're using this, then here's how to get a list of all of the aliases (including yours)
NSMutableArray *playerIds = [NSMutableArray arrayWithCapacity:match.participants.count];
for (GKTurnBasedParticipant *part in match.participants) {
if([participant.playerID isKindOfClass:[NSString class]]){
[playerIds addObject:part.playerID];
}
} //at this point you have an array full of playerID strings, next you call this:
[GKPlayer loadPlayersForIdentifiers:(NSArray *)playerIds withCompletionHandler:(void (^) (NSArray *players, NSError *error))completionHandler {
for (GKPlayer *playa in players) {
NSLog(#"%#",playa.alias); // here i'm just logging the aliases but you can do whatever..
}
}];
2.GKMatch : this case is much easier since your GKMatch has already playerIDs array, same as before:
[GKPlayer loadPlayersForIdentifiers:(NSArray *) match.playerIDs withCompletionHandler:(void (^)(NSArray *players, NSError *error))completionHandler {
//again you get the array players full of GKPlayer objects , simply pull the alias you want
}];
I'm having a lot of trouble deciphering Apple's documentation around UIManagedDocument, specifically the following methods:
- (id)additionalContentForURL:(NSURL *)absoluteURL error:(NSError **)error
- (BOOL)readAdditionalContentFromURL:(NSURL *)absoluteURL error:(NSError **)error
- (BOOL)writeAdditionalContent:(id)content toURL:(NSURL *)absoluteURL originalContentsURL:(NSURL *)absoluteOriginalContentsURL error:(NSError **)error
Has anyone successfully managed to save additional content into the "addition content" directory inside their UIManagedDocument packages? I'm looking to save straight images (PNGs, JPEGs, etc) and videos (m4v, etc) into this directory using UUIDs as the filenames (with the correct file extension), and storing references to these individual files as NSString file paths within my persistent store.
Credit goes to Apple DTS for helping me understand this class. I'm sharing some of the example they helped me with here (modified slightly).
OK, so basically it works like this: subclass UIManagedDocument, and implement the following methods (where the extraInfo property is just an NSDictionary implemented on our subclass):
- (BOOL)readAdditionalContentFromURL:(NSURL *)absoluteURL error:(NSError **)error
{
NSURL *myURL = [absoluteURL URLByAppendingPathComponent:#"AdditionalInformation.plist"];
self.extraInfo = [NSDictionary dictionaryWithContentsOfURL:myURL];
return YES;
}
- (id)additionalContentForURL:(NSURL *)absoluteURL error:(NSError **)error
{
if (!self.extraInfo) {
return [NSDictionary dictionaryWithObjectsAndKeys: #"Picard", #"Captain", [[NSDate date] description], #"RightNow", nil];
} else {
NSMutableDictionary *updatedFriendInfo = [self.extraInfo mutableCopy];
[updatedFriendInfo setObject:[[NSDate date] description] forKey:#"RightNow"];
[updatedFriendInfo setObject:#"YES" forKey:#"Updated"];
return updatedFriendInfo;
}
}
- (BOOL)writeAdditionalContent:(id)content toURL:(NSURL *)absoluteURL originalContentsURL:(NSURL *)absoluteOriginalContentsURL error:(NSError **)error
{
if (content) {
NSURL *myURL = [absoluteURL URLByAppendingPathComponent:#"AdditionalInformation.plist"];
[(NSDictionary *)content writeToURL:myURL atomically:NO];
}
return YES;
}
UIManagedDocument will call these methods when it needs to, automatically saving whatever you need to save to the document package inside an AdditionalContent directory.
If you need to force a save, simply call the following on your UIManagedDocument instance:
[self updateChangeCount:UIDocumentChangeDone];
At present, I'm not using this for images and videos — but the example should give you enough to go off.
The documentation for -additionalContentForURL:error: indicates that returning a nil supposed to signal an error.
A return value of nil indicates an error condition. To avoid generating
an exception, you must return a value from this method. If it is not always
the case that there will be additional content, you should return a sentinel value (for example, an NSNull instance) that you check for in
writeAdditionalContent:toURL:originalContentsURL:error:.
I override -writeContents:andAttributes:safelyToURL:forSaveOperation:error: for another purpose (doing some stuff on first save of a new document), and calling super invokes the NSException gods because contents value is nil, not an NSDictionary as seemingly expected by UIManagedDocument. Hmm.
The more you know...
P.S. I guess it depends on the time of day with -writeContents:andAttributes:... It once threw an exception complaining about expecting an NSDictionary, but later threw an exception complaining that I didn't pass it an NSData. My eyebrow could not be raised in a more Spock-like fashion than it is right now.