组件化 - CocoaPods

958 阅读11分钟

原文链接1

原文链接2

原文链接3

Xcode 工程结构

Target

  • Target 工程中最小的可编译单元,指定了要编译的product,它根据 Build Phases 和 Build Settings 将源码作为输入,经编译后输出结果产物。

  • 一个project可以包含一个或多个target,每个target可以对应产生一个product。

  • 一个target和由它产生的product可以关联其他的target。

Project - Targets 的载体

  • Project 就是一个独立的 Xcode 工程,作为一个或多个 Targets 的资源管理器,Project 所管理的资源都来自它所包含的 Targets。

  • Project 本身无法被编译,但它包含编译product所用的所有元素,并帮我们组织这些元素之间的关系。

  • Project 为所包含的 Targets 定义了一份默认编译选项,如果 Target 有自己的配置,则会覆盖 Project 的预设值;

  • Project 能将其他 Project 作为依赖嵌入其中;

Workspace - 容器

  • Workspace 不参与任何编译链接过程,仅用于管理同层级的 Project,一般包含多个 Projects

  • 默认情况下,workspace中的projects编译在同一目录下(workspace build directory),文件彼此之间是相互可以引用的。

  • 同一个 Workspace 中的 Proejct 文件对于其他 Project 是默认可见的,

  • 一个 Xcode Project 可以被包含在多个不同的 Workspace 中,因为每个 Project 都有独立的 Identity,默认是 Project Name;

Scheme

  • Scheme 是对于整个 Build 过程的一个抽象,它描述了 Xcode 应该使用哪种 Build Configurations 、执行什么任务、环境参数等来构建我们所需的 Target。

  • Scheme 中预设了六个主要过程:BuildRunTestProfileAnalyzeArchive。包括了我们对 Target 的所有操作,每一个过程都可以单独配置。

CocoaPods

CocoaPods 利用了 Xcode 工程结构的特点,引入  Pods.project 这一中间层,将主工程的 Pods 依赖全部转接到 Pods.project 上,最后再将 Pods.project 作为主项目的依赖。

1. Pod 命令

pod install

  • 项目首次进行 cocoaPods 或 Podfile 新增或删除某个Pod后,使用这个命令。

  • 首次运行 pod install 会生成 podfile.lock 文件, 同时顺带生成 .xcworkspace和Pods 目录。

  • 运行 pod install 下载安装新的 Pod 时,它会为 Podfile.lock 文件中的每下Pod 写入已安装的版本,此文件跟踪并锁定每个Pod 已安装的版本。

  • 运行 pod install,它只解析 Podfile.lock 中尚未列在其中的 Pod 依赖库。

pod outdated

  • 查看当前项目中所有引用第三方当前版本和最新版本的状态,包括Podfile.lock中已存在的版本

pod update

  • 指定 pod 库,pod update PODNAME 时, CocoaPods将尝试查找PODNAME更新的pod版本, 并忽略掉Podfile.lock中已经存在的版本

  • 不指定 pod 库,直接 pod update时 CocoaPods会把Podfile中所有的pod都更新到最新版本.(如果已经是最新版本了, 则不更新)

pod repo update

更新pod资源目录,也就是master下的资源。 简单说,如果有一个第三方库发布了一个最新的版本,如果你不执行pod repo update,那么你的本地是不会知道有一个最新的版本的,还会一直以你本地的资源目录为准,那么你永远都拿不到这个库的最新版本。

但平时使用pod update是会默认执行一遍pod repo update,它会先拉取远程最新目录,再根据目录中的资源重新更新一遍pod.

所以如果不需要更新远程源的话,需要 执行pod repo update --no-repo-update

pod repo

如果有trunk源就删除

pod repo remove trunk

查看本地源 pod repo list

artsy
- Type: git (master)
- URL:  https://github.com/Artsy/Specs.git
- Path: /Users/wangpengfei/.cocoapods/repos/artsy

cocoapods
- Type: git (master)
- URL:  https://github.com/CocoaPods/Specs.git
- Path: /Users/wangpengfei/.cocoapods/repos/cocoapods

maste
- Type: git (master)
- URL:  https://mirrors.tuna.tsinghua.edu.cn/git/CocoaPods/Specs.git
- Path: /Users/wangpengfei/.cocoapods/repos/maste

trunk
- Type: CDN
- URL:  https://cdn.cocoapods.org/
- Path: /Users/wangpengfei/.cocoapods/repos/trunk

4 repos

pod env

查看当前 pod 环境信息

1. Stack
    CocoaPods : 1.10.1
         Ruby : ruby 2.6.3p62 (2019-04-16 revision 67580) [universal.x86_64-darwin20]
     RubyGems : 3.0.3
         Host : macOS 11.0.1 (20B50)
        Xcode : 12.4 (12D4e)
          Git : git version 2.24.3 (Apple Git-128)
 Ruby lib dir : /System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/lib
 Repositories : artsy - git - https://github.com/Artsy/Specs.git @ 55b2a0e46468586d3d93cd77a78dfe12684aca54

               cocoapods - git - https://github.com/CocoaPods/Specs.git @ 11fa1b295dbb4a90f4d66d6a5eedb9afb1b96b5e

               maste - git - https://mirrors.tuna.tsinghua.edu.cn/git/CocoaPods/Specs.git @ a0abf73604cb65bd66eb158d5aac5bc3366927b5

               trunk - CDN - https://cdn.cocoapods.org/


2. Installation Source
Executable Path: /usr/local/bin/pod


3. Plugins
cocoapods-deintegrate : 1.0.4
cocoapods-plugins     : 1.0.0
cocoapods-search      : 1.0.0
cocoapods-stats       : 1.1.0
cocoapods-trunk       : 1.5.0
cocoapods-try         : 1.2.0

2. Podfile

vim 文本编辑器

open podfile  -->  打开 Podfile 文件
vim podfile   -->  创建 Podfile 文件并使用 VIM 编写

:w  --> 保存
:w! --> 强行保存
:q  --> 退出
:q! --> 不保存并退出
:wq --> 保存并退出
:x  --> 保存并退出

Podfile 写法

简单写法
target 'EWDemo' 

  pod 'YYModel'

  pod 'AFNetworking', '~> 4.0.1'

普通写法
# 下面两行是指明依赖库的来源地址
source 'https://github.com/CocoaPods/Specs.git'
source 'https://github.com/Artsy/Specs.git'

# 说明平台是ios,版本是9.0
platform :ios, '9.0'

# 忽略引入库的所有警告(强迫症者的福音啊)
#inhibit_all_warnings!

# 针对 MyApp      引入AFNetworking
# 针对 MyAppTests 引入OCMock,
target 'MyApp' do 

    pod 'AFNetworking', '~> 3.0' 
    
    target 'MyAppTests' do
       inherit! :search_paths 
       pod 'OCMock', '~> 2.0.1' 
    end
end

# 指定"安装完成,但是生成的工程还没有写入磁盘"时要执行的操作.
post_install do |installer|

  installer.pods_project.targets.each do |target|
    puts "#{target.name}"
  end
  
end

Pod 指定项目依赖

依赖项规范是由Pod的名称和一个可选的版本组合一起。

pod 'YYModel' 

如果需要特定依赖库的版本,就需要在后面写上具体的版本号

pod 'Objection', '0.9'

如果需要指定版本范围

> 0.1  高于0.1版本(不包含0.1版本)的任意一个版本
>= 0.1 高于0.1版本(包含0.1版本)的任意一个版本
< 0.1  低于0.1版本(不包含0.1版本)的任意一个
<= 0.1 低于0.1版本(包含0.1版本)的任意一个

~> 0.1.2  版本 0.1.2的版本到0.2 ,不包括0.2。

Build configuraton 编译配置

默认情况下, 依赖项会被安装在所有 target 的 build configuration中。为了调试或者处于其他原因,依赖项只能在给定的build configuration中被启用。 下面写法指明只有在Debug和Beta模式下才有启用配置

pod 'PonyDebugger', :configuration => 'Debug'

pod 'PonyDebugger', :configurations => ['Debug', 'Beta']

SubSpecs

一般情况我们会通过依赖库的名称来引入,cocoapods会默认安装依赖库的所有内容。 我们也可以指定安装具体依赖库的某个子模块.

# 仅安装QueryKit库下的Attribute模块
pod 'QueryKit/Attribute'# 仅安装QueryKit下的Attribute和QuerySet模块
pod 'QueryKit', :subspecs => ['Attribute', 'QuerySet']

使用本地文件

如果我们想引入我们本地的一个库,可以如下指定依赖库地址

pod 'AFNetworking', :path => '~/Documents/AFNetworking'

使用外部文件

podspec可以从另一个源库的地址引入

pod 'JSONKit', :podspec => 'https://example.com/JSONKit.podspec'

target

在给定的块内定义pod的target(Xcode工程中的target)和指定依赖的范围。默认情况下,target会包含定义在块外的依赖,除非指定不使用inherit!来继承(说的是嵌套的块里的继承问题)

  • 单个target
target 'ZipApp' do 
  pod 'SSZipArchive'
end
  • 在 MyApp 仅引入 AFNetworking 库,MyAppTests 引入OCMock的同时也会继承 MyApp 里面的 AFNetworking 库
target 'MyApp' do 

    pod '', '~> 3.0' 
    
    target 'MyAppTests' do
       inherit! :search_paths 
       pod 'OCMock', '~> 2.0.1' 
    end
    
end
  • target块中嵌套多个子块
target 'ShowsApp' do

    # ShowsApp 仅仅引入ShowsKit
    pod 'ShowsKit' 
    
    # 引入 ShowsKit 和 ShowTVAuth 
    target 'ShowsTV' do 
        pod 'ShowTVAuth' 
    end 
    
    # 引入了Specta和Expecta以及ShowsKit
    target 'ShowsTests' do 
        inherit! :search_paths 
        pod 'Specta' 
        pod 'Expecta' 
    end
end
抽象target

定义一个新的抽象目标,它可以方便的用于目标依赖继承

  • 简单写法
abstract_target 'Networking' do

    pod 'AlamoFire' 
    
    target 'Networking App 1' 
    target 'Networking App 2'
    
end
  • 定义一种abstract_target 包含多个target
# 注意:这是个抽象的target也就是说在工程中并没有这个target引入ShowsKit

abstract_target 'Shows' do
    pod 'ShowsKit'
    
    # ShowsiOS target会引入ShowWebAuth库以及继承自Shows的ShowsKit库
    target 'ShowsiOS' do
        pod 'ShowWebAuth'
    end
    
    # ShowsTV target会引入ShowTVAuth库以及继承自Shows的ShowsKit库
    target 'ShowsTV' do
        pod 'ShowTVAuth'
    end
    
    # ShowsTests target引入了Specta和Expecta库,并且指明继承Shows,所以也会引入ShowsKit
    target 'ShowsTests' do
        inherit! :search_paths 
        pod 'Specta' 
        pod 'Expecta' 
    end
end

abstract! 和 inherit!

abstract! 表示当前的Target 是抽象的,因此不会直接链接Xcode target inherit 设置当前target的继承模式

  • :complete 目标从父节点继承所有行为。
  • :none 目标不会从父节点继承任何行为。
  • :search_paths 目标只继承父类的搜索路径。
target 'App' do
    target 'AppTests' do
       inherit! search_paths
    
    end
end

Target Configuration(目标配置)

platform

platform 用于指定应建立的表态库的平台。CocoaPods提供的默认的平台版本配置。

   iOS->4.3
   OS X->10.6
   tvOS->9.0
   watchOS->2.0
   
   如果指定具体平台和版本,则
   platform :ios, '4.0'
   platform :ios
project

如果没有显示的project被指定,那么会默认使用target的父target指定的project作为目标。如果没有任何一个target指定目标,那么就会使用和Podefile在同一目录下的project。

同样也能够指定是否这些设置在release或者debug模式下生效。为了做到这一点,你必须指定一个名字和:release/:debuge关联起来


# MyGPSApp这个target引入的库只能在 FastGPS 工程中引用
target 'MyGPSApp' do 
    project 'FastGPS' 
    ...
end

# MyNotesApp 这个target引入的库只能在 FastNotes 工程中引用
target 'MyNotesApp' do 
    project 'FastNotes' 
    ...
end

#自定义的编译配置
project 'TestProject', 'Mac App Store' => :release, 'Test' => :debug

inheib_all_warnings!

inhibit_all_warnings! 屏蔽所有来自于cocoapods依赖库的警告。 可以全局定义,也能在子target里面定义,也可以指定某一个库:


# 隐藏SSZipArchive的警告而不隐藏ShowTVAuth的警告

pod 'SSZipArchive', :inhibit_warnings => true
pod 'ShowTVAuth',   :inhibit_warnings => false

user_frameworks!

通过指定use_frameworks!要求生成的是framework而不是静态库。 如果使用use_frameworks!命令会在Pods工程下的Frameworks目录下生成依赖库的framework 如果不使用use_frameworks!命令会在Pods工程下的Products目录下生成.a的静态库

