iOS 知识大全技术重塑Objective-C

Swift/Objective-C-使用Cocoapods创建/

2019-03-26  本文已影响24人  sky_storming

接着上篇文章"Swift/Objective-C-使用Cocoapods创建/管理私有库(初中级用法)"的探索之路。

另外两篇文章:
Swift/Objective-C-使用Cocoapods创建/管理公共库
Swift/Objective-C-使用Cocoapods创建/管理私有库(初中级用法)

一、通过subspec创建子模块,及子模块之间的引用

Subspecs是一种分解Podspec功能的方法,它允许人们安装库的一个子集。也就是说,一个私有库中,包含很多模块,有时我们只需要pod这个私有库中的某个或者某些模块,选择性的使用,并不是pod全部,这时我们就需要在私有库中添加subspec,将各个模块开放分解出来,成为一个单独的子模块,以实现这样的需求。或者私有库中的某个模块和某个模块之间有引用关联,为了不影响其他模块的使用和冗余,也需要将这部分和其他模块区分开,也需要添加subspec。
在添加subspec时需要我们在编码的过程中尽量减少模块之间的依赖,使各个模块儿可以独立运行。

Pod::Spec.new do |s|
  s.name             = 'JYPrivateLibTest0'
  s.version          = '1.0.1'
  s.summary          = '这是一个私有测试库!'

  s.homepage         = 'https://git.asd.net/pod/JYPrivateLibTest0'
  s.license          = { :type => 'MIT', :file => 'LICENSE' }
  s.author           = { 'JYanshao' => '654565181@qq.com' }
  s.source           = { :git => 'https://git.asd.net/pod/JYPrivateLibTest0.git', :tag => s.version.to_s }

  s.ios.deployment_target = '8.0'
  s.requires_arc = true
  s.swift_version = '4.0'
  # s.source_files = 'JYPrivateLibTest0/Classes/**/*'

  # ------ 创建子模块 ------
  s.subspec 'TextField' do |tf|  # tf为子模块键
      tf.source_files = 'JYPrivateLibTest0/Classes/TextField/*'
      tf.dependency 'JYPrivateLibTest0/Constant'  # TextField模块中文件使用了Constant模块的东西,所以需要引入依赖。如果不写依赖,当你只引入TextField模块的时候,就会报错,由于找不到Constant模块中你使用的东西。这里不可以直接引入主spec,即tf.dependency 'JYPrivateLibTest0',因为cocospods不允许(podspec文件:[!子规范不能要求它的父规范。),且在实际项目中引入时引入不成功,错误移步下边。
  end
  
  s.subspec 'Constant' do |c|  # c为子模块键
      c.source_files = 'JYPrivateLibTest0/Classes/Constant/*'
  end
   
end

在上面的例子中,一个工程中,有多个模块,并把各模块分解为子模块,使用 pod 'JYPrivateLibTest0' 的Podfile会包含整个库,而 pod 'JYPrivateLibTest0/Constant' 会只包含Constant模块。只引入你的项目中需要的模块,其他不需要的可以不引入。

Analyzing dependencies
Fetching podspec for `JYPrivateLibTest0 ` from `../`
[!] Failed to load 'JYPrivateLibTest0' podspec: 
[!] Invalid `JYPrivateLibTest0.podspec` file: [!] A subspec can't require one of its parents specifications.

 #  from /Users/123456/Desktop/PrivateRepository/JYPrivateLibTest0/JYPrivateLibTest0.podspec:49
 #  -------------------------------------------
 #      tf.source_files = 'JYPrivateLibTest0/Classes/TextField/*'
 >      tf.dependency 'JYPrivateLibTest0'
 #    end
 #  -------------------------------------------

解释:[!]加载“ JYPrivateLibTest0”podspec失败:
[! JYPrivateLibTest0]无效”。podspec文件:[!子规范不能要求它的父规范。
也就是说:同一工程中一子模块使用了另一子模块的东西,需要引入对另一子模块的依赖,且另一子模块也必须为subspec,不能直接依赖父spec。

二、私有库中ARC(自动内存管理)和MRC(手动内存管理)文件的配置

我这里找到一个使用Objective-C写的Base64加密的封装(Base64.h/Base64.m),且为MRC(手动内存管理)。下面让我们看一下如何来区别对待MRC和ARC文件吧,即如何让Cocoapods自动给MRC的文件添加-fno-objc-arc标识,使MRC和ARC共存。

Pod::Spec.new do |s|
    ...
    // 此处省略一些不便的配置
    ...

    s.subspec 'OCBase64' do |ocb|
        ocb.requires_arc = false
        ocb.requires_arc = ['JYPrivateLibTest0/Classes/OCBase64/*.{h,m}']
    end
end
Pod::Spec.new do |s|
    ...
    // 此处省略一些不便的配置
    ...

    s.subspec 'OCBase64' do |ocb|
        ocb.requires_arc = true # 如果其他地方已经写了这句,可省略;或者直接省略也可
        ocb.source_files = 'JYPrivateLibTest0/Classes/OCBase64/*'
      
        non_arc_files = 'JYPrivateLibTest0/Classes/OCBase64/Base64/*.{h,m}'
        ocb.exclude_files = non_arc_files # 排除MRC文件
        ocb.subspec 'no-arc' do |dd|
            dd.source_files = non_arc_files
            dd.requires_arc = false
        end
    end
end

这两种配置方法的区别就是:
方法一,在项目中拉取私有库的时候,不会自动创建一个包含MRC文件的文件夹,看起来代码比较整齐。
方法二,在项目中拉取私有库的时候,会自动创建一个包含MRC文件的文件夹。

三、私有库中依赖其他私有库
  1. 将自己使用了TESTRely私有库的源代码封装文件,添加到Classes文件夹目录下,步骤参考上边的步骤;

  2. 配置.podspec文件,参数如下:(这里都是以子模块的形式添加的)

Pod::Spec.new do |s|
  ...
  // 这里省略没改变的配置
  ...

  s.subspec 'TestController' do |tc|
        tc.source_files = 'JYPrivateLibTest0/Classes/TestController/*'
        tc.dependency 'TESTRely', '~>0.0.6'  # 依赖TESTRely私有库
  end

end
  1. 配置demo工程的Podfile文件,通过source添加TESTRely私有库对应的索引库地址和TESTRely依赖的公共库AFNetworking对应的索引库地址,如下:
source 'https://git.asd.net/pod/JYPrivateRepoTest0.git'  # JYPrivateRepoTest0对应的私有索引库地址
source 'https://git.asd.net/CocoaPods/TESTRelyLibrary.git'  # TESTRely对应的私有索引库地址
source 'https://github.com/CocoaPods/Specs.git'  # 官方公共库对应的公共索引库地址

注意:在项目中使用私有库,这个私有库又中的封装又依赖其他私有库或公共库,则项目的Podfile文件中,需要将该私有库,以及其依赖的其他私有库或公共库的索引库地址都添加到Podfile文件中,否则会报错,找不到依赖的其他私有库或公共库。
pod install时错误如下:

Analyzing dependencies
[!] Unable to find a specification for `TESTRely (~> 0.0.6)` depended upon by `JYPrivateLibTest0/TestController`

You have either:
 * out-of-date source repos which you can update with `pod repo update` or with `pod install --repo-update`.
 * mistyped the name or version.
 * not added the source repo that hosts the Podspec to your Podfile.

Note: as of CocoaPods 1.0, `pod repo update` does not happen on `pod install` by default.
  1. 验证本地的.podspec文件,命令如下:
$ pod lib lint --sources=https://git.asd.net/pod/TESTRelyLibrary.git,https://github.com/CocoaPods/Specs.git --allow-warnings

Cloning spec repo `asd-pod-testrelylibrary` from `https://git.asd.net/pod/TESTRelyLibrary.git`
 -> JYPrivateLibTest0 (1.0.2)
    - NOTE  | [JYPrivateLibTest0/TextField, JYPrivateLibTest0/Constant, JYPrivateLibTest0/NetworkService, and more...] xcodebuild:  note: Using new build system
    - NOTE  | [JYPrivateLibTest0/TextField, JYPrivateLibTest0/Constant, JYPrivateLibTest0/NetworkService, and more...] xcodebuild:  note: Planning build
    - NOTE  | [JYPrivateLibTest0/TextField, JYPrivateLibTest0/Constant, JYPrivateLibTest0/NetworkService, and more...] xcodebuild:  note: Constructing build description
    - NOTE  | [JYPrivateLibTest0/TextField, JYPrivateLibTest0/Constant, JYPrivateLibTest0/NetworkService, and more...] xcodebuild:  warning: Skipping code signing because the target does not have an Info.plist file. (in target 'App')

JYPrivateLibTest0 passed validation.

注意:这里验证命令不能直接使用$ pod lib lint,这样会报找不到该私有库中依赖的其他私有库,导致验证通不过,后边必须通过--sources命令添加其他私有库和公共库的索引库地址,且用“,”隔开,如:$ pod lib lint --sources=https://git.asd.net/pod/TESTRelyLibrary.git,https://github.com/CocoaPods/Specs.git。运行pod sepc lintpod repo push [REPO] [NAME.podspec]命令时,也需要通过--sources命令添加其他私有库和公共库的索引库地址,否则也会验证通不过。

验证不通过,错误信息如下:

$ pod lib lint

 -> JYPrivateLibTest0 (1.0.2)
    - ERROR | [iOS] unknown: Encountered an unknown error (Unable to find a specification for `TESTRely (~> 0.0.6)` depended upon by `JYPrivateLibTest0/TestController`

You have either:
 * out-of-date source repos which you can update with `pod repo update` or with `pod install --repo-update`.
 * mistyped the name or version.
 * not added the source repo that hosts the Podspec to your Podfile.

Note: as of CocoaPods 1.0, `pod repo update` does not happen on `pod install` by default.
) during validation.

[!] JYPrivateLibTest0 did not pass validation, due to 1 error.
You can use the `--no-clean` option to inspect any issue.
  1. 运行git命令,提交修改到远程端私有代码库;

  2. 验证远程端.podspec文件,命令:

$ pod spec lint --sources=https://git.asd.net/pod/TESTRelyLibrary.git,https://github.com/CocoaPods/Specs.git --allow-warnings
  1. 将.podspec文件提交到远程端私有索引库,命令:
$ pod repo push JYPrivateRepoTest0 JYPrivateLibTest0.podspec --sources=https://git.asd.net/pod/TESTRelyLibrary.git,https://github.com/CocoaPods/Specs.git --allow-warnings
  1. 项目中验证,注意别忘了通过source引入私有库对应的私有索引库地址。
四、私有库中依赖第三方公共库

私有库中引入Cocoapods中的公共库,包含两种情况。
1. 引入的公共库中不包含动态文件(.framework)、静态文件(.a)
在制作我自己私有Pod时,我们往往需要用到第三方提供的工具包,比如说网络请求Alamofire、图片加载SDWebImage等,对于这些只有源代码文件的框架的使用是很简单的,我们在制作Pod的时候,只需要在.podspec文件中直接通过s.dependency引入即可,如:s.dependency 'Alamofire', '~> 4.8.1'。添加、验证、上传步骤请参考上边。

具体配置信息如下:

Pod::Spec.new do |s|
   ...
   // 这里省略没改变的配置
   ...

   s.subspec 'NetworkService' do |ns|
      ns.source_files = 'JYPrivateLibTest0/Classes/NetworkService/*'
      ns.dependency 'Alamofire', '~> 4.8.1'
   end
end

当执行pod install命令时,Pod会自己检测并且install我所引用到的第三方仓库。

2. 引入的公共库中包含动态文件(.framework)、静态文件(.a)
有时呢,我们也会用到诸如ShareSDK分享、高德地图等制作成动态库(framework)、静态库(library/.a)方式的第三方库,其中有些库是给你提供了Pod方式导入的,有些没有提供。下面让我们都试试吧。
这里以ShareSDK为例。
1). 提供了Pod方式导入的,可能你会想到直接通过dependency依赖,如下:

