nacho4d
nacho4d

Reputation: 45118

Prevent class from being subclassed in Objective-c

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) :

enter image description here

Upvotes: 8

Views: 5674

Answers (4)

Brian Nickel
Brian Nickel

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

Ephemera
Ephemera

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

tia
tia

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

smileyborg
smileyborg

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

Related Questions