Workspace

默认情况下,我们不需要指定,直接使用与Podfile所在目录的工程名一样就可以了。如果要指定另外的名称,而不是使用工程的名称,可以这样指定

Source

source是指定pod的来源。如果不指定source,默认是使用CocoaPods官方的source。(建议使用默认设置)

CocoaPods Master Repository
# 使用其他来源地址
source 'https://github.com/artsy/Specs.git'
# 使用官方默认地址(默认)
source 'https://github.com/CocoaPods/Specs.git'

添加master源

cd ~/.cocoapods/repos
pod repo remove master
git clone https://mirrors.tuna.tsinghua.edu.cn/git/CocoaPods/Specs.git master


在工程的Podfile第一行加上(也可以不加 执行pod update操作后 会自动加上):
source 'https://mirrors.tuna.tsinghua.edu.cn/git/CocoaPods/Specs.git'

def

可以能过def 命令来声明一个Pod 集合

def 'CustomPods'
   pod 'IQKeyboardManagerSwift'
end 

然后就可以在需要引的的targget 处引入

target 'MyTarget' do
    customPods
end 

3. Podspec 文件

Specification描述了Pod库的一个版本,它包括关于应该从哪里获取源代码、使用什么文件、要应用的构建设置和其他通用元数据(如名称、版本和描述)的详细信息

3.1 创建Podspec

  • pod lib create name 按照 github 模板创建一个组件库,生成的文件中包含一个 Podspec文件,这个文件内容会比较多。

  • pod spec create name 直接创建一个 Podspec 模板文件。

Pod::Spec.new do |spec|

  # ―――--  Spec Metadata ―――-- 
 
  spec.name         = "EWDemo"                           # 库名
  spec.version      = "0.0.1"                            # 版本号
  
  spec.summary      = "A short description of EWDemo."   # 简短说明  
  spec.description  = <<-DESC
                   DESC
  spec.homepage     = "http://EXAMPLE/EWDemo"            # 主页 URL      
  

  # ―――-- Spec License  ―――--  

  spec.license      = "MIT"                              # 许可证               
  # spec.license      = { :type => "MIT", :file => "FILE_LICENSE" }


  # ―――-- Author Metadata  ―――--                         # 维护者名称邮件
 
  spec.author              = { "wangpengfei" => "wpf_register@163.com" }
  
  # spec.authors           = { "wangpengfei" => "wpf_register@163.com" }
  # spec.social_media_url  = "https://twitter.com/wangpengfei"  



  # ―――-- Platform Specifics ―――-- 

  # spec.platform     = :ios
  # spec.platform     = :ios, "5.0"
  
  #  When using multiple platforms
  # spec.ios.deployment_target = "5.0"
  # spec.osx.deployment_target = "10.7"
  # spec.watchos.deployment_target = "2.0"
  # spec.tvos.deployment_target = "9.0"


  # ―――-- Source Location ―――--                        # 检索库的位置
  
  spec.source       = { :git => "http://EXAMPLE/EWDemo.git", :tag => "#{spec.version}" }




============================ 分割线 ================================




  # ―――-- Source Code ―――-- #
  
  #  For source files
  #  giving a folder will include any swift, h, m, mm, c & cpp files.
  #  For header files it will include any header in the folder.
  #  Not including the public_header_files will make all headers public.

  spec.source_files  = "Classes", "Classes/**/*.{h,m}" 
  spec.exclude_files = "Classes/Exclude"
  # spec.public_header_files = "Classes/**/*.h"


  # ―――-- Resources ―――-- #
  
  #  A list of resources included with the Pod. These are copied into the
  #  target bundle with a build phase script. Anything else will be cleaned.
  #  You can preserve files from being cleaned, please don't preserve
  #  non-essential files like tests, examples and documentation.


  # spec.resource  = "icon.png"
  # spec.resources = "Resources/*.png"
  # spec.preserve_paths = "FilesToSave", "MoreFilesToSave"




============================ 分割线 ================================


  # ―――-- Project Linking ―――-- 

  # spec.framework  = "SomeFramework"              #当前target所需系统framework列表
  # spec.frameworks = "SomeFramework", "AnotherFramework"

  # spec.library   = "iconv"                       #当前target所需系统library列表
  # spec.libraries = "iconv", "xml2"


  # ―――-- Project Settings ―――-- #

  # spec.requires_arc = true                     

  # spec.xcconfig = { "HEADER_SEARCH_PATHS" => "$(SDKROOT)/usr/include/libxml2" }
  
  # spec.dependency "JSONKit", "~> 1.4"  

