2018-04-04 更新:添加 pod lib create 的方式建立 framework,推荐使用该方式。
Xcode: 9.3
Swift: 4.1
CocoaPod: 1.4.0
复制代码
本文将主要讨论以下几个问题:html
最近公司因为和其余公司创建了各类合做关系,“建立 SDK”工做被提上了日程,因为以前没有本身作过,在生成 framework 时踩了一些坑,在这里记录下来以作总结,若码友能用得上则不胜开心。 工做中不免会遇到这种状况,想把某个功能包装起来给其余人用,可是出于某种缘由又不想公开本身的实现方式,这时就须要静态库(framework 或 .a)了。(PS:.a 和 .framework 的区别能够看下这篇文章:iOS开发-.a与.framework区别?ios
XCode -> File -> New -> Project -> Cocoa Touch Framework
复制代码
建立 SJTutorialSDK,并新建测试类名为 HelloWorld(Swift)、HelloOC(OC),目录以下:git
├── SJTutorialSDK
│ ├── HelloOC.h -> 暴露的 OC 类
│ ├── HelloOC.m
│ ├── HelloWorld.swift -> 暴露的 Swift 类
│ ├── Info.plist
│ └── SJTutorialSDK.h -> 当前静态库头文件
└── SJTutorialSDK.xcodeproj
├── project.pbxproj
├── project.xcworkspace
复制代码
建立完成后的头文件以下:github
// SJTutorialSDK.h
// SJTutorialSDK
#import <UIKit/UIKit.h>
FOUNDATION_EXPORT double SJTutorialSDKVersionNumber;
FOUNDATION_EXPORT const unsigned char SJTutorialSDKVersionString[];
// 第三方库的头文件
#import <SJDemoSDK/Animals.h>
// 对外暴露的头文件(仅限于 OC,Swift 添加 public 后会自动导入)
#import <SJTutorialSDK/HelloOC.h>
复制代码
Build Phases 配置以下: swift
Build Settings 中 Mach-o Type 设置为 Static Library。 至此,静态库.framework 建立完毕。xcode
咱们开发过程当中常常提到的 arm64,x86_64 具体是什么东西呢?这里能够参考这篇文章: iOS 中的 armv7,armv7s,arm64,i386,x86_64 都是什么。 简单点来讲:模拟器 32/64 位处理器分别须要 i386/x86_64 架构,真机 32/64 位处理器分别须要 armv7(armv7s)/arm64 架构。 因此,若是你构建的静态库只须要支持 iPhone 5S 及以上(或 iPad mini2 及以上)的真机和模拟器,那么你的静态库将只需支持 arm64 和 x86_64 架构。bash
1.调整到 Release(发布)模式:Edit Scheme -> Run -> Info -> Build Configuration -> Release; 架构
2.分别使用真机、模拟器编译,生成对应的 SJTutorialSDK.framework;app
3.合并 frameworks框架
# 查看静态库支持的架构
lipo -info xxx
# 合并 xxx1 和 xxx2,最后的文件支持二者支持的全部架构
lipo -create xxx1 xxx2 -output xxx1
eg:
lipo -creat Release-iphoneos/SJTutorialSDK.framework/SJTutorialSDK Release-iphonesimulator/SJTutorialSDK.framework/SJTutorialSDK -output Release-iphoneos/SJTutorialSDK.framework/SJTutorialSDK
复制代码
生成 framework 时踩过的坑:-output 的文件是 xxx,而不是 xxx.framework
Architectures in the fat file: /Users/shijian/Desktop/SJTutorialSDK.framework/SJTutorialSDK are: x86_64 arm64
# congratulations
复制代码
在 iOS 中,能够经过 Bundle 文件管理资源文件(图片,语音,视频,plist,xib,storyboard等),Bundle 文件实际上就是个普通的文件夹,只是在名字中添加了 .Bundle 的后缀而已。 对图片的命名最好添加上 @3x/@2x,这样系统会自动放在对应的位置,不须要咱们额外的操做。
.
└── SJTutorialRes.Bundle
├── alipay@3x.png
└── wechat@3x.png
复制代码
.
├── Base.lproj
│ ├── LaunchScreen.nib
│ └── Main.storyboardc
│ ├── Info.plist
│ ├── UIViewController-vXZ-lx-hvc.nib
│ └── vXZ-lx-hvc-view-kh9-bI-dsS.nib
├── Frameworks
│ ├── HHMedicSDK.framework
│ │ ├── ControlView.nib
│ │ ├── HHMedicSDK
│ │ ├── HHPB.bundle
│ │ │ ├── HHCameraCancleCap@2x.png
│ │ │ ├── camear_del_btn_normal@2x.png
│ │ ├── HMSDK.bundle
│ │ │ ├── angle-mask.png
│ │ │ └── success@3x.png
│ │ ├── Info.plist
│ └── libswiftsimd.dylib
├── HHMedicSDK_Example
├── Info.plist
├── PkgInfo
├── _CodeSignature
│ └── CodeResources
└── libswiftRemoteMirror.dylib
复制代码
注:本文中的 bundle 指自建的资源文件, Bundle 指代 swift 的类(对应 OC 中的 NSBundle)。 咱们能够知道 Bundle.main 指代当前根目录,那么如何获取 camear_del_btn_normal@2x.png 这张图片呢?经过路径咱们能够知道其路径为 ./Frameworks/HHMedicSDK.framework/HHPB.bundle/camear_del_btn_normal@2x.png。那么一种简单的获取方式便明了了:Bundle.main.url(forResource: "Frameworks/HHMedicSDK.framework/HHPB.bundle/camear_del_btn_normal@2x", withExtension: "png"),固然也能够经过层层的 urlForResoure 来读取。
转过来再想一想,获取 bundle 的方式就简单了。咱们能够事先不用苦苦思考资源 bundle 的具体生成位置,先 debug 获取 app,而后再打开 app 查看各个 Bundle 对应位置,最后就能够再回过来重写键入获取方式,大功告成。
class HHResManager {
static func getImg(_ name: String) -> UIImage? {
let url = Bundle.main.url(forResource: "Frameworks/HHMedicSDK", withExtension: "framework") ?? URL(fileURLWithPath: "")
let bundle = Bundle(url: url)?.url(forResource: "HMSDK", withExtension: "bundle") ?? URL(fileURLWithPath: "")
return UIImage(named: name, in: Bundle(url: bundle), compatibleWith: nil)
}
static func xibBundle() -> Bundle {
let apath = Bundle.main.path(forResource: "Frameworks/HHMedicSDK", ofType: "framework")!
return Bundle(path: apath)!
}
static func getAudio(_ name: String) -> URL? {
let url = Bundle.main.url(forResource: "Frameworks/HHMedicSDK", withExtension: "framework") ?? URL(fileURLWithPath: "")
guard let bundle = Bundle(url: url)?.url(forResource: "HMSDK", withExtension: "bundle") else { return nil }
guard let path = Bundle(url: bundle)?.path(forResource: name, ofType: "mp3") else { return nil }
return URL(string: path)
}
}
复制代码
至此,当前建立的静态库 SJTutorialSDK 有以下特性:支持 OC 和 Swift 混编,引用了其余第三方库,支持资源文件读取,支持多种架构。
若是本身的静态库是私有的,能够跳过 trunk 环节,直接在本身的代码仓库中建立好仓库,而后配置相应的 podspec 文件,pod 引用时指定对应的路径便可。但大多数状况下仍是要给其余人使用的,咱们固然也能够不经过 trunk,直接在引用时指定地址来使用当前仓库,可是这样用起来老是不那么直观。
# 经过名称和指定地址使用
pod 'SJTutorialSDK',:git => "git@code.XXX/SJTutorialSDK.git"
# 经过名称
pod 'SJTutorialSDK'
复制代码
二者对比起来,高下立判。因此建议仍是经过 trunk 来 push 本身的代码。
如何在 github 上新建仓库,网上已经有不少的教程,这里再也不赘述,这里主要谈谈配置 podspec 文件。
podSpec 官方解释: A Podspec, or Spec, describes a version of a Pod library. ,其实就是一个描述 pod 库的信息(版本,依赖,做者,描述,系统库等)的文件。
Pod::Spec.new do |s|
s.name = "podSDK"
s.version = "0.0.1"
s.summary = "当前库的总结。"
s.description = <<-DESC
描述文件
DESC
s.homepage = "http://EXAMPLE/podSDK"
s.license = "MIT"
s.author = { "shmily" => "shmilyshijian@foxmail.com" }
s.platform = :ios, "9.0"
s.source = { :git => "https://github.com/515783034/podSDK.git", :tag => "#{s.version}" }
s.resources = "podSDK/Resources/*.*"
# 本库提供的framework静态库
s.vendored_frameworks = 'podSDK/Sources/*.framework'
#################
# 依赖的系统动态库
# s.frameworks = "SomeFramework", "AnotherFramework"
# 依赖的系统静态库
# s.libraries = "iconv", "z"
# 本库提供的 .a 静态库
#s.vendored_libraries = 'podSDK/Sources/*.a'
# 本库添加的第三方依赖库
#s.dependency "SJLineRefresh", "~> 1.4"
end
复制代码
关于 podspec 的内容,能够查看我以前写过的文章:建立公共/私有pod --podspec,这里也再也不过多赘述。 因为当前仓库只是引用了几个静态库,因此 sources 能够不配置,只须要配置 vendored_frameworks 便可。
pod lib lint
# 若是验证中有警告,能够添加参数 --allow-warnings 能够忽略
复制代码
pod trunk push SJTutorialSDK.podspec
# 忽略警告的方式同上
复制代码
git tag -m "desc" 1.0.0
git push --tag
# podspec 中修改对应的 s.version
复制代码
上传成功,enjoy
🎉 Congrats
🚀 podSDK (0.0.1) successfully published
📅 March 19th, 05:04
🌎 https://cocoapods.org/pods/podSDK
👍 Tell your friends!
复制代码
上面讲到咱们经过 Cocoa Touch Framework 建立 framework,而后再上传到 CocoaPod 供别人使用,可是在使用这种方式的过程当中遇到了几个问题。 使用 Xcode 建立 framework,为了测试咱们固然能够经过在当前工程的基础上再 File -> New -> Target -> Single View App 对 framework 进行测试,可是在实际使用中遇到了几个问题,经过拖拽 framework 的方式与 CocoaPod 管理的方式有些出入,会致使部分功能有差别,好比上面提到的 Bundle 问题,在两者表现上彻底不同,再好比在某些 Build Settings 的设置上也会有一些差异,总之这不是个友好的方式。 既然使用了 CocoaPod 管理 framework,那么索性也用 CocoaPod 来"生成" Framework 吧,都是同一套东西,用起来也不会有各类各样匪夷所思的问题。
官方 pod lib create 命令使用详解:Using Pod Lib Create 使用起来很是简单,只须要 经过 pod lib create xxxx
而后选择相关配置便可。
What platform do you want to use?? [ iOS / macOS ] (选择平台)
>
ios
What language do you want to use?? [ Swift / ObjC ] (选择语言)
>
swift
Would you like to include a demo application with your library? [ Yes / No ] (是否包含 demo)
>
yes
Which testing frameworks will you use? [ Quick / None ] (测试框架)
> None
Would you like to do view based testing? [ Yes / No ]
> No
Running pod install on your new library.
复制代码
目录大体以下(其中篇幅缘由部分不相关目录已移除):
.
├── Example
│ ├── Podfile
│ ├── Podfile.lock
│ ├── Pods
│ │ ├── Headers
│ │ ├── Local Podspecs
│ │ ├── Manifest.lock
│ │ ├── Pods.xcodeproj
│ │ │ ├── project.pbxproj
│ │ └── Target Support Files
│ ├── SJPlcSDK
│ │ ├── AppDelegate.swift
│ │ ├── Base.lproj
│ │ │ ├── LaunchScreen.xib
│ │ │ └── Main.storyboard
│ │ ├── Info.plist
│ │ └── ViewController.swift
│ ├── SJPlcSDK.xcodeproj
│ │ ├── project.pbxproj
│ │ ├── project.xcworkspace
│ ├── SJPlcSDK.xcworkspace
├── LICENSE
├── README.md
├── SJPlcSDK (源代码和资源等)
│ ├── Assets
│ └── Classes
│ └── ReplaceMe.swift
├── SJPlcSDK.podspec (仓库配置文件)
└── _Pods.xcodeproj
复制代码
添加代码后,配置 podspec 文件(上文已给出,不赘述),可打开 ./Example/SJPlcSDK.xcworkspace 进行测试。
可在 ./Pods -> targets -> SJPlcSDK -> Build Settings 下进行 framework 的相关配置。
lipo -create -output
命令合并便可(详细操做请看上文)。
有了 framework 和相关资源文件后,发布和上面相同。提交代码,打 tag,pod trunk push。
静态库的建立并无太复杂的操做,主要步骤以下: