背景:
目前所在的项目组是多媒体开发组,项目开发的场景包括了播放器,特效,视频编辑,视频模板,代码规范工具等多个场景,随着开发迭代,项目已经变得越来越庞大,所有代码放在一个工程中不利于团队开发和维护,于是决定将项目根据功能拆分成不同的库(模块化),各自维护。
iOS平台中使用了cocoapods这个管理工具方便进行库的管理,使用它自制私有库的关键需要自己编写podspec文件(描述库的特定版本信息的文件),于是各种史诗级灾难开始了。
以下是各种灾难现场
作为一个刚才深坑里面爬出来的人,趁着还有一口气在,赶紧记录下这次爬坑经历,给使用podspec的同学做一个参考~
问题解析
问题一
首先我们来看图一,这里报的错误是<cassert>头文件找不到。
<cassert>是c++标准库里面的一个系统头文件,一般出现这种情况的原因是在Objective-C类型文件中引用了C++的头文件导致的,如果要引用C++的头文件必须将后缀名改成(.mm)即将文件类型改为Objective-C++类型。
然而,目前的问题场景并不是这样的,我们并没有在Objective-C文件中引用c++头文件。目前我们用到了库有两个,一个是渲染库(illusion)和一个播放器库(GDMMCore)。illusion库中有c++文件和Objetcive-C++文件(即封装c++,给外部使用的中间件)和Objective-C文件,GDMMCore库依赖于这个illusion库(即GDMMCore.dependency 'illusion'),我们只有在GDMMCore的Objective-C文件中引用了illusion库的Objective-C文件,如下图所示,就造成了图一的错误。这个是为什么呢?
我们沿着引用路径查看,最后定位到了一个叫做illusion-umbrella.h的文件中。
如图所示,我们明明只有引用GDCoreGraphics.h(Objective-C类型)这个头文件,为什么引用路径会定位到GDInOutAnimation.h(c++头文件,这个文件里面间接的引用的报错的<cassert>这个头文件)呢?这个illusion-umbrella.h到底又是个什么东西呢?
首先,这个illusion-umbrella.h是因为我们在Podfile中使用了use_frameworks!后由cocopods帮我们自动生成的头文件,这里面导入了所有我们在podspec中配置的共有头文件,有点类似于pch文件的作用,如果不使用use_frameworks!则不会有这个文件。然后这里有个坑,就是只要我们引用了这个文件中导入的头文件(无论是什么类型的头文件),就会引用整个illusion模块,即illusion-umbrella.h中所有的头文件,所以这就造成了上面的引用路径。
这里提到了use_framework!,那么使用和不使用这个有什么区别呢?
上面两幅图分别是使用了use_framework!和没有使用use_framework!的结果,首先正如我们上面说到的使用了use_framework!之后会生成一个xx.umbrella.h文件。其次,使用use_framework之后最终组件会编译成.framework类型的文件,而不使用的话则是.a类型的文件形式。当然,还不只有这些区别哦~
它还会导致最终的头文件路径不一样,下面两幅图分别是使用了use_framework!和没有使用最终头文件的路径。
那么这个信息有什么用呢?接下来我就介绍一下我是怎么解决第一个问题的,正如上面提到的,问题在于引用了xx.umbrella.h中的文件就会引用整个umbrella.h中的所有头文件(这些头文件中包含了c++文件)。
第一个思路,不使用use_framework!就不会生成umbrella.h这个文件,也就不会导致引用整个模块的头文件,也就不会有图一的问题了。但是在Podfile中使不使用use_framework!是由业务方(即需要用这个库的同学)自己决定的,我们作为服务方(即提供组件的同学)当然不能限制使用的同学要这样做,毕竟我们的目标是要让业务方同学用起来方便,爽。而这种方案是对业务方同学做了一个很大的限制,方案pass。
第二个思路,上面我们说到了umbrella.h里面引入的是所有的我们在podspec中配置的公开(public)的头文件,也就是说私有的(private)头文件不会出现在这里面,那么我们可以将c++文件配置成私有的头文件,这样的话即使引用了整个umbrella.h里面的头文件也不会引用到c++头文件,方案可行,配置如下图所示。
综上,方案二能够解决图一引用c++文件的问题,但是以方案二执行之后又导致了另一个问题,就是由于把文件设置成了私有导致了GDMMCore库中找不到c++文件,无法使用,这个问题又要怎么处理呢?
这个我们可以通过在Build Setting中设置Header Search Paths,这样编译器就能根据我们配置的路径去找到私有的头文件了,如下图所示。
那么头文件的路径如何设置呢?又该如何在podspec中配置这些呢?让我们回到podspec中,看看具体的配置代码:
search_paths = [
#Podfile不使用use_frameworks搜索路径
'$(PODS_ROOT)/Headers/Public/illusion',
'$(PODS_ROOT)/Headers/Private/illusion',
#Podfile使用use_frameworks库内搜索路径
'$(PODS_ROOT)/illusion/Headers',
'$(PODS_ROOT)/illusion/PrivateHeaders',
#Podfile使用指定路径链接
'$(PODS_TARGET_SRCROOT)/src',
'$(PODS_TARGET_SRCROOT)/src/animation/inout',
'$(PODS_TARGET_SRCROOT)/src/animation/transition',
'$(PODS_TARGET_SRCROOT)/src/entity',
'$(PODS_TARGET_SRCROOT)/src/filter',
'$(PODS_TARGET_SRCROOT)/src/math',
'$(PODS_TARGET_SRCROOT)/src/stb',
'$(PODS_TARGET_SRCROOT)/src/util',
]
private_header_path = [
'${PODS_CONFIGURATION_BUILD_DIR}/illusion/illusion.framework/PrivateHeaders',
'$(PODS_ROOT)/Headers/Private/illusion',
]
s.pod_target_xcconfig = {
'CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES' => 'YES' ,
'HEADER_SEARCH_PATHS' => search_paths.join(' '),
}
s.user_target_xcconfig = {
'HEADER_SEARCH_PATHS' => private_header_path.join(' '),
}
代码中我们可以看到一个关键key值 'HEADER_SEARCH_PATHS',这个对应的就是头文件搜索路径了。我们可以看到代码中有两个地方都进行配置了,分别是pod_target_xcconfig和user_target_xcconfig,那么这两个有什么区别呢?
pod_target_xcconfig设置的是当前库的Build Settings,这里对应的是illusion库。user_target_xcconfig设置的是project中的Build Settings,即在运行的target中的Build Settings。这个在Cocoapods的官网上不建议使用,上面说设置这个有可能会和工程中本身的设置造成冲突。这里因为我们这个库有可能被工程中直接使用,c++头文件被设置为私有头文件,所以这里进行了配置。
接下来我们看下头文件的路径,如代码所示,分为三种情况:
1.使用了use_frameworks,此时的头文件在framwork中寻找,公有的头文件在$(PODS_ROOT)/Headers/Public/库名中,私有的头文件在对应的Private/库名中。
2.不使用use_frameworks,此时的头文件在Pods文件夹对应库中的Headers(公有)和PrivateHeaders(私有)文件夹中。
3.还有一种情况是在Podfile中是使用本地库的方式引用,即通过path = > '../'的方式,此时的头文件路径会到本地工程中对应的路径中寻找。
根据以上不走配置好podspec之后,我们执行pod install,再次编译发现引用c++头文件的问题解决了。
问题二
这里报的错误是头文件找不到,我们发现这里引用头文件的方式是通过相对路径去寻找的,使用相对路径的前提是必须能够找到当前所在的目录层级,于是还是像上面一样,我们需要把当前的目录层级配置到HEADER_SEARCH_PATHS中。
search_paths = [
#Podfile使用指定路径链接
'$(PODS_TARGET_SRCROOT)/source',
'$(PODS_TARGET_SRCROOT)/source/common',
'$(PODS_TARGET_SRCROOT)/source/module/media',
]
s.pod_target_xcconfig = {
'CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES' => 'YES',
'HEADER_SEARCH_PATHS' => search_paths.join(' '),
}
再次pod install之后这个问题也解决了。
问题三
图中的问题信息说的是GDSizeFormatToVideo这个函数在两个模块中有不同的定义,但是搜索了整个工程中对于这个函数的定义只存在于illusion库中的GDCoreGraphics.h中,我们找到报错的引用路径看看
如果所示,我们的引用方式用的是<GDMMCore/GDTimelineInfo.h>的方式找到GDTimelineInfo.h,而GDTimelineInfo.h中间接的引用到了GDCoreGraphics.h这个头文件,这里有一个坑就是用这种方式引用的话,编译器会认为这里间接引用到的GDCoreGraphics.h这个文件是属于GDMMCore中的文件,所以这样就造成了编译器认为在GDMMCore和ilusion两个库中都存在这个GDCoreGraphics.h头文件,以及头文件中的函数,也就造成了图中所示的错误。
这个问题的解决方式比较简单,我们只需要更改引用文件的方式,不使用<模块/文件>的方式引用,而是直接使用"文件"的方式,这样就解决了这个问题。
以上就是我这次的podspec踩坑经历,如果有遇到类似问题的同学可以参考一下,也可以和我交流哈~
参考链接: