PDA

View Full Version : NSKeyedArchiver: why isn't there a method to key all ivars with KVC default names?




GorillaPaws
Dec 15, 2011, 03:15 PM
I was wondering if there is a technical limitation on why the Foundation Framework couldn't implement a method that would implement -(void)encodeFloat:forKey:, -(void)encodeDouble:forKey:... etc. iterating through all of the ivars in a given class, using the default KVC values as the keys and picking the appropriate encoding methods based on the corresponding variable types. It seems like this would be the standard desired behavior in many cases, would shorten the amount of code (likely by several lines) for each class that implements it, would make code more maintainable, would prevent errors due to typos in keys (which are a pain), and would still allow for the programmer to implement different behavior using the existing methods if they needed to implement something more customized.

I'm seeing lots of advantages, and no real disadvantages, so I was wondering if it's such a great idea, why it's not there already. I doubt I'm the first person to conceive of such a method, and the fact that it doesn't exist leads me to believe there is probably a good technical reason why this isn't worth the trouble (or is actually somehow impossible).



Sydde
Dec 15, 2011, 10:18 PM
... would prevent errors due to typos in keys (which are a pain) ...

Here is how you prevent errors due to typos in keys:

MyObject.h:
#define keyForAnIvar @"SomeUniqueKeyString"

...

MyObject.m:
...
// in the encode method
...
[coder encodeObject:anIvar forKey:keyForAnIvar];
...
// in the decode method
...
anIvar = [[coder decodeObjectForKey:keyForAnIvar] retain];


You really ought to do this in all your code, where ever constants are used (including most numeric constants). Use verbose, descriptive names for your definitions, to enhance your documentation. It makes code much easier to maintain and easier to modify, since you can change behaviors in one place instead of going through many lines of code or using search/replace. #define is your friend.

Literal constants are OK for snippets used to test an idea but really have no place in production-quality source.

GorillaPaws
Dec 16, 2011, 12:02 AM
I've tended to go with defining constant strings in a separate file and used those for my keys (so I get autocompletion). This really only adds to my point of all of the potentially unnecessary code that could be mopped up if there was a method for encoding all ivars in one line. Any thoughts on why this doesn't exist?

Sydde
Dec 16, 2011, 12:20 AM
I have not had any experience with CoreData or Managed Objects, but from seeing some posts in here, it appears that these things abstract away the archiving for you. Hence, there is no real need for Apple to make <NSCoding> conforming behavior easier for programmers. Writing the necessary methods takes a few minutes, and copy/paste can make it even a little faster.

I could possibly see them adding a coding flag to the @property declaration, to be @synthesized, but I am not holding my breath.

chown33
Dec 16, 2011, 12:40 AM
First, isn't KVC tied to properties (accessor methods), not ivars? Not every ivar has a corresponding property. Not every property has a corresponding ivar. Archiving is frequently tied to ivars (or other internal state), not necessarily to properties (which may represent internal or externally visible state).

Second, how would an automatic coder and decoder know if there are dependencies between ivars? For example, one array needing to be set before some other array is set. Or any other required ordering where one ivar needs to be assigned a value before some other ivar. In non-trivial objects, ordering isn't that unusual.

Third, how would you tell the automatic archiver to exclude a particular ivar or property in its automatic archiving? For example, in Java the transient keyword prevents automatic serialization of an ivar. That keyword becomes an attribute of the variable (field) in the compiled class file.

I'm not saying these are insurmountable obstacles. Just writing down a few thoughts on issues that come to mind.

It does seem like something worth trying to engineer, even if you eventually conclude that it's unworkable. I've found that some of the best learning experiences come from an initially promising idea that ultimately fails. For one thing, it teaches you a lot about thinking things through, and writing down lines of reasoning.

Sydde
Dec 16, 2011, 01:30 AM
Not every property has a corresponding ivar.
Sorry, what? As I understand it, if you do not declare an ivar in the @interface { } scope, @property/@synthesize effectively creates one for you. Am I not understanding correctly?
Archiving is frequently tied to ivars (or other internal state)
Here is the tricky part. Apple recommends only archiving the context that you cannot recreate at runtime, in order to keep your archive as compact as practical. So, if you have ivars that you regenerate instead of decoding, you must have some way to know when those values need to be generated. Presumably, you could have some kind of method like -didDecodeKeys that would be called right after (or inside, at the end of) the -initWithCoder method. Presumably, a -didEncodeKeys analog would be needed as well. These optional methods would offer the needed flexibility for handling complex object coding.

If you wanted to apply this to ivars not declared as properties, perhaps a keyword in the declaration like AutoKeyCode, placed like IBOutlet would work?

Decoded objects are normally autoreleased presumably ARC could take care of that, though. I would assume that non-object types (like NSInteger, CGFloat, NSRect) would be NSValue wrapped instead of using primitive style coding, for consistency.

Where it could become a real problem is like one storage model I designed: I had data for a table inside a tarball that was connected to images stored in a subfolder of the tarball, so I had to subclass NSKeyedArchiver in order to be able to relink the table to the images during decoding. This might prove problematic with automatic coding.

chown33
Dec 16, 2011, 09:44 AM
Sorry, what? As I understand it, if you do not declare an ivar in the @interface { } scope, @property/@synthesize effectively creates one for you. Am I not understanding correctly?

That's correct, to some extent. It's not the whole story.

Not every property needs an ivar.

For example, consider a Rectangle used for geometry. It has height and width, which are properties backed by distinct ivars. But a rectangle also has area and perimeterLength properties, which are defined entirely by the height and width. That is, not backed by distinct ivars. There is no ivar for area, because area is a function of height and width. Same for perimeter length.

