My application, during a process I've written, is rising in memory and seems to not be releasing it.
The first thing I'd like to mention is that the basic outline of what I've written is this:
- Request a url (fetching data using NSData -initWithContentsOfURL)
- Parse NSData into NSArray of NSDictionarys using NSJSONSerialization +JSONObjectWithStream
- Loop through decoded NSArray inserting/updating/deleting records in a sqlite database using the FMDB framework with the data decoded
The application does the above, however it does it in a loop for an undetermined period of time, in which the application displays a "Loading" HUD. I thought it may be worth mentioning this, although I find it insignificant how many times it does this process, as that shouldn't affect the memory usage if it were releasing properly. If I am wrong here, please advise me.
My code works fine, well, it does what it's intended to do. However, when I profile the application code, the memory seems to just keep rising. It does drop in segments throughout, but overall it keeps rising (IE doesn't release in full what it previously used).
I have, as previously stated, profiled the application with Allocations, Leaks, VM Tracker, and used Trace Highlights.
Trace Highlights: Shows that the memory usage is gradually going up, but dropping some memory (not all) meaning if the process is running for long enough the memory will reach high usage and terminate.
Allocations: Seems OK. The allocations has spikes but always comes back down to where it started. I took heapshots and they always drop down leaving maximum 500-700kb per segment (left for about 10 minutes)
VM Tracker: Proves to show that memory consistently rises, and is not releasing full memory (as discovered in trace highlights). Resident seems to get really high
Leaks: No leaks found in application
Here's some screenshots of Allocations/VM Tracker running:
It's worth noting that I have in fact tried:
- Adding autoreleasepools
- "force releasing" by assigning each properties; such as NSURLs, NSRequests, etc; to nil
My questions:
- Should I be doing something special to release the memory?
- How could I further debug this issue?
- How best can I find out what's wrong from the data Instruments gives me?
---- EDIT: ----
Here's the code that sends the url request to fetch the data.:
- (void) requestAndParse : (NSString *)url
{
NSURL *theURL;
ASIHTTPRequest *request;
NSData *collectedData;
NSError *error;
#try {
// File cache the NSData
theURL = [[NSURL alloc] initWithString: url];
request = [ASIHTTPRequest requestWithURL: theURL];
[request setDownloadDestinationPath: [[NSHomeDirectory() stringByAppendingPathComponent:#"Documents"] stringByAppendingString:#"/cachefile.txt"]];
[request startSynchronous];
[request waitUntilFinished];
collectedData = [[NSData alloc] initWithContentsOfFile:[[NSHomeDirectory() stringByAppendingPathComponent:#"Documents"] stringByAppendingString:#"/cachefile.txt"]];
if ([collectedData length] > 0) {
records = [NSJSONSerialization JSONObjectWithData:collectedData options:NSJSONReadingMutableContainers error:&error];
}
}
#catch (NSException *exception) {
// Failed
NSLog(#"Parse error: %#", error);
}
#finally {
// DB updates with the records here
...
// remove file
[[NSFileManager defaultManager] removeItemAtPath:[[NSHomeDirectory() stringByAppendingPathComponent:#"Documents"] stringByAppendingString:#"/cachefile.txt"] error:nil];
// release properties used
collectedData = nil;
request = nil;
theURL = nil;
}
}
This above method is called from within a while loop in the Application Delegate. The while loop is an undetermined length, as previously mentioned.
--- EDIT 2: ---
The following is what happens within the #finally statement (updating the SQLite database using FMDB). There are a lot of these methods in my class, one for each table. They all follow the same pattern though, as they are all duplicated from the first one:
-(BOOL) insertBatchOfRecords:(NSArray *)records {
__block BOOL queueReturned = YES;
#autoreleasepool {
FMDatabaseQueue *dbQueue = [self instantiateDatabaseQueue];
[dbQueue inTransaction:^(FMDatabase *tdb, BOOL *rollback) {
if (![tdb open]) {
NSLog(#"Couldn't open DB inside Transaction");
queueReturned = NO;
*rollback = YES;
return;
}
for (NSDictionary *record in records) {
[tdb executeUpdate:#"INSERT OR REPLACE INTO table (attr1, attr2) VALUES (?,?)", [record valueForKey:#"attr1"], [record valueForKey:#"attr2"]];
if ([tdb hadError]) {
queueReturned = NO;
*rollback = YES;
NSLog(#"Failed to insert records because %#", [tdb lastErrorMessage]);
return;
}
}
}];
[dbQueue close];
dbQueue = nil;
}
return queueReturned;
}
And follows is the -instantiateDatabaseQueue method:
-(FMDatabaseQueue *) instantiateDatabaseQueue {
#autoreleasepool {
return [FMDatabaseQueue databaseQueueWithPath: [self.getDocumentsDirectory stringByAppendingPathComponent:#"localdb.db"]];
}
}
The autoreleasepools may make it messy, but the code originally did not have these. I implemented them in various locations to see if there was any improvement (there was not).
--- EDIT 3 ---
I have been profiling the application the past few days, and still have no luck in finding an answer. I have separated the part of the app in question to a separate project of it's own, to make sure that it is indeed this causing the memory usage. This proved to be correct, as the app is still acting the same.
I have taken further profiling pictures, and am still having a hard time identifying what is actually wrong. See below that the allocations looks OK (the VM also doesn't look too bad to me?), and there's still no leaks (no picture of this, because there's none!!)
However, when I profiled on Trace Highlights, the memory usage just keeps going up, until reaching too much usage (around 70+MB on a 3GS) and then crashes due to using so much memory.
I reduced the problem by using ASIHTTPRequest for grabbing the NSData (stores to file instead). Please see revised code above. However, problem still persists, just takes longer to happen!
As per originally, question:
- Is there something wrong with the second part of this app process?
Using try / catch in iOS with ARC can cause memory leaks, and is best avoided.
an alternative approach is to use an async NSURLConnection, or an NSOperation with a synch NSURLConnection.
Related
My app offers the option to download 3430 high resolution images from our server, each image of size 50k - 600k bytes.
The original approach was to just download all of them - but we realized that gave a lot of NSURLErrorTimedOut errors and crashed our program. We then implemented it such that we download all of the images, but in batches of 100 images at a time. Someone on SO suggested we actually implement our download like this:
Create a list of all file URLs that need to be downloaded.
Write your code so that it downloads these URLs sequentially. I.e. do
not let it start downloading a file until the previous one has
finished (or failed and you decided to skip it for now).
Use NSURLSession's support for downloading an individual file to a
folder, don't use the code to get an NSData and save the file
yourself. That way, your application doesn't need to be running while
the download finishes.
Ensure that you can tell whether a file has already been downloaded or
not, in case your download gets interrupted, or the phone is restarted
in mid-download. You can e.g. do this by comparing their names (if
they are unique enough), or saving a note to a plist that lets you
match a downloaded file to the URL where it came from, or whatever
constitutes an identifying characteristic in your case.
At startup, check whether all files are there. If not, put the missing
ones in above download list and download them sequentially, as in #2.
Before you start downloading anything (and that includes downloading
the next file after the previous download has finished or failed), do
a reachability check using the Reachability API from Apple's
SystemConfiguration.framework. That will tell you whether the user has
a connection at all, and whether you're on WiFi or cellular (in
general, you do not want to download a large number of files via
cellular, most cellular connections are metered).
We create a list of all images to download here:
- (void)generateImageURLList:(BOOL)batchDownloadImagesFromServer
{
NSError* error;
NSFetchRequest* leafletURLRequest = [[[NSFetchRequest alloc] init] autorelease];
NSEntityDescription* leafletURLDescription = [NSEntityDescription entityForName:#"LeafletURL" inManagedObjectContext:managedObjectContext];
[leafletURLRequest setEntity:leafletURLDescription];
numberOfImages = [managedObjectContext countForFetchRequest:leafletURLRequest error:&error];
NSPredicate* thumbnailPredicate = [NSPredicate predicateWithFormat:#"thumbnailLocation like %#", kLocationServer];
[leafletURLRequest setPredicate:thumbnailPredicate];
self.uncachedThumbnailArray = [managedObjectContext executeFetchRequest:leafletURLRequest error:&error];
NSPredicate* hiResPredicate = [NSPredicate predicateWithFormat:#"hiResImageLocation != %#", kLocationCache];
[leafletURLRequest setPredicate:hiResPredicate];
self.uncachedHiResImageArray = [managedObjectContext executeFetchRequest:leafletURLRequest error:&error];
}
We use NSURLSession to download an individual image to a folder by calling hitServerForUrl and implementing didFinishDownloadingToURL:
- (void)hitServerForUrl:(NSURL*)requestUrl {
NSURLSessionConfiguration *defaultConfigurationObject = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *defaultSession = [NSURLSession sessionWithConfiguration:defaultConfigurationObject delegate:self delegateQueue: nil];
NSURLSessionDownloadTask *fileDownloadTask = [defaultSession downloadTaskWithURL:requestUrl];
[fileDownloadTask resume];
}
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location {
if (isThumbnail)
{
leafletURL.thumbnailLocation = kLocationCache;
}
else
{
leafletURL.hiResImageLocation = kLocationCache;
}
// Filename to write to
NSString* filePath = [leafletURL pathForImageAtLocation:kLocationCache isThumbnail:isThumbnail isRetina:NO];
// If it's a retina image, append the "#2x"
if (isRetina_) {
filePath = [filePath stringByReplacingOccurrencesOfString:#".jpg" withString:#"#2x.jpg"];
}
NSString* dir = [filePath stringByDeletingLastPathComponent];
[managedObjectContext save:nil];
NSError* error;
[[NSFileManager defaultManager] createDirectoryAtPath:dir withIntermediateDirectories:YES attributes:nil error:&error];
NSURL *documentURL = [NSURL fileURLWithPath:filePath];
NSLog(#"file path : %#", filePath);
if ([[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
//Remove the old file from directory
}
[[NSFileManager defaultManager] moveItemAtURL:location
toURL:documentURL
error:&error];
if (error){
//Handle error here
}
}
This code calls loadImage, which calls `hitServer:
-(void)downloadImagesFromServer{
[self generateImageURLList:NO];
[leafletImageLoaderQueue removeAllObjects];
numberOfHiResImageLeft = [uncachedHiResImageArray count];
for ( LeafletURL* aLeafletURL in uncachedHiResImageArray)
{
//// Do the same thing again, except set isThumb = NO. ////
LeafletImageLoader* hiResImageLoader = [[LeafletImageLoader alloc] initWithDelegate:self];
[leafletImageLoaderQueue addObject:hiResImageLoader]; // do this before making connection!! //
[hiResImageLoader loadImage:aLeafletURL isThumbnail:NO isBatchDownload:YES];
//// Adding object to array already retains it, so it's safe to release it here. ////
[hiResImageLoader release];
uncachedHiResIndex++;
NSLog(#"uncached hi res index: %ld, un cached hi res image array size: %lu", (long)uncachedHiResIndex, (unsigned long)[uncachedHiResImageArray count]);
}
}
- (void)loadImage:(LeafletURL*)leafletURLInput isThumbnail:(BOOL)isThumbnailInput isBatchDownload:(BOOL)isBatchDownload isRetina:(BOOL)isRetina
{
isRetina_ = isRetina;
if (mConnection)
{
[mConnection cancel];
[mConnection release];
mConnection = nil;
}
if (mImageData)
{
[mImageData release];
mImageData = nil;
}
self.leafletURL = leafletURLInput;
self.isThumbnail = isThumbnailInput;
NSString* location = (self.isThumbnail) ?leafletURL.thumbnailLocation :leafletURL.hiResImageLocation;
//// Check if the image needs to be downloaded from server. If it is a batch download, then override the local resources////
if ( ([location isEqualToString:kLocationServer] || (isBatchDownload && [location isEqualToString:kLocationResource])) && self.leafletURL.rawURL != nil )
{
//NSLog(#"final loadimage called server");
//// tell the delegate to get ride of the old image while waiting. ////
if([delegate respondsToSelector:#selector(leafletImageLoaderWillBeginLoadingImage:)])
{
[delegate leafletImageLoaderWillBeginLoadingImage:self];
}
mImageData = [[NSMutableData alloc] init];
NSURL* url = [NSURL URLWithString:[leafletURL pathForImageOnServerUsingThumbnail:self.isThumbnail isRetina:isRetina]];
[self hitServerForUrl:url];
}
//// if not, tell the delegate that the image is already cached. ////
else
{
if([delegate respondsToSelector:#selector(leafletImageLoaderDidFinishLoadingImage:)])
{
[delegate leafletImageLoaderDidFinishLoadingImage:self];
}
}
}
Currently, I'm trying to figure out how to download the images sequentially, such that we don't call hitServer until the last image is finished downloading. Do I need to be downloading in the background? Thank you for suggestions!
My app offers the option to download 3430 high resolution images from our server, each image of size 50k - 600k bytes.
This seems like a job for on-demand resources. Just turn these files into on-demand resources obtained from your own server, and let the system take care of downloading them in its own sweet time.
This sounds very much like an architectural issue. If you fire off downloads without limiting them of course you're going to start getting timeouts and other things. Think about other apps and what they do. Apps that give the user the ability to do multiple downloads often limit how may can occur at once. iTunes for example can queue up thousands of downloads, but only runs 3 at a time. Limiting to just one at a time will only slow things down for your users. You need a balance that consider your user's available bandwidth.
The other part of this is to again consider what your users want. Does every one of your uses want every single image? I don't know what you are offering them, but in most apps which access resources like images or music, it's up to the user what and when they download. Thus they only download what they are interested in. So I'd recommend only downloading what the users are viewing or have somehow requested they want to download.
I'm trying to migrate a core data database to Realm (somewhere between 0-2 million rows), and am running into a deadlock that as far as I can tell, shouldn't be happening.
From a Singleton class, I'm launching the migration like this:
_queue = dispatch_queue_create("DiagnosticMigrationQueue", NULL);
dispatch_async(_queue, ^{
_realmMigrator = [[CoreDataToRealmMigrator alloc] init];
[_realmMigrator performMigrationToRealm];
});
Within the performMigrationToRealm method, I set up the Core Data stack thusly:
- (void) performMigrationToRealm
{
self.migrationIsRunning = YES;
NSURL *url = [[[NSFileManager defaultManager] URLsForDirectory:NSLibraryDirectory inDomains:NSUserDomainMask] lastObject];
url = [url URLByAppendingPathComponent:#"persistentStore"];
NSError *error;
NSManagedObjectModel *model = [NSManagedObjectModel mergedModelFromBundles:nil];
NSManagedObjectContext *context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSConfinementConcurrencyType];
context.persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model];
[context.persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
configuration:#"DiagnosticData"
URL:url
options:nil
error:&error];
Understanding Check: When I create the queue, none of the Core Data stack has been set up yet. Therefore, the NSManagedObjectContext is only created on whatever thread GCD decides to put my block on.
So far, so good. No problems. I now run a method that - in batches of 100,000 - grabs all the NSManagedObjectIds in the Entity. It looks something like this:
for (NSInteger numberOfMigratedBatches = 0; numberOfMigratedBatches < totalNumberOfBatches; numberOfMigratedBatches++)
{
NSArray *samples = [context executeFetchRequest:fetchRequest error:&error];
if (samples && !error)
{
[self transferWeightSamplesToRealmWithObjectIds:samples withContext:context];
}
[context reset];
fetchRequest.fetchOffset = batchSize * (numberOfMigratedBatches+1);
}
The line that goes funky is the fetchRequest in the code block above. Even though I've launched this process on a serial queue, I somehow end up with this:
Each of those minion_duties2 threads is stuck on the same line of code, namely the fetchRequest above.
What is going on here? I understand that queues != threads, and that GDC will put my code on whichever thread is sees fit. However, I wouldn't expect that it would put my code on THREE threads. Also, what is com.apple.root.user-initiated-qos.overcommit? I'll say it's overcommitted. I only want this code run one time!
So, this is technically not an answer to the question you posted, but, it could potentially provide you with a way that would allow you to avoid the problem you're running into (workaround?).
Since my guess is that it's the several different fetch requests trying to execute on several different threads that's causing the deadlock, I would suggest that instead of doing the batching manually, you do it by setting the fetchBatchSize property on NSFetchRequest, and let core data do it for you, and hence avoid doing multiple fetch requests altogether, if memory usage is an issue, try wrapping the innards of your for loop in an autoreleasepool block (I'm guessing there is a for loop inside the transferWeightSamplesToRealmWithObjectIds:withContext: method).
Core data has a concept called batching & faulting. From Apple documentation of NSFetchRequest:
If you set a non-zero batch size, the collection of objects returned
when the fetch is executed is broken into batches. When the fetch is
executed, the entire request is evaluated and the identities of all
matching objects recorded, but no more than batchSize objects’ data
will be fetched from the persistent store at a time. The array
returned from executing the request will be a proxy object that
transparently faults batches on demand. (In database terms, this is an
in-memory cursor.)
Hopefully, this helps you somewhat.
I probably ask my question the wrong way and risk being blocked by stackoverflow completely. I have Asperger and no social skills, so I am very sorry for asking my last (?) question (because systems like these are only made for people without handicaps).
I am using GCD to load images and video from Instagram. I do this in an app that is very 'busy' with its user interface, and I want to keep that running smoothly, so I load the Instagram-media in the background.
The code below (which I probably formatted the wrong way so I apologize up front) works fine and does exactly want I want it to do. It loads images in the background (I left video's out to keep things simple) and my main UI is response while the images load. I display them between loads, and there works fine too.
However.
After 10 minutes, sometimes 20 minutes, sometimes 30 minutes and even sometimes after two hours my app gets OSSpinLockLock freezes. If I remove the code below I get no media, but the app never freezes.
I have searched for days on the web about alternative ways to do the job and to find an explanation for the OSSpinLockLock. No luck. Only thing I found was that using GCD could not result in an OSSpinLockLock. I have used all my knowledge of Instruments (which I must admit is more limited than I thought), but I cannot find a fault.
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0ul);
dispatch_async(queue, ^(void) {
[[InstagramEngine sharedEngine] getMediaAtLocation:location count:kInstagramLocationBufferSize maxId:nil withSuccess:^(NSArray* media, InstagramPaginationInfo* paginationInfo) {
if (media && media.count>0) {
for (InstagramMedia* mediaObject in media) {
NSData* data = [[NSData alloc] initWithContentsOfURL:mediaObject.standardResolutionImageURL];
if (data) {
UIImage* img = [UIImage imageWithData:data];
if (img)
[self.locationBuffer addObject:img];
data = nil;
}
}
}
} failure:^(NSError *error) {
;
}];
});
If you look at this code, do you see anything that might cause that lock? Because I most certainly don't.
self.locationBuffer is declared in the .h as
#property (nonatomic,strong) NSMutableArray* locationBuffer;
and is properly allocated and initialized (otherwise it would be rather clear what the problem was). I have also tried not to put the UIImage but the NSData in the array but that made no difference whatsoever.
On my iPad mini retina for instance the CPU-load goes to 195% and stays around that number for a very long time. Eventually, sometimes after several hours, the app crashes.
Any suggestions would be very welcome.
Edit: As I see now on the ips-file on the iPad itself (which for some mysterious reason I cannot paste into this webpage (is stackoverflow still in an experimental stage?)) I see that the iPad did spent 16.000+ seconds on NSURLConnection...
my tought is that some timeout or failure blocks your gcd queues for too much time. try rewriting that code with NSOperationQueue, that way you can stop the queue on errors or view/controller going away.
https://developer.apple.com/library/ios/documentation/Cocoa/Reference/NSOperationQueue_class/index.html
Update 1:
Here is my trial at your code, i added queue and logs, check them and also check the timeout value. This way if a request does not finish (most likely) you can trace it. All requests are serial so if one of them stops you should immediately notice it.
You can create more than one queue and access them sequentially (round robin) to have more requests simultaneously. I would not go with more than 4 queues which is also the default for most desktop internet browsers.
// keep the same queue for all request (class member?), don't put it in the same block
NSOperationQueue* queue= [[NSOperationQueue alloc] init];
// keep this in another code block from queue
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0ul), ^(void) {
[[InstagramEngine sharedEngine] getMediaAtLocation:location count:kInstagramLocationBufferSize maxId:nil withSuccess:^(NSArray* media, InstagramPaginationInfo* paginationInfo) {
if (media && media.count>0) {
for (InstagramMedia* mediaObject in media) {
NSURL* url= mediaObject.standardResolutionImageURL;
NSLog(#"Start loading from %#", url);
NSURLRequest* req= [NSURLRequest requestWithURL:mediaObject.standardResolutionImageURL
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:30]; // 30 seconds timeout
[NSURLConnection sendAsynchronousRequest:req queue:queue completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
NSLog(#"Stop loading from %# %# %#", url, response, connectionError);
if (data != nil && connectionError == nil) {
UIImage* img = [UIImage imageWithData:data];
if (img) {
// if you are triggering some ui update run on main thread
// [self.locationBuffer addObject:img];
[self.locationBuffer performSelectorOnMainThread:#selector(addObject:)
withObject:img
waitUntilDone:NO];
}
}
}];
}
}
} failure:^(NSError *error) {
NSLog(#"Error getting media list: %#", error);
}];
});
OSSpinLocks has bug on iOS8.3+: report
,which leads to such freezes.
I think you should replace OSSpinLocks with something else to fix this in your app (maybe create your own realization of spin lock - search SO for OSSpinLock).
I am trying my hand at some very basic implementation of MagicalRecord to get the hang of it and run into the following.
When I save an entry and then fetch entries of that type it will come up with the entry I just saved. However, when I save the entry, close the app, start it again, and then fetch, it comes up empty.
Code for saving:
- (void)createTestTask{
NSManagedObjectContext *localContext = [NSManagedObjectContext contextForCurrentThread];
Task *task = [Task createInContext:localContext];
task.tName = #"First Task";
task.tDescription = #"First Task created with MagicalRecord. Huzzah!";
NSError *error;
[localContext save:&error];
if (error != Nil) {
NSLog(#"%#", error.description);
}
}
Code for fetching: (all I want to know here if anything is actually saved)
- (void) fetchTasks{
NSArray *tasks = [Task findAll];
NSLog(#"Found %d tasks", [tasks count]);
}
I am sure I am missing something here, but not anything I can seem to find on stackoverflow or in the Tutorials I looked at.
Any help is welcome.
I have to ask the obvious "Is it plugged in" question: Did you initialize the Core Data Stack with one of the +[MagicalRecord setupCoreDataStack] methods?
Did your stack initialize properly? That is, is your store and model compatible? When they aren't, MagicalRecord (more appropriately, Core Data) will set up the whole stack without the Persistent Store. This is annoying because it looks like everything is fine until it cannot save to the store...because there is no store. MagicalRecord has a +[MagicalRecord currentStack] method that you can use to examine the current state of the stack. Try that in the debugger after you've set up your stack.
Assuming you did that, the other thing to check is the error log. If you use
[localContext MR_saveToPersistentStoreAndWait];
Any errors should be logged to the console. Generally when you don't see data on a subsequent run of your app, it's because data was not saved when you thought you called save. And the save, in turn, does not happen because your data did not validate correctly. A common example is if you have a required property, and it's still nil at the time you call save. "Normal" core data does not log these problems at all, so you might think it worked, when, in fact, the save operation failed. MagicalRecord, on the other hand, will capture all those errors and log them to the console at least telling you what's going on with your data.
When i have started with magical record I was also facing this problem, problem is context which you are using to save data. here is my code which might help you
[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) {
NSArray *userInfoArray = [UserBasicInfo findByAttribute:#"userId" withValue:[NSNumber numberWithInt:loggedInUserId] inContext:localContext];
UserBasicInfo* userInfo;
if ([userInfoArray count]) {
userInfo = [userInfoArray objectAtIndex:0];
} else {
userInfo = [UserBasicInfo createInContext:localContext];
}
userInfo.activeUser = [NSNumber numberWithBool:YES];
userInfo.firstName = self.graphUser[#"first_name"];
userInfo.lastName = self.graphUser[#"last_name"];
userInfo.userId = #([jsonObject[#"UserId"] intValue]);
userInfo.networkUserId = #([jsonObject[#"NetworkUserId"] longLongValue]);
userInfo.userPoint = #([jsonObject[#"PointsEarned"] floatValue]);
userInfo.imageUrl = jsonObject[#"Picturelist"][0][#"PictureUrL"];
userInfo.imageUrlArray = [NSKeyedArchiver archivedDataWithRootObject:jsonObject[#"Picturelist"]];
} completion:^(BOOL success, NSError *error) {
}];
Use this when your done
[[NSManagedObjectContext MR_defaultContext]saveToPersistentStoreAndWait];
Here is a snippet of relevant code:
NSURL *url = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
url = [url URLByAppendingPathComponent:#"Demo Document"];
UIManagedDocument *document = [[UIManagedDocument alloc] initWithFileURL:url];
if (document.documentState == UIDocumentStateClosed) {
NSLog(#"file is closed");
NSLog(#"%#",[url path]);
NSDate *start = [NSDate date];
[document openWithCompletionHandler:^(BOOL success) {
if (success) {
NSLog(#"finished OPEN");
NSDate *methodFinish = [NSDate date];
NSTimeInterval executionTime = [methodFinish timeIntervalSinceDate:start];
NSLog(#"time = %f",executionTime);
self.managedObjectContext = document.managedObjectContext;
}
}];
}
The time interval between "file is closed" and "finished open" was 16.6 seconds. Is this normal behavior? Bad coding? Or is it because I am running on a simulator?
I don't know if it would matter, but the file is barely 50KB...
This is not normal behavior. Have you tried clearing all iCloud data using Xcode 5 under "Debug->iCloud->Delete iCloud Contents"? It takes a few minutes to be fully realized but starting with a clean slate might help.
If you have already done this have you looked at what else might be working with that document that has a temporary hold on it? Or is something else blocking the main thread? Because that block won't execute until the main thread's event queue comes back around.
Believe it or not, this is normal. It isn't really anything to do with your code, but more or less how it is intended to work when you use autosave. This autosaving behavior is actually turned on/enabled by default ( you can also turn it off - but I would highly recommend not doing that unless you need to and know what you're doing heh).
Anyways, UIManagedDocument actually automatically saves your data to a document store for you by default and in the background no less - but the caveat to this is that it will do it whenever it can ( or better, whenever it wants to ). Realize that it does this in the background for you so that you don't have to worry about it ( its a feature I really love about it - but can also be a pain int he ass ).
It sounds like you're needing it to be saved because it isn't showing your data or you aren't seeing the results you would expect.
If that's the case, then I think you should look more into how to have it actually save your changes so that it can show up in your controllers. In reality, it really has very little to do with when UIManagedDocument saves anyways, and more about when your context is updated. If this is, indeed, the case you may find my answer to a different thread/question on this same subject helpful: Multiple UIManagedDocument For Read & Write
This may or may not be what you're after, but I believe it will get you on the right path at the very least.