正确使用Objective C的方法

时间:2009-08-14 23:19:44

标签: objective-c cocoa

这不是一个风格问题。它更多的是正确使用语言本身。我对编程很新,而且我对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

这似乎(至少对我来说)是一种更抽象的语言用法,并将客户端与他们不应依赖的实现细节隔离开来。我的问题是我应该努力遵循哪一个。我理解可能会出现一种情况比另一种情况更可取的情况,但一般而言,一种情况应该是常态,一种情况是例外吗?

一些指导意见将不胜感激。

谢谢, 罗马

5 个答案:

答案 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关键字来定义类,并且仅在需要一些不完全属于的类的额外功能时才使用协议。类层次结构,但仍需要在多个类之间共享。

如果您正在寻找指导,我会说:

  1. 接受@interface@implementation关键字的命名,即使它与您的预期不相符。在Objective-C中编程时,喝Kool-Aid并按照语言设计者的意图查看这些关键词。
  2. 在代码中使用协议时,请不要过度使用协议,以使Objective-C的行为更像另一种语言。它可能有用,但是当他们看到你的代码时,其他人会畏缩,这会使协作变得困难。
  3. 继续问这么好的问题。你提出了一些非常有效的观点!

答案 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