如何在Xcode中组织大型Cocoa应用程序的源代码是最佳方式?

5
这是我正在寻找的内容:
我想将功能分离成模块或组件,以限制其他类的可见性,防止每个类都可以访问所有其他类,这样随着时间的推移会导致代码混乱。
例如,在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)

2
插件的主要缺点是:它们在 iOS 下无效。希望你的目标是 OS X。在 Xcode 4 下,在同一个窗口中管理多个目标和项目 - 包括框架 - 相当容易,你只需要转到“文件”>“新建工作区”。 - Morgan Harris
4个回答

9

我曾经参与过一些规模较大的Mac项目(在我的最后一个项目中,代码行数超过了2M,包含了90个xcodeproj文件),以下是我对这些项目管理的一些想法:

  • 避免使用像框架、捆绑包或动态库等动态加载方式,除非你确实需要在不同组之间共享二进制文件。根据我的经验,这些方法往往会带来比解决问题更多的复杂性。而且它们不能轻易地移植到iOS,这意味着需要维护多种方法。最糟糕的是,有许多动态库会增加重复包含相同符号的可能性,从而导致各种奇怪的错误。当你直接在多个库中直接包含某些“助手”类时,就会发生这种情况。如果它包含全局变量,由于不同线程使用全局变量的不同实例,会产生奇怪的错误。

  • 在许多情况下,静态库是最好的选择。它们在编译时解决了所有问题,允许在C/C++中进行代码削减和其他无法在动态库中优化的内容。它们可以消除“嘿,在我的系统上运行良好,但在客户机上却不行”的情况(当您使用错误的框架路径值时)。计算来自崩溃堆栈的行号时,无需处理幻灯片。它们可以在构建时捕获重复符号,节省了许多调试时间。

  • 将主要组件分成单独的xcodeproj。但要认真思考这里的“主要”意味着什么。我的90个项目产品就太多了。只是进行依赖关系检查就可能变得非常复杂。(Xcode 4可以改善这一点,但在我们成功地使Xcode 4能够可靠地构建它之前,我已经离开了这个项目,所以我不知道它最终表现如何。)

  • 将公共头文件与私有头文件分开。你可以使用静态库来实现这一点,就像使用框架一样。将公共头文件放到不同的目录中。我建议每个组件都有自己的公共include目录。

  • 不要复制头文件。直接从组件的公共include目录中包含它们。将头文件复制到共享目录似乎是一个好主意,直到你这么做了。然后你会发现你正在编辑复制品而不是真正的文件,或者你正在编辑真正的文件,但实际上并没有复制它。无论如何,这会让开发变得麻烦。

  • 使用xcconfig文件,而不是构建面板。在这些大型项目中,构建面板会让你发疯。我的面板通常有这样一行:


common="../../common"
foo="$(common)/foo"
HEADER_SEARCH_PATHS = $(inherited) $(foo)/include
  • 在公共头文件路径中,包含你自己的bundle名称。在上面的示例中,主头文件的路径将是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"

如果您有一份稳定的代码,该代码被多个项目共享,并且在开发这些项目时您不会对此代码进行修改(也不希望源代码公开),那么框架可能是一个合理的选择。如果您需要插件来避免加载大量不必要的代码(而在大多数情况下您确实不会加载这些代码),那么捆绑包可能是一个合理的选择。但是对于应用程序开发人员来说,在大多数情况下,从静态库链接在一起的一个大型可执行文件是最好的选择。只有当共享库和框架在运行时真正被“共享”时,它们才有意义。

非常有趣。我从未考虑过静态库。这可能是我要走的路。我有一些代码在两个应用程序之间共享,但我不介意将每个链接单独合并为一个二进制文件。谢谢! - Mark
我找到了一份非常好的教程,展示了如何在Xcode中实现你上面建议的内容:http://www.clintharris.net/2009/iphone-app-shared-libraries/ - Mark
关于框架(Frameworks)的一些问题,命名冲突可以通过使用“命名空间”前缀来轻松避免,就像苹果在自己的框架中所做的那样。这就是为什么 CoreAnimation 中的每个类都以“CA”开头,而 UIKit 中的所有内容都以“UI”开头。如果您愿意,还可以静态链接到一个框架。没有规定框架必须使用动态链接。实际上对于iOS而言,它们只能被静态链接。由于Karl Stenerud的努力,它们也可以很好地移植到iOS上。 - aroth
关于命名空间和动态链接的问题是,当您拥有一个非常庞大的代码库并在同一团队内广泛重用时。他们使用相同的前缀。因此,您会发现MYHandyUtility()被复制到多个框架中,并且在动态链接时会出现问题。为了避免这种情况,您可以使您的框架非常小(然后有太多),或者非常大(然后您会膨胀您的产品)。苹果通过拥有专门的团队来避免这种情况,但大多数组织没有特殊的团队。UIKit中的膨胀也不太重要,因为它是共享的。 - Rob Napier
关于静态框架,如果你有人分发它们,那是很有用的。当我在多个产品之间共享代码时,我考虑过这一点,但对于一个单独的大团队来说,打包框架的开销通常不会带来比仅从源代码树中读取包含文件更好的回报。框架的好处在于当你可以将框架开发与应用程序开发隔离开来以推动重用(通常通过让不同的人从应用程序中创建框架,具有自己的测试、文档、时间表等)。但许多组织并没有这样的人员配置。 - Rob Napier

4
我的建议是:
  1. 使用框架。它们是您列出的选项中最易于重用的构建工件,而您试图实现的结构非常类似于创建一组框架。

  2. 为每个框架使用单独的项目。如果所有内容都被倾泻到一个项目中,您将永远无法让编译器强制执行所需的访问限制。如果您无法让编译器强制执行它,那么祝您好运,让开发人员这样做。

  3. 升级到XCode4(如果您还没有)。这将允许您在单个窗口中处理多个项目(几乎与Eclipse的方式相同),而不会混淆项目。这基本上消除了您在框架选项下列出的缺点。

如果您的目标是iOS,则强烈建议您构建真正的框架,而不是使用bundle-hack方法获得的虚假框架,如果您尚未构建真正的框架。


我的目标是 Mac OS X。也许现在是时候升级到 Xcode 4 并使用框架了... - Mark
奇怪的是,我真的很想看到针对Xcode 7.x的2015年这个答案更详细的解释。我面临着与规模相关的同样问题,并发现管理测试应用程序目标以及单元测试目标而无需将我的应用程序项目拆分成许多较小的项目是不可能的。问题是,您应该如何共享内部代码以及管理外部依赖项。 - fatuhoku

2
我通过刻意练习Model-View-Control(MVC)以及适量的注释和必不可少的源代码控制(先是subversion,然后是git),成功地保持了对我项目的理智。如今,我的项目已经变得相当庞大(类的数量很多)。
总的来说,我遵循以下原则:
“Model”类用Objective-C 1.0编写,可以是NSObject的子类或自定义的继承自NSObject的“model”类。这些类负责序列化数据(无论数据来自何处,包括应用程序的“状态”)。我选择Objective-C 1.0主要出于兼容性考虑,因为它是最低公共分母,我不想因为依赖Objective-C 2.0功能而在未来被卡住,必须从头开始编写“model”类。
View类在XIB中设置为支持我需要支持的最老的工具链(这样我就可以使用之前版本的Xode 3以及Xcode 4)。我倾向于使用苹果提供的Cocoa Touch API和框架,以从任何优化/增强中受益,因为这些API会随着时间的推移而不断发展。
Controller类包含通常的代码,用于管理视图的显示/动画(无论是通过编程还是从XIBs中实现),以及从“model”类中序列化数据。
如果我发现自己多次重复使用某个类,我会探索重构代码并将其优化(使用Instruments进行衡量),转换成我所谓的“utility”类或协议。
希望这能有所帮助,祝你好运。

0

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

如果您正在编写“正确”的面向对象类,则将具有方法和变量的类结构隐藏在其他必要的类中。除非您的项目庞大且由数百个不同可区分的模块构建,否则仅将类和资源分组到XCode中的文件夹/组中并以此方式处理可能已足够。

如果您真的拥有一个易于区分的巨大项目,那么请务必创建一个框架。但我建议,只有在您在不同应用程序中使用相同代码的情况下,才真正需要创建框架/额外项目,这样可以有效地在项目之间复制代码。在几乎所有其他情况下,这可能只是过度设计,比实际需要复杂得多。

您的最后一个想法似乎是前两者的混合。插件(如果我理解您的描述正确,请告诉我是否错误)只是同一项目中的分离类?这可能是最好的方法,并且在任何情况下都应该这样做(在某种程度上)。如果您正在创建绘制图形的功能(例如),则应将其分成新的文件夹/组,并在其中开始您的类和功能,仅在必要时将这些类包含到主应用程序中。

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

感谢您的建议。我今天正在使用组和面向对象的方法,但这取决于我自己的纪律,不包括头文件并引入不应该存在的依赖项。通过插件,我指的是实际的Cocoa捆绑包:http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/LoadingCode/Concepts/Plugins.html - Mark

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接