日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網(wǎng)為廣大站長提供免費收錄網(wǎng)站服務(wù),提交前請做好本站友鏈:【 網(wǎng)站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(wù)(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網(wǎng)站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

1.背景

  就目前而言,IOS 項目的組件化在業(yè)內(nèi)已經(jīng)有比較成熟的方案了。雖然各個公司都有自己的組件化方案,但這些方案的具體實現(xiàn)方式也都大同小異。截止到本次組件化改造之前,我所在的 iOS 開發(fā)團隊尚未對項目進(jìn)行組件化改造,單個模塊在多個項目中的復(fù)用仍使用手動復(fù)制遷移的方式。現(xiàn)有的一些功能模塊也基本是使用 OC 語言開發(fā)的。如下圖所示,假如現(xiàn)有項目使用了功能模塊A,而功能模塊A又依賴功能模塊B,此時有新項目也要使用功能模塊A,就需要將功能模塊A和功能模塊B的源碼全部手動復(fù)制到新項目的工程中。

 

這樣做有以下幾個問題:

  1. 不利于模塊的統(tǒng)一管理

如果有N個項目依賴同一個模塊,就會有N個該模塊的實體副本分散的存在于N個項目工程中。如果該模塊有內(nèi)容更新,就需要對全部的這N個模塊副本進(jìn)行更新,不僅操作起來十分麻煩,也很容易產(chǎn)生遺漏。

  1. 不利于模塊的獨立調(diào)試

在這種管理方式下,模塊依附于其宿主工程而存在,要想調(diào)試模塊的功能,需要打開宿主工程并在其上進(jìn)行調(diào)試。大于大型項目而言,主工程的編譯和運行往往需要較長時間。

  1. 難以維護(hù)、不可持續(xù)

業(yè)務(wù)邏輯和功能模塊之間、功能模塊和功能模塊之間沒有嚴(yán)格的界限,代碼耦合程度完全依靠編碼人員自身素質(zhì)決定。隨著版本的不斷迭代,加之開發(fā)人員的更替,項目代碼將快速劣化變得難以維護(hù)。

以上只是列舉了幾點這種管理代碼復(fù)用方式的不足之處,此外還有模塊版本管理、自動化等其他問題,就不一一展開說明了。

鑒于 Swift 語言的高效率和安全性,業(yè)內(nèi)對其的應(yīng)用也越來越廣泛,團隊內(nèi)的一些新 App 以及功能模塊逐漸開始使用 Swift 開發(fā),這些老舊的功能模塊也將逐漸被取代。此外,隨著部門內(nèi)開發(fā)和維護(hù)的 App 越來越多,組件化作為一項基礎(chǔ)設(shè)施急需得到落實,不僅方便開發(fā)人員進(jìn)行項目管理,也可以方便的進(jìn)行組件在多個項目中的復(fù)用,提升開發(fā)效率。

目前焦點 iOS 組件化已初見成效,本篇文章將通過其中一個組件的改造實踐作為案例,給大家介紹一下進(jìn)行組件化改造的基本流程。

2.基本原理

開始之前我先為大家說明一下我們組件化的基本原理。我們采取的組件化方案是基于cocoapods實現(xiàn)的,也是業(yè)內(nèi)使用比較普遍的一種方案。最終效果是,將我們希望實施組件化的模塊從主工程中解耦出來成為獨立組件,并制作成本地 pod 庫,再通過cocoapods集成到項目中,被獨立出來的組件使用單獨的 git 倉庫管理。類似于使用 cocoapods 集成第三方庫一樣,只不過我們的組件庫是一個本地的 pod 庫。

因此,要想制作一個組件庫,首先要知道如何制作本地的 pod 庫。

pod 庫主要由三部分組成:源碼文件、資源文件和podespec文件。源碼文件和資源文件暫且不說,每個 pod 庫都要有一個名字為庫名稱.podspec的文件,官方稱其為specification,cocoapods官方對該文件的解釋為:

?

A specification describes a version of Pod library. It includes details about where the source should be fetched from, what files to use, the build settings to apply, and other general metadata such as its name, version, and description.

大意為:該文件描述了關(guān)于 pod 庫的所有配置。包括從何處獲取源代碼、使用哪些文件、應(yīng)用構(gòu)建設(shè)置以及其他一些元數(shù)據(jù)(如名稱、版本和描述)的詳細(xì)信息。

可以通過三種方式來創(chuàng)建podspec文件:

1. pod lib create xxxx

xxxx表示創(chuàng)建的文件名,這種方式比較適合從零開始開發(fā)一個組件,因為它會自動幫我們生成許多模版。在終端執(zhí)行以上命令,命令執(zhí)行過程中,會詢問幾個問題,根據(jù)實際情況和需要回答即可。這里以QRCodeReader為例:

lanfudong@macBook-Pro ~ % pod lib create QRCodeReader 
Cloning `https://github.com/CocoaPods/pod-template.git` into `QRCodeReader`.
Configuring QRCodeReader template.
security: SecKeychainSearchCopyNext: The specified item could not be found in the keychain.

------------------------------

To get you started we need to ask a few questions, this should only take a minute.

If this is your first time we recommend running through with the guide: 
 - https://guides.cocoapods.org/making/using-pod-lib-create.html
 ( hold cmd and double click links to open in a browser. )

What platform do you want to use?? [ iOS / macOS ]
 > iOS

What language do you want to use?? [ Swift / ObjC ]
 > Swift

Would you like to include a demo application with your library? [ Yes / No ]
 > Yes

Which testing frameworks will you use? [ Quick / None ]
 > None

Would you like to do view based testing? [ Yes / No ]
 > No

執(zhí)行完成后,會在當(dāng)前目錄下創(chuàng)建一個以QRCodeReader命名的文件夾,并在文件夾內(nèi)自動生成了QRCodeReader.podspec文件和若干模板文件,如下圖所示:

 

其中QRCodeReader文件夾中存放的就是該組件的源碼和資源文件。Example文件夾下是該命令幫我們創(chuàng)建的一個示例工程,Example工程默認(rèn)已經(jīng)集成了新創(chuàng)建的組件,我們可以直接在Example工程的基礎(chǔ)上進(jìn)行編碼。_Pods.xcodeproj文件是Example文件夾下的QRCodeReader.xcodeproj文件的替身。

打開Example工程,先來看下Podfile文件:

use_frameworks!

platform :ios, '10.0'

target 'QRCodeReader_Example' do
  pod 'QRCodeReader', :path => '../'

  target 'QRCodeReader_Tests' do
    inherit! :search_paths

  end
end

主要看pod 'QRCodeReader', :path => '../'這行代碼,表示通過指定路徑的方式集成QRCodeReader組件。這里的QRCodeReader組件目錄位于Podfile文件的前一級目錄下。

再來看QRCodeReader.podspec文件,內(nèi)部已經(jīng)自動填充了代碼模版,關(guān)于每行代碼的具體含義,我們后續(xù)再著重介紹。

?

podspec其實是一個ruby語言的腳本文件,里面的文本內(nèi)容也都是ruby代碼。這里不需要我們懂得ruby語言,只要能讀懂其大體含義即可。

#
# Be sure to run `pod lib lint QRCodeReader.podspec' to ensure this is a
# valid spec before submitting.
#
# Any lines starting with a # are optional, but their use is encouraged
# To learn more about a Podspec see https://guides.cocoapods.org/syntax/podspec.html
#

Pod::Spec.new do |s|
  s.name             = 'QRCodeReader'
  s.version          = '0.1.0'
  s.summary          = 'A short description of QRCodeReader.'

# This description is used to generate tags and improve search results.
#   * Think: What does it do? Why did you write it? What is the focus?
#   * Try to keep it short, snappy and to the point.
#   * Write the description between the DESC delimiters below.
#   * Finally, don't worry about the indent, CocoaPods strips it!

  s.description      = <<-DESC
TODO: Add long description of the pod here.
                       DESC

  s.homepage         = 'https://github.com/lanfudong/QRCodeReader'
  # s.screenshots     = 'www.example.com/screenshots_1', 'www.example.com/screenshots_2'
  s.license          = { :type => 'MIT', :file => 'LICENSE' }
  s.author           = { 'lanfudong' => 'lanfudong214839@sohu-inc.com' }
  s.source           = { :git => 'https://github.com/lanfudong/QRCodeReader.git', :tag => s.version.to_s }
  # s.social_media_url = 'https://Twitter.com/<TWITTER_USERNAME>'

  s.ios.deployment_target = '10.0'

  s.source_files = 'QRCodeReader/Classes/**/*'
  
  # s.resource_bundles = {
  #   'QRCodeReader' => ['QRCodeReader/Assets/*.png']
  # }

  # s.public_header_files = 'Pod/Classes/**/*.h'
  # s.frameworks = 'UIKit', 'MapKit'
  # s.dependency 'A.NETworking', '~> 2.3'
end

2. pod spec create xxxx

xxxx就是要創(chuàng)建的podspec文件名,這種方式比較適合對現(xiàn)有模塊進(jìn)行組件化改造。執(zhí)行此命令后,僅會在當(dāng)前文件夾中創(chuàng)建一個xxxx.podspec文件,不會生成任何模版文件。此處仍以QRCodeReader為例:

lanfudong@MacBook-Pro ~ % pod spec create QRCodeReader

Specification created at QRCodeReader.podspec

打開文件后可以看見里面同樣也預(yù)填充了代碼模板和注釋。

#
#  Be sure to run `pod spec lint QRCodeReader.podspec' to ensure this is a
#  valid spec and to remove all comments including this before submitting the spec.
#
#  To learn more about Podspec attributes see https://guides.cocoapods.org/syntax/podspec.html
#  To see working Podspecs in the CocoaPods repo see https://github.com/CocoaPods/Specs/
#

Pod::Spec.new do |spec|

  # ―――  Spec Metadata  ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
  #
  #  These will help people to find your library, and whilst it
  #  can feel like a chore to fill in it's definitely to your advantage. The
  #  summary should be tweet-length, and the description more in depth.
  #

  spec.name         = "QRCodeReader"
  spec.version      = "0.0.1"
  spec.summary      = "A short description of QRCodeReader."

  # This description is used to generate tags and improve search results.
  #   * Think: What does it do? Why did you write it? What is the focus?
  #   * Try to keep it short, snappy and to the point.
  #   * Write the description between the DESC delimiters below.
  #   * Finally, don't worry about the indent, CocoaPods strips it!
  spec.description  = <<-DESC
                   DESC

  spec.homepage     = "http://EXAMPLE/QRCodeReader"
  # spec.screenshots  = "www.example.com/screenshots_1.gif", "www.example.com/screenshots_2.gif"


  # ―――  Spec License  ――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
  #
  #  Licensing your code is important. See https://choosealicense.com for more info.
  #  CocoaPods will detect a license file if there is a named LICENSE*
  #  Popular ones are 'MIT', 'BSD' and 'Apache License, Version 2.0'.
  #

  spec.license      = "MIT (example)"
  # spec.license      = { :type => "MIT", :file => "FILE_LICENSE" }


  # ――― Author Metadata  ――――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
  #
  #  Specify the authors of the library, with email addresses. Email addresses
  #  of the authors are extracted from the SCM log. E.g. $ git log. CocoaPods also
  #  accepts just a name if you'd rather not provide an email address.
  #
  #  Specify a social_media_url where others can refer to, for example a twitter
  #  profile URL.
  #

  spec.author             = { "lanfudong" => "lanfudong214839@sohu-inc.com" }
  # Or just: spec.author    = "lanfudong"
  # spec.authors            = { "lanfudong" => "lanfudong214839@sohu-inc.com" }
  # spec.social_media_url   = "https://twitter.com/lanfudong"

  # ――― Platform Specifics ――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
  #
  #  If this Pod runs only on iOS or OS X, then specify the platform and
  #  the deployment target. You can optionally include the target after the platform.
  #

  # 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 ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
  #
  #  Specify the location from where the source should be retrieved.
  #  Supports git, hg, bzr, svn and HTTP.
  #

  spec.source       = { :git => "http://EXAMPLE/QRCodeReader.git", :tag => "#{spec.version}" }


  # ――― Source Code ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
  #
  #  CocoaPods is smart about how it includes 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 ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
  #
  #  Link your library with frameworks, or libraries. Libraries do not include
  #  the lib prefix of their name.
  #

  # spec.framework  = "SomeFramework"
  # spec.frameworks = "SomeFramework", "AnotherFramework"

  # spec.library   = "iconv"
  # spec.libraries = "iconv", "xml2"


  # ――― Project Settings ――――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
  #
  #  If your library depends on compiler flags you can set them in the xcconfig hash
  #  where they will only apply to your library. If you depend on other Podspecs
  #  you can include multiple dependencies to ensure it works.

  # spec.requires_arc = true

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

end

本篇文章不會逐一對里面的內(nèi)容進(jìn)行講解,在接下來的篇章中會結(jié)合具體案例來講解其中幾個比較重要的部分。其余部分讀者可參照官方文檔進(jìn)行閱讀

https://guides.cocoapods.org/syntax/podspec.html#specification

3. 手動創(chuàng)建

如果你對podspec文件已經(jīng)非常熟悉了,可以直接手動創(chuàng)建,或者復(fù)制一份現(xiàn)有的并在其基礎(chǔ)上進(jìn)行修改。

總結(jié):如果你準(zhǔn)備從零開始開發(fā)一個新的組件,那么適合使用第一種方式來初始化組件化工程,它會幫你自動生成一些列的模版文件和代碼,可以直接在Example工程上進(jìn)行組件的開發(fā)和調(diào)試;如果你是想對現(xiàn)有的模塊進(jìn)行組件化改造,已經(jīng)存在了源碼和資源等文件,那么適合使用第二或第三種方式來初始化組件化工程。

3.創(chuàng)建第一個組件庫

本文主要講對現(xiàn)有項目進(jìn)行組件化改造,這里仍然以QRCodeReader為例,繼續(xù)為大家講解。

還記得上面提到的pod庫三大組成部分嗎?源碼 + 資源 + podspec 文件,接下來要搞定源碼文件。QRCodeReader的源碼文件目前還在項目的主工程里面,若想將其獨立出來,我們需要將其進(jìn)行適當(dāng)?shù)男薷模譃閮蓚€方面:

 

