Това добър (подобен на какао, одобрен от Apple) клас модели ли е?

Използвам Objective-C от известно време, но не следвам много добре указанията на Apple. Наскоро прочетох Шаблони за дизайн на Cocoa и Ръководство за внедряване на моделни обекти и се опитвам да направя някои много прости неща, но ги правя много добре.

Пропуснал ли съм някои основни понятия? Моля, не споменавайте 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) е окончателен отговор. Можех да го попитам какъв е пример за подобен на Cocoa, одобрен от 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];

Вмъквате нула или повече обекти в един индекс. Създайте своя набор от индекси с диапазон: местоположение = 0, дължина = броят на масива 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 (където стилистично съм против наличието на 2 различни точки за връщане, но това е само аз), но нищо не пречи на init на super да връща обект, различен от себе си или нула, напр. различен обект от неговия клас или дори съвсем друг обект. Поради тази причина 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