用脑思考,用心琢磨,用行动证实 [登录·注册]

吕滔博客

首页 开发 运维 工具 摄影

代码组织和 Framework

Swift TIP memory 发布于May 30, 2016 标签: Swift

Apple 为了 iOS 平台的安全性考虑,是不允许动态链接非系统的框架的。因此在 app 开发中我们所使用的第三方框架如果是以库文件的方式提供的话,一定都是需要链接并打包进最后的二进制可执行文件中的静态库。最初级和原始的静态库是以 .a 的二进制文件加上一些 .h 的头文件进行定义的形式提供的,这样的静态库在使用时比较麻烦,我们除了将其添加到项目和配置链接外,还需要进行指明头文件位置等工作。这样造成的结果不仅是添加起来比较麻烦,而且因为头文件的路径可能在不同环境下会存在不一样的情况,而造成项目在换一个开发环境后就因为配置问题造成无法编译。有过这种经历的开发人员都知道,调配开发环境是一件非常让人讨厌和耗费时间的事情。

而 Apple 自己的框架都是 .framework 为后缀的动态框架,是集成在操作系统中的,我们使用这些框架的时候只需要在 target 配置时进行指明就可以,非常方便。

因为 framework 的易用性,因此很多开发者都很喜欢类似的“即拖即用,无需配置”的体验。一些框架和库的开发者为了使用体验一般会用一些第三方提供的方法来模拟地生成行为类似的框架,比如 Dropbox 或者 Facebook 的 iOS SDK 都是基于这种技术完成的。

但是要特别指出,虽然和 Apple 的框架的后缀名一样是 .framework,使用方式也类似,但是这些第三方框架都是实实在在的静态库,每个 app 需要在编译的时候进行独立地链接。

Xcode 6 开始 Apple 官方提供了单独制作类似的 framework 的方法,这种便利性可能会使代码的组织方式发生重大变化。我们现在可以在一个 app 项目中添加新的类型为 Cocoa Touch Framework 的 target,并在同一个项目中通过 import 这个 target 的 module 名字 (一般和这个 target 的名字是一样的,除非使用了一些像中杠 - 这样在 module 名中的非法字符),来引入并进行使用。

这类框架在运行时是嵌入在 app 的 bundle 中进行动态链接的,我们的 app 本体以及各个 extension 都可以使用。这么做的一个明显的好处是我们可以在不同 target 之间很简单地重用代码,因为我们总会有一些在 app 本身和扩展中重复的东西,这时候将它们用框架的形式组织起来会是一个很好的选择。另一方面,就算你没有计划开发扩展,尝试将一部分代码分离到框架中也是有助于我们梳理项目的架构的。比如将所有的模型层组织为一个框架,如果你在这个过程中发现有困难的话,这很可能就是你需要重新考虑和重构项目架构的信号了。

但是这样制作的框架只能嵌入在自己的 app 中,如果我们希望将自己制作的框架提供给别人使用的话,我们可以新建一个专门生成框架的项目。接下来我们会通过一个简单的例子来告诉你应该怎么做,但是在这之前,需要先说明,使用框架项目并单独导出 framework 文件这种做法,是为 Objective-C 准备的。因为 Swift 暂时并不是稳定版本,而 Swift 的运行时也是经常改变,并且没有集成到操作系统中,所以官方并不推荐单独为 Swift 制作框架。我们虽然可以使用纯 Swift 制作可用的第三方库 (接下来你会看到要怎么做),但是并不能保证它在所有的运行环境中都能良好工作。关于 Swift 代码的兼容性,可以参看相关章节的内容。

在当前 Swift 中,我们使用 Swift 框架的最佳实践是将整个框架项目 (包括其中源代码) 以项目依赖的方式添加到自己的项目中,并一起进行编译使用。本节所要讲述的是制作单独的编译好的框架文件供别人使用,虽然暂时还不建议将这种方法用在实际项目之中,但是这里着重想展现的是使用 Swift 制作框架文件的可能性。

当然你也可以使用 Objective-C 来制作框架,这样就没有这些限制了,因为本来这个特性现在暂时也只是为 Objective-C 准备的。使用 Objective-C 制作框架的过程和 Swift 大同小异,因为我们这是一本关于 Swift 的书籍,所以就只使用 Swift 来进行介绍了。
首先通过新建菜单的 Framework & Library 创建一个 Cocoa Touch Framework 项目,命名为 HelloKit,然后添加一个 Swift 文件以及随便一些什么内容,比如:

public class Hello {
    public class func sayHello() {
        print("Hello Kit")
    }
}

注意我们在这里添加了 public 声明,这是因为我们的目的是在当前 module 之外使用这些代码。将运行目标选择为任一 iOS 模拟器,然后使用 Shift + Cmd + I 进行 Profiling 编译。我们可以在项目的生成的数据文件夹中 (使用 Window 菜单的 Organizer 可以找到对应项目的该文件夹位置) 的 /Build/Products/Release-iphonesimulator 里找到 HelloKit.framework。

如果直接使用 Cmd + B 进行编译的话我们得到的会是一个 Debug 版本的结果,在绝大多数情况下这应该不是我们想要的,除非你是需要用来进行调试。

然后新建一个项目来看看如何使用这个框架吧。建立新的 Xcode 项目,语言当然是选择为 Swift,然后将刚才的 HelloKit.framework 拖到 Xcode 项目中就可以了。我们最好勾选上 Copy items if needed,这样原来的框架的改动就不会影响到我们的项目了。