Pod::Spec.new do |s|
  ...
  // 这里省略没改变的配置
  ...

  s.static_framework = true # 是否包含静态库框架(注意:不能写在subspec子模块中)
  
  s.subspec 'ShareService' do |ss|
        ss.dependency 'mob_sharesdk', '~>4.2.3'
        ss.dependency 'mob_sharesdk/ShareSDKPlatforms/QQ', '~>4.2.3'
        ss.dependency 'mob_sharesdk/ShareSDKPlatforms/SinaWeibo', '~>4.2.3'
        ss.dependency 'mob_sharesdk/ShareSDKPlatforms/WeChat', '~>4.2.3'
        ss.dependency 'mob_sharesdk/ShareSDKUI', '~>4.2.3'
        ss.dependency 'mob_sharesdk/ShareSDKExtension', '~>4.2.3'
  end
end

这样引入是没问题的,可以通过pod lib lintpod sepc lint命令的验证,提交到远程端的私有代码库。在项目中测试,是没有问题的,可以引入和使用,如下图:

第三方库头文件引入.png

遇到的错误:
如果你在pod lib lint验证时,遇到如下错误,导致验证不通过,说明你没有配置s.static_framework = true,因为你使用了动态库(.framework)、静态库(.a),如果use_frameworks!指定时,pod应包含静态库框架,所以需要允许静态库的使用。错误信息如下:

$ pod lib lint JYPrivateLibTest0.podspec

 -> JYPrivateLibTest0 (1.0.4)
    - ERROR | [iOS] unknown: Encountered an unknown error (The 'Pods-App' target has transitive dependencies that include static binaries: (/private/var/folders/qt/d9rd9h7n2m3cb9014qph7xkm0000gn/T/CocoaPods-Lint-20190320-5532-92gozf-JYPrivateLibTest0/Pods/mob_sharesdk/ShareSDK/ShareSDK.framework, /private/var/folders/qt/d9rd9h7n2m3cb9014qph7xkm0000gn/T/CocoaPods-Lint-20190320-5532-92gozf-JYPrivateLibTest0/Pods/mob_sharesdk/ShareSDK/Support/Required/ShareSDKConnector.framework, /private/var/folders/qt/d9rd9h7n2m3cb9014qph7xkm0000gn/T/CocoaPods-Lint-20190320-5532-92gozf-JYPrivateLibTest0/Pods/mob_sharesdk/ShareSDK/Support/Optional/ShareSDKExtension.framework, /private/var/folders/qt/d9rd9h7n2m3cb9014qph7xkm0000gn/T/CocoaPods-Lint-20190320-5532-92gozf-JYPrivateLibTest0/Pods/mob_sharesdk/ShareSDK/Support/PlatformSDK/QQSDK/TencentOpenAPI.framework, /private/var/folders/qt/d9rd9h7n2m3cb9014qph7xkm0000gn/T/CocoaPods-Lint-20190320-5532-92gozf-JYPrivateLibTest0/Pods/mob_sharesdk/ShareSDK/Support/PlatformConnector/QQConnector.framework, /private/var/folders/qt/d9rd9h7n2m3cb9014qph7xkm0000gn/T/CocoaPods-Lint-20190320-5532-92gozf-JYPrivateLibTest0/Pods/mob_sharesdk/ShareSDK/Support/PlatformSDK/SinaWeiboSDK/libWeiboSDK.a, /private/var/folders/qt/d9rd9h7n2m3cb9014qph7xkm0000gn/T/CocoaPods-Lint-20190320-5532-92gozf-JYPrivateLibTest0/Pods/mob_sharesdk/ShareSDK/Support/PlatformConnector/SinaWeiboConnector.framework, /private/var/folders/qt/d9rd9h7n2m3cb9014qph7xkm0000gn/T/CocoaPods-Lint-20190320-5532-92gozf-JYPrivateLibTest0/Pods/mob_sharesdk/ShareSDK/Support/PlatformSDK/WeChatSDK/libWeChatSDK.a, /private/var/folders/qt/d9rd9h7n2m3cb9014qph7xkm0000gn/T/CocoaPods-Lint-20190320-5532-92gozf-JYPrivateLibTest0/Pods/mob_sharesdk/ShareSDK/Support/PlatformConnector/WechatConnector.framework, and /private/var/folders/qt/d9rd9h7n2m3cb9014qph7xkm0000gn/T/CocoaPods-Lint-20190320-5532-92gozf-JYPrivateLibTest0/Pods/mob_sharesdk/ShareSDK/Support/Optional/ShareSDKUI.framework)) during validation.

[!] JYPrivateLibTest0 did not pass validation, due to 1 error.
You can use the `--no-clean` option to inspect any issue.

以上错误信息解读:在验证过程中,遇到一个未知的错误(' podcast - app '目标有传递依赖关系,包括动静态二进制文件。

2). 这种不管能不能pod导入,都要下载你需要的SDK包,手动导入。以ShareSDK为例,首先在ShareSDK官网下载你所需要的ShareSDK包,然后将下载的ShareSDK包,导入到你的私有库的Classes文件夹下,再然后配置你的.podspec文件。(这里仅仅是导入第三方库,不包含其他文件)
.podspec文件配置如下:

