一步步创建自己的iOS框架
创建你的第一个iOS框架
作者:Jake Craige,时间:2016/1/7
翻译:BNCoding, 如有错误欢迎指出。 原文链接
如果你曾经试图去创建一个自己的iOS框架的话,你应该知道这件事并不是那些畏惧困难的人能够成功完成的-毕竟管理依赖和编写测试并不容易。这篇文章将从开始到最终完成一步步的进行讲解,以便你掌握后可以更好的创建自己的框架。
在教程中我们会构建一个框架,框架里面会暴露一个名为 RGBUIColor(red:green:blue) 的函数,该函数会返回使用这些参数创建的 UIColor 对象。我们会使用 Swift 语言,并且使用 Carthage 作为依赖项的管理工具。我们的框架将会支持通过 Carthage 、 CocoaPods 或者 git 来使用。
让我们开始吧!
创建Xcode工程
-
选择 File -> New -> Project
-
在左侧的选择 iOS -> Framework & Library ,右侧选择“Cocoa Touch Framework”。
-
点击“下一步”,并填写选项提示。确保以及勾选了“Include Unit Tests”。
-
选择工程保存的位置。
-
不要勾选“Create Git repository on My Mac”,我们在后面手动进行设置。
-
点击“创建”并且打开工程。
-
选择 File -> Save As Workspace 并使用工程相同的名字保存到相同的目录中。之所以创建 workspace 是因为我们需要添加 Carthage 中的依赖作为子模块;使用 Xcode
编译他们的时候必须是在一个 workspace 中。
-
选择 File -> Close Project 关闭工程。
-
然后选择 File -> Open 打开*workspace*文件。
-
Xcode左上角的 scheme 并选择“Manage Schemes”。我们需要确保 sheme 勾选了“shared”, 以便能使用“Carthage”来构建工程 。
初始化git
首先,切换到工程所在的目录。
-
运行 git init 初始化空版本库。
-
创建一个 .gitignore的文件。该文件会过滤一些Xcode或者依赖文件中一些我们不想也不需要上传的文件。
这里 是一个标准的Swift工程的gitignore文件,我们只是添加了 .DS_Store 并移除了 fastlane 和一些多余的部分。
## OS X Finder .DS_Store ## Build generated build/ DerivedData ## Various settings *.pbxuser !default.pbxuser *.mode1v3 !default.mode1v3 *.mode2v3 !default.mode2v3 *.perspectivev3 !default.perspectivev3 xcuserdata ## Other *.xccheckout *.moved-aside *.xcuserstate *.xcscmblueprint ## Obj-C/Swift specific *.hmap *.ipa # Swift Package Manager .build/ # Carthage Carthage/Build
添加Carthage和依赖项
-
在工程的文件目录下创建一个名为 Cartfile 的文件以及运行时的依赖性。我们添加** Curry]**([链接 )
github "thoughtbot/Curry"
-
创建一个名为 Cartfile.private 的文件。它会负责私有的一些依赖就像我们的测试框架一样。我们使用 Quick 和 Nimble 。
github "Quick/Quick" github "Quick/Nimble"
-
新建 bin/setup 脚本。它可以提供一个简单的方式来处理依赖和工程,无论时对于贡献者还是我们自己。
mkdir bin touch bin/setup chmod +x bin/setup
-
打开 bin/setup 并将一下代码加入:
#!/usr/bin/env sh if ! command -v carthage > /dev/null; then printf 'Carthage is not installed.\n' printf 'See https://github.com/Carthage/Carthage for install instructions.\n' exit 1 fi carthage update --platform iOS --use-submodules --no-use-binaries
在这个脚本里面,我们假设用户一句安装了 Carthage 链接 ,然后我们使用 update 命令来安装那些依赖项。
我们使用 --use-submodules ,所有那些依赖项会以子模块的方式被添加。当用户需要的时候,他就可以直接使用我们的框架而不需要使用 Carthage 。我们使用了 --no-use-binaries ,所有这些依赖项都会在我们自己的系统上进行编译。
当 bin/setup 建好后,我们直接在终端运行脚本让 Cartfile 自行下载依赖项。
现在我们就可以设置我们的工程并且编译这些依赖项了。
添加依赖到工作区
因为我们的依赖是作为子模块,我们需要将这些自模块添加到工作区。
1.打开 Carthage/Checkouts 然后将每个依赖项的 .xcodeproj 添加到工作区。你可以使用直接拖拽到项目的工作区。
添加完结束后:
链接运行时依赖
-
在工作区的导航栏选择"RGB" ,然后在中间选择"RGB"目标,进而选择"Build Phases",展开"Link binary with libraries"。
-
点击"+"然后选择 Curry.framework 框架的 Curry-iOS 。
-
点击添加。
链接开发依赖项
-
在中间的工具栏选择"RGBTests"。
-
使用上面一样的步骤,将"Quick"和"Nimble"框架添加到"Link binary with libraries"。
当我们将依赖添加到两个目标的时候, Xcode 会自动在"Build Settings"下添加"Framework Search Paths"。我们可以在"RGB"和"RGBTests"中移除,因为同处同一工作区, Xcode 将他们本身的一部分。
-
选择目标下的两个目标,选中"Build Settings"下的"Framework Search Paths",然后按“退格键”删除。
-
接下来,在导航栏选择"RGB"工程的时候,你就会看见下面you三个刚刚添加的三个框架。然后全选这三个框架,然后右击选择"New group from selection"然后将他们放到一个组里, 我将组命名为"Frameworks"。
现在 Carthage 已经设置完成,接下来是 CocoaPods 。
添加CocoaPods支持
为了添加 CocoaPods 支持,我们需要在工程的根目录新建 .podspec ,并且包含工程的信息。
-
新建 RGB.podspec 文件。
-
将下面的实例拷贝并复制到文件中(自行对照修改相应的部分)。
-
使用项目的信息来设置那些选项。更多的选项详情 链接 ,但是该工程中你所需要的那些选择如下。
Pod::Spec.new do |spec| spec.name = "RGB" spec.version = "1.0.0" spec.summary = "Sample framework from blog post, not for real world use.Functional JSON parsing library for Swift." spec.homepage = "https://github.com/jakecraige/RGB" spec.license = { :type => 'MIT', :file => 'LICENSE' } spec.authors = { "Jake Craige" => 'james.craige@gmail.com', "thoughtbot" => nil, } spec.social_media_url = "http://推ter.com/thoughtbot" spec.source = { :git => "https://github.com/jakecraige/RGB.git", :tag => "v#{spec.version}", :submodules => true } spec.source_files ="RGB/**/*.{h,swift}" spec.requires_arc = true spec.platform = :ios spec.ios.deployment_target = "9.1" spec.dependency "Curry", '~> 1.4.0' end
这里面需要注意到的一行是 spec.dependency "Curry", '~> 1.4.0' 。因为我们需要支持 CocoaPods ,我们假设框架的使用者会使用 CocoaPods 而不是 Carthage ,
所有我们我们在最后一行也声明依赖而不仅仅只在 Carthfile 声明。
当我们设置好了之后,我们在终端中运行 pod lib lint 命令测试所有的东西是不是都配置好了。如果没错的话,我们能看见如下的提示:
当工程的依赖项设置好后,我们就可以写代码了。但是在我们开始之前,先提交代码。
git commit -am "Project and dependencies set up"
编写第一个测试
打开 RGBTests/RGBTests.swift 文件,你可以看见一个默认的模版。她使用了 @testable 和 XCTest( ,但是接下来我们会作出一些调整。
首先,我们会移除 @testable ,因为我们需要测试那些框架使用者可能调用的API接口。随着框架的增长,我们可能会需要 @testable 去测试那些不是作为公共接口暴露的部分;总的来说,就是我们想避免测试那些暴露给使用者的接口。这个特性在测试应用的时候会更加有效,而不是在框架测试中。
来源于 苹果关于测试部分的文档 :
伴随者可测试性,你系那种能够在 Swift 2.0 框架和应用中编写测试并且不需要要测试所有的 internal 和 public 部分。在 XCTest 目标而不是其他框架或者应用的测试代码中
使用 @testable import {ModuleName} 。
我们使用 Quick 和 Nimble 作测试。 Quick 提供以一个行为驱动类型的测试接口,与 RSpec 和 Specta 非常相近; Nimble 给我们提供了强大的断言以及少量模版就能写成异步代码的能力。
写完之后,代码如下:
import Quick import Nimble import RGB class RGBTests: QuickSpec { override func spec() { describe("RGB") { it("works") { expect(true).to(beTrue()) } } } }
使用快捷键 CMD + U 或者 Product -> Test 运行测试代码,会显示测试成功。
所以,到现在已经完成了!
开玩笑而已。让我们来一些真正的测试。
我们暴露一个 RGBUIColor(red: 195, green: 47, blue: 52) 调用接口,接口会返回一个漂亮的 thoughtbot red 的UIColor。
代码如下:
describe("RGBUIColor") { it("is a correct representation of the values") { let thoughtbotRed = UIColor( red: CGFloat(195/255), green: CGFloat(47/255), blue: CGFloat(52/255), alpha: 1 ) let color = RGBUIColor(red: 195, green: 47, blue: 52) expect(color).to(equal(thoughtbotRed)) } }
如果你此时运行此时的话,会像预料中的那样-失败。因为 Swift 语言的类型检查会组织我们运行一个没有定义的 RGBUIColor 函数。接下来让我们完成它。
编写实现代码
右击 RGB 选择新建一个文件。创建一个名为 RGBUIColor.swift 的文件,并将下面的代码拷贝过去。
import Curry func RGBUIColor(red red: Int, green: Int, blue: Int) -> UIColor { return curry(createColor)(red)(green)(blue) } private func createColor(red: Int, green: Int, blue: Int) -> UIColor { return UIColor( red: CGFloat(red/255), green: CGFloat(green/255), blue: CGFloat(blue/155), alpha: 1 ) }
这里使用 Curry 作为一个运行时的依赖性的例子来使用。这里采用了一个不标准的使用斌强没有提供任何值。让我们继续测试!
第一眼看过去,我们可能会感到很奇怪。我们明明已经定义了 RGBUIColor 函数啊?
确实我们定义了该函数但是,我们并没有将她声明为 public 。
这意味着,如果有人系有人使用我们的框架的话,他们是不能使用这个函数接口的。如果你想看见什么不同的话,将 @testable 添加回来,你会发现你的测试通过了。
通过这个错误我们就知道为什么要在 iomport 前面将 @testable 移除。这能让我们在发布框架之前更好的捕捉到错误。
让我们将函数声明为 public ,来修复这个问题。运行测试,问题解决了。然后我们提交代码。
git commit -am "Completed my first iOS framework!"
我写的 代码 。转载请表明出处,谢谢。
</div>