其一:解耦合。對于像QRCodeReader這種比較簡單的組件,幾乎沒什么耦合,可直接將其從主工程中移出來。文件被移出后自然會出現(xiàn)“某某類或某某方法找不到“之類的錯誤,先不要著急,等完成組件化配置后再來解決此類編譯問題;若是比較龐大的組件,耦合性較高,最好是先理清依賴關(guān)系,完成解耦。

對于復(fù)雜系統(tǒng),解耦合的主要思路是使用中間件橋接,即各個組件間不直接相互訪問,而是都通過一個中間者來實現(xiàn),這就要求每個組件將自身所具有的能力注冊到中間件中,是六大設(shè)計原則之一的依賴倒轉(zhuǎn)原則的具體實現(xiàn)。如下圖所示,組價A和組件B彼此獨立,組件A通過中間件來訪問組件B。這里僅提供一種思路,感興趣的讀者可以自行搜索相關(guān)資料閱讀,本篇不做過多介紹。

 

其二:修改訪問級別(Access Levels)。對于 Swift 來說,我們聲明的類和方法等的訪問級別默認(rèn)都是internal的,即只能供同一 Module 內(nèi)的文件訪問。進(jìn)行組件化改造后,我們制作的組件會成為一個獨立的 Module,因此需要將組件內(nèi)暴露給外部的類、方法、屬性等設(shè)置為public或open,才能被其他 Module 訪問。

