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

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

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

1.背景

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

 

這樣做有以下幾個(gè)問(wèn)題:

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

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

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

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

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

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

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

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

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

2.基本原理

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

因此,要想制作一個(gè)組件庫(kù),首先要知道如何制作本地的 pod 庫(kù)。

pod 庫(kù)主要由三部分組成:源碼文件、資源文件和podespec文件。源碼文件和資源文件暫且不說(shuō),每個(gè) pod 庫(kù)都要有一個(gè)名字為庫(kù)名稱(chēng).podspec的文件,官方稱(chēng)其為specification,cocoapods官方對(duì)該文件的解釋為:

?

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 庫(kù)的所有配置。包括從何處獲取源代碼、使用哪些文件、應(yīng)用構(gòu)建設(shè)置以及其他一些元數(shù)據(jù)(如名稱(chēng)、版本和描述)的詳細(xì)信息。

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

1. pod lib create xxxx

xxxx表示創(chuàng)建的文件名,這種方式比較適合從零開(kāi)始開(kāi)發(fā)一個(gè)組件,因?yàn)樗鼤?huì)自動(dòng)幫我們生成許多模版。在終端執(zhí)行以上命令,命令執(zhí)行過(guò)程中,會(huì)詢(xún)問(wèn)幾個(gè)問(wèn)題,根據(jù)實(shí)際情況和需要回答即可。這里以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í)行完成后,會(huì)在當(dāng)前目錄下創(chuàng)建一個(gè)以QRCodeReader命名的文件夾,并在文件夾內(nèi)自動(dòng)生成了QRCodeReader.podspec文件和若干模板文件,如下圖所示:

 

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

打開(kāi)Example工程,先來(lái)看下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 => '../'這行代碼,表示通過(guò)指定路徑的方式集成QRCodeReader組件。這里的QRCodeReader組件目錄位于Podfile文件的前一級(jí)目錄下。

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

?

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

#
# 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' => '[email protected]' }
  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就是要?jiǎng)?chuàng)建的podspec文件名,這種方式比較適合對(duì)現(xiàn)有模塊進(jìn)行組件化改造。執(zhí)行此命令后,僅會(huì)在當(dāng)前文件夾中創(chuàng)建一個(gè)xxxx.podspec文件,不會(huì)生成任何模版文件。此處仍以QRCodeReader為例:

lanfudong@MacBook-Pro ~ % pod spec create QRCodeReader

Specification created at QRCodeReader.podspec

打開(kāi)文件后可以看見(jiàn)里面同樣也預(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" => "[email protected]" }
  # Or just: spec.author    = "lanfudong"
  # spec.authors            = { "lanfudong" => "[email protected]" }
  # 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

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

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

3. 手動(dòng)創(chuàng)建

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

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

3.創(chuàng)建第一個(gè)組件庫(kù)

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

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

 

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

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

 

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

?Access Levels

open:最高訪(fǎng)問(wèn)級(jí)別,只能修飾類(lèi)和類(lèi)成員,允許任何地方的代碼訪(fǎng)問(wèn),允許被Module外的代碼繼承和重寫(xiě)

public:允許任何地方的代碼訪(fǎng)問(wèn),但不允許被Module外的代碼繼承和重寫(xiě)

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

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

private:最低訪(fǎng)問(wèn)級(jí)別,僅允許同一個(gè)實(shí)體內(nèi)的代碼訪(fǎng)問(wèn)

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

?

這里的組件化目錄指的是磁盤(pán)上創(chuàng)建的一個(gè)文件夾,不是 Xcode 項(xiàng)目目錄,也不要把這個(gè)目錄放到主工程的 git 倉(cāng)庫(kù)下,因?yàn)楹罄m(xù)會(huì)把該目錄提交到一個(gè)單獨(dú)的遠(yuǎn)程倉(cāng)庫(kù)管理。

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

 

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

 

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       = { "用戶(hù)名" => "你的郵箱" }
    spec.homepage     = "項(xiàng)目主頁(yè)"
    spec.source       = { :git => "項(xiàng)目git倉(cāng)庫(kù)", :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是固定語(yǔ)法,用來(lái)聲明一個(gè)新的specification,分別表示聲明的開(kāi)始和結(jié)束,中間部分設(shè)置specification的各項(xiàng)屬性。其中多數(shù)屬性可以顧名知義,這里選幾個(gè)比較重要的說(shuō)明一下。

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

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

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

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

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

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

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

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

spec.dependency:用來(lái)指定該組件所依賴(lài)的其他組件;

spec.vendored_frameworks:用來(lái)指定該組件依賴(lài)的第三方framework。

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

4.集成組件到項(xiàng)目中

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

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

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

之后我們?cè)谥鞴こ讨袌?zhí)行pod install命令,看見(jiàn)如下輸出,說(shuō)明集成成功了。

 

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

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

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

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

 

