我一直在使用Objective-C但是来自静态类型背景(C#)我想我是以非常静态的方式使用它。将对象声明为id对我来说感觉陌生,我看不出有什么好处。任何人都可以为我更好地理解这一点吗?
答案 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撰写了一系列关于运行时如何工作以及如何有效使用它的博客文章。你只需要阅读它! DO,KVO,message forwarding等。网上还有许多其他有趣的帖子,例如fun with kvc和高阶信息1,2。
答案 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;
}
}