?Access Levels

open:最高訪問級別,只能修飾類和類成員,允許任何地方的代碼訪問,允許被Module外的代碼繼承和重寫

public:允許任何地方的代碼訪問,但不允許被Module外的代碼繼承和重寫

internal:默認(rèn)訪問級別,允許Module內(nèi)任何地方的代碼訪問,Module外無法訪問

fileprivate:僅允許同一個源文件內(nèi)的代碼訪問

private:最低訪問級別,僅允許同一個實體內(nèi)的代碼訪問

完成這兩步后就可以把源碼文件移動到組件化目錄下了,大家可根據(jù)需要把”組件化目錄“放到磁盤上的任意位置,這里我將其放到和主工程同級的目錄下:

?

這里的組件化目錄指的是磁盤上創(chuàng)建的一個文件夾,不是 Xcode 項目目錄,也不要把這個目錄放到主工程的 git 倉庫下,因為后續(xù)會把該目錄提交到一個單獨的遠(yuǎn)程倉庫管理。

前面已經(jīng)完成了podspec文件的創(chuàng)建,現(xiàn)在開始編輯其內(nèi)容,資源文件留到最后處理。需要注意的是,podspec文件必須與組件庫同名且放在其根目錄下。

 

我這里使用第三種方法手動創(chuàng)建的QRCodeReader.podspec文件,文件內(nèi)容我已經(jīng)編輯好,一起來看下:

 

