我最近需要创建类似于NSRect的类型,它有一个锚点(本质上是一个NSRect,其中包含另一个NSPoint)。
在some research之后我发现我实际上可能更好地将它变成一个类(如在NSObject子类中),而不是使用struct。为什么Apple会制作这些类型的结构,而不是类?它似乎有许多好处,例如不必将它们包装在NSValues中,不必使用C函数与它们交互等等。
我确信他们有理由。它只是内存使用量略少吗?这只是历史吗?或者我错过了更大的东西?
答案 0 :(得分:12)
每个编程范例都有自己的优点和缺点,object-oriented programming(OOP)也不例外。
拥有类(即以面向对象的方式编写)的所有主要好处是encapsulation,polymorphism和inheritance。
如果我们声明像Rect或Point这样简单的单元,我们就不会使用该列表中的任何内容,但是我们会带来OOP的所有缺点,包括隐式可变性,更复杂的内存管理,指针引用,隐式actor,破坏封装(使用setter / getters)等。我不会在此列出OOP中存在的数百个anti-patterns。
我怀疑Cocoa中Rect和Point的结构选择是否合理,首先是因为Objective-C从一开始就支持类,其次是因为全新的Swift语言没有这个遗产,但几乎所有标准类型和容器都在其标准库中声明为结构,而不是类。
通常,如果您不需要封装,多态和继承 - 您应该考虑将新实体声明为结构并尽可能避免使用OOP。
答案 1 :(得分:8)
最重要的原因是性能 - 所有Cocoa的性能关键API(图形,音频)都倾向于使用C结构和函数来达到这种目的。
原因是C编译器可以更容易地优化这些。可以比方法更便宜地调用C函数,并且它们可以由编译器自动内联,完全消除函数调用开销。 C结构在堆栈而不是堆上分配,这意味着处理主要可以在寄存器或低级CPU缓存中完成,而不是调用主内存,这要慢几百倍。
动态调度Cocoa方法。它们可能被子类或混合所覆盖,因此编译器无法内联它们,因为在运行时执行它们之前无法确定它们将执行的操作。 Cocoa类在堆上分配,因此必须分配/解除分配,并且还有其他所有开销,例如引用计数。它们也很可能最终在内存中传播,这意味着由于它们驻留在内存中的内存块必须被分页进出缓存,因此可能会有很大的性能损失。
答案 2 :(得分:4)
另一个好处是操作速度。这些点和rects主要用于渲染某些东西,因此需要比以往更快的操作。而不是分配一个会触发递归init
的对象,花费大量的字节和cpu周期来进行秘密工作,只是说“这里我有16个字节,这是NSPoint结构的2个CGFloats”。当将工作委托给GPU时,这也很有意义,因为GPU对OOP和对象的方法一无所知,但需要在屏幕上绘制很多而没有毛刺。
答案 3 :(得分:3)
为什么Apple会制作这些类型的结构,而不是类?
出于性能原因,这些实体经常使用,经常处理和小。将它们封装在对象中的开销并不能被它们作为对象的优点所证明。
答案 4 :(得分:2)
通常,当存在对数据进行操作的方法时,使用类。如果您只有数据,那么只需使用结构。 似乎没有任何有用的方法可以与point或rect一起使用,因此不需要类。
答案 5 :(得分:2)
我认为这与每个结构都保存原始类型的数据(通常是浮点数和双精度数)而没有任何动作(如函数或方法)这一事实有关,因此不需要像功能齐全的类(没有导入和没有样板代码,例如初始化程序),当然,正如其他同事提到的那样,因为Objective-C是C的超集。
答案 6 :(得分:1)
如果你已经玩了一段时间的Objective-C,那么你就会知道它是在coreFoundation之上的OOP层,它是所有结构和'不透明类型'(带有秘密的结构)..
例如,NSDictionary实际上只是CFtype,CFDictionaryRef的包装器。那么,你在这里询问的东西,大小,点,矩形,边缘插入等只是很少的数据类型,确定不保证升级。基元(int,double,bool等)也没有得到(公共)类到免费电话桥。他们得到了NSNumber集群,这些人都在NSValue包装器中聚集在一起。如果需要,可以编写一个具体的类来包装特定的类...
答案 7 :(得分:1)
因为需要是值类型。实际上,点或矩形不是对象。它们只是一堆数据。他们没有表现,他们只是存储您传递给他们的数据。你可以写
CGRect frame = ...
UIView *view1 = [[UIView alloc] initWithFrame:frame];
frame.origin = ...
UIView *view2 = [[UIView alloc] initWithFrame:frame];
它工作正常。对于引用类型,哪些类是,您将在将帧移动到另一个原点时更改view1
的帧。
[myRect intersectsWithRect: otherRect]
肯定会很好,实际上Swift允许这样做。结构和枚举等值类型可以包含Swift中的方法,这使得它成为可能
答案 8 :(得分:0)
当您希望通过引用传递值时,可以使用值和类传递事物。