接下来,我们在需要使用这个框架的地方加上对框架的导入和调用。为了简单,我们就在 AppDelegate.swift 的 didFinishLaunching 方法中对 sayHello 进行一次调用:

func application(application: UIApplication!,
    didFinishLaunchingWithOptions launchOptions: NSDictionary!) -> Bool {
    // Override point for customization after application launch.

    Hello.sayHello()

    return true
}

当然,别忘记在顶部加上 import HelloKit 来导入框架。

和其他的只做链接的添加框架的方式略有不同,最后一步我们还需要在编译的时候将这个框架复制到项目包中。在 Build Phases 选项卡里添加一个 Copy File 的阶段 (如图),然后将目标设定为 Frameworks,将我们的 HelloKit.framework 添加到新建的阶段里,来指定 IDE 在编译时进行复制。
add-file.jpg

我想就这么多了,现在使用模拟器运行这个项目,我们应该可以在控制台中看到我们的输出了:Hello Kit。

但是故事还没有最终结束。我们刚才编译的时候只做了模拟器的版本,如果你尝试一下在 app 项目中将目标切换为真机的话,会发现根本无法编译,这是由于模拟器和实际设备所使用的架构不同导致的。我们需要回到框架项目中,将编译目标切换为 iOS Device,然后再次使用 Shift + Cmd + I 进行编译。这时我们可以在 Release-iphoneos 文件夹下得到真实设备可以使用的框架。最后我们通过 lipo 命令将适用于多个架构的二进制文件进行合并,以得到可以在模拟器和实际设备上通用的二进制文件:

lipo -create -output HelloKit \
       Release-iphoneos/HelloKit.framework/HelloKit \
       Release-iphonesimulator/HelloKit.framework/HelloKit

然后将得到的包含各架构的新的 HelloKit 文件复制到刚才的模拟器版本的 HelloKit.framework 中 (没错其实它是个文件夹),覆盖原来的版本。最后再将 Release-iphoneos 中的框架文件里的 /Modules/HelloKit.swiftmodule 下的 arm.swiftmodule 和 arm64.swiftmodule 两个文件复制到模拟器版本的对应的文件夹下 (这个文件夹下最终应该会有 i386,x86_64,arm 和 arm64 四个版本的 module 文件)。我们现在就得到了一个通吃模拟器和实际设备的框架了,用这个框架替换掉刚才我们复制到 app 项目中的那个,app 应该就可以同时在模拟器和设备上使用这个自制框架了。

再次提醒,本文所述的用 Swift 构建框架项目,然后在其他项目中使用这个框架的做法并不是推荐做法。对于 Objective-C 来说这个做法没有什么太大问题,但是对于 Swift 的框架来说,因为现在 Swift 的解释和运行环境还没有非常稳定,因此在项目中使用非同项目 target 的框架的时候,很有可能项目和框架的 Swift 运行环境 有所差异。有时候这会导致不必要的问题和麻烦。

相关推荐

添加新评论

全部评论:已有 2 条评论

  1. 工控资料窝

    还不错

  2. 好书推荐

    进来看看

网站状态

  • 栏目分类:49个
  • 发布文章:1308篇
  • 用户评论:687条
  • 开博至今:3976天

正则速查

[abc] 匹配中括号中的单个字符,如a或b或c
[^abc] 匹配除了a、b、c等字符的其他单个字符
[a-z] 匹配一个字符范围,如a到z
[a-zA-Z] 匹配一个字符范围,如a-z 或 A-Z
^ 匹配行的开始
$ 匹配行的结束
\A 匹配一个字符串的开始
\z 匹配一个字符串的结束
. 匹配任意单个字符
\s 匹配空白字符,如空格,TAB
\S 匹配非空白字符
\d 匹配一个数字
\D 匹配非数字
\w 匹配一个字母
\W 匹配非字幕
\b 匹配字符边界
(...) 引用所有括号中的内容
(a|b) a或者b
a? 零个或多个a
a* 零个或多个a
a+ 1个或多个a
a{3} 3次重复的a
a{3,} 3次或3次以上重复的a
a{3,6} 3到6次重复的a

修正符

/g 查找所有可能的匹配
/i 不区分大小写
/m 多行匹配
/s 单行匹配
/x 忽略空白模式
/e 可执行模式,PHP专有
/A 强制从目标字符串开头匹配
/D 使用$限制结尾字符,则不允许结尾有换行
/U 只匹配最近的一个字符串;不重复匹配

最新回复

  • memory: 这是我3年前玩过一次,当时因为安装完后,发现日常办公还好,但是好...
  • sdf: 想问一下,激活工具激活后,后面会出现提示要再次重新激活吗?
  • memory: 你要用碗装我吗?还是要我把扔的扔进你碗里???!!! &^_^&
  • 薛才杰: 同感,快扔到我的碗里来。。。
  • memory: 要说明一下,在大陆及香港的云主机或VPS,都是不支持的。原因都懂...
  • 历史趣谈LishiQtan: 很好的文章,值得收藏
  • memory: 现在回头看,,,这么烂的代码,也是醉了.
  • memory: 有空试试apache的
  • 脚踏实地: rInfo数组是怎么来的
  • 朋友们: 我都是这么建站的
  • memory: 明眼人.
  • 上官元恒: $lunarInfo数组是怎么来的,这是重点
  • memory: 你知道的太多了...
  • yjl: 这个肯定解决的不是空间的问题,可能是复用的问题,有可能是“站群”的问题
  • Smithd994: Hello! This post could not be wr...
  • memory: controll文件assign ,然后模板文件中直接foreach
  • mike: 你好,yaf怎么使用循环输出数据到模板中