Pod::Spec.new do |spec|

    spec.name         = "QRCodeReader"
    spec.version      = "0.0.1"
    spec.summary      = "掃碼組件"
    spec.description  = <<-DESC
                     掃碼組件
                      DESC
    spec.license      = { :type => "MIT" }
    spec.author       = { "用戶名" => "你的郵箱" }
    spec.homepage     = "項目主頁"
    spec.source       = { :git => "項目git倉庫", :tag => "#{spec.version}" }
    spec.platform     = :ios, "11.0"

    spec.swift_versions = ['5.0', '5.1', '5.2', '5.3']

    spec.source_files    = "*.{swift,m,mm,c,cpp,h}"
    # spec.resources     = "Resources/**/*.{xcassets,json,plist}"
    spec.resource_bundle = { "QRCodeReader" => "Resources/**/*.{xcassets,json,plist}" }

    spec.xcconfig = {
        'DEFINES_MODULE' => 'YES'
    }

    spec.frameworks = "AudioToolbox", "AVFoundation"
    
end

Pod::Spec.new do |spec|和end是固定語法,用來聲明一個新的specification,分別表示聲明的開始和結(jié)束,中間部分設(shè)置specification的各項屬性。其中多數(shù)屬性可以顧名知義,這里選幾個比較重要的說明一下。

