Mutable and immutable class cluster implementation / "method swizzling" question
Mutable and immutable class cluster implementation / "method swizzling" question
- Subject: Mutable and immutable class cluster implementation / "method swizzling" question
- From: John Engelhart <email@hidden>
- Date: Fri, 25 Mar 2011 00:49:37 -0400
[This is a re-send as my previous message has been sitting in the moderators
queue for ~week now...]
I've got an odd question. I haven't found anything via Google that matches
it exactly, but it could be that I'm just searching for the wrong thing.
I have a need to create subclasses of NSArray and NSDictionary. The
@interface line is "@interface MYArray : NSArray" (etc). No problems here-
implemented the various required class cluster interfaces. (In the examples
below, I only discuss the "array" collection class, but assume that the same
"thing" applies to the dictionary collection class, modulo the obvious
differences)
I also have a need to create mutable versions of my custom subclasses....
(if you're curious as to why I'm doing all this, the short answer is
"performance"... and yes, /all/ of it is justified by extensive (hours and
hours) of profiling / Shark.app... which are in no way "micro-benchmarking"
results. It's for serializing/deserializing JSON-
https://github.com/johnezang/JSONKit - I can serialize and deserialize JSON
faster than binary plists can for the exact same data... I beat binary plist
serialization by such a huge margin I can literally gzip the result and
STILL beat binary .plists!).
In all cases, the classes are guaranteed to only be created by my code and
it is documented that it is verboten to directly instantiate objects of
these special classes. It is also documented that it is verboten to do any
kind of subclassing of these classes. They are purely "private
implementation details".
They DO need to behave and respond "identically" to an instantiated instance
of their respective class (that is to say the class and instance methods
used to instantiate objects of these special classes are documented to be
verboten / undefined behavior). Unless there is an exceptionally good
reason, they should be completely compatible in every way and
indistinguishable from their NSArray / NSMutableArray Cocoa concrete
instances.
There is no .h header file publicly exposed for these special classes- they
don't add any extra behavior. In effect, they are the epitome of "duck
typed" collection objects- the exposed public interfaces that eventually
return these custom classes are prototyped with -(NSArray *).
I am currently using "@interface MYMutableArray : NSMutableArray". But just
like NSMutableArray is a subclass of NSArray, MYMutableArray is a subclass
of MYArray, not really NSMutableArray.
The "problem" is that with the @interface written as MYMutableArray :
NSMutableArray", I don't inherit the MYArray methods, which I'd very much
like to.
While one possible solution is to just copy and paste the code from MYArray
in to the MYMutableArray and chalk it up to "required bloat", I would much
rather only have a single copy of the reusable immutable code- one advantage
is that changes, if required, only need to be made in one place, instead of
remembering to keep both copies in sync.
To accomplish this, I've used a form of "method swizzling", but it's not
quite method swizzling as the term is normally used. It's more like "method
copying". Here's the current code:
static void _swizzleInstanceMethod(Class fromClass, Class toClass, SEL
selector) {
fromClass = class_isMetaClass(fromClass) ?
objc_getClass(class_getName(fromClass)) : fromClass;
toClass = class_isMetaClass(toClass) ?
objc_getClass(class_getName(toClass)) : toClass;
class_replaceMethod(fromClass, selector,
method_getImplementation(class_getInstanceMethod(toClass, selector)),
method_getTypeEncoding(class_getInstanceMethod(fromClass, selector)));
}
static void _swizzleClassMethod(Class fromClass, Class toClass, SEL
selector) {
fromClass = class_isMetaClass(fromClass) ? fromClass :
objc_getMetaClass(class_getName(fromClass));
toClass = class_isMetaClass(toClass) ? toClass :
objc_getMetaClass(class_getName(toClass));
class_replaceMethod(fromClass, selector,
method_getImplementation(class_getClassMethod(toClass, selector)),
method_getTypeEncoding(class_getClassMethod(fromClass, selector)));
}
I'm not 100% certain that the from / to MetaClass bits are needed as they
are probably redundant / unnecessary with the later class_getClassMethod /
class_getInstanceMethod calls (i.e., they probably automatically do the
exact same meta class checks).
I am using class_replaceMethod, which for now isn't a problem since I'm
assuming a "modern" 10.5+ runtime. I can't find a handy reference for the
minimum version of iOS that this function is supported (I'm sort of assuming
"all" as it started as a "modern" objc abi). If, for some reason, < 10.5 /
"modern" ObjC run time support is required, I suppose I could hack up
class_replaceMethod equivalent behavior, but this isn't a pressing issue
right now.
These functions are used in the mutable classes +load method to:
{
Class MYMutableArrayClass = objc_getClass("MYMutableArray"); Class
MYArrayClass = objc_getClass("MYArray");
// We swizzle the methods from MYArray in to this class (MYMutableArray).
_swizzleClassMethod(MYMutableArrayClass, MYArrayClass,
@selector(allocWithZone:));
_swizzleInstanceMethod(MYMutableArrayClass, MYArrayClass,
@selector(dealloc));
_swizzleInstanceMethod(MYMutableArrayClass, MYArrayClass,
@selector(count));
_swizzleInstanceMethod(MYMutableArrayClass, MYArrayClass,
@selector(objectAtIndex:));
_swizzleInstanceMethod(MYMutableArrayClass, MYArrayClass,
@selector(getObjects:range:));
_swizzleInstanceMethod(MYMutableArrayClass, MYArrayClass,
@selector(countByEnumeratingWithState:objects:count:));
}
... which are all the methods I have specifically created in the immutable
class that I want to "inherit" in MYMutableArray. So it's not automagic, I
have to manually keep this list in sync with the immutable methods I've
written. For my purposes, this is OK, although I suppose a super whiz bang
version could walk all the methods for the MYArray and NSArray classes, and
look for differences when the MYArray method != NSArray method, but I don't
need that much coolness.
The collection objects themselves contain either your standard Cocoa
NSString / NSNumber / etc (what you would expect for JSON, basically) or
other MY... collection class objects.
I'm curious if anyone sees any problems with this approach... Again, I
haven't found anything on the web that covers this exactly, so there's no
prior experience to draw on / best practices. This is the first time I've
had to do both mutable and immutable classes of a collection class cluster.
I didn't even anticipate that there was a problem until I literally wrote
"@interface MYMutableArray : NSMutableArray .... ergh, but how exactly do I
inherit the MYArray methods I already wrote... ? hmmmm"
Anyone from Apple want to comment on how kosher this is likely to be for an
iOS / iPhone / iPad app? I ask because the three20 framework started to
cause rejections due to its use of method swizzling. However, in the
three20 case, they were clearly doing something that was borderline
questionable (I think they swizzled -dealloc of -UIView or something to
their own -dealloc replacement to hack in behavior?). I'm not doing
anything like that, it's a fairly legitimate (at least, IMHO) use of
swizzling, and I'm only swizzling over the immutable methods that I've
written code for- I'm not overriding / swizzling the NSArray /
NSMutableArray methods at all- the swizzling only happens to my (mutable)
classes methods.
I've considered the "assume everyone and everything practices proper duck
typing" and just make @interface MYMutableArray : MYArray. There is one
corner case that I can think of where this breaks:
[myMutableArrayInstance isKindOfClass:[NSMutableArray class]]. Granted,
doing this is frowned on as "not proper Objective-C programming", but none
the less, I'm sure some people do it. I'm also not entirely sure how this
might interact with serialization, either NSCoder or property lists.
Currently, my spot checks of NSCoder behavior causes the instances to be
serialized as NSArray or NSMutableArray, which is just fine and what I want
since the sole reason that I'm creating these custom classes is for bulk
creation of collection objects very, very quickly.
Can anyone think of corner cases in which these would not behave
"identically" to their respective concrete Cocoa versions? KVO / KVC?
Anything "weird" but still considered legitimate? The CoreFoundation
CFArray... / CFDictionary calls seem to work as expected (but I can't say
I've done a comprehensive test of everything). Fast enumeration and block
based enumeration works, though come to think of it I haven't tried the
"parallel" option. Distributed Objects... CoreData? I'd like to provide
"maximally compatible" if at all possible, and if not, then clearly document
the limitations / caveats.
TIA.
_______________________________________________
Cocoa-dev mailing list (email@hidden)
Please do not post admin requests or moderator comments to the list.
Contact the moderators at cocoa-dev-admins(at)lists.apple.com
Help/Unsubscribe/Update your Subscription:
This email sent to email@hidden