Цель C - Пользовательское представление и реализация метода init? - PullRequest
26 голосов
/ 29 августа 2011

У меня есть пользовательское представление, которое я хочу иметь возможность инициализировать in-code и nib.

Как правильно написать методы initWithFrame и initWithCoder? Оба они совместно используют блок кода, который используется для некоторой инициализации.

Ответы [ 3 ]

59 голосов
/ 29 августа 2011

В этом случае правильно создать другой метод, содержащий код, общий для -initWithFrame: и -initWithCoder:, а затем вызвать этот метод из -initWithFrame: и -initWithCoder::

- (void)commonInit
{
    // do any initialization that's common to both -initWithFrame:
    // and -initWithCoder: in this method
}

- (id)initWithFrame:(CGRect)aRect
{
    if ((self = [super initWithFrame:aRect])) {
        [self commonInit];
    }
    return self;
}

- (id)initWithCoder:(NSCoder*)coder
{
    if ((self = [super initWithCoder:coder])) {
        [self commonInit];
    }
    return self;
}

Прислушайтесь к проблемам, изложенным в ответе Джастина, особенно в том, что любые подклассы не должны отменять -commonInit. Я использовал это имя в качестве иллюстративного значения, но вы, вероятно, захотите, чтобы оно было более тесно связано с вашим классом и с меньшей вероятностью было бы случайно переопределено. Если вы создаете специально созданный подкласс UIView, который вряд ли будет разделен на подклассы, используйте общий метод инициализации, как описано выше, что вполне нормально. Если вы пишете фреймворк для использования другими или если вы не понимаете проблему, но хотите сделать максимально безопасную вещь, используйте вместо этого статическую функцию.

8 голосов
/ 29 августа 2011

решение не так просто, как кажется на первый взгляд.Есть некоторые опасности при инициализации - подробнее об этом ниже.по этим причинам я обычно использую один из двух следующих подходов в программах objc:

для тривиальных случаев дублирование не является плохой стратегией:

- (id)initOne
{
    self = [super init];
    if (nil != self) { monIntIvar = SomeDefaultValue; }
    return self;
}

- (id)initTwo
{
    self = [super init];
    if (nil != self) { monIntIvar = SomeDefaultValue; }
    return self;
}

для нетривиальных случаев, я рекомендуюстатическая функция инициализации, которая принимает общий вид:

// MONView.h

@interface MONView : UIView
{
    MONIvar * ivar;
}

@end

// MONView.m

static inline bool InitMONView(MONIvar** ivar) {
    *ivar = [MONIvar new];
    return nil != *ivar;
}

@implementation MONView

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (nil != self) {
        if (!InitMONView(&ivar)) {
            [self release];
            return nil;
        }
    }
    return self;
}

- (id)initWithCoder:(NSCoder *)coder
{
    self = [super initWithCoder:coder];
    if (nil != self) {
        if (!InitMONView(&ivar)) {
            [self release];
            return nil;
        }
    }
    return self;
}

// …

@end

Objective-C ++:

если вы используете objc ++, то вы можете просто реализовать подходящие конструкторы по умолчанию дляваши c ++ ivars и опускают большую часть инициализации и освобождают леса (при условии, что вы правильно включили флаги компилятора).

Обновление:

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

Типичная проблема с обычными инициализаторами, которые вызывают методы экземпляра во время инициализации, заключается в том, что они злоупотребляют графом наследования, как правило,ввести сложности и ошибки.

Рекомендация: вызовпереопределенные методы экземпляра на частично сконструированном объекте (например, во время инициализации и освобождения) небезопасны и их следует избегать.аксессоры особенно плохи.В других языках это ошибка программиста (например, UB).Просмотрите документацию objc на эту тему (ссылка: «Реализация инициализатора»).Я считаю это обязательным, но я все еще знаю людей, которые настаивают на том, что методы экземпляров и методы доступа лучше в частично сконструированных состояниях, потому что это " обычно работает для них".

Требование: Соблюдайте график наследования.инициализировать от базы вверх.уничтожить сверху вниз.всегда.

Рекомендация: сохранить инициализацию согласованной для всех.если ваша база возвращает что-то из init, вы должны предполагать, что все хорошо.не вводите хрупкий танец инициализации для ваших клиентов и подклассов для реализации (это может вернуться как ошибка).вам нужно знать, есть ли у вас действительный экземпляр.Кроме того, субклассеры (по праву) предполагают, что ваша база правильно инициализирована, когда вы возвращаете объект из назначенного инициализатора.Вы можете уменьшить вероятность этого, сделав ивары базового класса приватными.как только вы вернетесь из init, клиенты / подклассы предполагают, что объект, из которого они получены, может использоваться и правильно инициализироваться.По мере роста графов классов ситуация становится очень сложной, и ошибки начинают появляться.

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

Хорошо, а как насчет метода общего экземпляра?

exmaple заимствован и изменен из другого поста:

@implementation MONDragon

- (void)commonInit
{
    ivar = [MONIvar new];
}

- (id)initWithFrame:(CGRect)aRect
{
        if ((self = [super initWithFrame:aRect])) {
                [self commonInit];
        }
        return self;
}

- (id)initWithCoder:(NSCoder*)coder
{
        if ((self = [super initWithCoder:coder])) {
                [self commonInit];
        }
        return self;
}

// …

(кстати, обработки ошибок вэтот пример)

Калеб: самая большая «опасность», которую я вижу в приведенном выше коде, заключается в том, что кто-то может создать подкласс рассматриваемого класса, переопределить -commonInit и потенциально инициализировать объект дважды.

в частности, подкласс - [MONDragon commonInit] будет вызываться дважды (утечка ресурсов, поскольку они будут созданы дважды) и инициализатор базы и обработка ошибок не будут выполняться.

Калеб: Если это реальный риск ...

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

Caleb:… самый простой способ справиться с этим - сохранить -commonInit закрытым и / или задокументировать его как нечто, что не следует переопределять

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

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

, если кто-то настаивает на использовании метода экземпляра, зарезервированное вами соглашение, такое как -[MONDragon constructMONDragon] и -[MONKomodo constructMONKomodo], может значительно уменьшить ошибку в большинствеслучаев.инициализатор, вероятно, будет виден только для TU реализации класса, поэтому компилятор может пометить некоторые из наших потенциальных ошибок.

примечание: общий конструктор объекта, такой как:

- (void)commonInit
{
    [super commonInit];
    // init this instance here
}

(что я также видел) еще хуже, потому что оно ограничивает инициализацию, удаляет контекст (например, параметры), и вы по-прежнему получаете людей, смешивающих свой код инициализации между классами между назначенным инициализатором и -commonInit.

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

это не опция в OP, основанная на указанном методе, а общее замечание: выможет обычно объединять общую инициализацию легче, используя удобные конструкторы.это особенно полезно для минимизации сложности при работе с кластерами классов, классами, которые могут возвращать специализации, и реализациями, которые могут выбрать выбор из нескольких внутренних инициализаторов.

3 голосов
/ 29 августа 2011

Если они совместно используют код, просто попросите их вызвать третий метод инициализации.

Например, initWithFrame может выглядеть примерно так:

- (id)initWithFrame:(CGRect)frame {
    if ((self = [super initWithFrame:frame])) {
        [self doMyInitStuff];
    }

    return self;
}

Обратите внимание, если вы включеныВ OS X (в отличие от iOS) фрейм будет NSRect вместо CGRect.

Если вам нужно выполнить проверку ошибок, ваш метод инициализации должен вернуть состояние ошибки, например:

- (id)initWithFrame:(CGRect)frame {
    if ((self = [super initWithFrame:frame])) {
        if (![self doMyInitStuff]) {
            [self release];
            self = nil;
        }
    }

    return self;
}

Предполагается, что метод doMyInitStuff возвращает NO при ошибке.

Кроме того, если вы еще не просматривали, есть немного документации по инициализации, которая может быть полезна для вас (хотя этот вопрос напрямую не рассматривается):

Руководство по кодированию для какао: советы и методики для разработчиков платформ

...