iOS-组件化

538 阅读9分钟

1、介绍

  • 组件化将项目拆分成独立组件,使用特定通讯方式来解耦

1.1、组件化优点

  1. 解耦
  2. 模块复用
  3. 单元测试
  4. 功能拆分给不同组去开发

1.2、组件化分层

  • 一般项目的组件化分为业务层通用层基础层 image.png
  • 划分层时可能会用到UML整理关系
    • 实现关系虚线 + 空心箭头
    • 依赖关系虚线 + 实心箭头
    • 继承关系实线 + 空心箭头
    • 关联关系实线 + 实心箭头
      • 组合关系实心菱形表示,由关联主体指向被关联的部分菱形处于关联主体的位置;这种关联作用是一种强拥有关系,体现了严格的部分和整体的关系,部分和整体的生命周期一样(类似鸟与翅膀的关系,没有翅膀的鸟就不是鸟)
      • 聚合关系空心菱形表示,由关联主体指向被关联的部分,菱形处于关联主体的位置;这种关联作用是一种弱拥有关系,体现的是A对象可以包含B对象,但是B对象不是A对象的一部分(类似班级和学生,学生转学了这个班级照旧)

2、CocoaPods

  • CocoaPods 是第三方依赖库的管理工具,组件化可以将功能模块做成库,再通过CocoaPods引入进工程

2.1、CocoaPods本地索引库

  • 导入一个三方框架时,会在 本地CocoaPods的索引库 中进行查找,路径:.cocoapods隐藏文件夹 --> repos文件夹 --> 仓库名 --> Specs --> MD5加密文件名分类 --> 三方库 image.png

  • 三方库名称经过 MD5加密 会得到一串数,其中 前3个字符 就是所属的数字文件夹;试一下 YYKitimage.png image.png 再试一个AFNetworking image.png image.png

  • .podspec.json文件中,找到该框架在远程仓库的下载地址,从远程仓库将其导入
    image.png

  • CocoaPods流程图
    image.png

2.2、创建组件

  • 使用 CocoaPods,可分为 本地远程 两种方式搭建组件化工程
    • 本地:通过项目中创建模块,利用 CocoaPodsworkspec进行本地管理,不需要将项目上传 git,而是在项目的Podfile中指定目录
    • 远程:利用 CocoaPods 进行模块的远程管理,需要将项目上传 git;公司项目一般使用私有库
搭建本地组件化工程
  1. cd 到想创建模块的文件夹下

    pod lib create LZHomeModule
    
    -------------------------
    //对模块进行以下配置:
    
    //工程类型
    What platform do you want to use?? [ iOS / macOS ]
     > iOS
    
    //开发语言
    What language do you want to use?? [ Swift / ObjC ]
     > objc
    
    //创建App测试项目
    Would you like to include a demo application with your library? [ Yes / No ]
     > yes
    
    //提供frameworks的测试
    Which testing frameworks will you use? [ Specta / Kiwi / None ]
     > none
    
    //提供测试文件
    Would you like to do view based testing? [ Yes / No ]
     > no
    
    //设置前缀
    What is your class prefix?
     > LZ
    
    • 创建时可能遇到报错

      fatal: unable to access 'github.com/CocoaPods/p…': HTTP/2 stream 1 was not closed cleanly before end of the underlying stream

    • 解决方法:提示默认 通信协议 出错,更改默认通信协议:

      git config --global http.version HTTP/1.1

  2. 直接生成带workspace的工程 image.png

  3. 组件库功能代码 文件夹中路径是创建的 同名文件夹(LZHomeModule),工程中是Pods中的内容,Example 中的是测试用例 image.png image.png

  4. 替换 Replace.m 文件,实现业务代码 image.png

  5. 在测试用例Example目录下,执行pod install,将组件集成进来

    • 这里可能会报错:project 'path/to/Project.xcodeproj',原因是 没有指定工程目录

      image.png

    • 解决方法:在 podfile 文件中指定下工程目录就行了

      project '项目名.xcodeproj' image.png

    • Xcode会有反应,问你文件在外部被修改,是 Read From Disk 还是 Keep Xcode Version

  6. install成功后,工程中 Pods文件夹 中就能看到组件导入成功 image.png

2.3、组件与三方库的依赖

2.3.1、三方与本地组件的依赖
  1. 配置 组件依赖库.podspec文件Pods --> Development Pods --> 组件名 --> Pod --> LZHomeModule.podspec

  2. 在文件后边追加:s.dependency '库名'

  3. 在测试工程 Example 目录下,执行pod install,解决三方框架的依赖问题
    image.png

2.3.2、组件与下层本地组件的依赖
  • 除了依赖三方框架,我们的组件也会对下层的本地组件进行依赖,比如:分类宏定义
  1. 我们重复上边的步骤,再创建一个 LZCommonModule 基础组件,添加一个 Macro 文件,并引入几个三方库 image.png image.png

  2. 我们想要在 LZHomeModule 中使用 LZCommonModule 中的 Macro 文件内容和三方库的话,要在 .podspec文件中添加对 基础组件 的引用与 头文件导入
    image.png

    • prefix_header_contents:类似于pch文件,多个引用用逗号隔开
  3. 但是光在 .podspec 文件中添加依赖还是不够的,因为本地组件还需要在 Podfile文件 中指定 组件路径(如果在云端则不报错),并在Example中执行 pod install image.png

  4. 依赖完成后即可在 LZHomeModule 组件中使用 LZCommonModule 中的 宏、分类与三方库 等(两个组件同时依赖同一个三方库,但是版本号不同的话会报错) image.png

2.4、加载资源文件

2.4.1、加载图片资源
  • 与正常项目不同,组件资源不能使用imageNamed方法加载图片(该方法在主Bundle中加载图片),图片要放入Images.xcassets
  1. 组件内的图片资源存储在组件 / Assets目录下,需要我们创建Images.xcassets文件夹 image.png

  2. .podspec 文件中配置图片资源路径,并在 测试用例Example 中执行 pod install image.png

  3. 将图片添加进 Images.xcassets
    image.png

  4. 在测试用例中通过指定Bundle访问组件内的图片资源

    @implementation LZViewController
    
    - (void)viewDidLoad
    {
        [super viewDidLoad];
    
        UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(50, 50, 100, 100)];
    
        // Bundle中读取图片
        NSString *bundlePath = [[NSBundle bundleForClass:[HomeXViewController class]].resourcePath stringByAppendingPathComponent:@"/LZHomeModule.bundle"];
        NSBundle *resoure_bundle = [NSBundle bundleWithPath:bundlePath];
        imageView.image = [UIImage imageNamed:@"xuetang" inBundle:resoure_bundle compatibleWithTraitCollection:nil];
    
        [self.view addSubview:imageView];
    }
    
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
        HomeXViewController *homeVC = [HomeXViewController new];
        [self presentViewController:homeVC animated:YES completion:nil];
    }
    @end
    
    • HomeXViewController 是组件中的页面,用于获取Pods 的bundle路径,因为 不能用 [NSBundle mainBundle] 的方式获取组件的Bundle路径
    @implementation HomeXViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        // 使用 CommonMacro.h 文件中的宏
        WS(self);
    
        // 使用 SDWebImage
        UIImageView *imgView = [[UIImageView alloc] init];
        [imgView sd_setImageWithURL:[NSURL URLWithString:@"图片链接"]];
    
        UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(100, 100, 150, 150)];
        imageView.backgroundColor = [UIColor redColor];
    
        // 组件页面加载组件图片
        NSString *bundlePath = [[NSBundle bundleForClass:[self class]].resourcePath stringByAppendingPathComponent:@"/LZHomeModule.bundle"];
        NSBundle *resoure_bundle = [NSBundle bundleWithPath:bundlePath];
        imageView.image = [UIImage imageNamed:@"xueyang" inBundle:resoure_bundle compatibleWithTraitCollection:nil];
    
        [self.view addSubview:imageView];
    }
    @end
    

    image.png

2.4.2、加载Json文件
  • 资源路径也是在 组件 / Assets 目录下,操作基本一致,只是不用使用 Images.xcassets
  • 调用方法也与图片类似
    NSString *bundlePath = [[NSBundle bundleForClass:[self class]].resourcePath stringByAppendingPathComponent:@"/LZHomeModule.bundle"];
    NSString *path = [[NSBundle bundleWithPath:bundlePath] pathForResource:[NSString stringWithFormat:@"JsonFile_%@", num] ofType:@"json"];
    NSData *data = [NSData dataWithContentsOfFile:path];
    
2.4.3、加载xib文件

image.png

  • 读取方式,指定Bundle(可以 将Home模块中有xib的cell集中到宏中
    // 将有xib的Cell集中到宏中
    #define HomeTableViewCellIdentifiers  @{@"homeTable": @"HomeXTableViewCell",\
                                            @"homeTitleTable": @"HomeTitleTableViewCell",\
                                            @"homeContentTable": @"HomeContentTableViewCell"}
    
    for (NSString *className in HomeTableViewCellIdentifiers.allValues) {
        NSString *bundlePath = [NSBundle bundleForClass:[self class]].resourcePath;
        [self.tableView registerNib:[UINib nibWithNibName:className bundle:[NSBundle bundleWithPath:bundlePath]] forCellReuseIdentifier:className];
    }
    

2.5、远程私有组件

  • 我们使用gitee来搭建远程私有组件,需要两个东西,一个是私有库索引,另一个是组件库;因此我们在 gitee创建2个仓库
2.5.1、创建私有库索引
  • 我们将上边的 LZCommonModule 做成远程组件,所以私有库索引我们命名为 LZCommonModuleSpec

    image.png

  • 私有库索引 创建完成,与创建一个项目是一样的 image.png

2.5.2、本地添加私有库索引
  1. 查看本地索引库

    // 终端命令,查看 本地的公有索引库
    % pod repo
    

    image.png

  2. 添加私有库索引

    // 终端命令,添加索引库
    % pod repo add 索引库名 索引库地址 
    
    // 如果需要删除索引库
    % pod repo remove 索引库名
    

    image.png

    • 添加后再执行 pod repo 命令,在本地索引库中能看到
  3. 创建远程组件库 image.png

2.5.3、创建本地库
  • 执行 2.2 中的操作,搭建本地组件化工程;执行 2.3 中操作添加依赖;执行 2.4 中操作加载资源文件

  • 修改.podspec文件 image.png

    • s.homepage:组件库 主页地址
    • s.source:组件库 链接地址
2.5.4、关联组件代码
  1. 将组件代码关联到仓库

    % cd 组件文件夹(文件夹下内容都会被上传到云端)
    % git add .
    % git commit -m "关联组件代码"
    % git remote add origin 组件链接地址
    % git push -f origin master
    

    image.png 组件代码和远程仓库关联完成

  2. 打tag并上传到git image.png

    % git tag -m 'tag描述' '0.1.2'
    % git push --tags
    

    : 所打的 tag 要和.podspec中的s.version保持一致,否则后面不能通过远程验证 image.png

2.5.5、验证
  1. 本地验证

    % pod lib lint --private --allow-warnings
    

    image.png

    • 验证成功:CCBasicComponents passed validation
  2. 远程验证

    % pod spec lint --private --allow-warnings
    

    image.png :在远程验证会验证远程tag下面的tag的编号和所有的组件,tag必须要相同才能通过;另外组件更新后需要重新传到远程tag下,否则也会验证失败

2.5.6、提交.podspec文件
  1. 终端执行指令

    % pod repo push 远程库索引Spec名 本地组件.podspec文件 --allow-warnings
    

    image.png image.png

  2. 此时码云的私有索引库可以看到组件已经上传成功 image.png

  3. 验证组件

    % pod search 组件名
    

    image.png

    • 此时组件已可使用
2.5.7、更新组件
  1. 对组件代码进行修改

  2. cd 进组件文件夹,执行下面命令,更新远程仓库代码

    % git add .
    % git commit -m "修改描述"
    % git push origin master
    
  3. 更新tag版本

  • 修改 podspec文件,将 s.version 改为最新
  • 执行命令:
    % git tag -a '0.2.0' -m '新tag:0.2.0'
    % git push --tags
    
  • 此时远程组件库会多出一个 tag image.png
  1. 重复 2.5.5 的操作进行 本地与远程验证

  2. 重复 2.5.6 的操作进行 更新索引库

    % pod repo push LZComponentSpec LZCommonModule.podspec
    

2.6、使用远程组件

  • 与本地组件相比,将本地路径换成远程链接.podspec文件 中内容还是不能减少,否则头文件等会找不到)--> pod install image.png

注意:

  1. github关联仓库使用组件 时,要使用access tokens
  • 关联仓库
    #关联仓库
    # % git remote add origin https://access tokens远程链接
    % git remote add origin https://ghp_xxxxx@github.com/Wind/LZCommonModule.git
    
  • 使用组件
    platform :ios, '9.0'
    
    target 'LZHomeModule_Example' do
      # use_frameworks!
      pod 'LZCommonModule', :git => 'https://ghp_xxxxx@github.com/Wind/LZCommonModule.git'
    end
    
  1. 如果本地代码已经关联其他仓库,再次关联时会报错:remote origin already exists

    #关联仓库
    % git remote add origin https://ghp_xxxxx@github.com/Wind/LZCommonModule.git
    
    -------------------------
    fatal: remote origin already exists
    

    此时需要解除之前的关联

    % git remote rm origin
    
  2. 其余配置

3、通讯方式