1. CocoaPods 原理
CocoaPods是iOS项目的依赖管理工具,该项目源码在Github上管理。
CocoaPods的原理是将所有的依赖库都放到另一个名为Pods的项目中,然后让主项目依赖Pods项目,这样,源码管理工作都从主项目移到了Pods项目中。Pods项目最终会编译成一个名为libPods.a的文件,主项目只需要依赖这个.a文件即可。
-
运行pre-install hook
-
生成Pod Project
-
将该pod文件添加到工程中
-
添加对应的framework、.a库、bundle等
-
链接头文件,生成Target
-
运行post-install hook
-
生成podfile.lock ,之后生成文件副本mainfest.lock并将其放在Pod文件夹内。(如果出现 The sandbox is not sync with the podfile.lock这种错误,则表示manifest.lock和podfile.lock文件不一致),此时一般需要重新运行pod install命令。
-
配置原有的project文件(add build phase)
-
添加了
Embed Pods Frameworks -
添加了
Copy Pod Resources
其中,pre-install hook和post-install hook可以理解成回调函数,是在podfile里对于install之前或者之后(生成工程但是还没写入磁盘)可以执行的逻辑,逻辑为:pre_install do |installer| # 做一些安装之前的hook end
post_install do |installer| # 做一些安装之后的hook end
注意:pod install优先遵循 Podfile 里指定的版本信息,其次遵循 Podfile.lock 里指定的版本信息来安装对应的依赖库。
-
对比本地Pod和podfile.lock文件中的版本,如果不一致会提示存在风险。 -
对比podfile是否发生了变化,add/remove pod依赖
如果存在 ,会生成两个列表,一个是需要add的pods,一个是需要remove的pods。 -
如果存在remove的,删除remove的pods(会删除podfile.lock里的版本依赖)。
-
添加需要的pods依赖
此时,如果是常规的CocoaPods库(基于git),会先去:
Spec下查找对应的Pods文件夹
找到对应的tag
找到对应tag下面的podspec文件
git clone下来代码并copy到Pod目录下 -
目录结构.png·
-
运行pre-install hook
-
生成Pod Project
-
将该pod文件添加到工程中
-
添加对应的framework、.a库、bundle等
-
链接头文件,生成Target
-
运行post-install hook
-
生成podfile.lock ,之后生成文件副本mainfest.lock并将其放在Pod文件夹内。(如果出现 The sandbox is not sync with the podfile.lock这种错误,则表示manifest.lock和podfile.lock文件不一致),此时一般需要重新运行pod install命令。
-
配置原有的project文件(add build phase)
-
添加了
Embed Pods Frameworks -
添加了
Copy Pod Resources
其中,pre-install hook和post-install hook可以理解成回调函数,是在podfile里对于install之前或者之后(生成工程但是还没写入磁盘)可以执行的逻辑,逻辑为:pre_install do |installer| # 做一些安装之前的hook end
post_install do |installer| # 做一些安装之后的hook end
注意:pod install优先遵循 Podfile 里指定的版本信息,其次遵循 Podfile.lock 里指定的版本信息来安装对应的依赖库。
3. 让自己的框架支持cocoapods
那么我们从第一步开始,一步一步来使自己的框架支持cocoapods安装(关于如何在自己的电脑上安装cocoapods的,就请自行搜索,教程很多)。
首先概括一个大概的步骤
- 代码上传到github
- 创建podspec文件
- 在github上创建release版本
- 注册cocoapods账号
- 上传代码到cocoapods
- 检验是否上传成功
- 更新框架版本
重点
解决cocoapods下载框架中
-
xib初始化crash的问题
-
图片无法正常显示的问题
解决cocoapods下载框架中xib初始化crash的问题
由于框架中使用到了UITableView及UICollectionView等UI,所以有存在对应的cell的xib文件。
我们通过cocoapods去下载我们的框架,如果存在xib,那么我们平常初始化xib的代码(如下)便不能正常工作了。[[[NSBundle mainBundle] loadNibNamed:@"xibName" owner:self options:nil] lastObject]; [self.collectionView registerNib:[UINib nibWithNibName:@"xibName" bundle:nil] forCellWithReuseIdentifier:@"ZLCollectionCell"];因为通过mainBundle不能获取到我们的xib了,解决方法如下
#define kZLPhotoBrowserBundle [NSBundle bundleForClass:[self class]] [[kZLPhotoBrowserBundle loadNibNamed:@"ZLPhotoActionSheet" owner:self options:nil] lastObject]; [self.collectionView registerNib:[UINib nibWithNibName:@"ZLCollectionCell" bundle:kZLPhotoBrowserBundle] forCellWithReuseIdentifier:@"ZLCollectionCell"];这样之后,无论是通过copy文件夹方式还是cocoapods下载安装的方式,都能正常使用xib进行初始化了
解决cocoapods下载框架中图片无法正常显示的问题
解决完了xib的初始化问题,图片资源不显示又是一个令人头痛的问题。我在测试期间,得出如下结果:
- 通过cocoapods下载安装,如果xib中直接填写好的图片,则图片资源能直接显示,如果通过代码"[UIImage imageNamed:@""]"去设置的话,则图片资源根本显示不了
最终解决方法:
-
创建bundle资源目录
command+N -> Resource -> Settings Bundle
删除bundle携带的无用文件,把图片资源添加到bundle资源内,
-
改变代码图片路径
// 图片路径 #define kZLPhotoBrowserSrcName(file) [@"ZLPhotoBrowser.bundle" stringByAppendingPathComponent:file] #define kZLPhotoBrowserFrameworkSrcName(file) [@"Frameworks/ZLPhotoBrowser.framework/ZLPhotoBrowser.bundle" stringByAppendingPathComponent:file]
-
kZLPhotoBrowserSrcName(file) 为通过copy文件夹方式获取图片路径的宏
-
kZLPhotoBrowserFrameworkSrcName(file) 为通过cocoapods下载安装获取图片路径的宏
之后修改代码中设置图片的方式如下
UIImage *img = [UIImage imageNamed:kZLPhotoBrowserSrcName(@"img.png")]?:[UIImage imageNamed:kZLPhotoBrowserFrameworkSrcName(@"img.png")];到了这一步,ok,很好,问题已经解决。
-
。
。 -
-
4. 使用CocoaPods进行组件化
前段时间借助了CocoaPods进行组件化开发,现在总结下操作步骤和遇到的问题,以供以后使用,借助Carthage进行组件化实际原理和步骤是相同的。
创建私有Pod仓库
-
在git服务器上创建私有仓库
-
执行命令
pod repo add添加私有仓库(HTSpecs是私有仓库名称)$ pod repo add HTSpecs git@192.168.80.136:IOS/HTSpecs.git
-
执行命令
pod repo list检查是否添加成功$ pod repo list
创建私有Pod组件库
-
创建模板项目
pod lib create 项目名第一个问题是我们需要选择的开发语言,这里我们选择
ObjC;第二个问题是询问是否包含一个Demo项目,一般会选择
Yes,其他问题根据实际情况选择。 -
Pod会自动在当前目录生成一个项目,配置项目中的
podspec文件s.version表示的是当前类库的版本号;//要和tag值对应s.source表示当前类库远程地址;s.sources_files表示类库的源文件存放目录;s.resource_bundles表示资源文件存放目录;s.frameworks表示类库依赖的framework;s.static_framework = true把当前项目内的framework编译成静态库,如果项目中包含其他闭源静态库需要加上此句;s.dependency表示依赖的第三方类库;s.requires_arc = true,如果项目依赖了第三方类库,必须加上此句;
其他字段请参考guides.cocoapods.org/syntax/pods…
-
把需要导入的文件导入工程中,导入位置要与
podspec中s.sources_files配置的地址对应导入完成后执行git命令
只执行一次,确认当前远程地址是否正确 1. 添加远程仓库 git remote add origin 仓库地址 // 仓库地址与.podspec文件中的s.source对应 2.提交修改的文件并打tag git add . git commit -a -m '修改内容' git tag -a tag版本号 -m '修改内容' // tag版本号和podspec配置文件里的s.version保持一致 3.推送修改的分支和tag到远程仓库 git push -u origin master git push --tags -
使用
pod lib lint验证类库是否符合 pod 的要求,可以使用--allow-warnings来忽略一些警告,建议在打 tag 前运行此命令,检查自己写的pod是否报错,在push到远程Specs的时候(步骤5),会自动运行此步骤。
如果项目依赖了私有库,需要加上--sources指定依赖库的所属 specs 地址,如下:
pod lib lint 项目名.podspec --allow-warnings --sources='git@192.168.80.136:IOS/HTSpecs.git,https://github.com/CocoaPods/Specs'
-
把当前项目的
podspec文件push到私有源中pod repo push HTSpecs YourPod.podspec --allow-warnings -- sources='git@192.168.80.136:IOS/HTSpecs.git,github.com/CocoaPods/S…'
提交成功后gitlab里的私有仓库能看到上传的项目配置文件
之后提交新版本时按以下步骤进行:
检查是否可以编译成功(pod lib lint)-> 提交并打上对应的 tag -> 推送到对应的 spec(pod repo push)
使用私有库
在主项目里Podfile文件里添加需要引用私有源地址:
source 'git@192.168.80.136:IOS/HTSpecs.git'
source 'https://github.com/CocoaPods/Specs.git'
使用下面命令可以指定引入第三方的远程仓库地址:
pod 'HTNetwork',:git => 'git@192.168.80.136:IOS/HTNetwork.git'
编码注意
为避免提供给Pod外部时,出现引入错误,Pod中引用和继承第三方库需要按以下方式进行导入:
#if __has_include(<AFNetworking/AFNetworking.h>)
#import <AFNetworking/AFNetworking.h>
#else
#import "AFNetworking.h"
#endif
先用宏检查是否存在以第三方库形式导入的,是的话以此方式导入,不是的话以直接引入源码的方式导入。
。
-
-
-
4. Cocoapod 组件化之中间件通信
app内部的路由设计主要两种思路: target-action 和 url-scheme 方案
一. target-action 方案 (推荐)
CTMediator
该方案借助OC的runtime特性,通过实现了服务的自动发现,无需注册即可实现组件间调用。优于url-scheme方案,所以重点讲一下CTMediator及其使用。
组件化之前各组件页面通过import调用,互相侵入,耦合严重
组件化前.png
组件化之后,把组件具体的方法都写在Category里,调用的方式都是performTarget: action: params: shouldCacheTarget:方法,最终去掉了中间件Mediator对组件的依赖,各个组件之间互相不再依赖,组件间调用只依赖中间者Mediator,Mediator不依赖其他任何组件。
组件化后.png
CTMediator实现流程如下图
组件化架构方案.png
二. url-scheme 方案
#import typedef void (^componentBlock) (NSDictionary *param);
@interface URL_Roueter : NSObject
+ (instancetype)sharedInstance;
- (void)registerURLPattern:(NSString *)urlPattern toHandler:(componentBlock)blk;
- (void)openURL:(NSString *)url withParam:(id)param;
@end
====================
#import "URL_Roueter.h"
@interface URL_Roueter()
@property (nonatomic, strong) NSMutableDictionary *cache;
@end
@implementation URL_Roueter
+ (instancetype)sharedInstance
{
static URL_Roueter *router;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
router = [[URL_Roueter alloc] init];
});
return router;
}
-(NSMutableDictionary *)cache{
if (!_cache) {
_cache = [NSMutableDictionary new];
}
return _cache;
}
- (void)registerURLPattern:(NSString *)urlPattern toHandler:(componentBlock)blk {
[self.cache setObject:blk forKey:urlPattern];
}
- (void)openURL:(NSString *)url withParam:(id)param {
componentBlock blk = [self.cache objectForKey:url];
if (blk) blk(param);
}
@end
#import "A_VC.h"
#import "URL_Roueter.h"
@implementation A_VC
//把自己对外提供的服务(block)用url标记,注册到路由管理中心组件
+(void)load{
[[URL_Roueter sharedInstance]registerURLPattern:@``"test://A_Action"` `toHandler:^(NSDictionary* para) {`
NSString *para1 = para[@"para1"];
[[self new] action_A:para1];
}];
}
-(void)viewDidLoad{
[super viewDidLoad];
UIButton *btn = [UIButton new];
[btn setTitle:@"调用组件B" forState:UIControlStateNormal];
btn.frame = CGRectMake(100, 100, 100, 50);
[btn addTarget:self action:@selector(btn_click) forControlEvents:UIControlEventTouchUpInside];
[btn setBackgroundColor:[UIColor redColor]];
self.view.backgroundColor = [UIColor blueColor];
[self.view addSubview:btn];
}
//调用组件B的功能
-(void)btn_click{
[[URL_Roueter sharedInstance]openURL:@"test://B_Action" withParam:@{@"para1":@"PARA1", @"para2":@(222),@"para3":@(333),@"para4":@(444)}];
}
-(void)action_A:(NSString*)para1 {
NSLog(@"call action_A: %@",para1);
}
@end
组件B实现的代码类似.
实现原理很简单:每个组件在自己的load方面里面,把自己对外提供的服务(回调block)通过url-scheme标记好,然后注册到URL-Router里面。
URL-Router接受各个组件的注册,用字典保存了每个组件注册过来的url和对应的服务,只要其他组件调用了openURL方法,就会去这个字典里面根据url找到对应的block执行(也就是执行其他组件提供的服务)
4. __block的原理
Block不允许修改外部变量的值,这里所说的外部变量的值,指的是栈中指针指向的内存地址。__block所起到的作用就是只要观察到该变量被block持有,就将外部变量的地址指针从栈中转移到堆中。(栈是红灯区,堆是绿灯区)。进而在block内部也可以修改外部变量的值了。