end


3.2 File patterns

  • 本地私有库配置
Pod::Spec.new do |spec|

  //本地私有库基本设置
  spec.name         = "EWCom"                                      
  spec.version      = "0.0.1"
  spec.summary      = "这是一个测试简要"
  spec.homepage     = "https://github.com/WFregister/TestDemo"
  spec.author       = { "wangfei" => "wpf_register@163.com" }
  
  spec.source       = { :git => "", :tag =>  "#{spec.version}" }
  # 框架的资源路径:路径可以指向远端代码库,也可以指向本地项目:
  # 指向远端代码库: { :git => "XXXX", :tag => "1.0.0" }
  # 指向本地项目:   { :path => 'EWCom', }

 //本地私有库至少要设置一个资源 
 
 #文件平夹及其子文件夹下所有.h.m文件
 spec.source_files  = 'EWCom/**/*.{h,m}' 

end
  • 匹配所有文件
 - c* 匹配所有以c开头的文件 
 - *c 匹配所有以c结尾的文件 
 - c 将匹配其中包含c的所有文件(包括开头或结尾)

spec.source_files = 'Classes/**/*.{h,m}'
spec.source_files = 'Classes/**/*.{h,m}', 'More_Classes/**/*.{h,m}'
  • 需要包含的源文件
public_header_files   用作公共头的文件模式列

spec.public_header_files = 'Headers/Public/*.h'


private_header_files  用来标记私有文件模式列表

spec.private_header_files = 'Headers/Private/*.h'

  • resource_bundels 为了将Pod构建为静态库,官方强烈建议使用此属性来管理资源文件,因为使用resources属性可能会发生名称冲突

spec.ios.resource_bundle = { 'MapBox' => 'MapView/Map/Resources/*.png' }

spec.resource_bundles = {
    'MapBox' => ['MapView/Map/Resources/*.png'],
    'MapBoxOtherResources' => ['MapView/Map/OtherResources/*.png']
  }

  • resources 为了将Pod构建为静态库,官方建议是使用resource_bundle,因为使用resources属性可能会发生名称冲突。
spec.resource = 'Resources/HockeySDK.bundle'
spec.resources = ['Images/*.png', 'Sounds/*']

3.3 加载资源文件

当不使用 use_frameworks!

3.3.1 使用resource
spec.resource  = spec.resource = 'EWCom/Resource/*'

WX20210331-202745@2x.png

查看产品包内容,图片资源直接在主Bundle中,

UIImage *iamge = [UIImage imageNamed:@"yin.jpg"];

iShot2021-03-31 20.31.23.png iShot2021-03-31 21.00.55.png

或者项目中直接新建bundle,可以通过传入class类型查找对应bundle 目录

-(UIImage*)getImageWithClass:(id)obj{
        
    NSBundle *bundle = [NSBundle bundleForClass:[obj class]];
    
    NSURL *bundleURL = [bundle URLForResource:@"EWCom" withExtension:@"bundle"];
    
    NSBundle *resourceBundle = [NSBundle bundleWithURL:bundleURL];
    
    NSInteger scale = [UIScreen mainScreen] scale];
    
    //完整路径
    NSString *path  = [NSString stringWithFormat:@"%@%zdx.png",name,scale];
    
    UIImage *image = [UIImage imageWithContentsOfFile:[bundle pathForResource:imgName ofType:nil];
    
    return image;
}

3.3.2 使用 resource_bundles
  spec.resource_bundles  = {'EWCom' => 'EWCom/Resource/*'}

查看产品包内容,图片资源被打包成Bundle 放在在主Bundle下 iShot2021-03-31 21.08.18.png

图片加载方式

-(UIImage*)getImageWithName:(NSString*)name{
    
    NSURL *associateBundleURL = [NSBundle mainBundle] URLForResource:@"EWCom" withExtension:@"bundle"];
    
    NSBundle *bundle = [NSBundle bundleWithURL:associateBundleURL];
    
    NSInteger scale = [UIScreen mainScreen] scale];
    
    //完整路径
    NSString *imgName = [NSString stringWithFormat:@"%@%zdx.png",name,scale];
    
    UIImage *image = [UIImage imageWithContentsOfFile:[bundle pathForResource:imgName ofType:nil];
    
    return image;
}