陈江川

邮箱:jiangchuanc@gmail.com

iOS项目开发中组件化的应用

在之前的几篇文章中写了iOS组件化的两个方案,这两个多月没有更新的blog,是因为手上有三个模块需要组件化,利用这次机会实践了这两个方案。workspace的方案很顺利,并没有棘手的问题,下面以CocoaPods方案为例。

CocoaPods

组件关系图

整个项目有4个组件:Hori-pjsip、HRWebRTC、HoriSip、HoriWebRTC;

  1. Hori-pjsip组件:修改编译好的pjsip静态库由CocoaPods管理;
  2. HRWebRTC组件:修改编译好的webRTC动态库由CocoaPods管理:
  3. HoriSip组件:HoriSip组件中封装了一层基于pjsip的C++接口,然后基于这套C++接口再封装OC接口;
  4. HoriWebRTC组件:基于HRWebRTC组件和HoriSip发出的音视频通知封装的一套音视频接口;

准备工作(都是使用git管理)

  1. 考虑到公司网络问题,建立内部的CocoaPods公有库镜像:Specs;
  2. 创建CocoaPods私有库:HoriSpecs。
  3. 分别创建4个组件的git仓库;

开发阶段

我主要是负责Hori-pjsip、HoriSip封装,HoriWebRTC组件主要负责podspec的编写。

Hori-pjsip组件

pjsip官方提供了一个pod lib,目前是2.6版本pjsip官方pod lib。因为封装C++接口是另外一个同事在做,而且修改了pjsip的源码,所以pjsip相关的静态库必须自己编译。不过还好,官方提供了一套完整的podsepc文件,直接拿来用。

HoriSip组件

同事提供一个一份基于pjsip的C++接口,而后通过这套接口封装OC的API,这就是HoriSip组件做的事情。

这是HoriSip的框架,主要是采用外观设计模式,分成3个子系统,然后由HoriFacade类统一对子系统进行调用,Proxy存放着C++接口文件。

框架有了,然后就是填代码了,这里不过多描述。现在主要说下podspec文件的编写,如何在pod lib中分文件夹在podspec文章中已有讲解。说下之前没有碰到的参数:

header_search_paths   =['"$(PODS_ROOT)/Headers/Public/Hori-pjsip/pjlib/include"',
                        '"$(PODS_ROOT)/Headers/Public/Hori-pjsip/pjlib-util/include"',
                        '"$(PODS_ROOT)/Headers/Public/Hori-pjsip/pjmedia/include"',
                        '"$(PODS_ROOT)/Headers/Public/Hori-pjsip/pjnath/include"',
                        '"$(PODS_ROOT)/Headers/Public/Hori-pjsip/pjsip/include"']
s.xcconfig            = {
'HEADER_SEARCH_PATHS'          => header_search_paths.join(' '),
'GCC_PREPROCESSOR_DEFINITIONS' => 'PJ_AUTOCONF=1'

s.xcconfig实际是对Build Settings进行配置:
1. HEADER_SEARCH_PATHS配置Target的搜索头文件的路径,这里配置了pjsip中所有静态库的头文件路径;
2. GCC_PREPROCESSOR_DEFINITIONS是GCC预编译头参数,这里配置了PJ_AUTOCONF=1,是pjsip官方推荐配置。

1.这里有个需要注意,在pod lib中两个文件夹不能相互dependency!

// 如下所示,pod lib lint会报错,循环引用
subsystem.subspec 'Facade' do |facade|
        facade.source_files = 'HoriSip/Classes/Subsystem/Facade/*'
        facade.public_header_files = 'HoriSip/Classes/Subsystem/Facade/*.h'
        
        # 依赖Newwork
     facade.dependency 'HoriSip/Subsystem/Network'
end

subsystem.subspec 'Network' do |network|
      network.source_files = 'HoriSip/Classes/Subsystem/Network/*'
      network.public_header_files = 'HoriSip/Classes/Subsystem/Network/*.h'
      
      # 依赖Facade
      network.dependency 'HoriSip/Subsystem/Facade'
end

2.因为使用了静态库,在pod lib lint的时候需要加上参数 --use-libraries,否则会出现如下错误信息:

 - ERROR | [iOS] unknown: Encountered an unknown error (The 'Pods-App' target has transitive dependencies that include static binaries: 

HoriWebRTC

这个组件我只是把框架搭好,主要由同事负责,说说pod lib lint遇到的问题吧。
因为这个组件是基于HRWebRTC->webRTC,我第一次pod lib lint的时候,报错:

Undefined symbols for architecture i386:
  "_OBJC_CLASS_$_WebrtcEngine", referenced from:
      objc-class-ref in libHoriWebRTC.a(HoriMediaCallEngineCtrl.o)
  "_OBJC_CLASS_$_HRMediaParameter", referenced from:
      objc-class-ref in libHoriWebRTC.a(HoriMediaCallEngineCtrl.o)
  "_OBJC_CLASS_$_RTCEAGLVideoView", referenced from:
      objc-class-ref in libHoriWebRTC.a(HoriRemoteVideoView.o)
  "_OBJC_CLASS_$_RTCCameraPreviewView", referenced from:
      objc-class-ref in libHoriWebRTC.a(HoriLocalVideoView.o)
ld: symbol(s) not found for architecture i386

追到本质,就是webRTC.framework不支持i386架构,为验证使用的webRTC.framework有没有支持i386:
lipo -info WebRTC.framework/WebRTC

Architectures in the fat file: WebRTC.framework/WebRTC are: x86_64 armv7 arm64

确实没有i386的支持,那怎么办,让同事添加i386的支持吗?其实还有一种方式,修改xcconfig文件(上面已经提到过),我们知道可以在Build Settings -> Valid Architectures中修改支持的架构,所以同理在podsepc中添加:

# 只支持arm64和x86_64架构
valid_archs = ['arm64',
                 'x86_64',]
s.xcconfig            = {
 'VALID_ARCHS'          =>  valid_archs.join(' '),
}

这算是对这两个多月的技术总结吧,这段时间让我在组件化的道路上迈出了实际性的一步。如有问题,请发邮件到:jiangchuanc@gmail.com

« 设计模式 - 桥接模式 iOS组件化之路 »