可可界面风格

3
我正在进行一个iPhone应用程序的项目,我们请了一位Cocoa顾问来工作几周。他向我展示了一种有趣的Cocoa习惯用法,涉及接口,但是我们之间存在着困难的语言障碍,而且他无法解释为什么要这样做以及在哪里可以找到相关文档,以便我自己学习更多知识。我只是模仿他的风格,但是我一直很烦恼我不知道更多关于这种风格的历史背景。它肯定不是非正式协议。确实,看看一些Cocoa API头文件,我有时会看到他所说的“Cocoa”方式的风格。这里有一个示例(注意访问器、修改器等,每个都有自己的接口声明,没有有趣的大括号):
@interface AViewController : UIViewController <UITextViewDelegate> {

@public
    UITableView *tableView;
@private
    NSUInteger someIndex;
}

@property (nonatomic, retain) ...
@end

@interface AViewController (AViewControllerCreation)

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil withController:(id)controller;

@end

@interface AViewController (AViewControllerMutator)

- (void) doSomeSettingStuff;

@end

@interface AViewController (AViewControllerAccessor)

- (NSString *)doSomeAccessorStuff;

@end

@interface AViewController (AViewControllerAction)

- (IBAction)cancel:(id)sender;

@end

@interface AViewController (AViewControllerTableViewDelegate)  <UITableViewDelegate, UITableViewDataSource>

@end

你可以在NSButton、NSControl等中看到这种设置界面的风格。有趣的是,相应的类别如UIButton、UIControl并没有使用这种惯用语法。嗯,这些可能是后来才出现的,因为我认为UIKit是在AppKit之后完成的。那么这个惯用语法是“老掉牙”的吗?除了风格外,还有其他原因吗?它是好的风格还是坏的风格?有没有文档介绍这个?谢谢大家。
7个回答

13

这些在Objective-C中被称为“分类”。分类使得同一个类可以有多个@interface和@implementation块。实际上,你甚至可以在标准的Apple框架中为类添加方法,例如:添加一个NSString的分类来增加新的方法。分类可以增加方法但不能增加实例变量,因此在您的示例中第一个@interface是核心类声明,所有其他的都是AViewController类的分类。

这种做法绝不是“老套路”,但您的示例将分类的使用推向了一个非常奇怪的极端。分类在逻辑上需要将一个类的实现分成多个块时是有意义的,例如,如果该类有一堆方法可以逻辑地分为两个或多个组。有时还会使用它们来声明伪私有方法,即通过在同一文件中放置一个名为“private”的分类@interface来实现。ObjC的动态分派意味着没有私有方法,但这种方法避免了公开您不希望别人使用的方法名称。

上面的示例实际上并没有错,但有点荒谬。它表明承包商之所以会想到每个新方法都应该有自己的分类,这是不正确的。


谢谢Tom。顺便说一下,我删除了很多方法,所以现在它纯粹是迂腐的。我确实明白他为什么喜欢将事物分组(如访问器/变异器)。再次看了NSButton与UIButton头文件后,我发现两者都被使用。但你的答案指出这些实际上是类别。到目前为止最好的答案,Tom! - Rob

2
那个例子非常奇怪,对于任何有经验的Cocoa程序员来说都会引起警惕。
通常情况下,使用类别将私有方法与公共实现分离是一种常见做法。我过去也曾这样做,将私有线程方法与在主线程上运行的代码分离。但我无法看出将所有公共方法分离出来会有什么好处。
对于这种情况,一个好的工具是#pragma mark <label>关键字。它允许您在实现中将相似的方法分组。我想这就是你的目标,尽管你不需要过度创建分组。例如,在窗口控制器类中,我会有#pragma mark API#pragma mark NSWindowController Overrides#pragma mark NSObject Overrides#pragma mark NSWindow Delegate Methods等。这有助于我在Xcode中快速找到并跳转到我要查找的方法,虽然这只是一种风格问题,所以您可以根据自己的需求使用它。

嗨Marc。是的,我喜欢使用#pragma,在xcode中已经在使用它了,它确实有帮助!但它似乎只有组织方面受益。对,这个习惯用法不是我发明的,他发明的!一开始我不喜欢,但接受了。它在NSButton,NSControl等中使用,那可是苹果的代码;) - Rob

1

有一个非常好的理由来使用公共类别,苹果经常使用这个理由,那就是通过扩展类别的框架来实现类的功能扩展,但是这些功能在定义类的框架中并不存在。例如,NSString 在 Foundation.framework 中定义,但是在 AppKit.framework 中定义了用于将 NSString 绘制到屏幕上的方法。

另一个很好的类别用法是用于依赖隐藏;例如,如果您真的需要 boost 来部分实现类,您可以将其放在单独的头文件和实现文件中,需要 boost 部分的类的用户可以将该头文件与最初定义类的头文件一起导入,只有那个文件需要花费很长时间编译。这在 64 位运行时中更有用,因为类别可以添加实例变量。

对于多个源文件(但单个头文件)的大型实现也是一个很好的选择,正如 Tom 所指出的那样 :)

我想补充一下 Tom 的原始答案:通常,在声明私有方法时最好使用类扩展而不是类别。这样,您可以在同一个 @implementation 块中实现扩展和公共方法,而不会收到“缺少类别实现”的警告。例如:

// .h file

@interface Foo : NSObject
-(void)publicMethod;
@end

// .m file

@interface Foo () 
// Notice the empty paren; this is how you define
// a class extension, which is not the same as a category
-(void)somePrivateMethod;
@end

@implementation Foo
#pragma mark Public methods
-(void)publicMethod;
{ ... }

#pragma mark Private methods
-(void)privateMethod;
{ ... }
@end

0

我不确定,但那些看起来很像非正式的协议,主要是为了委托。请参阅《使用Mac OS X进行Cocoa编程,第3版》的297-298页。这些协议是通过类别实现的...老实说,在你的示例中它们似乎被过度使用了。


这是实现非正式协议的一种方式(Obj-C 2.0也有新的可选协议方法),但init方法、访问器和修改器等内容不会成为协议的一部分。 - Marc Charbonneau

0
关于John Rudy的回答 - 非正式协议确实是作为类别实现的,但它们通常是NSObject上的类别。由于几乎任何对象都可能使用非正式协议,因此它需要成为采用对象继承自的类的类别,并且几乎所有东西都将继承自NSObject。在特定情况下,您可以将非正式协议作为某些其他类别的类别,但这是一种不寻常的方法,绝对不是“Cocoa方式”。

0

首先,仅仅因为某些东西出现在苹果的头文件中,并不意味着你应该将其视为一个好的示例。苹果的开发人员也是人,同样受到技能和时间压力等方面的限制。

以这种方式使用多个类别表明 NSButton 的实现被分成了几个源文件。这可能是因为 NSButton 的不同方面由不同的人编写,或者在不同的时间编写,或者可能是其他原因。无论如何,这种划分很可能部分基于开发团队及其流程的组织方式,类别系统提供了一种分工的方式。在理想的设置中,您会按照逻辑功能边界来拆分事物,但在实践中,其他因素可能会起作用。


0

好的,我相信Tom的回答到目前为止是最有用的。然而,由于每个人似乎都认为这是对类别的过度使用,我再次查看了NSButton。下面是一个篡改版本的头文件:

@interface NSButton : NSControl <NSUserInterfaceValidations>

- (NSString *)title;
- (void)setTitle:(NSString *)aString;
...
...
@end

@interface NSButton(NSKeyboardUI)
- (void)setTitleWithMnemonic:(NSString *)stringWithAmpersand;
@end

@interface NSButton(NSButtonAttributedStringMethods)
- (NSAttributedString *)attributedTitle;
- (void)setAttributedTitle:(NSAttributedString *)aString;
- (NSAttributedString *)attributedAlternateTitle;
- (void)setAttributedAlternateTitle:(NSAttributedString *)obj;
@end

@interface NSButton(NSButtonBezelStyles)
- (void) setBezelStyle:(NSBezelStyle)bezelStyle;
- (NSBezelStyle)bezelStyle;
@end

@interface NSButton(NSButtonMixedState)
- (void)setAllowsMixedState:(BOOL)flag;
- (BOOL)allowsMixedState;
- (void)setNextState;
@end

@interface NSButton(NSButtonBorder)
- (void) setShowsBorderOnlyWhileMouseInside:(BOOL)show;
- (BOOL) showsBorderOnlyWhileMouseInside;
@end

@interface NSButton (NSButtonSoundExtensions)
- (void)setSound:(NSSound *)aSound;
- (NSSound *)sound;
@end

所以,如果他们使用类别将NSButton组织成如上所示的几个操作(NSButtonMixedState)(NSButtonBorder),为什么将其用于组织访问器/变异器是不好的风格呢?当然,我的第一个例子是一个愚蠢的学究式接口,但分离操作分组的意图是相同的。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接