Pod::Spec.new do |s|
  ...
  // 这里省略没改变的配置
  ...

  # 是否包含静态库框架(注意:不能写在subspec子模块中)
  s.static_framework = true  
  
  s.subspec 'ShareService' do |ss|
          # 文件的路径和公开头文件路径
          #ss.source_files = 'JYPrivateLibTest0/Classes/ShareService/JYMobShareSDK/**/*.framework/Headers/*.h'
          #ss.public_header_files = 'JYPrivateLibTest0/Classes/ShareService/JYMobShareSDK/**/*.framework/Headers/*.h'

          # ShareSDK的所有动态库路径(也可以写具体的路径,ShareSDK的framework太多了,偷懒一下)
          ss.vendored_frameworks = 'JYPrivateLibTest0/Classes/ShareService/JYMobShareSDK/**/*.framework'
          # 第三方的静态文件路径
          ss.vendored_libraries = 'JYPrivateLibTest0/Classes/ShareService/JYMobShareSDK/ShareSDK/Support/PlatformSDK/**/*.a'
          # 第三方的资源文件
          ss.resources = 'JYPrivateLibTest0/Classes/ShareService/JYMobShareSDK/ShareSDK/Support/**/*.bundle'
         
          # 第三方用到的系统动态库
          ss.frameworks = 'UIKit', 'JavaScriptCore', 'ImageIO'
          # 第三方用到的系统静态文件(前面的lib要去掉,否则会报错)
          ss.libraries = 'icucore', 'z', 'c++', 'sqlite3'
        
          # Build Settings里边的设置
          ss.pod_target_xcconfig = {
              #'FRAMEWORK_SEARCH_PATHS' => '${PODS_ROOT}/JYPrivateLibTest0/Classes/ShareService/JYMobShareSDK',
              'HEADER_SEARCH_PATHS' => '$(PODS_ROOT)/JYPrivateLibTest0/Classes/ShareService/JYMobShareSDK/**/*.framework/Headers',
              'LD_RUNPATH_SEARCH_PATHS' => '$(PODS_ROOT)/JYPrivateLibTest0/Classes/ShareService/JYMobShareSDK/',
              'OTHER_LDFLAGS' => ['-ObjC']
          }
  end
end

将修改提交到远程端私有代码库,通过pod lib lintpod sepc lint命令验证,并提交到远程端的私有代码库。
注意:s.static_framework = true 必须和s.name同级,不能写在s.subspec子模块中,否则回报错误。

遇到的问题:
demo工程pod install后,编译运行,以及pod lib lint验证时都会报以下错误,错误信息如下:

