CocoaPods原理 及 组件化

·  阅读 2003

 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仓库

  1. 在git服务器上创建私有仓库

  2. 执行命令pod repo add添加私有仓库(HTSpecs是私有仓库名称)

    $ pod repo add HTSpecs git@192.168.80.136:IOS/HTSpecs.git

  3. 执行命令pod repo list检查是否添加成功

    $ pod repo list

创建私有Pod组件库

  1. 创建模板项目

    pod lib create 项目名
    复制代码

    第一个问题是我们需要选择的开发语言,这里我们选择ObjC

    第二个问题是询问是否包含一个Demo项目,一般会选择Yes,其他问题根据实际情况选择。

  2. 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…

  1. 把需要导入的文件导入工程中,导入位置要与podspecs.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  
    复制代码
  2. 使用 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'
复制代码

  1. 把当前项目的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 方案

MGJRouter

#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内部也可以修改外部变量的值了。

分类:
iOS
标签:
分类:
iOS
标签: