Подтвердить что ты не робот

Цель C - Пользовательский просмотр и реализация метода init?

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

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

4b9b3361

Ответ 1

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

- (void)commonInit
{
    // do any initialization that 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, который вряд ли будет подклассифицирован сам по себе, использование общего метода инициализации, как указано выше, отлично. Если вы пишете фреймворк для других, или если вы не понимаете проблему, но хотите сделать все возможное, используйте статическую функцию.

Ответ 2

решение не так просто, как изначально появляется. есть некоторые опасности при инициализации - больше на тех, кто дальше. по этим причинам я обычно беру один из двух следующих подходов в программах 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 ++:

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

Update:

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

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

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

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

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

Рекомендация: проверить наличие ошибок в init. также сохраняют совместимость обработки ошибок и обнаружения. return 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;
}

// …

(btw, без ошибок в этом примере)

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

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

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

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

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

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

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

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

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

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

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

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

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

Ответ 3

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

Например, 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 при ошибке.

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

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