编译运行demo报错.png
$ pod lib lint --sources=https://git.asd.net/pod/TESTRelyLibrary.git,https://github.com/CocoaPods/Specs.git --allow-warnings

 -> JYPrivateLibTest0 (1.0.5)
    - ERROR | [iOS] xcodebuild: Returned an unsuccessful exit code. You can use `--verbose` for more information.
    - NOTE  | [JYPrivateLibTest0/Constant, JYPrivateLibTest0/TextField, JYPrivateLibTest0/NetworkService, and more...] xcodebuild:  note: Using new build system
    - NOTE  | [JYPrivateLibTest0/Constant, JYPrivateLibTest0/TextField, JYPrivateLibTest0/NetworkService, and more...] xcodebuild:  note: Planning build
    - NOTE  | [JYPrivateLibTest0/Constant, JYPrivateLibTest0/TextField, JYPrivateLibTest0/NetworkService, and more...] xcodebuild:  note: Constructing build description
    - NOTE  | [JYPrivateLibTest0/Constant, JYPrivateLibTest0/TextField, JYPrivateLibTest0/NetworkService, and more...] xcodebuild:  warning: Skipping code signing because the target does not have an Info.plist file. (in target 'App')
    - NOTE  | xcodebuild:  <module-includes>:1:9: note: in file included from <module-includes>:1:
    - NOTE  | [iOS] xcodebuild:  //privateTarget Support Files/JYPrivateLibTest0/JYPrivateLibTest0-umbrella.h:45:9: note: in file included from //privateTarget Support Files/JYPrivateLibTest0/JYPrivateLibTest0-umbrella.h:45:
    - ERROR | [iOS] xcodebuild:  /Users/123/Desktop/PrivateRepository/JYPrivateLibTest0/JYPrivateLibTest0/Classes/ShareService/JYMobShareSDK/Required/MOBFoundation.framework/Headers/MOBFOAuthService.h:9:9: error: include of non-modular header inside framework module 'JYPrivateLibTest0.MOBFOAuthService': '/Users/123/Desktop/PrivateRepository/JYPrivateLibTest0/JYPrivateLibTest0/Classes/ShareService/JYMobShareSDK/Required/MOBFoundation.framework/Headers/MOBFoundation.h'
    - NOTE  | [iOS] xcodebuild:  //privateTarget Support Files/JYPrivateLibTest0/JYPrivateLibTest0-umbrella.h:64:9: note: in file included from //privateTarget Support Files/JYPrivateLibTest0/JYPrivateLibTest0-umbrella.h:64:
    - ERROR | [iOS] xcodebuild:  /Users/123/Desktop/PrivateRepository/JYPrivateLibTest0/JYPrivateLibTest0/Classes/ShareService/JYMobShareSDK/ShareSDK/ShareSDK.framework/Headers/ShareSDK+Base.h:9:9: error: include of non-modular header inside framework module 'JYPrivateLibTest0.ShareSDK_Base': '/Users/123/Desktop/PrivateRepository/JYPrivateLibTest0/JYPrivateLibTest0/Classes/ShareService/JYMobShareSDK/ShareSDK/ShareSDK.framework/Headers/ShareSDK.h'
    - NOTE  | xcodebuild:  //privateTarget Support Files/JYPrivateLibTest0/JYPrivateLibTest0-umbrella.h:84:9: note: in file included from //privateTarget Support Files/JYPrivateLibTest0/JYPrivateLibTest0-umbrella.h:84:
    - ERROR | [iOS] xcodebuild:  /Users/123/Desktop/PrivateRepository/JYPrivateLibTest0/JYPrivateLibTest0/Classes/ShareService/JYMobShareSDK/ShareSDK/Support/Optional/ShareSDKUI.framework/Headers/ShareSDKUI.h:13:9: error: include of non-modular header inside framework module 'JYPrivateLibTest0.ShareSDKUI': '/Users/123/Desktop/PrivateRepository/JYPrivateLibTest0/JYPrivateLibTest0/Classes/ShareService/JYMobShareSDK/ShareSDK/Support/Optional/ShareSDKUI.framework/Headers/SSUIEditorConfiguration.h'
    - ERROR | [iOS] xcodebuild:  /Users/123/Desktop/PrivateRepository/JYPrivateLibTest0/JYPrivateLibTest0/Classes/ShareService/JYMobShareSDK/ShareSDK/Support/Optional/ShareSDKUI.framework/Headers/ShareSDKUI.h:14:9: error: include of non-modular header inside framework module 'JYPrivateLibTest0.ShareSDKUI': '/Users/asd/Desktop/PrivateRepository/JYPrivateLibTest0/JYPrivateLibTest0/Classes/ShareService/JYMobShareSDK/ShareSDK/Support/Optional/ShareSDKUI.framework/Headers/SSUIShareSheetConfiguration.h'
    - ERROR | [iOS] xcodebuild:  /Users/asd/Desktop/PrivateRepository/JYPrivateLibTest0/JYPrivateLibTest0/Classes/ShareService/JYMobShareSDK/ShareSDK/Support/Optional/ShareSDKUI.framework/Headers/ShareSDKUI.h:15:9: error: include of non-modular header inside framework module 'JYPrivateLibTest0.ShareSDKUI': '/Users/asd/Desktop/PrivateRepository/JYPrivateLibTest0/JYPrivateLibTest0/Classes/ShareService/JYMobShareSDK/ShareSDK/Support/Optional/ShareSDKUI.framework/Headers/SSUIPlatformItem.h'
    - NOTE  | [iOS] xcodebuild:  <unknown>:0: error: could not build Objective-C module 'JYPrivateLibTest0'
    - WARN  | [JYPrivateLibTest0/TestController] xcodebuild:  /Users/asd/Desktop/PrivateRepository/JYPrivateLibTest0/JYPrivateLibTest0/Classes/TestController/JYFather.swift:9:8: warning: file 'JYFather.swift' is part of module 'JYPrivateLibTest0'; ignoring import
    - WARN  | [JYPrivateLibTest0/TestController, JYPrivateLibTest0/OCBase64, JYPrivateLibTest0/OCBase64/Base64, and more...] xcodebuild:  /Users/asd/Desktop/PrivateRepository/JYPrivateLibTest0/JYPrivateLibTest0/Classes/OCBase64/Base64/Base64.m:63:63: warning: implicit conversion loses integer precision: 'long long' to 'NSUInteger' (aka 'unsigned int') [-Wshorten-64-to-32]
    - WARN  | [JYPrivateLibTest0/TestController, JYPrivateLibTest0/OCBase64, JYPrivateLibTest0/OCBase64/Base64, and more...] xcodebuild:  /Users/asd/Desktop/PrivateRepository/JYPrivateLibTest0/JYPrivateLibTest0/Classes/OCBase64/Base64/Base64.m:91:25: warning: implicit conversion loses integer precision: 'long long' to 'NSUInteger' (aka 'unsigned int') [-Wshorten-64-to-32]
    - WARN  | [JYPrivateLibTest0/TestController, JYPrivateLibTest0/OCBase64, JYPrivateLibTest0/OCBase64/Base64, and more...] xcodebuild:  /Users/asd/Desktop/PrivateRepository/JYPrivateLibTest0/JYPrivateLibTest0/Classes/OCBase64/Base64/Base64.m:107:58: warning: implicit conversion loses integer precision: 'long long' to 'unsigned long' [-Wshorten-64-to-32]
    - WARN  | [JYPrivateLibTest0/TestController, JYPrivateLibTest0/OCBase64, JYPrivateLibTest0/OCBase64/Base64, and more...] xcodebuild:  /Users/asd/Desktop/PrivateRepository/JYPrivateLibTest0/JYPrivateLibTest0/Classes/OCBase64/Base64/Base64.m:147:44: warning: implicit conversion loses integer precision: 'long long' to 'unsigned long' [-Wshorten-64-to-32]
    - WARN  | [JYPrivateLibTest0/TestController, JYPrivateLibTest0/OCBase64, JYPrivateLibTest0/OCBase64/Base64, and more...] xcodebuild:  /Users/asd/Desktop/PrivateRepository/JYPrivateLibTest0/JYPrivateLibTest0/Classes/OCBase64/Base64/Base64.m:149:54: warning: implicit conversion loses integer precision: 'long long' to 'NSUInteger' (aka 'unsigned int') [-Wshorten-64-to-32]

[!] JYPrivateLibTest0 did not pass validation, due to 6 errors.
You can use the `--no-clean` option to inspect any issue.

上面信息的摘取:

 error: include of non-modular header inside framework module 'JYPrivateLibTest0.ShareSDK_Base': '/Users/asd/Desktop/PrivateRepository/JYPrivateLibTest0/JYPrivateLibTest0/Classes/ShareService/JYMobShareSDK/ShareSDK/ShareSDK.framework/Headers/ShareSDK.h'

要解决这个错误其实很简单,只需要注释掉或删除掉ShareService子模块中的ss.source_files和ss.public_header_files这两个参数及配置即可。

那报这个错误是什么原因造成的呢?
在创建Pod,且引入Objective-C语言开发的第三方公共库供Swift使用时,我们并不需要创建xxx-Bridge-Header.h桥文件去引入Objective-C的头文件, 这个工作是交由xxx-umbrella.h文件完成,这个文件的其中一个作用:其实和xxx-Bridge-Header.h桥文件的作用基本相同,向外界暴露Objective-C的头文件供Swift使用,实现Swift和Objective-C的混编。当你再次通过ss.source_files和ss.public_header_files暴露第三方公共的头文件时会重复定义,所以需要注释掉或删除掉ss.source_files和ss.public_header_files这两个参数及配置。让我们试试能否成功吧,结果是肯定的。

