Nat! bio photo

Nat!

Senior Mull

Twitter Github Twitch

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]];
}
@end 

The actual YourClass would be gone, and MyClass would 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:

Sketch of the class hierarchy

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:

Sketch of improved class hierarchy

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.

Name:
E-mail: (not published)
Website: