Это хороший (какао, одобренный Apple) класс моделей?

Некоторое время я использую Objective-C, но я не очень хорошо следую рекомендациям Apple. Недавно я прочитал Какао-шаблоны дизайна и Руководство по реализации объекта модели, и я пытаюсь делать очень простые вещи, но делаю их очень хорошо.

Пропустил ли я какие-нибудь важные концепции? Пожалуйста, не упоминайте self = [super init]; это уже много раз освещалось на SO. Не стесняйтесь критиковать мои #pragma marks!

#import "IRTileset.h"
#import "IRTileTemplate.h"

@interface IRTileset () //No longer lists protocols because of Felixyz

@property (retain) NSMutableArray* tileTemplates; //Added because of TechZen

@end

#pragma mark -
@implementation IRTileset

#pragma mark -
#pragma mark Initialization

- (IRTileset*)init
{
    if (![super init])
    {
        return nil;
    }

    tileTemplates = [NSMutableArray new];

    return self;
}

- (void)dealloc
{
    [tileTemplates release];
    [uniqueID release]; //Added because of Felixyz (and because OOPS. Gosh.)
    [super dealloc]; //Moved from beginning to end because of Abizern
}

#pragma mark -
#pragma mark Copying/Archiving

- (IRTileset*)copyWithZone:(NSZone*)zone
{
    IRTileset* copy = [IRTileset new];
    [copy setTileTemplates:tileTemplates]; //No longer insertTileTemplates: because of Peter Hosey
    [copy setUniqueID:uniqueID];

    return copy; //No longer [copy autorelease] because of Jared P
}

- (void)encodeWithCoder:(NSCoder*)encoder
{
    [encoder encodeObject:uniqueID forKey:@"uniqueID"];
    [encoder encodeObject:tileTemplates forKey:@"tileTemplates"];
}

- (IRTileset*)initWithCoder:(NSCoder*)decoder
{
    [self init];

    [self setUniqueID:[decoder decodeObjectForKey:@"uniqueID"]];
    [self setTileTemplates:[decoder decodeObjectForKey:@"tileTemplates"]]; //No longer insertTileTemplates: because of Peter Hosey

    return self;
}

#pragma mark -
#pragma mark Public Accessors

@synthesize uniqueID;
@synthesize tileTemplates;

- (NSUInteger)countOfTileTemplates
{
    return [tileTemplates count];
}

- (void)insertTileTemplates:(NSArray*)someTileTemplates atIndexes:(NSIndexSet*)indexes
{
    [tileTemplates insertObjects:someTileTemplates atIndexes:indexes];
}

- (void)removeTileTemplatesAtIndexes:(NSIndexSet*)indexes
{
    [tileTemplates removeObjectsAtIndexes:indexes];
}

//These are for later.
#pragma mark -
#pragma mark Private Accessors

#pragma mark -
#pragma mark Other

@end

(Изменить: я внес предложенные до сих пор изменения и прокомментировал, в каких ответах они обсуждаются, на случай, если кому-то нужно знать, почему.)


person andyvn22    schedule 27.02.2010    source источник
comment
для dealloc вам следует позвонить [super dealloc] после того, как вы сделали свою собственную очистку.   -  person Abizern    schedule 28.02.2010
comment
Не могли бы вы оставить копию исходного кода, чтобы я мог понять, о чем говорят ответы? Спасибо ;)   -  person Mongus Pong    schedule 28.02.2010
comment
Монгус Понг: stackoverflow.com/posts/2349405/revisions   -  person Peter Hosey    schedule 28.02.2010
comment
Кроме того, поэтому я оставляю комментарии рядом с каждым изменением.   -  person andyvn22    schedule 28.02.2010
comment
Не для того, чтобы испортить вечеринку, но я понимаю, что SO не для подобных дискуссий, а для вопросов, на которые можно дать окончательный ответ.   -  person Colin Barrett    schedule 01.03.2010
comment
Я решил, что конечный результат (пример класса модели, подобного Cocoa, одобренного Apple) - это окончательный ответ. Я мог бы спросить, каков пример класса простой модели в стиле Какао, одобренной Apple? но тогда я думаю, что меня могли обвинить в том, что я не пытался писать код для себя.   -  person andyvn22    schedule 29.03.2010


Ответы (4)


Пожалуйста, не упоминайте self = [super init]

Итак, почему этого не?

То же самое и с initWithCoder:: вы должны использовать объект, возвращаемый [self init], не предполагая, что он инициализировал начальный объект.

- (void)dealloc
{
    [super dealloc];
    [tileTemplates release];
}

Как сказал Абизерн в своем комментарии, [super dealloc] должен идти последним. В противном случае вы обращаетесь к переменной экземпляра освобожденного объекта.

- (IRTileTemplate*)copyWithZone:(NSZone*)zone

Тип возврата здесь должен быть id, что соответствует типу возврата, объявленному протоколом NSCopying.

{
    IRTileset* copy = [IRTileset new];
    [copy insertTileTemplates:tileTemplates atIndexes:[NSIndexSet indexSetWithIndex:0]];
    [copy setUniqueID:uniqueID];

Вы вставляете ноль или более объектов в один индекс. Создайте свой набор индексов с диапазоном: location = 0, length = count массива tileTemplates. А еще лучше просто присвоить всему свойству значение:

copy.tileTemplates = self.tileTemplates;

Или получить доступ к переменным экземпляра напрямую:

copy->tileTemplates = [tileTemplates copy];

(Обратите внимание, что вы должны делать copy самостоятельно, обходя методы доступа к свойствам, и что вы copy обрабатываете массив от имени копии.)

    return [copy autorelease];
}

copyWithZone: не должен возвращать автоматически выпущенный объект. Согласно правилам управления памятью , вызывающий copy или copyWithZone: владеет копией, что означает, что задача вызывающего - освободить ее, а не copyWithZone:.

@synthesize tileTemplates;
[et al]

Вы также можете реализовать методы доступа к однообъектным массивам:

- (void) insertObjectInTileTemplates:(IRTileTemplate *)template atIndex:(NSUInteger)idx;
- (void) removeObjectFromTileTemplatesAtIndex:(NSUInteger)idx;

Это, конечно, необязательно.

person Peter Hosey    schedule 28.02.2010
comment
Замечательные комментарии. Я бы добавил, что вариант прямого доступа к переменным экземпляра не рекомендуется, так как это проще испортить и практически не имеет преимуществ. Обход сеттеров редко бывает хорошей идеей. - person Felixyz; 28.02.2010
comment
На самом деле, я бы пошел другим путем: тот же аргумент против использования аксессоров в init и dealloc (объект не полностью инициализирован, поэтому вы не должны отправлять ему сообщения) также применяется в copyWithZone:. - person Peter Hosey; 28.02.2010
comment
Я использую аксессоры как в init, так и в dealloc. Основная причина помещения логики в установщики - сделать код более СУХИМ, поэтому мне не нравится дублировать какой-либо код в init. Но да, есть недостатки, и я еще раз подумаю. - person Felixyz; 28.02.2010

// Однако следует ли мне перечислять здесь протоколы, даже если они уже перечислены в IRTileset.h?

Нет, не надо. Расширение класса, объявленное в файле реализации, является расширением, поэтому вам не нужно заботиться о том, каким протоколам должен следовать класс.

Я бы рекомендовал отмечать имена переменных вашего экземпляра знаком подчеркивания: _tileTemplates. (Пуристы посоветуют вам ставить подчеркивание вместо префикса; сделайте это, если вы их боитесь.)

Не используйте new для создания экземпляров классов. Насколько я понимаю, никогда не рекомендуется.

[NSMutableArray new];                     //  :(
[NSMutableArray arrayWithCapacity:20];    //  :)

Не звоните [super dealloc], пока не освободите свои ресурсы! При определенных обстоятельствах это может вызвать сбой.

- (void)dealloc
{
    [tileTemplates release];
    [super dealloc];          // Do this last
}

Я не уверен, какой у типа uniqueID, но разве он не должен быть выпущен в dealloc?

Я бы никогда не поместил свои директивы @synthesize в середину файла (поместите их сразу под «@ implementation»).

Кроме того, поскольку я не имею четкого представления о роли этого класса, countOfTileTemplates мне не нравится. Может быть, подойдет просто «count», если однозначно, что этот класс делает для хранения шаблонов тайлов?

person Felixyz    schedule 28.02.2010
comment
countOfTileTemplates реализован для соответствия KVO. - person andyvn22; 28.02.2010
comment
Я надеялся, что мне не стоит перечислять эти протоколы. :) Почему бы мне не использовать новую !? - person andyvn22; 28.02.2010
comment
Да, countOf<PropertyName> - это один из KVC-совместимых форматов доступа к массивам. См. Также developer.apple.com/documentation/Cocoa/Conceptual/ ModelObjects /, developer.apple.com/documentation/Cocoa/Conceptual/CoreData/ и boredzo.org/blog/archives/2007-08-07/. - person Peter Hosey; 28.02.2010
comment
Re: countOfTileTemplates - Ах да, очевидно. Кто-то проголосовал против меня! Re: создание экземпляра через новый - stackoverflow.com/questions/719877/ - person Felixyz; 28.02.2010
comment
andyvn22: new - не самый популярный вариант, но допустимый. Это не более чем выбор стиля. См. Также stackoverflow.com/questions/719877/ и stackoverflow.com/questions/1385410/. - person Peter Hosey; 28.02.2010

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

Например, что, если какой-то внешний код вызывает это:

myIRTileset.tileTemplates=someArray;

Бум, вы потеряли все свои данные.

Вы должны определить оба свойства данных как только для чтения. Затем напишите средства доступа внутри класса, которые будут управлять их сохранением в реализации класса. Таким образом, единственный способ для внешнего объекта изменить tileTemplates - это вызвать методы - insertTileTemplates:atIndexes: и removeTileTemplatesAtIndexes:.

Edit01:

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

Интерфейс

@interface PrivateTest : NSObject {
@private 
    //iVar is invisible outside the class, even its subclasses
    NSString *privateString; 
@public
    //iVar is visible and settable to every object. 
    NSString *publicString; 
}
@property(nonatomic, retain)  NSString *publicString; //property accessors are visible, settable and getable. 
//These methods control logical operations on the private iVar.
- (void) setPrivateToPublic;  
- (NSString *) returnPrivateString;
@end

Таким образом, при использовании это будет выглядеть так:

Выполнение

#import "PrivateTest.h"

//private class extension category defines 
// the propert setters and getters 
// internal to the class
@interface PrivateTest ()
@property(nonatomic, retain)  NSString *privateString;
@end

@implementation PrivateTest
//normal synthesize directives
@synthesize privateString; 
@synthesize publicString;

// Methods that control access to private
- (void) setPrivateToPublic{
    //Here we do a contrived validation test 
    if (self.privateString != nil) {
        self.privateString=self.publicString;
    }
}

- (NSString *) returnPrivateString{
    return self.privateString;
}

@end

Вы бы использовали это так:

PrivateTest *pt=[[PrivateTest alloc] init];
    // If you try to set private directly as in the next line
    // the complier throws and error
//pt.privateString=@"Bob"; ==> "object cannot be set - either readonly property or no setter found"
pt.publicString=@"Steve";
[pt setPrivateToPublic];
NSLog(@"private=%@",[pt returnPrivateString]); //==> "Steve"

Теперь у класса есть пуленепробиваемая целостность данных. Любой объект в вашем приложении может установить и получить строковое свойство publicString, но ни один внешний объект не может установить или получить private.

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

person TechZen    schedule 28.02.2010
comment
Хорошая точка зрения! Для записи я сделал обратное тому, что вы сказали: я поместил - (NSMutableArray*)tileTemplates; в свой заголовок, а затем определил tileTemplates как свойство в этом файле. Таким образом, я все еще мог быть ленивым и использовать @synthesize. - person andyvn22; 28.02.2010
comment
Смотрите мою правку, чтобы лучше объяснить, как защитить целостность данных. - person TechZen; 28.02.2010

две незначительные нюансы: один - это метод init (где стилистически я против двух разных точек возврата, но это только я), однако ничто не мешает super init возвращать объект, отличный от себя или nil, например, другой объект его класс или даже просто другой объект в целом. По этой причине self = [super init], как правило, является хорошей идеей, даже если на практике это вряд ли принесет много пользы. Во-вторых, в методе copyWithZone вы не копируете tileTemplates, что может быть преднамеренным, но, как правило, является плохой идеей (если только они не являются неизменяемыми). Предполагается, что копирование объекта имеет тот же эффект, что и выделение нового, например. сохранить счетчик 1, поэтому не отпускайте его автоматически. Кроме того, не похоже, что вы что-то делаете с зоной, поэтому вам, вероятно, следует заменить ее чем-то вроде

- (IRTileTemplate*)copyWithZone:(NSZone*)zone {
    IRTileset* copy = [[IRTileset allocWithZone:zone] init];
    [copy insertTileTemplates:[tileTemplates copyWithZone:zone]
                    atIndexes:[NSIndexSet indexSetWithIndex:0]];
    [copy setUniqueID:uniqueID];
    return copy;
}

вот и все, что я нашел; за исключением сохраняемого счетчика копий (что БУДЕТ приводить к ошибкам в дальнейшем), в основном это просто вещи, которые я предпочитаю, вы можете сделать это по-своему, если вам это больше нравится. Хорошая работа

person Jared Pochtar    schedule 28.02.2010
comment
Этот код приводит к утечке копии, так как метод доступа insertTileTemplates:atIndexes: не захватит данный массив, а скорее вставит объекты из него в собственный массив получателя. Нет причин копировать массив при передаче его в метод доступа. - person Peter Hosey; 28.02.2010
comment
ах да мой плохой, я думал, tileTemplates - это имя для единственного объекта модели - person Jared Pochtar; 28.02.2010