为了使你更好的了解,在这里我新创建了一个私有代码库JYPLibTest1,且使用百度地图Lib来演示,然后通过subspec在.podsepc中创建百度地图的子模块JYBaiDuMapKit(子模块文件夹名字,可随意取)。目录如下:

百度地图Lib目录结构.png

解决方案:让我们来优化一下吧,这里我将创建一个.modulemap文件来解决这个问题,让我们修改.podspec,为所引用到的framework创建Module

注意:如果我们手动创建一个. modulemap文件,然后直接将该文件拖到相应的目录下,这样在 pod install 时可能会导致丢失该文件,那么我们应该怎么办呢?解决办法是,我们需要使用prepare_command属性,来帮助我们自动创建.modulemap文件。

prepare_command属性的解释、使用场景及禁用条件:
prepare_command属性是下载Pod后将执行的bash脚本。此命令可用于创建、删除和修改下载的任何文件,并将在收集规范的其他文件属性的任何路径之前运行。
此命令在清理Pod和创建Pods项目之前执行。工作目录是Pod的根目录。
prepare_command属性必须在主模块中使用。
如果pod安装了:path选项,则不会执行此命令。

具体配置如下:

Pod::Spec.new do |s|
  s.name             = 'JYPLibTest1'
  s.version          = '1.0.0'
  s.summary          = '另一个测试私有库'
  s.homepage         = 'https://git.artron.net/CocoaPods/JYPLibTest1'
  s.license          = { :type => 'MIT', :file => 'LICENSE' }
  s.author           = { 'JYanshao' => '654565181@qq.com' }
  s.source           = { :git => 'https://git.123.net/CocoaPods/JYPLibTest1.git', :tag => s.version.to_s }
  s.ios.deployment_target = '8.0'
  s.source_files = 'JYPLibTest1/Classes/**/*.swift'
  s.swift_version = '4.2'
  
  s.subspec 'JYBaiDuMapKit' do |mk|
      
      mk.vendored_frameworks =  'JYPLibTest1/Classes/JYBaiDuMapKit/*.framework'
      mk.vendored_libraries = 'JYPLibTest1/Classes/JYBaiDuMapKit/thirdlibs/*.a'

      mk.resources = 'JYPLibTest1/Classes/JYBaiDuMapKit/BaiduMapAPI_Map.framework/mapapi.bundle'
      
      mk.frameworks   =  'CoreLocation', 'QuartzCore', 'OpenGLES', 'SystemConfiguration', 'CoreGraphics', 'Security', 'CoreTelephony'
      mk.libraries    = 'sqlite3', 'c++'
      
      mk.preserve_paths = 'JYPLibTest1/Classes/JYBaiDuMapKit/*.framework', 'JYPLibTest1/Classes/JYBaiDuMapKit/thirdlibs/*.a'

      mk.pod_target_xcconfig = {
          'HEADER_SEARCH_PATHS' => '$(PODS_ROOT)/JYPLibTest1/Classes/JYBaiDuMapKit/*.framework/Headers',
          'LD_RUNPATH_SEARCH_PATHS' => '$(PODS_ROOT)/JYPLibTest1/Classes/JYBaiDuMapKit/',
          'OTHER_LDFLAGS' => '-ObjC'
      }
  end
  
  # prepare_command属性必须在主模块中使用
  s.prepare_command = <<-EOF
      
      # 创建BaiduMapAPI_Base Module
      rm -rf JYPLibTest1/Classes/JYBaiDuMapKit/BaiduMapAPI_Base.framework/Modules
      mkdir JYPLibTest1/Classes/JYBaiDuMapKit/BaiduMapAPI_Base.framework/Modules
      touch JYPLibTest1/Classes/JYBaiDuMapKit/BaiduMapAPI_Base.framework/Modules/module.modulemap
      cat <<-EOF > JYPLibTest1/Classes/JYBaiDuMapKit/BaiduMapAPI_Base.framework/Modules/module.modulemap
      framework module BaiduMapAPI_Base {
          umbrella header "BMKBaseComponent.h"
          export *
          link "sqlite3"
          link "c++"
      }
      \EOF
      
      # 创建BaiduMapAPI_Map Module
      rm -rf JYPLibTest1/Classes/JYBaiDuMapKit/BaiduMapAPI_Map.framework/Modules
      mkdir JYPLibTest1/Classes/JYBaiDuMapKit/BaiduMapAPI_Map.framework/Modules
      touch JYPLibTest1/Classes/JYBaiDuMapKit/BaiduMapAPI_Map.framework/Modules/module.modulemap
      cat <<-EOF > JYPLibTest1/Classes/JYBaiDuMapKit/BaiduMapAPI_Map.framework/Modules/module.modulemap
      framework module BaiduMapAPI_Map {
          umbrella header "BMKMapComponent.h"
          export *
          link "sqlite3"
          link "c++"
      }
      \EOF
      
  EOF
  
end
配置完成,让我们更新一下demo工程,测试一下是否可以使用。在测试项目中的使用和在自己私有库中的使用,结果如下图: 测试项目中的使用.png 私有库中的使用.png 百度地图运行结果.png

相同的方法在ShareSDK相应的framework目录下创建Module却出现了错误,暂时未解决,有兴趣的朋友可以试试,分享一下。

五、私有库中Swift和Objective-C混编(两种情况)

这里我门先看一下,混编的两种情况的文件目录结构,然后再一步一步的往下走,目录结构如下:

