【问题标题】:What is the best way to organize source code of a large Cocoa application in Xcode?在 Xcode 中组织大型 Cocoa 应用程序的源代码的最佳方式是什么?
【发布时间】:2011-10-01 07:25:43
【问题描述】:

这就是我要找的东西:

我想将功能片段分成某种模块或组件,以限制其他类的可见性,以防止每个类都可以访问其他所有类,随着时间的推移,这会导致意大利面条式代码.

例如,在 Java 和 Eclipse 中,我会使用包并将每个包放入具有明确定义的依赖结构的单独项目中。

我考虑过的事情:

  1. 对源文件使用单独的文件夹并在 Xcode 中使用组
    • 优点:操作简单,几乎不需要 Xcode 配置
    • 缺点:没有编译时功能分离,即访问所有内容只需一个 #import 语句即可
  2. 使用框架
    • 优点:框架代码无法访问框架外的访问类。这会强制封装并使事物分开
    • 缺点:如果您同时在多个框架上工作,代码管理会很麻烦。每个框架都是一个带有单独窗口的单独 Xcode 项目
  3. 使用插件
    • 优点:与框架类似,插件代码无法访问其他插件的代码。在编译时进行干净的分离。插件源可以是同一个 Xcode 项目的一部分。
    • 缺点:不确定。这可能是要走的路...

根据您的经验,您会选择什么来保持独立,同时能够编辑同一项目中的所有源?

编辑:

  • 我的目标是 Mac OS X
  • 我真的在寻找一种在编译时强制分离的解决方案
  • 插件是指 Cocoa 包 (http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/LoadingCode/Concepts/Plugins.html)

【问题讨论】:

  • 插件的主要缺点:它们在 iOS 下无效。希望您的目标是 OS X。在 Xcode 4 下,在同一个窗口中管理多个目标和项目(包括框架)非常简单,您只需转到文件 > 新工作区。

标签: objective-c xcode cocoa modularity


【解决方案1】:

我参与过一些大型 Mac 项目(在 90 个 xcodeproj 文件中的最后一个中 >2M SLOC),以下是我对管理它们的想法:

  • 除非您实际上在组之间共享二进制文件,否则应避免动态加载,例如框架、捆绑包或 dylib。这些往往会产生比我的经验解决的更多的复杂性。此外,它们不容易移植到 iOS,这意味着要维护多种方法。最糟糕的是,拥有大量动态库会增加两次包含相同符号的可能性,从而导致各种疯狂的错误。当您直接在多个库中直接包含一些“帮助器”类时,就会发生这种情况。如果它包含一个全局变量,那么由于不同的线程使用不同的全局变量实例,所以这些错误非常棒。

  • 在大多数情况下,静态库都是最佳选择。它们在构建时解决所有问题,允许在您的 C/C++ 中进行代码剥离以及在动态库中不可能进行的其他优化。他们摆脱了“嘿,它加载在我的系统上而不是客户的系统上”(当您为框架路径使用错误的值时)。从崩溃堆栈计算行号时无需处理幻灯片。它们在构建时捕获重复的符号,从而节省了数小时的调试痛苦。

  • 将主要组件分离到单独的 xcodeproj 中。不过,请真正考虑一下“主要”在这里的含义。我的 90 个项目的产品太多了。仅仅做依赖检查就可以成为一个非常重要的练习。 (Xcode 4 可以改善这一点,但在我们能够让 Xcode 4 可靠地构建它之前,我就离开了这个项目,所以我不知道它最终做得如何。)

  • 将公共标头与专用标头分开。您可以像使用框架一样使用静态库来做到这一点。将公共标头放在不同的目录中。为此,我建议每个组件都有自己的公共include 目录。

  • 不要复制标题。直接从组件的公共include 目录中包含它们。将标头复制到共享树中似乎是一个好主意,直​​到您这样做。然后你发现你正在编辑副本而不是真正的副本,或者你正在编辑真实的副本,但实际上并未复制它。无论如何,这让开发变得头疼。

  • Use xcconfig files, not the build pane. 在这些大型项目中,构建窗格会让您发疯。我的往往有这样的线条:


common="../../common"
foo="$(common)/foo"
HEADER_SEARCH_PATHS = $(inherited) $(foo)/include

  • 在您的公共标头路径中,包含您自己的包名称。在上面的示例中,主标题的路径是common/foo/include/foo/foo.h。额外的关卡似乎很痛苦,但当您导入时,这才是真正的胜利。然后你总是像这样导入:#import <foo/foo.h>。保持一切都非常干净。不要使用双引号来导入公共标头。仅使用双引号在您自己的组件中导入私有标头。

  • 我还没有决定 Xcode 4 的最佳方式,但在 Xcode 3 中,您应该始终通过将项目添加为子项目并将“.a”目标拖到链接步骤中来链接自己的静态库.这样做可以确保您链接为当前平台和配置构建的版本。我真正庞大的项目还没有能够转换到 Xcode 4,所以我对那里的最佳方式还没有强烈的意见。

  • 避免搜索自定义库(链接步骤中的 -L-l 标志)。如果您将库构建为项目的一部分,请使用上面的建议。如果您预先构建它,则在LD_FLAGS 中添加完整路径。搜索库包括一些令人惊讶的算法,使整个事情难以理解。切勿将预建库放入链接步骤。如果将预构建的libssl.a 放入链接步骤,它实际上会为路径添加-L 参数,然后添加-lssl。在默认搜索规则下,即使您在构建窗格中显示libssl.a,您实际上也会链接到系统libssl.so。删除库将删除-l,但不会删除-L,因此您可能会遇到奇怪的搜索路径。 (我讨厌构建窗格。)在 xcconfig 中这样做:


LD_FLAGS = "$(openssl)/lib/libssl.a"

  • 如果您有在多个项目之间共享的稳定代码,并且在开发这些项目时您永远不会弄乱这些代码(并且不希望源代码可用),那么框架可能是一种合理的方法.如果您需要插件来避免加载大量不必要的代码(并且在大多数情况下您确实不会加载该代码),那么捆绑可能是合理的。但在大多数情况下,对于应用程序开发人员来说,从静态库链接在一起的大型可执行文件是 IMO 的最佳方法。共享库和框架只有在运行时真正共享才有意义。

【讨论】:

  • 非常有趣。我没有想到静态库。这很可能是我要走的路。我有两个应用程序之间共享的代码,但我不介意将每个应用程序分别链接到一个二进制文件中。谢谢!
  • 我找到了一个很好的教程,展示了如何在 Xcode 中实现上述建议:clintharris.net/2009/iphone-app-shared-libraries
  • 要回答您关于框架的一些观点,可以通过使用“命名空间”前缀轻松避免命名冲突,就像 Apple 在自己的框架中所做的那样。这就是为什么CoreAnimation 中的每个类都以“CA”开头,而UIKit 中的所有内容都以“UI”开头。如果您愿意,可以静态链接到框架。没有规定框架必须使用动态链接。事实上,对于 iOS,它们只能静态链接。由于 Karl Stenerud 的努力,他们将 just fine 移植到 iOS。
  • 关于命名空间和动态链接,问题是当您拥有大量代码并在同一团队中广泛重用时。它们使用相同的前缀。因此,您最终会发现 MYHandyUtility() 被复制到多个框架中,并且当您动态链接时事情会爆炸。为了避免这种情况,你要么让你的框架非常小(然后太多)要么非常大(然后你的产品膨胀)。 Apple 通过让整个团队致力于框架来避免这种情况,但大多数组织没有专门的团队。 UIKit 中的膨胀也无关紧要,因为它是共享的。
  • 关于静态框架,如果你有人分发它们,这很有用,而且我已经考虑过在许多产品之间共享代码时,但对于单个大型团队来说,打包框架的开销经常仅从源代码树中读取包含文件并不会给您带来回报。框架的好处是,您可以将框架开发与应用程序开发隔离开来推动重用(通常是让不同的人从应用程序中创建框架,并使用他们自己的测试、文档、时间表等)但是许多组织的员工不是这样的.