5.資源文件的管理

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

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

?

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

還記得podspec文件中的這兩個(gè)屬性么:

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

首先來(lái)具體看下這兩個(gè)屬性分別代表什么含義,以下內(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.

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

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

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

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

因?yàn)樵赑odfile中使用了use_frameworks!,組件最終會(huì)以 framework 的形式集成到 App 包中,而 bundle 文件位于 App 的"
/Frameworks/QRCodeReader.framework/QRCodeReader.bundle"目錄下,我們可以通過(guò)如下代碼來(lái)加載指定 bundle 中的圖標(biāo)資源:

// App main bundle 根路徑
let mainPath = Bundle.main.resourcePath 
// QRCodeReader.bundle的相對(duì)路徑
let pathComponent = "/Frameworks/QRCodeReader.framework/QRCodeReader.bundle"
// 獲取bundle對(duì)象
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) // 兜底策略
    }
}

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

let pathComponent = "/(bundleName).bundle"

其余部分不變。

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

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)
}

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

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

6.子組件

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

specification提供了subspec屬性專(zhuān)門(mén)用來(lái)配置 sub-specifications,下面是Cocoapods官網(wǎng)對(duì)于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描述了該組件庫(kù)中的一個(gè)子組件。具有兩層含義,一是,除非特殊說(shuō)明,所有的子組件都是依賴(lài)其父組件;二是,子組件繼承了父組件的屬性值。

從描述中可以看出,一個(gè)組件內(nèi)可以包含若干個(gè)子組件。以《跟客寶》為例,除了QRCodeReader,我們將項(xiàng)目中其他的功能組件也都獨(dú)立了出來(lái)放到統(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來(lái)聲明一個(gè)子組件,并配置它的源文件路徑。這里聲明了四個(gè)子組件,無(wú)論是父組件還是子組件,都需要對(duì)應(yīng)的podspec文件,子組件的podspec文件編寫(xiě)方式與之前介紹的一致,此處不再介紹。

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

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

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

7.結(jié)束語(yǔ)

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

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

這些都是實(shí)施一個(gè)完整的組件化方案所必須的,不過(guò)組件化是一個(gè)循序漸進(jìn)的過(guò)程,方案的內(nèi)容也會(huì)不斷地更新和完善。本篇僅作為組件化探索的初級(jí)方案供大家參考,隨著項(xiàng)目組件化的不斷深入,針對(duì)上述的幾個(gè)問(wèn)題再為大家?guī)?lái)更加完善的管理方案!

8.參考文檔

1.https://cocoapods.org


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

 

作者:蘭福東

來(lái)源:微信公眾號(hào):搜狐技術(shù)產(chǎn)品

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

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

網(wǎng)友整理

注冊(cè)時(shí)間:

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

  • 51998

    網(wǎng)站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會(huì)員

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

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

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

答題星2018-06-03

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

全階人生考試2018-06-03

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

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

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

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

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

體育訓(xùn)練成績(jī)?cè)u(píng)定2018-06-03

通用課目體育訓(xùn)練成績(jī)?cè)u(píng)定