Reputation: 45118
How do I prevent a particular class from being subclassed?
I am not aware of such functionality (say final
keyword for example) in the language. However Apple says it has done so for all classes in AddressBookUI.framework (in iOS)
For educational purposes, how can I achieve the same functionality, or how would they have done such thing?
From iOS7 Release Notes(Requires login) :
Upvotes: 8
Views: 5674
Reputation: 27550
There's a simpler way to prevent subclassing in Xcode 6 as a result of Swift interop. To prevent Swift classes from being subclassed in Objective-C the objc_subclassing_restricted
is added to all class definitions in the {ProjectName}-Swift.h
file.
You can use this in your projects:
#if defined(__has_attribute) && __has_attribute(objc_subclassing_restricted)
# define FOO_FINAL __attribute__((objc_subclassing_restricted))
#else
# define FOO_FINAL
#endif
FOO_FINAL
@interface Foo : NSObject
@end
@interface Bar : Foo
@end
The compiler will halt on the definition of Bar
with Cannot subclass a class with objc_subclassing_restricted attribute
Upvotes: 9
Reputation: 8980
Something like the following will ensure that every time an "impossible subclass" calls +alloc
, an object will be allocated that is an instance of FinalClass
, and not the subclass. This is essentially what NSObject's +alloc
method does, but here we specify an explicit class to create. This is how NSObject allocates instances (in Obj-C 2), but there is no guarantee this will always be the case, so you may want to add an appropriate -dealloc
which calls object_dispose
. This method also means you don't get a nil object back if you try to instantiate a subclass - you do get an instance of FinalClass
.
@interface FinalClass: NSObject
//...
+ (id)alloc; // Optional
@end
// ...
#import <objc/runtime.h>
@implementation FinalClass
+ (id)alloc {
if (![self isMemberOfClass:[FinalClass class]]) {
// Emit warning about invalid subclass being ignored.
}
self = class_createInstance([FinalClass class], 0);
if (self == nil) {
// Error handling
}
return self;
}
@end
@interface InvalidSubclass : FinalClass
// Anything not in FinalClass will not work as +alloc will
// create a FinalClass instance.
@end
Note: I'm not sure I'd use this myself - specifying that a class shouldn't be subclassed is more in the nature of a design-contract with the programmer rather than an enforced rule at compile- or runtime.
Upvotes: 0
Reputation: 9698
Here is possible solution:
@interface FinalClass : NSObject
@end
@implementation FinalClass
- (id)init
{
if (self.class != [FinalClass class]) {
return nil;
}
self = [super init];
if (self) {
// instance initialization
}
return self;
}
@end
@interface InvalidSubclass : FinalClass
@end
@implementation InvalidSubclass
- (id)init
{
self = [super init];
if (self) {
}
return self;
}
@end
I'm not sure this is 100% guaranteed because it's runtime-checking anyway, but it should be enough to block and warn people that they should not subclass this. Subclass might skip superclass's init, but then the instance will not be usable because it's not fully initialised by superclass.
Upvotes: 2
Reputation: 30469
Here's one way: override allocWithZone:
from within your "final" class (substituting MyFinalClassName
for your actual class name) like this:
+ (id)allocWithZone:(struct _NSZone *)zone
{
if (self != [MyFinalClassName class]) {
NSAssert(nil, @"Subclassing MyFinalClassName not allowed.");
return nil;
}
return [super allocWithZone:zone];
}
This will prevent a subclass that is not a member of MyFinalClassName
from being alloc
'ed (and therefore init
'ed as well), since NSObject
's allocWithZone:
must be called eventually, and by refusing to call super
from your "final" class, you will prevent this.
Upvotes: 13