让 Objective-C 类看起来很漂亮

Posted

技术标签:

【中文标题】让 Objective-C 类看起来很漂亮【英文标题】:Making Objective-C Classes look Beautiful 【发布时间】:2010-12-09 06:27:50 【问题描述】:

我想就Objective C 中的代码异味,特别是Cocoa Touch 向大家征求意见。我正在开发一款相当复杂的游戏,并且即将开始 Great December Refactoring。

我的很多类,尤其是模型,都充满了处理内部业务逻辑的方法;我将把它们隐藏在一个私有类别中,在我与大量头文件的战争中。那些私有类别包含大量声明,这让我感到不安……几乎就像 Objective-C 让我对所有这些方法感到内疚一样。

我重构得越多(一件好事!),我就越需要维护所有这些重复(不太好)。就是感觉不对。

在像 Ruby 这样的语言中,社区非常强调非常简短、清晰、漂亮的方法。我的问题是,对于 Objective C(特别是 Cocoa Touch),你的方法有多长,你的控制器有多大,以及每个类有多少方法在你的项目中变得很典型?在 Objective C 中是否有任何由短方法组成的类的特别好、漂亮的示例,或者这根本不是语言文化的重要组成部分?

披露:我目前正在阅读“The Little Schemer”,这应该可以解释我的悲伤,回复:目标 C。

【问题讨论】:

谢谢!当我开始思考回答 obj-c 问题比回答 ruby​​ 问题需要多长时间时,很明显这是一个严重的问题。呜呜呜…… 您可能对此stack-exchange proposal 感兴趣。它几乎可以开始测试了,只需要更多。 维克多,非常感谢,看起来很棒。我现在正在学习 Clojure,而且周围根本没有其他人可以检查工作。我刚刚承诺! 【参考方案1】:

是主观的。对我来说,如果一个 Objective-C 类是可读的(我知道它应该做什么)和可维护的(我可以看到哪些部分负责做什么),那么它就是美丽的。我也不喜欢被一个不熟悉的习语抛弃阅读代码。有点像当你在读一本书时,你读到的东西会让你脱离沉浸感并提醒你正在阅读。

你可能会得到很多不同的、相互排斥的建议,但这是我的想法。

私有方法属于私有类别并没有错。这就是它的用途。如果您不喜欢阻塞文件的声明,请在 IDE 中使用代码折叠,或者将您的扩展作为一个类别放在不同的文件中。 将相关方法组合在一起,并用#pragma mark 语句标记它们 无论您使用何种代码布局,一致性都很重要。花几分钟时间写下你自己的指导方针(这里是mine),所以如果你忘记了你应该做什么,你有一个参考。 控制器不必是委托和数据源,您始终可以为这些设置其他类。 为方法和属性使用描述性名称。是的,您可以记录它们,但是当 Xcode 应用代码完成时,您看不到文档,其中命名良好的方法和属性得到了回报。此外,如果在代码本身发生变化时未更新代码 cmets,它们也会变得陈旧。 不要尝试编写聪明的代码。您可能认为将一系列方法调用链接在一行上会更好,但编译器在优化方面比您想象的要好。如果可以提高可读性,则可以使用临时变量来保存值(大多数情况下这些只是指针,所以相对较小)。 编写供人类阅读的代码。 DRY 与其他语言一样适用于 Objective-C。不要担心将代码重构为更多方法。有很多方法,只要有用就没有错。

【讨论】:

我已经放弃了#pragma mark,转而使用// MARK:,它在Xcode 中的菜单上具有相同的效果,但不会在编译器之间可移植的代码中引起警告。此外,// MARK: - 是一个真正的帮助(也适用于编译指示)。它在菜单中放置了一条水平线。 感谢您,这些都很棒,在大重构期间非常有用。【参考方案2】:

在实现类或方法之前,我要做的第一件事就是问:“我想如何从外部使用它?”

我从来没有,从来没有首先编写我的类和方法的内部结构。从优雅的公共 API 开始,内部往往会免费变得优雅,如果它们不这样做,那么丑陋至少包含在单个方法或类中,并且不允许用它的气味污染其余代码.

有很多设计模式,二十年的编码告诉我,唯一经得起时间考验的模式是:KISS。保持简单愚蠢。

一些通用的经验法则,适用于任何语言或环境:

按照您的直觉,对您阅读或听到的任何建议进行操作! 提早纾困! 如果需要,尽早验证输入并快速退出!减少清理工作。 切勿在代码中添加您不使用的内容。 “reverse” 的选项可能会让人感觉不错。 在这种情况下添加它!不要浪费时间添加您不需要的复杂性。 方法名称应该描述做了什么,从不它是如何完成的。 只要结果相同,就应该允许方法在不更改名称的情况下更改其实现。 如果您无法从名称中理解方法的作用,请更改名称! 如果方法部分足够复杂,请使用 cmets 来描述您的实现。 不要害怕单身人士! 如果您的应用只有一个数据模型,那么它就是单例! 到处传递单个变量只是假装它不是单例,并增加复杂性作为奖励。 从一开始就计划失败。 从一开始就始终使用doFoo:error 而不是doFoo:。 从一开始就使用最终用户可读的本地化描述创建漂亮的 NSError 实例。 将错误处理/消息改进为现有的大型应用程序是一件很痛苦的事情。 如果涉及到用户和 IO,总会出现错误! Cocoa/Objective-C 是 面向对象*,而不是 **类,就像大多数声称是 OOP 的流行孩子一样。 不要引入只有属性的哑值类,没有执行实际工作的方法的类也可以是结构。 让您的对象变得智能!如果您只需要 Foo 上的 fooFromString: 方法,为什么还要添加一个全新的 FooParser 类? 在 Cocoa 中,你能做什么总是比 你是什么更重要。 如果目标/操作可以做到,请不要引入协议。 不验证实例是否符合协议,是一种类,这取决于编译器。

【讨论】:

Apple 更喜欢只有比例的类而不是结构(对不起,没有参考)。此外,在处理id、子类或可选协议方法时,自省可能很有用。 @Scott - Jupp,这在 ARC 介绍后发生了变化。 ARC 不能很好地处理结构中的对象引用,但可以处理类。 完全同意这一点,也总结一下我的经验:)【参考方案3】:

我的 2 美分:

    属性 通常比老式的 getter+setter 更好。即使您使用 @dynamic 属性 - 使用 @property 声明它们,这也会提供更多信息且更短。 我个人不会为类模拟“私有”方法。是的,我可以在 .m(m) 文件中的某处写一个类别,但是由于 Obj-C 没有纯粹的方法来声明私有方法——我为什么要发明一个?无论如何,即使您真的需要类似的东西 - 声明一个带有类别的单独“MyClassPrivate.h”并将其包含在 .m(m) 文件中以避免重复声明。 绑定。绑定大多数控制器 UI 关系,使用转换器、格式化程序,只是不要编写手动读取/写入控件值的方法。它使代码看起来像 MFC 时代的东西。 C++,很多代码在用 C++ 编写时看起来更好更短。由于编译器理解 C++ 类,因此它是重构的好点,尤其是在处理低级代码时。 我通常拆分大控制器。 超过 500 行代码对我来说是一个很好的重构候选者。例如,我有一个文档窗口控制器,因为它使用图像导入/导出选项扩展了某些版本的应用程序。控制器增长到 1.000 行,其中 1/2 是“图像内容”。这是我制作 ImageStuffController、在 NIB 中实例化它并将所有与图像相关的代码放入其中的“触发器”。

以上所有内容都使我更容易维护我的代码。对于一个大型项目,拆分控制器和类以保持它们的小结果大量文件,我通常尝试将一些代码提取到框架中。例如,如果应用程序的很大一部分与外部 Web 服务通信,通常有一种直接的方法可以从主应用程序中提取 MyWebServices.framework。

【讨论】:

#4 是一个非常糟糕的想法。我第一次听到有人说要翻译成 C++ 以使代码看起来更好。 jshier,例如,C++ 允许为 NSPoint 定义一个 + 运算符。对我来说,p1 + p2 看起来比 NSOffsetPoint(p1, p2) 要好得多。在处理地图和列表等数据结构时,C++ 更加简洁(除非结果直接在 UI 中使用——这就是你应该使用 NS 类的地方)。接下来 - 数学任务。用于矩阵和向量的 C++ 库通常比 NSAffineTransform 和其他更容易使用。 NS 类在使用 NSGraphicsContext 时再次表现出色,但某些后端数学绝对应该使用 C++ 处理以简化代码。恕我直言,当然

以上是关于让 Objective-C 类看起来很漂亮的主要内容,如果未能解决你的问题,请参考以下文章

如何在 R 中保存地块并让它们看起来很漂亮

快速调用objective-c类方法

外观模式(三层解耦)

Objective-C / iOS:为自定义视图子类化 UITableViewController

在 Objective-C 中解析 JSON 的最漂亮方法?

Objective-C中的 id, isa,和 instancetype