Is -[NSObject poseAsClass:] still useful in 2024 ?
What is posing ? This article Posing in Objective C explains it quite well, so go there for more detail. In a nutshell:
Posing in Objective-C is a potent method that enables a class to completely replace another class within a program.In source code, posing used to look something like this:
@interface MyClass : YourClass @end @implementation MyClass + (void) load { [self poseAsClass:[self superclass]]; } @endThe actual
YourClasswould be gone, andMyClasswould take its place.
Prior to last week, I would have said, that there is no need for poseAsClass:
anymore, since mulle-objc is open source.
Historically I used poseAsClass:
only for Apples Foundation classes, which were closed source and needed some
tweaking or bug fixes.
As Apple veered off track and started diluting Objective-C, poseAsClass:
was deprecated
and later removed. That escape hatch for vendor problems was shut.
But now I ran into the following situation:
I have a class UIView and another class UIScrollView that sits on top of it,
these classes live in the library MulleUIView.
@interface UIView : NSObject
@end
@interface UIScrollView : UIView
@end
MulleUIView is backend agnostic, therefore reusable across terminal or bitmap displays.
Now for terminal displays I have another view class UITTYView which is
on top of UIView in a library MulleTTY. I also need a scrollview
called UITTYScrollView:
@interface UITTYView : UIView
@end
@interface UITTYScrollView : UITTYView
@end
If we make a little sketch, the problem becomes apparent:
UITTYScrollView can either subclass UIScrollView or UITTYView but not
both. If I subclass from UIScrollView, I don’t have to rewrite the whole
class, but I would have to copy all the code from UITTYView. And, if I
subclass from UITTYView its the other way round.
Use a Protocolclass ?
Now wait a minute you say, isn’t the multiple inheritance scheme in mulle-objc protocolclasses ? Why are you not dogfooding here ?
If I rewrite the main of UIScrollView as a protocolclass (say UIScrollViewProtocol),
I could indeed get the code into both classes:
PROTOCOLCLASS_INTERFACE( UIScrollViewProtocol, NSObject)
PROTOCOLCLASS_END()
@interface UIScrollView : UIView < UIScrollViewProtocol>
@end
@interface UITTYScrollView : UITTYView < UIScrollViewProtocol>
@end
But I would forego direct instance variable access in UIScrollViewProtocol
though, I would have to use @property accessors.
Also mulle-objc does not allow categories on protocolclasses
(yet). So UIScrollViewProtocol would not be extensible and all the
code would have to be in one monolithic file. Not a good solution.
Use a Category ?
Maybe more sensibly, UITTYView could be ditched and replaced by a category.
Assume (which is not the case) that UITTYView is only:
@implementation UITTYView
- (void) setFrame:(CGRect) frame
{
[super setFrame:frame];
[_mainLayer setFrame:_frame];
}
@end
It could be changed into a category and still call the super implementation:
@implementation UIView( TTY)
- (void) setFrame:(CGRect) frame
{
// [super setFrame:frame];
(*MulleObjCClobberedIMP)( self, _cmd, _param);
[_mainLayer setFrame:_frame];
}
@end
OK, I wouldn’t mind doing this, but MulleObjCClobberedIMP actually does
a method search, which means no cached access. For a frequently run method
like -setFrame:, this is just not that cool.
Also the code becomes a bit weird, especially, if you have a lot of these
methods in your category and I would probably be prone to forget using
MulleObjCClobberedIMP properly.
Interposing or Posing ?
So what I really want is a class setup like this:
but I can’t mix TTY classes and pure UIView classes into one library,
as that would kill reuse.
The invention of +interposeSuperclass
So the first step was to write interposeSuperClass. Quite frankly it
eluded me, why +poseAsClass: was not +poseAsSuperclass since you would
only pose as the superclass anyway or ? So you would call [UITTYView interposeSuperClass]
and the class would wedge itself betwen the UIView and all subclasses of
UIView. Mission accomplished.
There are problems though. If you load subclasses of UIView
after you ran [UITTYView interposeSuperClass], they would not get interposed.
The runtime would have to maintain a list of interposing classes, and then
check against that list during load time. That seemed like a bit of overkill.
The reimplementation of +poseAsClass:
Of course I wanted to go one step further, and also try out completely posing
UIView away with UITTYView. That was a bit more involved to get it to
work properly and without leaks in mulle-objc. Once I had it, it became
apparent, that this does not suffer the drawbacks of +interposeSuperclass,
late loading classes would be hooked up correctly. Still the complete
elimination of the super class, seems too heavy handed.
The addition of +interposeBeforeClass: to mulle-objc
And that’s what I am settling on for now, I just interpose between two classes per interpose (1:1) and not between multiple (1:n).
So in my UITTYScollView class I have:
@interface UITTYScrollView : UIScrollView
@end
@implementation UITTYScrollView
+ (void) load
{
[UITTYView interposeBeforeClass:[UIScrollView class]];
}
@end
and that is it. Fairly neat and unproblematic.



Post a comment
All comments are held for moderation; basic HTML formatting accepted.