├── JYPLibTest1
│   ├── Assets
│   ├── Classes
│   │   ├── OCClass                      # Objective-C类的文件夹(OCClass子模块)
│   │   │   ├── JYFamily.h                    # 家庭类(NSObject)
│   │   │   ├── JYFamily.m
│   │   │   ├── JYFather.swift             # 父亲类(NSObject)
│   │   │   ├── JYKid.h                           # 孩子类(NSObject)
│   │   │   ├── JYKid.m
│   │   │   ├── JYMother.swift                # 母亲类(NSObject)
│   │   │   ├── JYPLibTest1.h                  # 头文件(项目要求要有的)
│   │   │   └── JYPersonProtocol.h       # 协议类
│   │   ├── SwiftClass                      # Swift类的文件夹(SwiftClass子模块)
│   │   │   ├── JYFather2.swift               # 父亲类2(NSObject)
│   │   │   └── JYMother2.swift             # 母亲类2(NSObject)

1. 同一个模块(OCClass)内的Swift和Objective-C混编
一个层面问题的解决,又伴随着另一层面的思考,那就是如何在私有库中实现Swfit和Objective-C的混编及混合打包(我们这一步只验证Swift与Objective-C混合打包)。

同一个模块(OCClass)内的Swift和Objective-C混编,可以是单向调用,即Swift调用Objective-C,或者Objective-C调用Swift,也可以是双向调用,即Swift调用Objective-C,Objective-C又调用Swift。

在这里我们继续使用JYPLibTest1私有库,且以双向调用为例。这个问题让我们通过一个案例来看,能更好的理解。
案例:一个周末的早上,家中的父母做好早餐,叫孩子起床,喂他/她吃饭。(一个家庭中,包括父母和孩子,父母中又包括孩子,父母、孩子都有吃的动作,孩子吃饭是由父母喂的)。

/// JYKid.h
#import <Foundation/Foundation.h>
#import "JYPersonProtocol.h"

@interface JYKid : NSObject <JYPersonProtocol> // 遵循协议

@property (nonatomic, strong) NSString *name;

@end


/// JYKid.m
#import "JYKid.h"

@implementation JYKid

/// 实现JYPersonProtocol协议方法
- (void)eat {
    NSLog(@"%@ is eating", self.name != nil ? self.name : @"Kid");
}

@end
/// JYPersonProtocol.h
#import <Foundation/Foundation.h>

@protocol JYPersonProtocol <NSObject>

@optional
- (void)eat;

@end
完成后,我们的这两个类肯定是需要给外部文件(Objective-C/Swift)使用的,那么该如何处理呢?其实解决办法很简单,只需将Objective-C类的.h文件设置为public即可。以JYKid.h为例,具体设置如下图: 将OC的.h文件设置为public.png

相同的方法,将JYPersonProtocol.h也设置为public。有的文件创建完默认就是public,那就可以略过此步了。

设置完成,修改.podspec文件,内容如下:

Pod::Spec.new do |s|
  s.name             = 'JYPLibTest1'
  s.version          = '1.0.1'
  s.summary          = '另一个测试私有库'
  s.homepage         = 'https://git.artron.net/CocoaPods/JYPLibTest1'
  s.license          = { :type => 'MIT', :file => 'LICENSE' }
  s.author           = { 'JYanshao' => '654565181@qq.com' }
  s.source           = { :git => 'https://git.artron.net/CocoaPods/JYPLibTest1.git', :tag => s.version.to_s }
  s.ios.deployment_target = '8.0'
  s.source_files = 'JYPLibTest1/Classes/**/*.swift'
  
  s.subspec 'OCClass' do |cc|
      cc.source_files = 'JYPLibTest1/Classes/OCClass/*.{h,m}'
  end  
end

在终端执行下pod install命令,更新下demo工程,编译运行没有错误。然后打开demo中的xxx-umbrella.h文件,你会发现JYKid和JYPersonProtocol两个文件的头文件已经被自动引入进来了。如下图:

xxx-umbrella.h文件可以被看成是你项目中桥接文件xxx-Bridge-Header.h,也可以看成是一个Objective-C的头文件。

查看JYPLibTest1-umbrella.h文件.png

让我们继续,创建两个Swift类JYFather和JYMother,然后实现Swift对Objective-C类的引用,使其有一个小孩JYKid并且实现JYPersonProtocol协议,然后喂这个小孩吃饭。
注意:Swift类与Objective-C类有些区别,Swift类不需要像Objective-C类一样设置他们的头文件为public,Swift类只需要定义其文件的访问权限即可。
JYFather和JYMother文件中的代码一样,以JYFather为例,代码如下:

/// JYFather.swift / JYMother.swift 这两个文件中的代码一样,copy一下
/// 父母这两个文件都继承JYPersonProtocol协议,并实现其方法
import UIKit

open class JYFather: NSObject, JYPersonProtocol {
    var name: String = ""
    var kid: JYKid?
    
    public init(name: String, kid: JYKid) {
        super.init()
        self.name = name
        self.kid = kid
    }
    
    @objc public func feed(_ food: String) {
        print("\(name) is feeding \(kid?.name ?? "kid") eat \(food)")
    }
    
    /// 实现协议方法
    public func eat() {
        print("\(name) is eating")
    }
}

修改.podspec文件,配置如下:

Pod::Spec.new do |s|
    ...
    # 没有变化的部分省略了
    ...

    s.subspec 'OCClass' do |cc|
          cc.source_files = 'JYPLibTest1/Classes/OCClass/*.{h,m,swift}'
    end
end

执行pod install,更新demo,编译运行,结果报错了,错误如下:

错误1.png 错误11.png

提示我们没有找到JYPLibTest1.h文件,因为项目中根本就没有这个文件,所以才找不到,既然它需要这么一个文件,那就按它的意思创建一个JYPLibTest1.h头文件,并 #import 引入已经创建的Objective-C类头文件。
注意:JYPLibTest1头文件,是一个Objective-C类的头文件。用来导入Objective-C类的头文件用的,相当于xxx-Bridging-Header.h文件。