【解决方案2】:

我的建议是:

  1. 使用框架。它们是您列出的选项中最容易重复使用的构建工件,而且您描述您尝试实现的结构的方式听起来很像创建一组框架。

  2. 为每个框架使用单独的项目。如果将所有内容都转储到单个项目中,您将永远无法让编译器强制执行您想要的访问限制。如果你不能让编译器强制执行它,那么祝你的开发人员这样做。

  3. 升级到 XCode4(如果您还没有)。这将允许您在单个窗口中处理多个项目(非常类似于 Eclipse 的做法),而无需混合项目。这几乎消除了您在“框架”选项下列出的缺点。

如果您的目标是 iOS,我强烈建议您构建 real frameworks,而不是使用 bundle-hack 方法获得的假的,如果您还没有构建真正的框架。

【讨论】:

  • 我的目标是 Mac OS X。也许是时候升级到 Xcode 4 并使用框架了...
  • 奇怪的是,我真的很想看到这个答案在 2015 年为 Xcode 7.x 详细说明。我在规模方面面临着同样的问题,并且发现如果不将我的应用程序项目拆分为许多较小的项目,就不可能管理测试应用程序目标和单元测试目标。问题是,您应该如何共享内部代码以及管理外部依赖项。
【解决方案3】:

通过强迫自己勤奋地练习模型-视图-控制 (MVC) 以及大量的cmets,以及不可或缺的源代码控制(subversion,然后是 git)。

总的来说,我观察到以下几点:

“模型”类在从 NSObject 子类化的 Objective-C 1 类或从 NSObject 继承的自定义“模型”类中序列化数据(无论从哪里开始,包括应用程序的“状态”)。我更多地选择了 Objective-C 1.0 是为了兼容性,因为它是最低的公分母,而且我不想因为 Objective-C 2.0 特性的依赖性而被困在将来从头开始编写“模型”类。

视图类在 XIB 中,XIB 版本设置为支持我需要支持的最旧的工具链(因此除了 Xcode 4 之外,我还可以使用以前版本的 Xode 3)。我倾向于从 Apple 提供的 Cocoa Touch API 和框架开始,以便受益于 Apple 随着这些 API 的发展而可能引入的任何优化/增强功能。

控制器类包含管理视图的显示/动画(以编程方式以及来自 XIB)和来自“模型”类的数据的数据序列化的常用代码。

如果我发现自己多次重用某个类,我会探索重构代码并优化(使用 Instruments 衡量)为我所谓的“实用程序”类或协议。

希望这会有所帮助,祝你好运。

【讨论】:

    【解决方案4】:

    这在很大程度上取决于您的情况和您自己的特定偏好。

    如果您正在编写“正确的”面向对象的类,那么您将拥有一个类结构,其中的方法和变量在必要时对其他类隐藏。除非您的项目很大并且由数百个不同的可区分模块构建,否则只需将类和资源分组到 XCode 中的文件夹/组中并以这种方式使用它就足够了。

    如果您真的有一个包含易于区分的模块的庞大项目,那么请务必创建一个框架。我建议尽管只有在不同应用程序中使用相同代码时才真正需要这样做,在这种情况下,创建框架/额外项目将是在项目之间有效复制代码的好方法。在几乎所有其他情况下,这可能只是矫枉过正,而且比需要的复杂得多。

    您的最后一个想法似乎是前两个的混合。插件(据我了解您正在描述 - 如果我错了,请告诉我)只是同一个项目中的独立类?这可能是最好的方法,无论如何都应该(在一定程度上)这样做。如果您正在创建绘制图形的功能(例如),您应该划分一个新文件夹/组并在其中启动您的类和功能,仅在必要时将这些类包含到您的主应用程序中。

    让我这样说。没有理由超越顶部...但是,即使只是为了您自己的理智 - 或代码的可维护性 - 您应该始终努力将所有内容分组到描述性组/文件夹中。

    【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-11-09
    • 2019-02-26
    相关资源
    最近更新 更多