Skip to content

Instantly share code, notes, and snippets.

@steipete
Last active January 6, 2024 07:24
Show Gist options
  • Star 148 You must be signed in to star a gist
  • Fork 7 You must be signed in to fork a gist
  • Save steipete/9482253 to your computer and use it in GitHub Desktop.
Save steipete/9482253 to your computer and use it in GitHub Desktop.
Declare on your main init that all other init methods should call. It's a nice additional semantic warning. Works with Xcode 5.1 and above. Not tested with earlier variants, but should just be ignored. A reference to this macro shortly appeared in https://developer.apple.com/library/ios/releasenotes/ObjectiveC/ModernizationObjC/AdoptingModernObj…
#ifndef NS_DESIGNATED_INITIALIZER
#if __has_attribute(objc_designated_initializer)
#define NS_DESIGNATED_INITIALIZER __attribute((objc_designated_initializer))
#else
#define NS_DESIGNATED_INITIALIZER
#endif
#endif
@steipete
Copy link
Author

Example:

// Regular init methods.
- (id)init NS_DESIGNATED_INITIALIZER;
- (id)initWithURL:(NSURL *)URL;
- (id)initWithData:(NSData *)data;
- (id)initWithDataArray:(NSArray *)data;
- (id)initWithDataProvider:(CGDataProviderRef)dataProvider;
- (id)initWithDataProviderArray:(NSArray *)dataProviders;
- (id)initWithBaseURL:(NSURL *)baseURL files:(NSArray *)files;
- (id)initWithBaseURL:(NSURL *)baseURL fileTemplate:(NSString *)fileTemplate startPage:(NSInteger)startPage endPage:(NSInteger)endPage;

All other init methods are calling internally [self init], and Clang will emit a warning if we forget this for some new init.

This is how the warning looks if we forget calling it

@Tricertops
Copy link

Great news! I think they didn't ship it, because of some issues. Try to mark designated initializer in a class extension and it is showing errors saying it must be in @interface (it is!). I'm using MyClass+Subclass.h to provide interface for subclasses, because the class is abstract.

@aprato
Copy link

aprato commented Mar 11, 2014

From my still open tab...

In Objective-C, object initialization is based on the notion of a designated initializer, an initializer method that is responsible for calling one of its superclass’s initializers and then initializing its own instance variables. Initializers that are not designated initializers are known as secondary initializers. Secondary initializers typically delegate to another initializer—eventually terminating the chain at a designated initializer—rather than performing initialization themselves.

The designated initializer pattern helps ensure that inherited initializers properly initialize all instance variables. A subclass that needs to perform nontrivial initialization should override all of its superclass’s designated initializers, but it does not need to override the secondary initializers. For more information about initializers, see “Object Initialization”.

To clarify the distinction between designated and secondary initializers clear, you can add the NS_DESIGNATED_INITIALIZER macro to any method in the init family, denoting it a designated initializer. Using this macro introduces a few restrictions:

  • The implementation of a designated initializer must chain to a superclass init method (with [super init...]) that is a designated initializer for the superclass.
  • The implementation of a secondary initializer (an initializer not marked as a designated initializer within a class that has at least one initializer marked as a designated initializer) must delegate to another initializer (with [self init...]).
  • If a class provides one or more designated initializers, it must implement all of the designated initializers of its superclass.

If any of these restrictions are violated, you receive warnings from the compiler.

@raphaelschaad
Copy link

Interesting. It breaks with the pattern of overriding another initializer and asserting in there with a note that you must use the class' designated initializer. E.g. on a UIView subclass:

  • have your designated initializer -initWithSomeCriticalData:, and
  • override
- (id)initWithFrame:(CGRect)frame {
    NSAssert(NO, @"Use -initWithSomeCriticalData: and supply the critical dataas an argument to initialize an object of CustomView.");
    return nil;
}

.

@JaviSoto
Copy link

If you're gonna do that, it's also a good idea to add -initWithFrame: to your UIView subclass' header file like so:

@interface XXMyUIViewSubclass : UIView

- (id)initWithSomeCriticalData:(id)criticalData;

- (id)initWithFrame:(CGRect)frame  __attribute__((unavailable("Use -initWithSomeCriticalData: instead"))); 

@end

This way you won't only crash at runtime, clang will emit a warning if you try to call the wrong init method.

@luca-bernardi
Copy link

Or if still want to crash at runtime with an assertion you can do that (and this is what I usually do):

@implementation XXMyUIViewSubclass

- (instancetype)initWithCriticalData:(id)criticalData
{
    NSParameterAssert(criticalData);
    // ....
}

- (instancetype)initWithFrame:(CGRect)frame
{
    return [self initWithCriticalData:nil];
}

@end

I think that this can be mixed with @JaviSoto approach because is nice to warning the user with a warning but if you're designing API that need to be used by others you need to enforce this and you cannot rely on the fact that the consumer of your API will necessarily play nice.

@nzhuk
Copy link

nzhuk commented Mar 15, 2014

This attribute is problematic in UIViewController subclasses. If you introduce your own init method and mark it as __attribute((objc_designated_initializer)), you'll get compiler warnings even if you do call the designated initializer of the superclass from it (i.e. [super initWithNibName:bundle:] ), since -[UIViewController initWithNibName:bundle:] is documented as designated initializer but not marked as such with this attribute.

@janodev
Copy link

janodev commented Mar 23, 2014

@lukabernardi unavailable produces a compiler error that prevents accidental misbehavior. Intentional wrongdoers will be able to skip any other check you add.

@Mazyod
Copy link

Mazyod commented Jul 9, 2014

@nzhuk This is so stupid :( And when you use the unavailable attribute, you can't redefine that method in the subclass in order to make it available again >__< .. Need.. Swift.. Now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment