这不是一个风格问题。它更多的是正确使用语言本身。我对编程很新,而且我对Objective-C和Cocoa完全不熟悉,但在阅读了该语言并查看了一些示例代码后,一些使用模式继续弹出,对我来说没有意义。或者说,在我看来,它们似乎并不是最优的。我希望你们都能帮助教育我正确使用其中一些语言结构。
接口与协议
我理解与抽象相关的接口和实现的概念。然而,我在Objective-C示例代码中看到的主导模式如下......
@interface Foo : NSObject
{
some ivars decls
}
some methods decls
@end
@interface Foo : NSObject
{
some ivars decls
}
some methods decls
@end
然后以...的形式提供客户端代码。
Foo* f = [[Foo alloc] init];
这对我来说似乎很奇怪。 Foo似乎是我脑海中的一个实现(无论@interface关键字的不幸命名如何)。我认为接口不会暴露实例变量。此类的客户不应该接触我的实现细节。
Objective-C确实有@protocol关键字,在我看来,它比@interface关键字更像是一个接口。它允许您定义方法和/或属性,就是这样。没有实施。没有实例变量。只是界面。
Foo* f = [[Foo alloc] init];
因此客户端代码将采用......
的形式
@protocol Foo <NSObject>
some methods
maybe some properties
@end
@interface FooProvider : NSObject
+ (FooProvider*) sharedProvider;
- (id<Foo>) fooWithBlahBlah;
@end
@protocol Foo <NSObject>
some methods
maybe some properties
@end
@interface FooProvider : NSObject
+ (FooProvider*) sharedProvider;
- (id<Foo>) fooWithBlahBlah;
@end
这似乎(至少对我来说)是一种更抽象的语言用法,并将客户端与他们不应依赖的实现细节隔离开来。我的问题是我应该努力遵循哪一个。我理解可能会出现一种情况比另一种情况更可取的情况,但一般而言,一种情况应该是常态,一种情况是例外吗?
一些指导意见将不胜感激。
谢谢, 罗马
答案 0 :(得分:12)
首先,您需要了解您犯了一个经典错误:您已经知道的语言定义了所有语言中使用的术语 - 就像某个特定语言已经建立了规范术语一样。
Java于1990年在Sun内部开始。 Objective-C已于1986年发布。因此,如果有的话,Java的接口术语与Objective-C不一致,而不是相反。但是,作为一个术语,接口的历史要比这两种语言中的任何一个都长。
在Objective-C 2.0 Programming Language Reference:
中概述了Objective-C中@interface背后的想法interface Objective-C类规范的一部分,它声明了它的公共接口,包括它的超类名,实例变量和公共方法原型。
实例方法是类方法是公共的,如果它们在接口中声明的话。如果它们在@implementation中声明它们是私有的。
实现进入@implementation。也许令人困惑的是,Java没有像Objective-C那样的类声明部分的概念。类声明以通用方式声明类接口,没有实现的细节。 @implementation具有实际实现,这是实现类@interface的详细信息。
Objective-c与Java不同,没有错,它只是不同。
但是你是对的,协议是你将要找到的objective-c中最接近java接口的模拟。它不是任何东西的超类,也没有任何相关的实现。您可以像在Java中使用接口一样提供协议。
但请注意,协议不像Objective-C中的类那样常见。这应该有助于指导你的思考。
答案 1 :(得分:10)
我可以看到您在@interface
关键字命名方面的来源,但Objective-C使用两个关键字@interface
和@implementation
来区分类声明(接口)及其定义(实现)。 @interface
部分通常放在类头文件中,@implementation
部分放在源文件中。我同意你100%的内部实例变量确实不应该出现在头文件中。我不确定是什么导致了这个决定,但我最好的猜测是它与对象继承有关。从父类继承时:
#import "ParentClass.h"
@interface MyClass : ParentClass
...
您还继承了所有实例变量,除非这些变量在类头中声明,否则编译器很难弄清楚。
你对@protocol
的用法也是正确的,因为它基本上定义了一个类必须遵守的接口。协议似乎是Objective-C对多重继承的回答。
根据我对Objective-C的经验,不经常使用协议。它们有它们的位置,但是大多数代码使用基本的@interface
和@implementation
关键字来定义类,并且仅在需要一些不完全属于的类的额外功能时才使用协议。类层次结构,但仍需要在多个类之间共享。
如果您正在寻找指导,我会说:
@interface
和@implementation
关键字的命名,即使它与您的预期不相符。在Objective-C中编程时,喝Kool-Aid并按照语言设计者的意图查看这些关键词。答案 2 :(得分:3)
还没有人解释为什么实例变量出现在Objective-C类的@interface
中。对象实现为包含实例变量的C结构。当您创建一个新的子类时,将定义一个新类型的结构,其中包含所有超类的ivars,后跟新类的ivars。超类结构必须包含其超类的ivars,然后它“一直向下”到一个代表基类的ivars的结构。
在NSObject
(实际上是Object
)的情况下,基类结构只包含一个ivar - 一个名为isa
的指针,代表该对象的Class
结构类。所以,如果我想像这样继承它:
@interface GLObject : NSObject {
int x;
}
@end
我需要知道如何制作一个看似如下的结构:
{
Class isa;
int x;
}
因此,父类的ivar布局(以及通过归纳任何类)需要成为类的公共合同的一部分,即其@interface
的一部分。它可能不是很漂亮,但确实如此。
答案 3 :(得分:1)
我也意识到没有什么能够迫使面向公众的接口包含实例变量......
@interface Foo : NSObject
// some methods but no ivars
+ (Foo*) fooWithBlahBlah
@end
实施文件......
@implementation Foo
+ (Foo*) fooWithBlahBlah {
return [[SomeDerivedFoo alloc] init];
}
@end
在内部声明SomeDerivedFoo对客户端不可见......
@interface SomeDerivedFoo : Foo
{
// instance vars
}
// overrides of methods
这将允许组件公开没有ivar依赖项的接口。事实上,这就是我在所谓的“Class Clusters”中看到的模型。这对我来说“感觉”好多了。
我在公共界面中使用ivars的大问题是依赖控制。如果我的ivars只是其他Objective-C类,那么是的我可以逃避前向声明但是在嵌入式C结构或其他非类型类型的情况下,我必须看到这些类型的定义,所以我的客户现在依赖于它们。
更改实例变量不应导致我的接口的客户端重新编译。重新链接很好,但不是重新编译。在共享框架的情况下,甚至不需要重新链接。这与任何特定语言无关。它与细节隔离有关。在我看来,这是一个非常普遍的目标。
这导致了我关于协议与接口的原始问题。似乎对于库或框架来说,更“正确”的做法是提供一组协议以及某种“提供者”或“工厂”类来销售这些协议的实现。
答案 4 :(得分:1)
如果你不能支持64位运行时并专门合成ivars,那么另一种选择就是在你的班级中创建一个id
ivar。如果您需要在发货后返回班级并添加更多实例变量,则可以使用此id
作为它们的容器。
这使您可以编写不那么复杂的代码,同时为您提供一些余地,可以在不强制重新编译的情况下更改客户端背后的内容。
通过输入ivar作为id
,您不会对客户端代码做任何承诺(无论如何都应该{i}}。
我认为让所有公共类充当私有实现的代理会变得有点难以管理。请记住,如果这样做,您可以使用运行时转发来减少必须在公共类中编写的代码量。见@private
。它记录在10.5 Foundation Release Notes