Как запретить основной метод инициализации в NSObject - PullRequest
17 голосов
/ 18 января 2012

Я хочу заставить пользователя использовать мой собственный метод инициализации (например, -(id)initWithString:(NSString*)foo;), а не базовый [[myObject alloc]init];.

как я могу это сделать?

Ответы [ 6 ]

20 голосов
/ 11 ноября 2014

Все остальные ответы здесь устарели. Теперь есть способ сделать это правильно!

Несмотря на то, что во время выполнения легко вызвать сбой, когда кто-то вызывает ваш метод, проверка во время компиляции будет гораздо предпочтительнее.

К счастью, это было возможно в Objective-C некоторое время.

Используя LLVM, вы можете объявить любой метод недоступным в классе, например

- (void)aMethod __attribute__((unavailable("This method is not available")));

Это заставит компилятор жаловаться при попытке вызвать aMethod. Отлично!

Поскольку - (id)init - это обычный метод, вы можете запретить вызов инициализатора по умолчанию (или любого другого) таким способом.

Обратите внимание, однако, что это не застраховано от вызываемого метода с использованием динамических аспектов языка, например, через [object performSelector:@selector(aMethod)] и т. Д. В случае init вы даже не будете получите предупреждение, потому что метод init определен в других классах, а компилятор не знает достаточно, чтобы дать вам необъявленное предупреждение селектора.

Итак, чтобы убедиться в этом, убедитесь, что при вызове метода init происходит сбой (см. Ответ Адама ).

Если вы хотите запретить - (id)init в рамках, обязательно также запретите + (id)new, так как это только перенаправит к init.

Джави Сото написал небольшой макрос, чтобы запретить использование назначенного инициализатора быстрее и проще и давать более приятные сообщения. Вы можете найти это здесь .

12 голосов
/ 04 февраля 2016

ТЛ;dr

Swift :

private init() {}

Поскольку все классы Swift по умолчанию включают в себя внутренний init, вы можете изменить его на private, чтобы другие классы не вызывали его.

Цель C:

Поместите это в файл .h вашего класса.

- (instancetype)init NS_UNAVAILABLE;

Это зависит от определения ОС, которое предотвращает метод, названный по именибудучи призванным.

9 голосов
/ 11 ноября 2012

Принятый ответ неверен - вы МОЖЕТЕ сделать это, и это очень просто, вам просто нужно быть немного ясным.Вот пример:

У вас есть класс с именем "DontAllowInit", который вы хотите запретить инициации людей:

@implementation DontAllowInit

- (id)init
{
    if( [self class] == [DontAllowInit class])
    {
        NSAssert(false, @"You cannot init this class directly. Instead, use a subclass e.g. AcceptableSubclass");

        self = nil; // as per @uranusjr's answer, should assign to self before returning
    }
    else
        self = [super init];

    return nil;
}

Объяснение:

  1. Когда вывызовите [super init], класс, который был выделен, был SUBCLASS.
  2. «self» - это экземпляр - то есть то, что было init'd
  3. «[self class]» - это класс, для которого был создан экземпляр - который будет SUBCLASS, когда SUBCLASS вызывает [super init], или будет SUPERCLASS, когда SUPERCLASS вызывается с обычным [[SuperClass alloc] init]
  4. Итак, когда суперкласс получает вызов init, ему просто нужно проверить, совпадает ли класс alloc'd с его собственным классом

Работает отлично.NB. Я не рекомендую этот метод для «обычных приложений», потому что обычно вы ВМЕСТО хотите использовать протокол.

ОДНАКО ... при написании библиотек ... этот метод ОЧЕНЬ полезен: вам часто хочется"сохранить (других разработчиков) от себя", и NSAssert легко сказать им: "Ой! Вы попытались выделить / инициализировать неправильный класс! Вместо этого попробуйте класс X ...".

3 голосов
/ 18 января 2012
-(id) init
{
    @throw [NSException exceptionWithName: @"MyExceptionName" 
                                   reason: @"-init is not allowed, use -initWithString: instead"
                                 userInfo: nil];
}

-(id) initWithString: (NSString*) foo
{
    self = [super init];  // OK because it calls NSObject's init, not yours
    // etc

Бросок исключения оправдан, если вы документируете, что -init не разрешен и, следовательно, его использование является ошибкой программиста.Тем не менее, лучшим решением было бы заставить -init вызывать -initWtihString: с некоторым подходящим значением по умолчанию, например

-(id) init
{
    return [self initWithString: @""];
}
0 голосов
/ 26 ноября 2012

Я на самом деле проголосовал за ответ Адама, но хотел бы добавить кое-что к нему.

Во-первых, настоятельно рекомендуется (как кажется в автоматически сгенерированных init методах в NSObject подклассах), чтоВы проверяете self против nil в init с.Кроме того, я не думаю, что объекты класса гарантированно равны, как в ==.Я делаю это больше как

- (id)init
{
    NSAssert(NO, @"You are doing it wrong.");
    self = [super init];
    if ([self isKindOfClass:[InitNotAllowedClass class]])
        self = nil;
    return self;
}

Обратите внимание, что вместо этого я использую isKindOfClass:, потому что IMHO, если этот класс запрещает init, он должен запретить его потомкам иметь его.Если один из его подклассов хочет вернуть его (что для меня не имеет смысла), он должен явно переопределить его, вызвав мой назначенный инициализатор.

Но что более важно, используете ли вы вышеуказанный подход или нет, вы всегда должен иметь соответствующую документацию.Вы должны всегда четко указывать, какой метод является вашим назначенным инициализатором, стараться изо всех сил стараться напоминать другим не использовать неуместные инициализаторы в документации и доверять другим пользователям / разработчикам, вместо того, чтобы пытаться «сохранить чьи-либо задания» с помощьюумные коды.

0 голосов
/ 18 января 2012

Краткий ответ: вы не можете.

Более длинный ответ: рекомендуется установить наиболее подробный инициализатор в качестве назначенного инициализатора, как описано здесь 'init' затем вызовет этот инициализатор с нормальными значениями по умолчанию.

Другим вариантом является «утверждение (0)» или сбой другим способом внутри «init», но это не очень хорошее решение.

...