JYPLibTest1.h头文件.png
更新demo,并编译运行发现没有问题了。

到这里,我们创建了父母、孩子、以及吃的协议类,接下来让我们创建一个Objective-C的家庭类JYFamily,把他们组合成一个家庭吧,这样才圆满,并实现Objective-C类调用Swift类。
JYFamily类的代码如下:

/// JYFamily.h
#import <Foundation/Foundation.h>
#import "JYKid.h"
@class JYFather, JYMother;   // 这里有个问题需注意,后面会讲到

@interface JYFamily : NSObject

@property (nonatomic, strong) JYKid *kid;
@property (nonatomic, strong) JYFather *father;
@property (nonatomic, strong) JYMother *mother;

// 打印父母各自的喂食
- (void)feed:(NSString *)fFood mFood:(NSString *)mFood;

@end


/// JYFamily.m
#import "JYFamily.h"
#import <JYPLibTest1/JYPLibTest1-Swift.h>

@implementation JYFamily

- (void)feed:(NSString *)fFood mFood:(NSString *)mFood {
    [self.father feed:fFood]; // 父亲喂孩子吃食物
    [self.mother feed:mFood]; // 母亲喂孩子吃食物
}

@end

更新demo,并编译运行,发现没有错误。然后在测试项目中引入,并测试是否可用。我的测试结果如下:


Swift和OC混合开发运行结果.png

本地和远程验证也是可以通过的,上传也是成功的。

这里有个问题需要注意一下
如果你在JYFamily.h文件中直接引入#import "JYPLibTest1-Swift.h"头文件的话,编译运行demo可能会报如下错误。

导入JYPLibTest1-Swift.h错误.png

原因有两点:1). 在你的ProjectName-Swift.h中是引用了JYFamily.h的,如果你这时候在定义JYFamily.h中又引用ProjectName-Swift.h造成了有点像循环引用的概念,所以在JYFamily.h中只需要用@Class声明一下用到的文件即可,在JYFamily.m中在真正引用需要用到的文件;
(下面这张图片是借用的,原文章在下边的参考文章部分有链接)

image

2). Swift只支持动态库,但并非完全意义的动态库,而我们的代码在Pod之后实际上是一个动态的Framework,Swift是有命名空间的一个概念,这时候你需要做的是在引用时需要写明命名空间。

基于上述两点原因,我们只能在Objective-C类的JYFamily.m文件中引用(#import)并且加上命名空间,而JYFamily.h文件中则用 @Class 声明一下引用的类。最终结果如下图:


循环关联和命名空间2解决方案.png 循环关联和命名空间1解决方案.png

解决完这个问题,记得更新demo,编译运行。

2.不同模块内的Swift和Objective-C的混编

不同模块内的Swift和Objective-C混编,可以是单向调用,即Swift调用Objective-C,或者Objective-C调用Swift,也可以是双向调用,但有些限制,不是很灵活。

紧接着第一种情况,我们来探索一下第二种情况(还是以双向引用为例)。
我们还使用上面OCClass子模块中的Objective-C类,然后在SwiftClass文件夹下创建Swift类JYFather2和JYMother2两个文件,这两个文件中的代码是copy的JYFather和JYMother两个文件中的代码。

创建并编辑完成后,将JYFamily文件中的JYFather和JYMother替换为JYFather2和JYMother2。如下图所示: 修改后的JYFamily.h文件.png

完成以上步骤,修改.podspec文件的配置,因为是两个不同的子模块之间的混编,所以需要通过dependency来引入另一个模块供本模块使用,具体配置如下:

Pod::Spec.new do |s|
  ...
  # 不变的配置省略
  ...
  
  s.subspec 'OCClass' do |cc|
      cc.source_files = 'JYPLibTest1/Classes/OCClass/*.{h,m,swift}'
      #cc.dependency 'JYPLibTest1/SwiftClass' # 这里不能再依赖SwiftClass子模块了,否则会造成循环依赖的问题
  end
  
  s.subspec 'SwiftClass' do |sc|
      sc.source_files = 'JYPLibTest1/Classes/SwiftClass/*.swift'
      sc.dependency 'JYPLibTest1/OCClass'
  end
end 

终端下更新demo工程,编译运行,没有问题。
但是在执行pod lib lint验证时却出现了错误,具体错误如下:

- ERROR | [JYPLibTest1/OCClass,JYPLibTest1/SwiftClass] xcodebuild:  /Users/artron/Desktop/PrivateRepository/JYPLibTest1/JYPLibTest1/Classes/OCClass/JYFamily.m:15:6: error: receiver type 'JYFather2' for instance message is a forward declaration
- ERROR | [JYPLibTest1/OCClass,JYPLibTest1/SwiftClass] xcodebuild:  /Users/artron/Desktop/PrivateRepository/JYPLibTest1/JYPLibTest1/Classes/OCClass/JYFamily.m:16:6: error: receiver type 'JYMother2' for instance message is a forward declaration

这个问题造成的原因是你的JYFamily.h文件中用了@Class声明了你的JYFather2和JYMother2,但是在JYFamily.m中使用的时候没有检测到你用#import引入这两个文件,所以验证失败。所以子模块之间的混编会有些局限。

子模块之间还要注意循环依赖的问题
如上.podspec中,cc.dependency 'JYPLibTest1/SwiftClass'sc.dependency 'JYPLibTest1/OCClass'两个子模块都互相依赖了对方,这时执行pod install,会报JYPLibTest1/SwiftClassJYPLibTest1/OCClass之间存在循环依赖关系的问题,解决方法就是断开一方的依赖即可。
具体错误如下:

两者之间循环依赖问题.png

以上若有不妥请指正。



参考文章:
Build with CocoaPods
使用私有Cocoapods仓库 中高级用法
pod库包含MRC的文件
组件化开发之-如何解决Swift/OC-Framenwork/Library混合创建pod问题

上一篇下一篇

猜你喜欢

热点阅读