充分利用Objective-C动态特性

时间:2010-01-04 11:02:22

标签: objective-c

我一直在使用Objective-C但是来自静态类型背景(C#)我想我是以非常静态的方式使用它。将对象声明为id对我来说感觉陌生,我看不出有什么好处。任何人都可以为我更好地理解这一点吗?

4 个答案:

答案 0 :(得分:13)

Objective-C是一种混合语言,您可以根据需要动态和静态地使用它。如果需要,您可以声明所有变量的所有类型,如果需要,您甚至可以将委托变量声明为NSObject< Protocol> *。 id类型作为一个真正的类型工作得更少,更像是对编译器的提示告诉他“嘿,我知道我在做什么,只要信任我”,使编译器避免任何类型检查该特定变量。

Objective-C类型系统的第一个明显好处是容器类型(NSArray,NSDictionary,NSSet)接受并返回id类型。这样就完全不需要模板和泛型(比如在C ++,Java和C#中)。

更好的是,你实际上可以在里面放置任何类型元素的容器。只要你知道里面有什么,如果你在同一个NSArray中添加两个NSStrings,一个NSNumber和一个NSValue,没有人会抱怨。您可以使用其他语言执行此操作,但必须使用“Object”基类或void *类型,然后您需要对变量进行包装和取消(或向上和向下)以获得相同的行为。在Objective-C中你只需要分配,它可以消除由于施放操作符和装箱操作而产生的噪音。然后,您可以向每个对象询问“respondsToSelector:”或“class”,以便在运行时了解您可以使用它们执行的标识和操作。在Objective-C中,反思是一流的公民。

另一个好处是减少了编译时间;一般来说,Objective-C程序的编译比它在C ++中的等价程序要快得多,因为没有执行那么多的类型检查,并且在运行时进行了很多链接。编译器更信任程序员。

最后,Objective-C的动态类型系统可以使用Interface Builder之类的工具。这是Cocoa和Cocoa Touch开发时间更快的主要原因; GUI可以在整个地方生成具有“id”类型的代码,只要NIB加载到内存中,就会反序列化。在UI设计体验方面,唯一接近Objective-C的语言是C#(当然还有VB.NET),但代价是更重的应用程序。

我个人更喜欢使用更静态的类型检查,我甚至打开Objective-C编译器中的“将警告视为错误”设置;我写了一篇关于它的博客文章:

http://akosma.com/2009/07/16/objective-c-compiler-warnings/

当您与不熟悉该语言的开发人员合作时,这尤其有用。它使编译器比平时更频繁地发牢骚:)

静态类型系统专家可能不同意所有这些观点,认为静态类型检查允许“智能感知”IDE和更好的维护。我使用.NET多年(2001年至2006年),我必须说动态语言往往产生更少的代码,更容易阅读,并且通常可以提供更多的工作自由。权衡(总是存在权衡)是编译时信息较少。但正如我倾向于说,编译器是一个穷人的测试套件。最好的事情是恕我直言,有一套好的测试,以及一大堆人类测试人员折磨你的代码来发现bug,无论你选择什么语言。

答案 1 :(得分:5)

Objective-C的动态不仅仅在于每个对象都是id。相反,它充分发挥了Objective-C运行时的强大功能和易用性。 Apple自身巧妙运用运行时的几个例子:

DO允许您在单独的应用程序/单独计算机中为Obj-C对象设置代理对象。这是通过拦截发送到代理对象的所有消息,将其打包,将其发送到另一个应用程序并在那里调用来完成的。

KVO是通过动态替换setter方法实现的,以便它自动通知观察者。 (嗯,事实上它比那更微妙......)

CoreData accessors在运行时为NSManagedObject等的每个子类生成。

而且,您也可以使用代码中的runtime。我曾经使用它来获得良好的效果,模仿CoreData并在运行时生成访问器,并且只在头文件中声明它们。因此,您可以获得静态类型(标头中的声明中的编译时错误)和动态(方法的运行时生成)的优点。

Mike Ash撰写了一系列关于运行时如何工作以及如何有效使用它的博客文章。你只需要阅读它! DOKVOmessage forwarding等。网上还有许多其他有趣的帖子,例如fun with kvc和高阶信息12

答案 2 :(得分:3)

实际上很少需要将对象声明为类型id,因为您通常应该知道您期望的类型。有时您可能会使用id<Protocol>类型,如果您不知道对象的实际类型但知道它应符合特定协议。

您是否正在考虑某种特定情况?

答案 3 :(得分:2)

在设计动作方法时,将实例作为id传递很常见;将按钮连接到方法时,目标看起来像doSomething:(id) sender;

在这种情况下,它允许不同类型的控件使用相同的操作方法,而不事先知道这些控件将是什么。在操作的方法代码中,您可以测试发件人的类,或者只使用其tag属性来决定要执行的操作。

-(void) doSomething:(id) sender {
    // Get the sender's tag whatever it is
    int tag = [sender tag]; 
    switch(tag) {
        case 1:
            // ...
            break;
        case 2:
            // ...
            break;
    }
}