The same logic applies to other kinds of objects. It depends on the object being modeled. For example, a Person with a birthDate has no distinct age property, because age depends on birthDate and the external variable currentDate.

In a simple design, these dependent properties are read-only. In a more complex design, they might be writable and affect the underlying property they're derived from. Imagine that age is settable and affects the underlying birthDate ivar, either by setting the birthDate property or by direct access to the ivar.

If you wanted to apply this to ivars not declared as properties, perhaps a keyword in the declaration like AutoKeyCode, placed like IBOutlet would work?

IBOutlet is #defined by the compiler to nothing. At least it used to be that way. I haven't looked recently.

IBOutlet is only used by Interface Builder at a point in time before compilation. It's part of the source, but not part of the compiled result, so not present at runtime. How would an automatic archiver detect the necessary flag on ivars if it's not there at runtime?

jared_kipe
Dec 16, 2011, 10:07 AM
It could work, 'AutoKeyCode' could be #define'd away.
And at compile time, some other program would parse through your .m files looking for it and create a new .plist or something with the type and name of the variables you wanted to auto archive. Then in code you would call something like [NSArchive autoArchive: id] and it uses introspection to find the class, the .plist, and then archive it.

GorillaPaws
Dec 16, 2011, 08:43 PM
Thanks for all of the insightful replies.

I like the idea of implementing an "archive" tag (or something similar) to the @property declaration. This of course raises the issue that @chown33 brought up where properties and ivars are quite often not a == relationship. It also makes even more clutter in the @property declaration lines.

I'm pretty sure KVO predated @properties. KVO was in 10.3 I believe, while Objective-C 2.0 was in 10.5. As such, the runtime should be able to use KVO to get at ivars that aren't properties.

Here is the tricky part. Apple recommends only archiving the context that you cannot recreate at runtime, in order to keep your archive as compact as practical.

This may be the primary reason Apple wouldn't want to do this in an encodeAll method. It could be too tempting for a lazy developer who was archiving 8/10 ivars, to simply archive all 10, wasting user resources to save himself the effort of writing those extra lines of code to only archive what he needed.

Sydde
Dec 17, 2011, 11:17 AM
That's correct, to some extent. It's not the whole story.

Not every property needs an ivar.

For example, consider a Rectangle used for geometry. It has height and width, which are properties backed by distinct ivars. But a rectangle also has area and perimeterLength properties, which are defined entirely by the height and width. That is, not backed by distinct ivars. There is no ivar for area, because area is a function of height and width. Same for perimeter length.

When talking about archiving, calculate results as you describe (which I think of as functions rather than actual properties, since they are typically effected with methods, not @property/@synthesize declarations) are not stored but generated at runtime.
IBOutlet is #defined by the compiler to nothing. At least it used to be that way. I haven't looked recently.

IBOutlet is only used by Interface Builder at a point in time before compilation. It's part of the source, but not part of the compiled result, so not present at runtime. How would an automatic archiver detect the necessary flag on ivars if it's not there at runtime?
I am suggesting something similar to the way @property works in conjunction with @synthesize. All it would amount to is less coding by the programmer.

You would put AutoKeyCode in front of any ivars or in the declaration parameters of any property you want to be stored. Then, in the .m file, you would have a line like "@synthesize AutoKeyCode;", which would generate your -encodeWithCoder: and -initWithCoder: methods for you. Each method would call -finishEncoding: or -finishDecoding: (defined as empty methods in NSObject) to allow for customization.

There would be no runtime issues. It would be exactly as though you wrote the NSCoding protocol support yourself, the compiler would simply save you the work.

chown33
Dec 17, 2011, 11:42 AM
There would be no runtime issues. It would be exactly as though you wrote the NSCoding protocol support yourself, the compiler would simply save you the work.

I don't disagree with any of this, in principle. But go back to the original post. It doesn't mention anything about having to change the compiler, add keywords, etc. It only mentions Foundation framework and the use of methods. To me, that implies something done entirely at runtime, without additional information being provided to the runtime from the compiler.

Adding IBOutlet as a #define to nothing can be done without changing the compiler. Adding keywords or syntax changes requires changing the compiler. Not exactly a project for a rainy weekend.

As a related example, AFAIK, the original exception handling (try/catch) in Objective-C also didn't use compiler keywords. It #defined some preprocessor macros, which were then expanded to the necessary function calls. These used setjmp()/longjmp() and were not nearly as fine-grained as @try/@catch and @finally.

I wish I could remember where I read about this early exception handling. I'm pretty sure it was a NextStep thing, because I recollect it used the NS* naming convention. It looked a lot like other setjmp/longjmp macrofied handlers from the 80's and early 90's.

GorillaPaws
Dec 17, 2011, 11:48 AM
I wish I could remember where I read about this early exception handling.

Could it have been in "Advanced Mac OS X Programming (http://www.amazon.com/Advanced-Mac-Programming-Core-Unix/dp/0974078514)" by Hillegass? What you describe sounds vaguely familiar to me.

chown33
Dec 17, 2011, 12:04 PM
^^^
I read it in an online article somewhere.

chown33
Dec 20, 2011, 01:14 PM
I found the old try/catch macros:
http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/Exceptions/Tasks/HandlingExceptions.html%23//apple_ref/doc/uid/20000059-BBCHGJIJ

It's the subheading "Handling Exceptions Using Macros". The macros are NS_DURING, NS_HANDLER, and NS_ENDHANDLER. Some of the restrictions relative to @try are:
- there is no equivalent to an @finally block.
- a literal goto or return is forbidden within the @try-block equivalent (there are macros for returning).