spec.platform:指定應(yīng)用的平臺和系統(tǒng)版本。案例中的含義是應(yīng)用于 iOS 系統(tǒng) 11.0 及以上版本;

spec.swift_versions:指定使用的 Swift 語言版本,值是一個數(shù)組;

spec.source_files:指定源代碼文件的路徑和文件名,等號右邊可使用通配符的方式指定。案例中的含義是:源碼文件包含當(dāng)前目錄下的所有文件類型為 swift、m、mm、c、cpp、h 的文件;

spec.resources:指定資源文件的路徑和文件名,等號右邊可使用通配符的方式指定。案例中的含義是:資源文件包含當(dāng)前目錄下的Resources目錄下的所有文件類型為 xcassets、json、plist 的文件。后續(xù)我們提取資源文件的時候會將其放到Resources目錄下;

spec.xcconfig:對 pod 庫進(jìn)行配置,配置的內(nèi)容最終會反應(yīng)到 pod 庫的Build Settings上。案例中的含義是:配置 pod 庫的Defines Module為 YES。除了DEFINES_MODULE外,還有很多內(nèi)容可以配置,這里不一一介紹;

spec.frameworks:指定需要鏈接的系統(tǒng)動態(tài)庫。

此外還有些案例中沒有提及的但也比較常用的屬性有:

spec.libraries:用來指定鏈接的系統(tǒng)靜態(tài)庫;

spec.dependency:用來指定該組件所依賴的其他組件;

spec.vendored_frameworks:用來指定該組件依賴的第三方framework。

