这是一个好的(类似 Cocoa 的、Apple 认可的)模型类吗?

发布于 2024-08-23 14:14:07 字数 2625 浏览 4 评论 0原文

我已经使用 Objective-C 一段时间了,但我并没有很好地遵循 Apple 的指导方针。最近我读了 Cocoa 设计模式模型对象实现指南,我正在尝试做一些非常简单的事情,但是做得很好。

我是否错过了任何主要概念?请不要提及 self = [super init]; SO 已经对此进行了多次介绍。不过,请随意批评我的#pragma mark

#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

(编辑:我已经做出了到目前为止建议的更改,并评论了哪些答案讨论它们,以防有人需要知道原因。)

I've been using Objective-C for a while, but I've not been following Apple's guidelines very well. Recently I read Cocoa Design Patterns and the Model Object Implementation Guide, and I'm trying to do some very simple things, but do them very well.

Have I missed any major concepts? Please don't mention self = [super init]; that's been covered many times on SO already. Feel free to critique my #pragma marks though!

#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

(Edit: I've made the changes suggested so far and commented which answers discuss them, in case anyone needs to know why.)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

别忘他 2024-08-30 14:14:08

请不要提及self = [super init]...

那么,为什么你不这样做呢?

initWithCoder: 也是如此:您应该使用 [self init] 返回的对象,而不是假设它初始化了初始对象。

- (void)dealloc
{
    [超级释放];
    [tileTemplates 发布];
}

正如 Abizern 在评论中所说,[super dealloc] 应该放在最后。否则,您将访问已释放对象的实例变量。

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

这里的返回类型应该是id,与NSCopying协议声明的返回类型匹配。

<前><代码>{
IRTileset* 复制 = [IRTileset 新];
[复制 insertTileTemplates:tileTemplates atIndexes:[NSIndexSet indexSetWithIndex:0]];
[复制setUniqueID:uniqueID];

您正在一个索引处插入零个或多个对象。使用范围创建索引集:位置 = 0,长度 = tileTemplates 数组的计数。更好的是,只需分配给整个属性值:

copy.tileTemplates = self.tileTemplates;

或者直接访问实例变量:(

copy->tileTemplates = [tileTemplates copy];

请注意,在绕过属性访问器时,您必须自己进行复制,并且您是复制 代表副本对数组进行操作。)

 return [复制自动释放];
}

copyWithZone: 不应返回自动释放的对象。根据内存管理规则copycopyWithZone: 的调用者拥有该副本,这意味着释放它是调用者的工作,而不是 copyWithZone: 的工作。

@synthesizetileTemplates;
[等]

您可能还想实现单对象数组访问器:

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

当然,这是可选的。

Please don't mention self = [super init]

So, why aren't you doing that?

The same goes for initWithCoder:: You should be using the object returned by [self init], not assuming that it initialized the initial object.

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

As Abizern said in his comment, [super dealloc] should come last. Otherwise, you're accessing an instance variable of a deallocated object.

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

The return type here should be id, matching the return type declared by the NSCopying protocol.

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

You're inserting zero or more objects at one index. Create your index set with a range: location = 0, length = the count of the tileTemplates array. Better yet, just assign to the whole property value:

copy.tileTemplates = self.tileTemplates;

Or access the instance variables directly:

copy->tileTemplates = [tileTemplates copy];

(Note that you must do the copy yourself when bypassing property accessors, and that you are copying the array on behalf of the copy.)

    return [copy autorelease];
}

copyWithZone: should not return an autoreleased object. According to the memory management rules, the caller of copy or copyWithZone: owns the copy, which means it is the caller's job to release it, not copyWithZone:'s.

@synthesize tileTemplates;
[et al]

You may want to implement the single-object array accessors as well:

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

This is optional, of course.

过气美图社 2024-08-30 14:14:08

//但是,我应该列出协议吗
在这里,尽管他们已经
在 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 对我来说听起来不太好。也许只是“计数”就可以了,如果这个类是用来保存图块模板的,这一点很明确的话?

//However, should I list protocols
here, even though they're already
listed in IRTileset.h?

No, you shouldn't. The class extension declared in the implementation file is an extension, so you don't have to care about which protocols the class has been declared to follow.

I would recommend to mark your instance variables' names with an underscore: _tileTemplates. (Purists will tell you to affix rather than prefix the underscore; do that if you're afraid of them.)

Don't use new to instantiate classes. It's not recommended ever, as far as I understand.

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

Don't call [super dealloc] before doing your own deallocation! This can cause a crash in certain circumstances.

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

I'm not sure what type uniqueID has, but shouldn't it also be released in dealloc?

I would never put my @synthesize directives in the middle of a file (place them immediately below ´@implementation´).

Also, having no clear idea about the role of this class, countOfTileTemplates doesn't sound good to me. Maybe just ´count´ will do, if it's unambiguous that what this class does it to hold tile templates?

落日海湾 2024-08-30 14:14:08

它看起来相当不错,只是您的属性对外部对象的任意操作开放。理想情况下,数据应该仅由模型类本身直接操作,并且外部对象只能通过专用方法进行访问。

例如,如果某些外部代码调用此命令:

myIRTileset.tileTemplates=someArray;

Boom,您已经丢失了所有数据,

该怎么办? 您应该将两个数据属性定义为只读。然后将访问器写入类内部,以管理它们在类实现中的保留。这样,外部对象更改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 属性。

这意味着您可以安全地允许应用程序中的任何对象访问实例,而不必担心某些次要对象或方法中的粗心代码行会破坏所有内容。

It looks pretty good except you've left your properties open to arbitrary manipulation by external objects. Ideally, the data should be manipulated directly only by the model class itself and external objects should have access only via dedicated methods.

For example what if some external code calls this:

myIRTileset.tileTemplates=someArray;

Boom, you've lost all your data.

You should define both the data properties as readonly. Then write accessors internal to the class that will managed their retention within the class implementation. This way the only way for an external object to change the tileTemplates is by calling the - insertTileTemplates:atIndexes: and removeTileTemplatesAtIndexes: methods.

Edit01:

I think I mangled it the first go, so let me try again. You should setup you data model class in the following pattern:

Interface

@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

So in use it would look like:

Implementation

#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

You would use it like so:

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"

Now the class has bullet proof data integrity. Any object in your app can set and get the publicString string property but no external object can set or get the private.

This means you can safely allow access to the instance by any object in your app without worrying that a careless line of code in some minor object or method will trash everything.

何处潇湘 2024-08-30 14:14:08

两个小问题:
一种是 init 方法,(从风格上来说,我反对有 2 个不同的返回点,但这只是我),但是没有什么可以阻止 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;
}

我发现的所有内容;除了副本的保留计数(这将导致稍后出现错误)之外,大部分只是我喜欢的东西,如果您更喜欢的话,您可以按照自己的方式进行。干得好

two minor nitpicks:
One is the init method, (where stylistically I'm against having 2 different return points but thats just me), however there's nothing stopping super's init from returning a different object than itself or nil, eg a different object of its class or even just another object altogether. For this reason, self = [super init] is generally a good idea, even if it probably won't do much in practice.
Second is in the copyWithZone method, you don't copy the tileTemplates, which could be intentional but is generally a bad idea (unless they're immutable). Copying an object is supposed to have the same effect as allocing a fresh one, eg. retain count of 1, so don't autorelease it. Also, it doesn't look like you do anything with the zone, so you should probably replace it with something like

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

thats everything I found; with the exception of the retain count of copy (which WILL lead to bugs later on) mostly just stuff that I prefer, you can do it your way if you like it better. Good work

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文