I have an app that is storing some data using the Cocoa/ObjC initWithCoder / encodeWithCoder style marshaling. Over the life of the app one of the classes that has been removed is an NSDate+Utils category.
The question is - if an NSDate+Utils is serialized to disk, and then deserialized with a later codebase when NSDate+Utils no longer exists; will it come back as an NSDate, or will it crash??
It won't crash. You just need to check whether it's nil. If that' nil, then just skip it, otherwise populate the data to your property. By the way, there is nothing to do with the codebase. It's just whether that local data being stored in the device. Codes are just things that tell device what or how to do stuff.
I wrote a small program to test this. Categories seem to get written as the base class - I assume as long as they don't add fields and/or mess with the withCoder methods.
Related
In Objc string, array and dictionary are all reference types, while in Swift they are all value types.
I want to figure out what's the reason behind the scenes, for my understanding, no matter it is a reference type or value type, the objects live in the heap in both Objc and Swift.
Was the change for making coding easier? i.e. if it is reference type then the pointer to the object might not be nil, so need to check both pointer and the object not nil for accessing the object. While if it is value type then only need to check the object itself?
But in terms of memory allocation, value types and reference types are same, right? both allocated same size of memory?
thanks
Arrays, dictionaries etc. in Objective-C are often mutable. That means when I pass an array to another method, and then that array is modified behind the back of the other method, surprising (to put it gently) behaviour will happen.
By making arrays, dictionaries etc. value types, this surprising behaviour is avoided. When you receive a Swift array, you know that nobody is going to modify it behind your back. Objects that can be modified behind your back are a major source for problems.
In reality, the Swift compiler tries to avoid unnecessary copying whenever possible. So even if it says that an array is officially copied, it doesn't mean that it is really copied.
The Swift team is very active on the official developer forums. So, I'm assuming that since you didn't ask there, you're more curious about the community's broader "sense" of what the change means, as opposed to the technical implementation details. If you want to understand exactly "why", just go ask them :)
The explanation that makes the most sense to me is that Objects should be responsible for reacting to, and updating the state of your application. Values should be the state of your application. In other words, an Array or a String or a Dictionary (and other value types) should never be responsible for responding to user input or network input or error conditions, etc. The Objects handle that and store the resulting data into those values.
One cool feature in Swift, which makes a complex Value Type (like a Dictionary or a custom type like Person, as opposed to a simple Float) more viable, is that the value types can encapsulate rules and logic because they can have functions. If I write a value type Person as a struct, then the Person struct can have a function for updating a name due to marriage, etc. That's solely concerned with the data, and not with /managing/ the state. The Objects will still decide WHEN and WHY to updating a Person's name, but the business logic of how to go about doing so safely/test-ably can be included in the Value Type itself. Hence giving you a nice way to increase isolation and reduce complexity.
In addition to the previous answers, there are also multi-threading issues to consider with sharing a Reference-Based collection type that we don't have to worry as much with sharing an instance of a type that is Value-Based and has Copy-On-Write behavior. Multi-core is becoming more and more proliferant even on iOS devices, so it has become more of an issue for the Swift language developers to consider.
I do not know, whether this is the real idea behind it, but have a historical view on it:
At the beginning, an array copy behaved by reference, when you changed an item in it. It behaved by value, when you changed the length of the array. They did it for performance reasons (less array copy). But of course this was, eh, how can I express that politly, eh, difficult with Swift at all, eh, let's call it a "do not care about a good structure if you can win some performance, you probably never need" approach. Some called that copy-on-write, what is not much more intelligent, because COW is transparent, while that behavior was not transparent. Typical Swift wording: Use a buzzword, use it the way, it fits to Swift, don't care about correctness.
Later on arrays got a complete by copy behavior, what is less confusing. (You remember, Swift was for readability. Obviously in Swift's concept, readability means "less characters to read", but does not mean "better understandable". Typical Swift wording: Use a buzzword, use it the way, it fits to Swift, don't care about correctness. Did I already mention that?)
So, I guess it is still performance plus understandable behavior probably leading to less performance. (You will better know when a copy is needed in your code and you can still do that and you get a 0-operation from Cocoa, if the source array is immutable.) Of course, they could say: "Okay, by value was a mistake, we changed that." But they will never say.
However, now arrays in Swift behave consistently. A big progress in Swift! Maybe you can call it a programming language one sunny day.
I have a question regarding the addition of multiple CoreData entries in multiple tables.
I have the following code:
while((item = [enumerator nextObject]))
{
DOCategory *cat;
cat = [[self categoryController] getNewCategory];
[cat setName:[item objectForKey: #"name"]];
[cat setDesc:[item objectForKey: #"description"]];
[cat setLastUpdate:updateTime];
//[[self categoryController] commitChanges];
}
I used to call the commitChanges after each enumeration, and that works, but takes a long time, especially on older devices as the initial load is quite substantial. Therefore I just want to save at the end of the whole operation.
What is the best way to add all those objectors to a NSSet (or NSArray) while preserving the link to the ManagedContext. Normally I would 'copy' them into the set, but that doesn't work here. A simple question, just have a block to seeing the best solution.
(I assume that I don't have to 'commit/save' after every new object I created, so the results are not written to the database yet, but are available for searches as there are different relational objects in the procedure)
Update:
After the suggestion below and more testing, it appears to me that when I haven't saved/committed the context it is not included NSFetchResultController. Is this right and supposed to be the way? Is there a way to do all the operations (including searches to create relations) in 'cache' and then commit once all is done?
Update 2:
In order to get the Managed Object I have a procedure in the Controller-class:
- (DOCategory *) getNewCategory{
return (DOCategory *)[NSEntityDescription insertNewObjectForEntityForName:#"Category" inManagedObjectContext:managedObjectContext];
}
It appears that the code runs fine, including cross references, until I come to adding the final object which uses all the other managed objects. There must be some problem in that code.
Your code is old school Objective-C 1.0 so it would be best to update it. You seldom use enumerators directly like that anymore.
The best way to handle this is to create the new managed objects by inserting them into the context using +[NSEntityDescription insertNewObjectForEntityForName:inManagedObjectContext:] this both creates the managed object and makes the context aware of it. The context will retain the managed object itself. That's were the "managed" of "managed object" comes from.
The normal practice when making a lot of changes is to change all the objects and then save the context only when you are done making all the changes.
Update:
It appears that the code runs fine,
including cross references, until I
come to adding the final object which
uses all the other managed objects.
There must be some problem in that
code.
That sounds like you might have a problem with the relationship being set properly in the data model itself. I can't say for certain without knowing what your data model looks like.
I discourage the use of cast with insertNewObjectForEntityForName: e.g.
(DOCategory *)[NSEntityDescription insertNewObjectForEntityForName:#"Category" inManagedObjectContext:managedObjectContext];
... because if you have a data model problem, such as not providing the name of the DOCategory class for the DOCatergory entity, the cast will obscure the problem. If there is no class defined the method will return an instance of the generic NSManagedObject class or if you accidentally define the wrong class for the entity e.g. MadeUpClassName instead of DOCategory then the cast will force the runtime to treat the returned object as a DOCategory object. That might work fine until way down in the code.
...it appears to me that when I haven't
saved/committed the context it is not
included NSFetchResultController. Is
this right and supposed to be the way?
Is there a way to do all the
operations (including searches to
create relations) in 'cache' and then
commit once all is done?
Not exactly sure what problem you are seeing. It is best practice to make all changes to all objects and then to save the context instead of saving after every change to any single object. Disk operations are expensive.
If your changes to the context are not reflected in the NSFetchResultsController (FRC) then you most likely did not implement the FRC delegate methods that update the tableview (see FRC docs.) Another possible error is that you've created two context accidentally. If you are using threads, each with a separate context (required practice) then you have to merge the background context with the front context before the changes made in the background are made known to the foreground.
I am attempting to write an app that reads images from the asset library, modifies the image's GPS data and writes it back to the asset library. I store the assets in a mutableArray via the "enumerating assets" methods. Most of the details on how to do the various steps, I got from searching this forum. Thanks!
I have found that when I write the first "asset" via the "writeimagedatatosavedphotosalbum" method, all the elements of the mutableArray associated with the assets' URL became null. Furthermore, I noticed that writing back an image does not replace the original image, but instead creates a second instance of the image.
Just thought I'd pass these results along, in case others had questions. And, of course, I'd be interested in other's comments, observations, etc.
This forum has provided me with great information. Thanks again.
Your ALAsset object is only as good for the amount of time that your ALAssetsLibrary object is around. You either need to do everything you want in the completion block when you get the ALAsset, or store the ALAssetsLibrary in an instance variable so ARC does not deallocate it.
An ALAsset is essentially a Core Data object who can have properties accessed from multiple threads but a NSManagedObject or a subclass of NSManagedObject does not make sense without a parent NSManagedObjectContext much in the same way an ALAsset doesn't make sense without an ALAssetsLibrary.
It is common practice to store the NSManagedObjectContext on the AppDelegate; and while I abstract that functionality into a wrapper/singleton there is a retained reference to the NSManagedObjectContext throughout the app lifecycle. Apply the same logic to the ALAssetsLibrary and everything will works as expected.
In an existing project I have tried to introduce Core Data long after the project was created, so its model is already in place.
I have created the xcdatamodel and added my only class to it.
That class should act as a global storage for objects in my application.
The class properly implement NSManagedObject and I have verified it gets created and saved in context, also retrieved with a fetch result.
The way of saving data in this class is by means of NSMutableArray. But this is just not working. Here's a fragment of this class:
#interface WZMPersistentStore : NSManagedObject<NSCoding> {
NSMutableArray *persistentStorage;
}
#property(nonatomic,retain) NSMutableArray *persistentStorage;
-(void)add:(id)element;
-(void)remove:(id)element;
-(id)objectAtIndex:(NSUInteger)index;
-(NSUInteger)num;
#end
In the implementation I also override the initWithEntity like this:
- (id)initWithEntity:(NSEntityDescription*)entity insertIntoManagedObjectContext:(NSManagedObjectContext*)context {
NSLog(#"init with entity");
[super initWithEntity:entity insertIntoManagedObjectContext:context];
return [self init];
}
The init method only initialize the mutable array, and I can see from the log that it gets properly called by the app delegate when creating entity.
The add method just send message insertObject to persistentStorage.
The questions that come from this:
Am I doing "conceptually" right ? I
mean, is it correct to have instance
variable in managed object and
initialize like I did ?
when ns logging the size of the
persistentStorage I always get 0
even when logging a moment after the
addObject message (edit: that's not
true, I have verified again and I
correctly got 1 added).
The object stored in managed object
class trough persistentStorage are
normal class with attribute. Is
there something I need to do with
them ? I suppose not because I am
not getting any error at runtime.
No, that is not the "right" approach. You can perform initialization of instance variables in awakeFromFetch. Apple guidelines for NSManagedObject subclasses include the following:
You are also discouraged from
overriding
initWithEntity:insertIntoManagedObjectContext:,
dealloc, or finalize. Changing values
in the
initWithEntity:insertIntoManagedObjectContext:
method will not be noticed by the
context and if you are not careful,
those changes may not be saved. Most
initialization customization should be
performed in one of the awake…
methods. If you do override
initWithEntity:insertIntoManagedObjectContext:,
you must make sure you adhere to the
requirements set out in the method
description [...] (NSManagedObject Class Reference)
To really help, I'd need a deeper understanding of what you're trying to accomplish. Regardless, I strongly suggest combing through Apple's Core Data Programming Guide and sample code before proceeding.
I finally manage to solve this issue. Even if I am a newbie in objective-c, I think that introducing core data after the project is done, is not a good idea. Even if many claim it's easy. Unfortunately, all the people saying so, are showing as proof some really simple tutorial of one entity with one string attribute to change.
Instead for my project I ended up writing much code in addition to the existing one, plus some subclassing (NSManagedObject for example) which break the original model. This added code has also to be written carefully. Derived problem can be as simple as an attribute not saved, or dangerous as deleting wrong entities.
Infact, my problem was due to a wrong configuration in decode and encode method in the classes involved in the process of serialization.
For my questions:
-Point one still remain unanswered, because I am not yet confident in objective-c
-Point two, as I said the related object had some problem with encode/code.
-Point three, I was wrong, there's a lot of code to write, depending how complex is the relevant class.
Objective c | xcode | iphone question
Im building a model(data) class for a monetary transaction and have kind of a basic/noob question regarding pointers and object copying. The Transaction class I'm creating contains 4 or 5 ivars/properties that represent object type variables. now when I get the user entered data from the view controller to populate these ivars is it safe to use pointers or do i need to make a copy of the object and then assign that to the ivar?
Because I'll need to store these transactions after the user exits the program I'm assuming that any references I made to pointers in a previous session will be essentially broken links. Am i wrong here, any explanation and maybe some code examples would be appreciated.
If you have any suggestions as far as how to store the data while the app is not in use that would also be helpful.
Thanks so much,
Nick
I would suggest re-reading the intro guides as you seem to be a bit off the rails here; over-thinking the basics. No big deal, we've all been there (still are there when faced with new stuff, often!).
First, for any string value, copy it. In terms of properties, which you should use, you would want:
#property(copy) NSString *myString;
Make sure you -release myString in your -dealloc method.
For other kinds of values, it is really context dependent. Copying is often the safe route. NSDate and NSNumber instances happen to be immutable so copies are irrelevant, but free.
As far as saving data, you are semi-correct. Pointers do not remain valid/same across running sessions with your application. If you need to save data, you explicitly do so through any of a number of common mechanisms. For dead-simple data in an entirely non-document based, app specific, role, user defaults might be enough. Otherwise, see the documentation regarding archiving data.