上面例舉的都是常用屬性,Cocoapods提供了非常多的屬性供我們使用,讀者可參照官方文檔進(jìn)行閱讀:
https://guides.cocoapods.org/syntax/podspec.html#specification

4.集成組件到項目中

修改Podfile文件,添加如下代碼:

pod 'QRCodeReader', :path => '../QRCodeReader'

:path后面的路徑即為組件庫相對于Podfile文件的位置,還記得前面我們把組件庫放到和主工程同級目錄了嗎?這里的含義是”集成QRCodeReader組件,組件位于Podfile文件上級目錄中的QRCodeReader目錄“。

之后我們在主工程中執(zhí)行pod install命令,看見如下輸出,說明集成成功了。

 

打開項目后,可以看見在左側(cè)導(dǎo)航區(qū) Pods 工程下多了一個Development Pods目錄,我們集成的QRCodeReader組件就在這里面了。

編譯項目后可能會出現(xiàn)“Cannot find 'xxxx' in scope”等找不到類或方法的問題,這是因為這些類和方法已經(jīng)被我們封裝成為獨立 Module,主工程若想訪問其中的內(nèi)容需要先引入該 Module。

在使用了掃碼組件的文件中添加代碼“import QRCodeReader”,編譯通過~

需要注意的是,不同于在Podfile中使用pod 'xxx', "~> 1.0.0"的方式集成第三方庫,通過指定組件庫本地路徑的方式集成,組件庫中的文件不會被拷貝到主工程中,主工程中的組件庫只是一個”引用“,類似于面向?qū)ο缶幊讨械?rdquo;對象指針“的概念。

 

5.資源文件的管理

QRCodeReader組件里包含一個掃碼界面,會用到一些圖標(biāo)資源。若把這些圖標(biāo)保留在主工程的Assets中,雖然仍然可以訪問到,可一旦我們將QRCodeReader組件集成到其他項目中,這些圖標(biāo)資源就不能被自動集成過去,需要我們手動遷移。這樣做非常麻煩不說,也和容易發(fā)生遺漏。

因此,最好的方式是將這些圖標(biāo)資源轉(zhuǎn)移到對應(yīng)的組件庫中,并由組件庫統(tǒng)一管理。

?

QRCodeReader組件里用到的資源文件只有圖標(biāo),其他可能的資源文件還有plist、json、xib等文件,視實際情況而定。

還記得podspec文件中的這兩個屬性么:

spec.resources       = "Resources/**/*.{xcassets,json,plist}"
spec.resource_bundle = { "QRCodeReader" => "Resources/**/*.{xcassets,json,plist}" }

首先來具體看下這兩個屬性分別代表什么含義,以下內(nèi)容引用自Cocoapods官方文檔:

?resources

A list of resources that should be copied into the target bundle.

For building the Pod as a static library, we strongly recommend library developers to adopt resource bundles as there can be name collisions using the resources attribute. Moreover, resources specified with this attribute are copied directly to the client target and therefore they are not optimised by Xcode.

?resource_bundles

This attribute allows to define the name and the file of the resource bundles which should be built for the Pod. They are specified as a hash where the keys represent the name of the bundles and the values the file patterns that they should include.

這倆個屬性選擇其中一個使用即可,二者最主要的區(qū)別在于 resource_bundles 會創(chuàng)建獨立的 bundle,能夠有效解決資源文件重名問題,也是 CocoaPods 強烈建議使用的屬性。resource 屬性則不會創(chuàng)建屬于 Pod 庫自己的 bundle,打包時資源文件會被拷貝到 main bundle 里。

這里我們使用resource_bundles屬性,=>符號左側(cè)表示 bundle 的名字,建議 bundle 名字中至少要包含 Pod 庫的名字以最大限度的防止命名沖突。=>符號右側(cè)是資源文件的路徑。

首先在 QRCodeReader 根目錄下創(chuàng)建Resources文件夾,用于存放資源文件,并在其中創(chuàng)建一個Assets.xcassets文件夾用于管理圖標(biāo)資源。之后在主工程運行pod install,打開工程后可以看見在QRCodeReader目錄下面多了一個Assets,把組件用到的圖標(biāo)從主工程中移除并添加到組件庫的Assets中。

這時編譯運行起來后會發(fā)現(xiàn)這些圖標(biāo)沒有加載成功,原因在于我們使用的UIImage(named: String)方法只會在main bundle中查找圖片資源,而我們之前的操作把圖標(biāo)資源放到了一個單獨的 bundle 中,因此在加載圖片時需要指定圖片所在的 bundle。

因為在Podfile中使用了use_frameworks!,組件最終會以 framework 的形式集成到 App 包中,而 bundle 文件位于 App 的"
/Frameworks/QRCodeReader.framework/QRCodeReader.bundle"目錄下,我們可以通過如下代碼來加載指定 bundle 中的圖標(biāo)資源:

// App main bundle 根路徑
let mainPath = Bundle.main.resourcePath 
// QRCodeReader.bundle的相對路徑
let pathComponent = "/Frameworks/QRCodeReader.framework/QRCodeReader.bundle"
// 獲取bundle對象
let bundle = Bundle(path: mainPath + pathComponent)
// 獲取圖片資源
let image = UIImage(named: imageName, in: bundle, compatibleWith: nil)

稍作封裝:

func image(named: String, in bundleName: String) -> UIImage? {
    let mainPath = Bundle.main.resourcePath
    let pathComponent = "/Frameworks/(bundleName).framework/(bundleName).bundle"
    let bundle = Bundle(path: mainPath + pathComponent)
    if let image = UIImage(named: named, in: bundle, compatibleWith: nil) {
       return image
    } else {
       return UIImage(named: named) // 兜底策略
    }
}

如果沒有使用use_frameworks!,而是采用靜態(tài)庫的形式集成, bundle 文件會位于 App main bundle 的根目錄下。此時上述代碼的pathComponent應(yīng)該修改為:

let pathComponent = "/(bundleName).bundle"

其余部分不變。

為了能夠兼容使用靜態(tài)庫和使用動態(tài)庫兩種情況,我們把兩種情況下加載圖片的代碼合并處理:

public func image(named: String, in bundleName: String) -> UIImage? {
    if let image = _dynamicImage(named: named, in: bundleName) {
        return image
    } else if let image = _staticImage(named: named, in: bundleName) {
        return image
    } else {
        return UIImage(named: named)
    }
}

private func _staticImage(named: String, in bundleName: String) -> UIImage? {
    let pathComponent = "/(bundleName).bundle"
    return _image(named: named, with: pathComponent)
}

private func _dynamicImage(named: String, in bundleName: String) -> UIImage? {
    let pathComponent = "/Frameworks/(bundleName).framework/(bundleName).bundle"
    return _image(named: named, with: pathComponent)
}

private func _image(named: String, with pathComponent: String) -> UIImage? {
    guard let mainPath = Bundle.main.resourcePath else { return nil }

    let path = mainPath + pathComponent
    let bundle = Bundle(path: path)
    return UIImage(named: named, in: bundle, compatibleWith: nil)
}

對于其他類型資源文件的加載,大家可參考圖片加載方式自行實現(xiàn),這里就不一一介紹了。

到此為止,一個最基本的組件化改造實踐就完成了,由于案例中的QRCodeReader作為演示,本身體量較小且和外部耦合性不強,整個改造過程比較順利。實際項目中尤其是對業(yè)務(wù)進(jìn)行組件化改造時,往往需要處理復(fù)雜的依賴關(guān)系,以及管理類和接口的訪問級別。

6.子組件

前文已經(jīng)提到了,QRCodeReader本身體量很小,實際開發(fā)中不足以單獨成立一個組件。但是又存在將其獨立出來的必要性,這種情況在項目中并不少見。這時,我們可以將它們配置成sub-specifications(暫且稱其為子組件),并放到同一個組件庫中。

specification提供了subspec屬性專門用來配置 sub-specifications,下面是Cocoapods官網(wǎng)對于subspec屬性的介紹:

?subspec

Represents specification for a module of the library.

Subspecs participate on a dual hierarchy.

On one side, a specification automatically inherits as a dependency all it children ‘sub-specifications’ (unless a default subspec is specified).

