目录
1 前戏
1.1 背景
1.2 回顾CocoaPod的使用
2 让你的SDK支持pod接入(编写你的.podspec)
2.1 野蛮版
2.2 优雅版
3 常见问题
3.1 SDK方面
3.1.1 如何编写.podspec文件可以让我的SDK在pod后呈现出目录结构?
3.1.2 SDK依赖第三方pod时,针对第三方pod头文件的包含怎样写?
3.1.3 SDK中的资源(图片,配置文件等)如何调取?
3.1.4 如何验证我的.podspec文件?
3.1.5 pod工程有建议的模板么?
3.2 接入问题(Podfile)
3.2.1 Podfile的常见结构是怎样的?
3.2.2 引入一个Pod工程的Podfile配置代码有哪些写法?
3.2.3 Podfile常用的配置有那些?
1 前戏
1.1 背景
做过几年开发的小伙伴都会懂得“封装”这个词在程序开发过程中的重要性,而极致的封装,无非就是生产一个SDK(Software Development Kit)了,即将为了实现某一功能概念的代码整合成一个工具包。
基于iOS,至少目前看来,集成一个SDK最理想的方式(没有之一)就是CocoaPod了。我们当然要想让自己做的SDK可以支持CocoaPod,但“支持”就意味着“工作”,程序员的“工作”就意味着“踩坑”。
这篇文章即从应用的角度教你怎样让你的SDK快速支持CocoaPod!并附赠一些常见的“填坑”操作,行动起来吧!
1.2 回顾CocoaPod的使用
看图,回顾下我们用烂的pod
相信Podfile大家都已经很熟悉了,这边我只想让大家看到第4步【.podspec】这个文件,没错,它就是让我们的SDK支持CocoaPod的关键所在!
Podfile:工程通过CocoaPod方式集成SDK时需要创建的文件,内容包括:系统版本支持信息,pod编译配置,具体要集成的SDK信息等。
.podspec:SDK支持CocoaPod方式接入所需要编写的配置文件,内容包括:SDK工程的基本信息,SDK打包的源文件、资源文件信息,SDK所依赖的库信息,pod后的目录结构信息等。
2 让你的SDK支持pod接入(编写你的.podspec)
2.1 野蛮版
我们名为“Test”的SDK的pod支持配置文件就命名为Test.podspec,而且位置就放在Test的git工程的根目录中好了!
野蛮版的话,Test.podspec内容可以这样写:
Pod::Spec.new do |s|
s.name = "Test" #必填
s.version = "0.0.1" #必填
s.summary = "" #非功能信息,自己玩的话,可以写得很随意,如:“123”
s.description = "" #非功能信息,自己玩的话,可以写得很随意,如:“123”
s.homepage = "" #非功能信息,没啥写的话,就填你的工程地址吧,如:"http://git.nonobank.com/yangyifan/useframeworktest"
s.license = "MIT" #必填,保持"MIT"就好
s.author = #建议填写,SDK作者,填写如:{ "Chris" => "yangyifan@nonobank.com" }
s.platform = :ios #必填,保持:ios就好(Android一般也不用pod……)
s.ios.deployment_target = '7.0' #必填,ios的最低系统版本,你可以改成"8.0"
s.source = #建议填写,工程位置,一般如:{ :git => "git@git.nonobank.com:yangyifan/useframeworktest.git" }
#SDK源文件,暴力方式就是将所有的源文件(比如.h.m)都在此罗列出来,不同的目录集间使用逗号分开
s.source_files = "Test/Classes/**/*.{h,m}","Test/Classes1/**/*.{h,m}"
#SDK公开头文件,暴力方式就是将所有的头文件(.h)都在此罗列出来,不同的目录集间使用逗号分开
s.public_header_files = "Test/Class/**/*.h","Test/Classes1/**/*.h"
#[没有就不写]SDK的资源文件,暴力方式就是将所有的资源文件(如图片,配置文件等)都在此罗列出来,不同的目录集间使用逗号分开(没有资源文件
s.resources = "Test/Resource/TBoundle.bundle"
#[没有就不写]SDK的依赖,暴力方式就是将所有的依赖都在这边写出来,依赖一般每个一行
s.dependency 'AFNetworking', '>= 3.1.0'
s.dependency 'Qiniu', '>= 7.1'
end
为什么说野蛮呢?因为它被集成后不分目录,看不出结构(如图,Masonry不要打我……)
当然,这么做无可厚非,因为SDK的开发者没有为使用者提供阅读代码方便性的显性义务。
2.2 优雅版
所谓优雅,最直观的展示便是拥有“结构”,分清内外(有对外头文件)
然而,想要呈现出美好的结构,可不如现象展示上那么简单,我们需要:
1) 思路:框架要求分清概念层次与模块,而且下层模块不能对上层模块有任何依赖(存在相互依赖的状况时,针对podspec的检查是无法通过的)
2) 过程:收集需求-->整合需求-->搭建框架-->反复“检验概念&修整框架”-->确认结构
3) 接口:确认你的对外头文件,并是不SDK所有的函数、变量都期望对外公开的哦
(那样会误导使用者并让使用者困扰,也会降低SDK的健壮性)
什么是分层,什么是设计,下图或许可以给大家一些参考
所以,如上图,相比“野蛮”,所谓“优雅”,关键是要在设计上花费一些心思。更重要的,如果你的SDK会进行版本迭代和维护的话,“野蛮”的方式最终必然会成为阑尾工程。
注:
记得一切从实际出发,如果你的SDK工程只有3、5个文件,再设计数日分个3级文件目录显然是没有必要的,显然没有哪个正常人会认为一个文件目录有三五个文件不是优雅的方式。
3 常见问题
3.1 SDK方面
3.1.1 如何编写.podspec文件可以让我的SDK在pod后呈现出目录结构?
针对上面这张已经第三次抛出来的图,跟省略了细节的下面的.podspec文件代码想比对,相信你可以一一对应起来并有一些收获
Pod::Spec.new do |s|
s.name = "..."
s.version = "..."
...
s.source_files = "MZLog/Classes/MZLog.h"
s.public_header_files = "MZLog/Classes/MZLog.h"
s.subspec 'Manager' do |ss|
ss.source_files = "MZLog/Classes/Manager/**/*"
ss.public_header_files = "MZLog/Classes/Manager/*.h"
ss.dependency 'MZLog/AutoEvent'
ss.dependency 'MZLog/ToolkitSenior'
ss.dependency 'MZLog/ToolkitJunior'
ss.dependency 'MZLog/Support'
end
s.subspec 'AutoEvent' do |ss|
...
end
s.subspec 'ToolkitSenior' do |ss|
...
end
s.subspec 'ToolkitJunior' do |ss|
ss.source_files = "MZLog/Classes/ToolkitJunior/MZLogToolkitJuniorInterface.h"
ss.public_header_files = "MZLog/Classes/ToolkitJunior/MZLogToolkitJuniorInterface.h"
ss.dependency 'MZLog/Support'
ss.dependency 'MZLog/ThirdPart'
ss.subspec 'ModelTransfer' do |sss|
...
end
ss.subspec 'ModelCreater' do |sss|
...
end
ss.subspec 'Config' do |sss|
...
end
ss.subspec 'Uploaders' do |sss|
...
end
ss.subspec 'ExtenedInfo' do |sss|
...
end
ss.subspec 'RouterManager' do |sss|
...
end
ss.subspec 'ViewPath' do |sss|
...
end
ss.subspec 'LogModels' do |sss|
...
end
end
s.subspec 'Support' do |ss|
...
end
s.subspec 'ThirdPart' do |ss|
...
end
下面是针对上面代码的一些说明Tip:
- 可以通过subspec关键字创建子级目录
- do 后面的 "ss" 就是子目录的标识,你当然可以任性地起别的名字
- 每层目录中一般都要指定source_files(源文件),public_header_files(该目录或说模块的对外公开头文件)
- source_files所参照的根目录即.podspec文件所在的目录(如下图)
- 如果你的SDK中的上层模块要依赖下层模块的支持,要使用.dependency关键字,但它后面定义的“目录”(比如“MZLog/Support”是你的模块名目录,而不是物理文件目录哦!(进行一下关键字搜索,可以理解得更清晰)
3.1.2 SDK依赖第三方pod时,针对第三方pod头文件的包含怎样写?
针对问题场景,以AFNetworking举例,我们常见的头文件引入方式有:
#import <AFNetworking/AFNetworking.h> /* 推荐!!!*/
#import <AFNetworking.h>
#import "AFNetworking.h"
这边我们会推荐使用 #import <AFNetworking/AFNetworking.h>
原因:
1) 非“本土资源”,尖括号当然是不二之选。
2)如果使用你SDK的工程在Podfile中加入了use_framework!选项,那么使用#import <AFNetworking.h>会报错。
3.1.3 SDK中的资源(图片,配置文件等)如何调取?
理想状况下,只要正确配饰了.podspec文件的resources,你都可以用下面的方式简单粗暴地获取资源文件。
[[NSBundle mainBundle] pathForResource:@"mzLogStaticConfigMap_iOS" ofType:@"json"];
[UIImage imageNamed:@"MZLogFloater.bundle/mzLogFloater_log"]
[UIImage imageNamed:@"mzLogFloater_log.png"]
但是上面方式的最大弊端就是要求:相关工程进行pod install后,SDK的相关资源必须被加载到mainBundle中。而这个期望,在相关工程的Podfile中加入use_framework!选项后完全打碎……
use_framework! 让被pod的工程以framework动态链接库的方式接入(即相关的资源被打包在了对应的SDK的framework包中,而不在进行pod install的工程的mainBundle中),所以,这时候,用上面的资源接入方式,SDK的资源找不到了!
解决这个问题的根本思路是找到资源所在的bundle!
/* 1. 选择一个SDK工程中的类(因为它一定是在你SDK的bundle中的)*/
Class curClass = NSClassFromString(@"MZLogFloaterManager");
/* 2. 获取SDK的bundle对象 */
NSBundle *curBundle = [NSBundle bundleForClass:curClass];
/* 3. 通过pathForResource:ofType:方法找到你要的资源文件目录 */
NSString *floaterBundlePath = [curBundle pathForResource:@"MZLogFloater" ofType:@"bundle"];
/* 4.
* 如果你的资源是一个图片,直接参考下第6步
* 如果你的资源还是一个bundle,用bundleWithPath:获取这个对象吧(如该行代码)
* 如果你的资源是一个要读取的文件,目录都有了你还能没法玩儿么?(亦可以直接跳过4、5、6步)
*/
NSBundle *floaterBundle = [NSBundle bundleWithPath:floaterBundlePath];
/* 5. 跟第三步相同了,只是因为之前的目标资源还是一个bundle */
NSString *imagePath = [floaterBundle pathForResource:imageName ofType:@"png"];
/* 6. 通过imageWIthContentsOfFile:可以通过路径获取一个图片的UIImage对象 */
UIImage *tmpImage = [UIImage imageWithContentsOfFile:imagePath];
3.1.4 如何验证我的.podspec文件?
只要在terminal中进入.podspec文件所在的目录,键入如下命令,然后就根据提示修改你的.podspec文件吧!(我不信你可以一次通关哦!warning酌情可以忽略,error“最好”解决掉)
选择1 : pod lib lint
说明:podspec基于本地的检查,我更常用这个,毕竟将代码推到远端前要保证你推的代码是经过测试的嘛!
选择2:pod spec lint
说明:功能和pod lib lint是相似的,不过是基于远端的检查
3.1.5 pod工程有建议的模板么?
从terminal进入一个空的文件夹执行下下面的代码试试吧。
pod lib create xxxxxx
说明:xxxxxx是你的SDK工程名称,如AFNetworking(效果如下图)
当然,这种结构只是一个参考,并不是强制要求的。
因为本质上,pod支持只是通过pod的命令找到目标代码、资源文件并将其打包,如果你的SDK早已有自己的结构,大可不必重新修改你自己的文件结构
3.2 接入问题(Podfile)
3.2.1 Podfile的常见结构是怎样的?
直接上代码~~
#指定工程支持的最低版本
platform :ios, '8.0'
#全局参数配置选项(一般不需要特殊设置)
use_frameworks!
#指定工程的Target,其内部每行引入一个Pod工程(SDK)
target 'Test' do
pod 'MZLog', :git => 'http://git.nonobank.com/yangyifan/mzapplog.git', :commit => '56757bb', :inhibit_warnings => true
pod 'MJRefresh', '3.1.12'
pod 'Test', :git => 'http://git.nonobank.com/yangyifan/useframeworktest.git', :commit => '191ad53', :inhibit_warnings => true
end
3.2.2 引入一个Pod工程的Podfile配置代码有哪些写法?
这边只做常见的举例,不保证全面,亦不做深入分析~
# 引入一个Pod工程的最新版本
pod 'SDWebImage'
# 引入一个Pod工程的指定版本
pod 'MJRefresh', '3.1.12'
# 指定工程地址和提交号,引入一个Pod工程的指定版本,一般企业内部小范围封装的SDK比较常用
pod 'MZLog', :git => 'http://git.nonobank.com/yangyifan/mzapplog.git', :commit => '56757bb'
3.2.3 Podfile常用的配置有那些?
#全局配置,让被pod 的工程以framework动态链接库的方式接入,只支持iOS8.0+,含swift的工程必用该选项
use_frameworks!
#忽略所有pod工程的警告,绝对金牌配置!
inhibit_all_warnings!