I'm using AssetsLibrary framework for saving assets to a specific album (ALAssetsGroup).
Since I'm using the ALAssetsGroup (for the album where I want to save the assets) quite often, I figured it would be wise to retain it, so I don't have to iterate (asynchronously) through the groups each time I need it.
When retrieving/creating the album everything shows up correctly (for valueForProperty:), but the group seems to invalidate itself after some time, and all its properties will return nil. Also, addAsset: won't work on it, so I have to search for the album again (this time it's searched by its URL, but it's still asynchronous).
Is there a way around this (to keep the ALAssetsGroup valid)?
This is happening on iOS 5, and the library (ALAssetsLibrary) is retained as well.
I am using a setter to make sure that I don't reset the group myself.
You need to add an observer for the ALAssetsLibraryChangedNotification for your ALAssetsLibrary and upon receiving it re-query for any of your cached AL* objects. That will be posted immediately before the vended AL* objects become invalid.
Related
Kinda new to Realm and I just took over a project which uses Realm. So I of course access in a lot of spots in my app our realm objects. Recently I've noticed that in 3 specific spots we get more and more crashes about Realm objects that have been invalidated or deleted. In all other spots I hardly ever get a crash..even though I do it always the same. So let's say we have a Realm object User *user, I always call user.firstname. In one particular spot I get often a crash so I assume I should check there if it has been invalidated with if(![user isInvalidated]). But why? Or how do I know when I need to check for that?
Or do I need to check it every single time when I access an object? What about if I pass a realm object in an Array to another method -> Do I need to check if after the passage again?
And more over: What do I need to do if it has been invalidated?
Would be glad for any help, this whole topic seems quite confusing
is it possible to use RESTKit for two way synchronization?
I played aroud with RESTKit and CoreDate. Now I can download all data from my REST service and all changes (create/modify/delete objects) in CoreDate will be overwritten by RESTKit.
Now I want to choose between both versions (the local version or the remote version). How do I do this? Is there a way to manipulat the mapping, or something like that?
update:
My app should synchronize after some changes or after a specific delay (not difficult). Every object has a change date. Until now I want to keep the newer one (if they are equal the local one).
I hope RestKit is made for changing the strategy how it merges objects. something like a block I can set, where I get both objects and can return the merged object.
What I got so far:
I load the object via RestKit but do not persist them. Also I setup a CoreData store where I store the local objects. After loading the remote object I start to synchronize my self. First searching for pairs and then decide which to take, delete, create, overwrite, and so on...
But this is a big bunch of work and I think RestKit is doing something similar. Why not simply changing the strategy of RestKit for my requirements.
Well this would be the "syncing down" thing. After this I could write the synchronized data set back to the service. The changes are not very frequently so I will not have to check for new changes.
I hope now it's a little bit clearer
What you really want to do is validate the data coming in.
Since RestKit is using CoreData it automatically uses the validation built into CoreData (see here)
Here is an example that will ensure that the date never gets changed to an earlier one.
- (BOOL) validateChangeDate:(id *)ioValue error: (NSError **)outError {
if ([[*ioValue laterDate:self.changeDate] compare:self.changeDate] == NSOrderedSame)
*ioValue = self.changeDate;
return YES;
}
Note: There may be better/faster ways to test to see if we need to change the date
Note 2: According to the docs, if RestKit sees a value rejected, it fails the entire object, which we don't want, so that's why we always return YES and just change the incoming date
Note 3: We also don't want to change the value unless we have to because of what the CoreData docs say here
You may be able to leverage KVC validation to achieve this as it allows you to check and then edit or deny the change for each object and key. Check these docs.
I'm developing an iPad-app and I'm currently struggling with finding the best approach to multithreading. Let me illustrate this with a simplified example:
I have a view with 2 subviews, a directory picker and a gallery with thumbnails of all the images in the selected directory. Since 'downloading' and generating these thumbnails can take quite a while I need multithreading so the interaction and updating of the view doesn't get blocked.
This is what I already tried:
[self performSelectorInBackground:#selector(displayThumbnails:) withObject:currentFolder];
This worked fine because the users interactions didn't get blocked, however it miserably fails when the user taps on another folder while the first folder is still loading. Two threads are trying to access the same view and variables which results in messing up each others proper execution. When the users taps another folder, the displayThumbnails of the currently loading folder should get aborted. I didn't find any way to do this..
NSThreads
I tried this but struggled with almost the same problems as with the first method, I didn't find a (easy) way to cancel the ongoing method. (Yes, I know about [aThread cancel] but didn't find a way to 'resume' the thread). Maybe I should subclass NSThread and implement my own isRunning etc methods? But isn't there any better way or a third (or even fourth and fifth) option I'm overlooking?
I think this is a fairly simple example and I think there is perhaps a better solution without subclassing NSThread. So, what would you do? Your opinions please!
NSOperationQueue should work well for this task.
Another option would be plain GCD, however, if you've never worked with it, NSOperationQueue is probably the better choice since it pretty much automatically guides you to implementing things "the right way", has obvious ways for cancellation, etc.
You want to use Concurrent NSOperations to download and process images in the background. These would be managed by an NSOperationsQueue. Essentially these operations would be configured to fetch one image per operation, process it, save it in the file system, then message back to the main app in the main thread that the image is available.
There are several projects on github that you can look at that show how to do this - just search github using "Concurrent" or "NSOperation".
iOS has a really nice facility for doing background work. Grand Central Dispatch (GCD) and Blocks, but those don't let you have an object using delegate callbacks - thus NSOperation.
So you need to read up on blocks, GCD, and then look at some open source Concurrent NSOperations code. Using Concurrent NSOperations is not as simple as using blocks.
If I had this problem, I would probably go for an approach like this:
a single thread that will load the images, and causes the main thread to display results (I'm not a big fan of having thread mess around with GUI objects)
when a new directory is requested... well, it depends on how you want to manage things. Basically, a standard queue construct (condition variable and array) could be used for the main thread to tell the thread that "this directory will be needed" by passing it the path name; the thread will check the queue even when it's loading images (like after every image or so), and switch to the new directory whenever one shows up
you could make a directory-reader object that keeps all the state, and store this indexed by the path into a dictionary. When a new directory is requested, check that dictionary first, and only create a new object if there's none for this directory. That way, partially loaded directories would stick around until they are needed again, and can continue to load instead of having to start from scratch.
Pseudocode for the thread:
while (forever)
new element = nil
if we have an active directory loader
tell directory loader to load one image
if false then make directory loader inactive
lock queue condition
if queue has elements
new element = retrieve LAST element (we aren't interested in the others)
empty queue
unlock with status "empty"
else
unlock queue
else
lock queue on condition "has elements"
new element = retrieve last element
empty queue
unlock with status "empty"
if new element != nil
if directory loader for new path does not exist
setup new directory loader for new path
store in dictionary
make it the "active" one
else
make the current one the "active"
As for the directory loader, it might look something like this:
read one image:
if there are still images to read:
read, process and store one
return true
else
performSelectorOnMainThread with an "update GUI" method and the image list as parameter
return false;
This is just a quick sketch; there's some code duplication in the thread, and the way I wrote it will only update the GUI after all images have been read, instead of making them appear as we read them. You'll have to copy the current image list, or add synchronization if you want to do that.
I write some photos to the photo library using UIImageWriteToSavedPhotosAlbum() and at the same time I display the contents of this asset group (ALAssetsGroupSavedPhotos) using enumerateAssetsUsingBlock: and friends. Sometimes the assets returned by enumerating the group become sort of “invalid”, meaning that the defaultRepresentation call returns nil, although the asset is still in memory.
I noticed that this seems to happen after the photo library gets modified by the UIImageWriteToSavedPhotosAlbum() call. Is this a documented behaviour? How can I prevent it? Reloading the assets is not a feasible option, as the user might already be somewhere deeper in the UI working with the asset.
this is an unfortunate, but documented behavior. For reference:
"ALAssetsLibraryChangedNotification Sent when the contents of the
assets library have changed from under the app that is using the data.
When you receive this notification, you should discard any cached
information and query the assets library again. You should consider
invalid any ALAsset, ALAssetsGroup, or ALAssetRepresentation objects
you are referencing after finishing processing the notification."
So what you have to do is to register an observer for ALAssetsLibraryChangedNotification. (And there is a bug in regarding this notification on iOS 5.X, see Open Radar.)
When you receive the notification you have to reenumerate all groups and assets. There is at the moment no other way. This is very unfortunate from a GUI perspective and we can only hope Apple improves this mechanism in the future.
Cheers,
Hendrik
I have an object - Config. I want to know if the Account attribute on Config has changed. When this happens, I want to send a NSNotification so that all code that cares about when the Account changes will know. My initial thought was in my NSManagedObject subclass that I would override the setAccount method to set a transient attribute AccountDidChange to true. Then in didSave if AccountDidChange was true I would send the notification and then set it back to false. Is there a better way? My issue though is that from what I've read, by changing AccountDidChange back to false, I would have dirtied my object and need to save again.
A little more info:
The Config object is the current configuration of the application. Account could actually be changed to ActiveAccount. There is a relationship to the Account Entity that has a list of all Accounts. The idea is that the user can change the active account of the application. So we have a set of servers and the user can only be logged into one at a time. Config.Account points to that active account and it is used to setup connections to the server to retrieve information. I am using this notification that Config.Account has changed to tell other objects to clean up their information - like list of alerts. Basically, all information is per Account so it needs to be removed and then refetched on its next load with the new active account.
Also, the given names are not my actual object names - just trying to make the example easier to follow.
Take a look at KVO (Key-Value Observing): Key-Value Observing Programming Guide. That's the standard way to do this in Cocoa, and is a fundamental technology that you need to understand to be a good Cocoa programmer.
KVO will let objects that care about changes to the Account property (which you should probably name account, not Account) register to be notified when the property is changed. KVO will "just work" for standard NSManagedObjects, without any additional work on your part.
The relevant methods are as follows:
-addObserver:forKeyPath:options:context: which you call on your Config object to set up the observation
-observeValueForKeyPath:ofObject:change:context: which will be called on the observer object anytime an observed value is changed
-removeObserver:forKeyPath: which you need to make sure you call when the observer no longer needs change notifications (including before the observer is deallocated).
This is all described in a lot more detail in the linked documentation.
EDIT BELOW:
Without knowing anything about your application, it's hard to know why you'd want to be notified only upon save. NSManagedObjectContext posts NSManagedObjectContextWillSaveNotification and NSManagedObjectContextDidSaveNotification. The notification's userInfo has arrays containing inserted, updated and deleted objects, but the notifications aren't as fine-grained as individual properties. I suppose you could manually keep track of changed accounts between didSave notifications. That'll probably get inefficient if you have lots of Configs in your store.
Changes to NSManagedObjects are immediate, they're just not saved to the persistent store until you call save: on the managed object context. Perhaps if you explain more about exactly what you're trying to accomplish and why, I can better help.