On the other side, a ‘sub-specification’ inherits the value of the attributes of the parents so common values for attributes can be specified in the ancestors.

大意為:subspec描述了該組件庫中的一個子組件。具有兩層含義,一是,除非特殊說明,所有的子組件都是依賴其父組件;二是,子組件繼承了父組件的屬性值。

從描述中可以看出,一個組件內(nèi)可以包含若干個子組件。以《跟客寶》為例,除了QRCodeReader,我們將項目中其他的功能組件也都獨立了出來放到統(tǒng)一的父組件內(nèi),并將這些功能組件配置成為子組件,父組件命名為FocusUtility。

在FocusUtility.podspec文件中添加以下配置:

spec.subspec 'QRCodeReader' do |reader|
    reader.source_files = 'QRCodeReader/**/*.{swift,m,c,h}'
end

spec.subspec 'ImageViewer' do |viewer|
    viewer.source_files = 'ImageViewer/**/*.{swift,m,c,h}'
end

spec.subspec 'ImagePicker' do |picker|
    picker.source_files = 'ImagePicker/**/*.{swift,m,c,h}'
end

spec.subspec 'DateRangePicker' do |picker|
    picker.source_files = 'DateRangePicker/**/*.{swift,m,c,h}'
end

使用spec.subspec來聲明一個子組件,并配置它的源文件路徑。這里聲明了四個子組件,無論是父組件還是子組件,都需要對應(yīng)的podspec文件,子組件的podspec文件編寫方式與之前介紹的一致,此處不再介紹。

在集成FocusUtility組件時,可以選擇集成它的全部子組件,也可以只集成它的部分子組件。

# 集成所有子組件
pod 'FocusUtility', :path => '../FocusUtility'
# 僅集成其中的 QRCodeReader 組件
pod 'FocusUtility/QRCodeReader', :path => '../FocusUtility'

這樣就把零碎的功能組件集中起來,相較于每個小組件都制作成獨立組件,子組件的方式無疑降低了管理復(fù)雜度。

7.結(jié)束語

到此為止,一個最簡單的組件化實踐流程就結(jié)束了。為什么說是最簡單的呢?

  1. 組件體量小;
  2. 不存在復(fù)雜依賴關(guān)系;
  3. 僅考慮 Swift 實現(xiàn),未加入 OC 支持;
  4. 一人對項目實施組件化后,未考慮如何讓組內(nèi)其他人也快速的完成組件化;
  5. 沒有組件的版本管理;
  6. 沒有組件間的依賴關(guān)系管理等。

這些都是實施一個完整的組件化方案所必須的,不過組件化是一個循序漸進(jìn)的過程,方案的內(nèi)容也會不斷地更新和完善。本篇僅作為組件化探索的初級方案供大家參考,隨著項目組件化的不斷深入,針對上述的幾個問題再為大家?guī)砀油晟频墓芾矸桨福?/p>

8.參考文檔

1.https://cocoapods.org


2.https://dreampiggy.com/2018/11/26/CocoaPods的資源管理和Asset%20Catalog優(yōu)化/

 

作者:蘭福東

來源:微信公眾號:搜狐技術(shù)產(chǎn)品

出處
:https://mp.weixin.qq.com/s/Nk2ig6SV-qG2Q90lwBOO4w

分享到:
標(biāo)簽:iOS
用戶無頭像

網(wǎng)友整理

注冊時間:

網(wǎng)站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網(wǎng)站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網(wǎng)站吧!
最新入駐小程序

數(shù)獨大挑戰(zhàn)2018-06-03

數(shù)獨一種數(shù)學(xué)游戲,玩家需要根據(jù)9

答題星2018-06-03

您可以通過答題星輕松地創(chuàng)建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學(xué)四六

運動步數(shù)有氧達(dá)人2018-06-03

記錄運動步數(shù),積累氧氣值。還可偷

每日養(yǎng)生app2018-06-03

每日養(yǎng)生,天天健康

體育訓(xùn)練成績評定2018-06-03

通用課目體育訓(xùn)練成績評定