本文大部分内容翻译自raywenderlich上的一篇教程,但这并不是一篇译文,并没有完全原文行文格式、顺序,而是对其有顺序调整和部分删减,另外还在原文的基础上新增、补全了一些内容
环境:
- Xcode Version 9.3 (9E145),
- Cocoapods 1.1+,
- iOS 8+
- Swift/Objective-C
1 引言
1.1 Why
为什么我们需要开源库或Framework呢?
- 代码封装
- 代码模块化
- 代码重用
现代软件开发当然是团队合作的方式更为主流,虽然不乏优秀的独立开发者,但是他们正是需要依赖于开源库才得以以一人之力完成开发。因此分享代码成了一件必须的事情。
1.2 How
我们有两种常见的方式:
- Framework
- 开源库
Framework更加封闭,他隐藏了源代码的实现,常用于商业软件,例如你对客户提供某些服务,而又不希望对方知道实现细节,以保护商业机密,Framework就是最好的选择
开源库则顾名思义,完全开源,大家熟知的GitHub就是开源库的免费代码托管地,他更加公开,根据MIT等协议开源代码
Raywenderlich上的教程主要介绍如何制作Framework,其实开源库也是同样的道理,因此我在原文的基础上增补了一些开源库的内容
1.3 Andriod
其实我曾经写过一篇类似的文章,只不过是在Android上制作Framework,并用maven发布,这次就是对应的在iOS平台上,并使用cocoapod发布,欢迎大家点赞
2 制作Framework
如果你去搜索制作iOS Framework的技术文章很可能找到许多早期的文章,其步骤之繁琐让人望而却步,好在xcode 6和iOS 8之后,apple推出了更简便、更快捷的Framework制作方法--Cocoa Touch Framework
,本文正是介绍如何实现利用此技术制作自己的Framework
首先请下载原教程里的xcode工程Phonercise,这个工程里是一个swift写的小功能,我们的目标就是把这个功能做成Framework,在其他工程里可以使用
2.1 新建Cocoa Touch Framework工程
- 在xcode中,选择新建工程(File/New/Project.)
- 在新建类型中选择Cocoa Touch Framework
- 选择Next
- 将项目名称设置成ThreeRingControl,并选上单元测试选项
- 将这个项目和之前下载的Phonercise项目放在同一个文件路径下,方便后面的操作
- 创建工程
2.2 添加模块和资源文件
现在你的Framework工程已经创建,但是当然它是空空如也,因此你需要把Phonercise工程里面的源文件给拖过来,如下图
- CircularGradient.swift
- coin07.mp3
- Fanfare.swift
- RingLayer.swift
- RingTip.swift
- ThreeRingView.swift
- Utilities.swift
- winning.mp3
在拖拽的时候切记一定要选上Copy items if needed
选项,如果不选,则是引用模式,你在这个工程里的修改会影响原工程,如果选上则是copy,就是源文件copy一份到此工程,两个工程互不影响了。
如果是copy模式,你在文件的Target Membership选择里,应该看到的是当前工程名ThreeRingControl,如下图
2.3 添加Framework
2.3.1 测试工程
然后你可以build一下工程,来确认没有错误。当然,你此时会发现你仅仅可以build,而不能run,你无法在真机或者模拟器上跑,因为还记得吗?这是个CocoaTouch Framework工程,而不是我们常见的application工程。所以你肯定是跑不起来的,见下图(工程图片是个黄色的小公文包)
于是问题产生了,我怎么测试呢,总不能每次发一个Framework文件,集成进一个工程,来测试一次吧?简直low爆。
最简单的当然是像平时开发一样,写好代码run一下来测试。因此我们还需要一个测试工程。
Phonercise工程就是最好的测试工程,原本代码也就是从Phonercise工程中copy出来的,连如何调用代码都已经是现成的了,我们在步骤2.2
中copy了8个文件到ThreeRingControl工程,现在只要把Phonercise中的这8个文件删除,然后把ThreeRingControl.Framework集成进去,如果程序不报错,效果和原来一样,就证明我们的Framework已经完成。
好,去删除这2个文件吧~
2.3.2 添加Framework
删除8个文件后,先关闭ThreeRingControl工程,因为工程文件不能被多次打开。
在Phonercise选择添加文件,然后把ThreeRingControl工程文件路径下的ThreeRingControl.xcodeproj
文件添加进来,注意,一定要先关闭ThreeRingControl工程。
这样ThreeRingControl就成了Phonercise的子工程(sub-project)。你可以在Phonercise中看到ThreeRingControl的全部内容,方便我们未来的调试。
注意:我这里遇到个bug,有时候拖拽进来后,并不能看到ThreeRingControl下文件,就像拖拽了一个文件而不是一个工程进来,这时候关闭xcode,再重新打开就好了。
当然此时工程还是跑不起来的,一堆报错,你还需要把Framework集成进来,如下图。
2.3.3 访问控制
如果你心急已经点了build,当然还是会一堆报错,因为原本这些文件是在同一个module下的,现在却在另外一个module下,因此需要引用一下
打开ActionViewController.swift文件,加入下列代码
import ThreeRingControl
复制代码
除此之外,swift语言还有一个 访问控制(access control) 的安全特性,访问控制可以限定其它源文件或模块中的代码对你的代码的访问级别。这个特性可以让我们隐藏代码的一些实现细节,并且可以为其他人可以访问和使用的代码提供接口。
- Open 和 Public 级别可以让实体被同一模块源文件中的所有实体访问,在模块外也可以通过导入该模块来访问源文件里的所有实体。通常情况下,你会使用 Open 或 Public 级别来指定框架的外部接口。
- Internal 级别让实体被同一模块源文件中的任何实体访问,但是不能被模块外的实体访问。通常情况下,如果某个接口只在应用程序或框架内部使用,就可以将其设置为 Internal 级别。
- File-private 限制实体只能在其定义的文件内部访问。如果功能的部分细节只需要在文件内使用时,可以使用 File-private 来将其隐藏。
- Private 限制实体只能在其定义的作用域,以及同一文件内的 extension 访问。如果功能的部分细节只需要在当前作用域内使用时,可以使用 Private 来将其隐藏。
所以,我们需要将一些内容设置成public来允许别人调用,打开ThreeRingView.swift文件,添加public
关键字
public class ThreeRingView : UIView {
复制代码
另外以下内容也需要添加public关键字
- init方法
- RingCompletedNotification, AllRingsCompletedNotification, layoutSubviews()
- @IBInspectable标记的属性,一共九个
此外 Fanfare.swift 也需要按以上内容添加public关键字
,需要公开的内容是:ringSound, allRingSound,sharedInstance,playSoundsWhenReady()
2.3.4 调用Framework
在Phonercise工程中是在storyboard中调用ThreeRingControl的,因此我们还需要一些工作要做
- 打开Phonercise工程的Main.Storyboard
- 选择Ring Control
- 切换到Identity Inspector,在Custom Class下的Module中选择ThreeRingControl
至此,你终于可以run成功了。试试看吧~
3 创建cocoapod
CocoaPod是当今最受欢迎的iOS依赖管理工具,一个pod就是一个开源库,他类似于Framework,也包括一段代码和资源文件。把Framework或者源代码放进pod,你就拥有把代码可以发布出去的能力。
3.1 准备工作
- 在Phonercise项目中选中“ThreeRingControl.xcodeproj”,并删除他,删除时,选择“Remove Reference”。
- 如果你没有cocoapod,请安装一下,CocoaPods Installation Guide
3.2 创建pod
- 打开一个Terminator,进入ThreeRingControl所在目录
- 执行命令:
pod spec create ThreeRingControl
,目录下会产生一个名字为ThreeRingControl.podspec的文件,这个podspec文件你在任何一个开源库,比如AFNetworking、YYModel的github中都能找到 - 用文本编辑或者vim打开并编辑它
- 修改默认的一些info,注意summary、description、homepage一定要修改,不能是默认值,否则别人无法
pod install
s.name = "ThreeRingControl"
s.version = "1.0.0"
s.summary = "A three-ring control like the Activity status bars"
s.description = "The three-ring is a completely customizable widget that can be used in any iOS app. It also plays a little victory fanfare."
s.homepage = "http://raywenderlich.com"
复制代码
选择一种License,比如:
s.license = "MIT"
根据具体情况,指定平台最低版本:
s.platform = :ios, "10.0"
指定source,这个字段告诉安装者,源文件在哪里,去哪里获得源文件,这个字段通常是github的地址,意思请去GitHub下载源文件
例如AFNetworking的podspec中,该字段是这样的
s.source = { :git => 'https://github.com/AFNetworking/AFNetworking.git', :tag => s.version, :submodules => true }
但是我们目前还没建git,所以暂时我们把它写成当前目录,就是podspec文件所在的目录,
s.source = { :path => '.' }
source_files字段是指哪些源文件会被发布出去,一个工程中不是所以文件都要发布出去,有些demo、Test文件就没有必要发布,因此该字段所指的文件才会被发布出去,我们需要的就是ThreeRingControl目录下的几个文件:
s.source_files = "ThreeRingControl", "ThreeRingControl/**/*.{h,m,swift}"
Resources字段同上,是指哪些资源文件需要发布,例如xib、nib、Image等等,这里就是mp3文件:
s.resources = "ThreeRingControl/*.mp3"
指定swift version,swift由于有目前已经有多个版本,且每个版本都有些不兼容的写法,因此需要指定
s.pod_target_xcconfig = { 'SWIFT_VERSION' => '3' }
3.3 添加依赖
3.3.1 第三方pod库依赖
这里的内容是原教程没有的,就是依赖,原教程中没有依赖别的第三方开源库,然而我们见过有些开源库是依赖第三方的,例如YTKNetwork,他其实依赖于AFNetworking,我们看到他的podspec中就有相应的字段
s.dependency "AFNetworking", "~> 3.0"
这意味着你安装YTKNetwork的时候pod会自动检查你是否有AFNetworking,且版本为3.0,如果有则继续,没有就会给你安装上。这就是cocoapod大受欢迎的一个重要原因,依赖管理
Framework依赖
上面的依赖管理前提是你依赖的工程也是由cocoapod管理并发布的,还有些依赖并不是pod管理的,比如另一个Framework,例如大名鼎鼎的SDWebImage,他就依赖ImageIO
,这是apple自己的一个Framework,那么就要用到下面的字段:
s.framework = 'ImageIO'
3.4 使用pod
步骤3.2
创建了一个本地版本的pod,无需网络,因为我们在第七步中,指定的路径就是本地,所以我们可以不依赖于网络或者GitHub来测试一下。
- 进入Phonercise根目录,使用命令:
pod init
,这会在根目录下创建一个Podfile文件,同样用文本编辑或者vim打开 - 加上代码:
pod 'ThreeRingControl', :path => '../ThreeRingControl'
,这里是个相对路径,根据你把这两个工程摆放的路径来写,我们教程一开始就说把两个工程放在同一目录下,这样方便一些 - 保存后退出,在根目录下执行命令:
pod install
- cocoapod会在目录下创建文件Phonercise.xcworkspace,你不在需要使用Phonercise.project文件来打开工程,而是使用xcworkspace文件打开,这样就完成了,可以去build试一试了
3.5 发布pod
3.5.1 创建仓库
刚刚的pod只是一个本地文件,你无法分享给别的同事,更不用提其他人了,所以你需要有个代码仓位来存放你的代码。
- 你可以把代码放在GitHub之类的开源仓库上
- 你也可以放在自己公司内部的代码库上,这样就是内部使用的了
这里以GitHub为例:
3.5.2 上传代码
打开terminator,进入ThreeRingControl目录,执行命令:
git init
git remote add origin xxxx.git // 你的github仓库地址
git add .
git commit -m "initial commit"
git push
复制代码
这样你的代码就上传到GitHub了
3.5.3 打tag和lint测试
打上tag,这个要和你podspec里面的版本号一致
git tag 1.0.0
git push --tags
复制代码
另外,cocoapod还提供了一个命令,来检测你的pod是否编写合法:pod spec lint
,如果不合法,他会告诉你哪里还没编写好
3.5.4 更新Podfile
我们在步骤3.2
的第七步中,把source指向了本地,现在既然GitHub已经建好了,当然要改成GitHub的地址,打开Phonercise目录下的Podfile文件,把s.source字段改成:s.source = { :git => 'https://github.com/xxx/xxx.git', :tag => s.version}'
指行命令
pod update
// 或者pod install
复制代码
4 cocoapod生成的Framework
大家还记得,Framework和开源库的区别吧,Framework更加封闭,只能看到header文件,而看不到实现文件,可是刚刚我们把code传到GitHub上就已经完全开源出去了。
如果我们就是需要做Framework,隐藏实现,而不是开源库呢?这就不符合我们的要求啊!
我们还能用cocoapod吗?
答案是当然可以啊!!!
其实很简单,我们以AFNetworking为例,首先打开你自己的工程来看看,如果已经引用了AF,你会在工程中看到cocoapod已经为我们生成了AFNetworking.framework文件
我们右键点击,选择show in Finder
其实Framework已经生成了,如下图
那么这个Framework能不能用呢?当然可以!!
有了这个Framework我们完全不需要在podfile中添加pod "AFNetworking", "~> 3.1.0"
了,你需要把这个文件copy到你的工程中,再添加到工程中就能用AFNetwork了!!就像我们在步骤2.3.2
中做的一样,添加Framework即可
这样你就可以使用了,而且注意,我们看不到源码了,如下图,全部都是.h文件,一个.m文件都没了。这不就是符合需求了吗
我们只需要在步骤3.5.1
创建代码库的时候不要去开源网站即可,我们把代码上传到私有git上即可,然后我们把cocoapod自动生成的Framework发布出去就行了。