【问题标题】:Proper way to use Objective C使用 Objective C 的正确方法
【发布时间】:2009-08-14 23:19:43
【问题描述】:

这不是风格问题。它更多地是关于正确使用语言本身。我对编程很陌生,而且我对 Objective-C 和 Cocoa 完全陌生,但是在阅读了该语言并查看了一些示例代码之后,一些使用模式继续弹出,这对我来说没有意义。或者更确切地说,它们在我看来不是最优的。我希望你们都可以帮助教育我正确使用其中一些语言结构。

接口与协议

我理解与抽象相关的接口和实现的概念。然而,我在 Objective-C 示例代码中看到的主要模式如下...

@interface Foo : NSObject
{
  some ivars decls
}
some methods decls
@end

然后客户端代码形式为...

Foo* f = [[Foo alloc] init];

这对我来说似乎很奇怪。 Foo 在我看来似乎是一个实现(不管@interface 关键字的不幸命名如何)。在我看来,接口不会暴露实例变量。此类的客户不应接触到我的实现细节。

Objective-C 确实有 @protocol 关键字,在我看来它更像是一个接口而不是 @interface 关键字。它允许您定义方法和/或属性,仅此而已。没有执行。没有实例变量。只是界面。

@protocol Foo <NSObject>
  some methods
  maybe some properties
@end

@interface FooProvider : NSObject
  + (FooProvider*) sharedProvider;
  - (id<Foo>) fooWithBlahBlah;
@end

所以客户端代码将采用...的形式

id<Foo> f = [[FooProvider sharedProvider] fooWithBlahBlah];
[f whatever];

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

我们将不胜感激。

谢谢, 罗马

【问题讨论】:

    标签: objective-c cocoa


    【解决方案1】:

    首先,您需要了解自己犯了一个典型的错误:您已经知道的语言定义了所有语言中使用的术语 - 就好像一种特定的语言已经建立了规范的命名法。

    Java 于 1990 年在 Sun 内部开始。 Objective-C 已经在 1986 年发布了。所以,如果有的话,Java 的接口术语与 Objective-C 不一致,而不是相反。但是,接口作为一个术语的历史比这两种语言中的任何一种都要长。

    Objective-C 中@interface 背后的想法在Objective-C 2.0 Programming Language Reference 中进行了概述:

    接口 声明其公共接口的 Objective-C 类规范的一部分,其中包括其超类名称、实例变量和公共方法原型。

    实例方法是类方法,如果它们在接口中声明,则它们是公共的。如果它们在@implementation 中声明,它们是私有的。

    实现进入@implementation。也许让你感到困惑的是Java没有像Objective-C那样的类声明部分的概念。类声明以通用方式声明类接口,没有具体实现。 @implementation 有 actual 实现,这是 @interface 类如何实现的细节。

    objective-c 与 Java 不同并没有错,只是不同而已。

    但你是对的,协议是你将要找到的 Objective-c 中最接近 Java 接口的模拟。它不是任何东西的超类,也没有任何相关的实现。您提供的协议与 Java 中的接口一样。

    但是,请注意,协议不像 Objective-C 中的类那样普遍。这应该有助于指导您的思考。

    【讨论】:

    • 如果我把 Objective-C 与 Java 进行比较,我很抱歉。那不是我的本意。 Java 这个词从未出现在我的原始帖子中。我只是在说明我认为什么是接口和 Objective-C 认为什么是接口的内部冲突。只做其他代码所做的事情的建议在我内部并不完全适合我,但它确实有一些优点。在罗马等地时...给我一些思考。
    • 嗯,您对 Java 使用术语“接口”的用户表示基于 Java 的术语和概念的理解,但在 Java 的概念框架之外不一定有效。学习语言是一项非常有用的练习 - 学会欣赏智力差异,以了解您使用其他语言。
    • (我不认为提问者是直接将 Obj-C 与 Java 相提并论,但你说得对,因为在这两种语言中使用了术语,存在一些暗示混淆的暗示是正确的。) C 实际上是在 1982 年发明的,当我发现 Objective-C 对 Java 产生了不小的影响时,我发现它非常有趣——virtualschool.edu/objectivec/influenceOnJava.html
    【解决方案2】:

    关于 @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. 继续问这么好的问题。您提出了一些非常有效的观点!

    【讨论】:

    • 很好的答案,特别是对于解决 OP 关于头文件中私有实例变量的问题。只需添加指向有关该特定问题的另一个 SO 问题的链接:stackoverflow.com/questions/966893/…
    • Objective-C 是 C 的一个非常薄的层。它实际上只是在编译期间管理一些符号表;与 c 预处理器本身非常相似。我确信大多数决定都是为了让编码更容易(比如使用方括号而不是括号,他们必须找到可以轻松解析并从周围代码中提取的东西。这可能是他们选择遵循 Lisp 语法的原因好吧--更容易解析/提取/替换。虽然整洁。我认为头文件只是输入,而不仅仅是符号替换,所以这可能就是他们将变量放在那里的原因。
    • 他们没有遵循 Lisp 语法,而是遵循 Smalltalk 语法。 Smalltalk 试图通过统一使用消息传递的方式来处理尽可能多的事情(包括条件语句),语法基本由嵌套块组成,体现了这一设计原则。
    【解决方案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 的一部分。它可能不漂亮,但它确实存在。

    【讨论】:

    • 事实上,在不使用固定布局的非脆弱运行时(64 位 Mac OS X 和 iPhone)中不再适用。在为非脆弱运行时构建时,您可以使用 @synthesize 指令在 @implementation 中隐式添加 ivars。没有技术原因不允许在 @implementation 中使用 {} ivar 块;一句话是这纯粹是因为时间的原因而被遗漏了。错误已提交。
    • 对,太对了。没有技术上的必要性,因为@interface 用于在编译引用类的源时通知编译器必要的类型和成员资格,而不会导致对实现的依赖。如果一个 ivar 是 @private,那么除了语法之外,它在 @interface 块中的存在没有真正令人信服的理由。
    • @both 评论者:对。我认为此时引入多个运行时不太合适;-)
    【解决方案4】:

    我也意识到没有什么真正迫使我面向公众的接口包含实例变量...

    @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 依赖项的接口。事实上,这就是我在所谓的“类集群”中看到的模型。这对我来说“感觉”好多了。

    我在公共界面中使用 ivars 的最大问题是依赖控制。如果我的 ivars 只不过是其他 Objective-C 类,那么是的,我可以摆脱前向声明,但在嵌入式 C 结构或其他非类类型的情况下,我必须查看这些类型的定义,所以我的客户现在依赖于他们。

    更改实例变量不应导致我的接口的客户端重新编译。重新链接很好,但不是重新编译。在共享框架的情况下,甚至不需要重新链接。这与任何特定语言无关。它与细节的隔离有关。在我看来,这是一个非常普遍的目标。

    这就是我最初提出关于协议与接口的问题的原因。似乎对于库或框架来说,更“正确”的做法是提供一组协议以及某种“Provider”或“Factory”类来出售这些协议的实现。

    【讨论】:

      【解决方案5】:

      如果您不能支持 64 位运行时并专门合成 ivars,另一种方法是在您的班级中创建一个 id ivar。如果您需要在发货后返回您的班级并添加更多实例变量,您可以使用此id 作为它们的容器。

      这允许您编写不太复杂的代码,同时为您提供一些回旋余地,以便在不强制重新编译的情况下在客户背后进行更改。

      通过将 ivar 键入为 id,您并没有向客户端代码承诺任何东西(无论如何都应该设为 @private)。

      我认为让所有公共类都充当私有实现的代理会有点难以管理。请记住,如果这样做,您可以使用运行时转发来减少您必须在公共类中编写的代码量。见-[NSObject forwardingTargetForSelector:]。它记录在10.5 Foundation Release Notes

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2022-01-17
        • 1970-01-01
        • 1970-01-01
        • 2011-04-24
        • 1970-01-01
        相关资源
        最近更新 更多