而這篇文章將會結(jié)合一個使用泛型編程的適配工具來談?wù)劮盒偷母唠A玩法。
懸念: 我們希望如下圖般的,在不同尺寸的設(shè)備適配不同的封面圖及文本。
而且,我們期望效果代碼越簡單越好,可讀性越高越好,像下面一樣就能達(dá)到效果:
ScreenFeatureManager.shared
.adapt(toDevice: .iPhone(inch35: 30, inch40: 40, inch47: 50, inch55: 60))
那么,我們該怎么做呢?在此之前,先介紹下即將使用到的泛型函數(shù)。
Swift 標(biāo)準(zhǔn)庫中的泛型函數(shù)
其實,如果你深諳函數(shù)式編程,那么你對這些泛型函數(shù)應(yīng)該了如指掌,如果你了解且喜歡上了函數(shù)式編程,何不使用 RxSwift 進(jìn)行函數(shù)響應(yīng)式編程呢?這里有幾篇 RxSwift 開發(fā)的實戰(zhàn),望有助于大家進(jìn)一步深入認(rèn)識 RxSwift 函數(shù)響應(yīng)式開發(fā):
- <薦> RxSwift + ReactorKit 構(gòu)建信息流框架
- 使用 RxSwift 構(gòu)建不同風(fēng)格的閱讀模式(附 Demo)
- 一年半開發(fā)經(jīng)驗,使用 RxSwift 構(gòu)建一個項目的基本框架,這種姿勢足夠優(yōu)雅嗎?
以上皆為實戰(zhàn)篇,往后會出其 知識點講解篇。
Map:
Map 函數(shù)一般是接受一個給定的數(shù)組,然后通過一些非降維的計算處理,得到并返回一個新的數(shù)組。
蘋果官方定義:
extension Array {
func map<T>( transform: Element -> T) -> [T] {
var result: [T] = []
for x in self {
result.append(transform(x))
}
return result
}
}
在 Map 中定義一個泛型類,經(jīng)過 transform 閉包函數(shù)處理之后,通過泛型數(shù)組去拿到處理后的新數(shù)據(jù),成為新的數(shù)組。
應(yīng)用
將以下數(shù)組中的每個元素增加1后輸出
let objects: [Int] = [1, 2, 3]
先使用熟悉不過的 For 循環(huán)
var newObjects: [Int] = []
for object in objects {
let newObject = object + 1
newObjects.append(newObject)
}
接下來,使用 Map 函數(shù)
// objects.map { newObject in return newObject + 1 }
// 上面是完整的 Map 函數(shù)編寫,但如果閉包中的代碼比較簡單,我們都會省略 return,如下:
objects.map { newObject in newObject + 1 }
可以看到,四行的的代碼塊經(jīng) Map 函數(shù)處理之后,成為了鏈?zhǔn)降拇a段,借此也可以引入一個新的概念,即函數(shù)式編程:主要是為了消滅冗余且復(fù)用場景極大的代碼塊,抽象成復(fù)用性極強(qiáng)的代碼段,當(dāng)然以上代碼還不夠函數(shù)式,我們可以繼續(xù)優(yōu)化:
// 定義好計算函數(shù)
func addCompute(_ object: Int) -> Int {
return object + 1
}
//進(jìn)一步優(yōu)化調(diào)整輸出函數(shù)
objects.map { newObject in addCompute(newObject) }
函數(shù)式編程:需要我們將函數(shù)作為其他函數(shù)的參數(shù)傳遞,或者作為返回值返還,有時亦被稱為高階函數(shù)。
Filter:
Filter 函數(shù)同樣是接收一個給定的數(shù)組,通過給定的篩選條件,取得數(shù)組中符合條件的元素,并返回一個新的數(shù)組。
蘋果官方定義
extension Array {
func filter( includeElement: Element -> Bool) -> [Element] {
var result: [Element] = []
for x in self {
result.append(includeElement(x))
}
return result
}
}
在 filter 中定義一個泛型元素 Element,經(jīng)過 includeElement 閉包函數(shù)篩選處理之后,再經(jīng)由泛型數(shù)組拿到處理后的新數(shù)據(jù),成為新的數(shù)組。
應(yīng)用
我們拿到以上定義好的 objects 數(shù)組,拿到其中所有的偶數(shù)
for 循環(huán)
let newObjects: [Int] = []
for oldObject in Objects {
if oldObject%2 == 0 {
newObjects.append(oldObject)
}
}
filter 函數(shù)
objects.filter { filterElement in filterElement%2 == 0 }
同樣的,你可以感受下 filter 函數(shù)處理之后,鏈?zhǔn)酱a的可讀性。
Reduce
Reduce 函數(shù)接收一個輸入數(shù)組,同時需要接收一個 T 類型的初始值,
通過 combine 函數(shù)處理之后,返回一個同為 T 類型的結(jié)果。在一些像 OCaml 和 Haskell 一樣的函數(shù)語言中,reduce 函數(shù)被稱為 fold 或 fold_left。而 reduce 可英譯為整合,簡單來說就是通過我們所想的方式整合一個數(shù)組中的元素。
蘋果官方定義
extension Array {
func reduce<T>( initialValue: T, combine: (T, Element) -> T) -> [T] {
var result = initialValue
for x in self {
result = combine(result, x)
}
return result
}
}
在 reduce 中有兩個泛型元素 T && Element,combine 是針對于數(shù)組的處理函數(shù),我們輸入初始值和數(shù)組中的每一個元素之后,即可輸出返回一個理想的值。
應(yīng)用
我們再次拿到 map 中定義好的 objects 數(shù)組,拿到其中每個元素相乘后的結(jié)果。
for 循環(huán)
func reduceInstance() {
let newObject: Int = 1
for oldObject in Objects {
newObject * oldObject
}
return newObject
}
reduce 函數(shù)
objects.reduce(1) { result, x in result * x }
// 我們也可以將運算符作為最后一個參數(shù),讓這段代碼更短且不影響可讀性
objects.reduce(1, combine: *)
以上,即為使用 reduce 后處理的結(jié)果
最后
我們試著同時使用以上三個函數(shù)去作用一個數(shù)組。
let lastObjects: [Int] = [2017, 10, 7, 11, 09, 6]
場景:
我們需要將一個整形數(shù)組中的元素:
- 先將所有的元素 + 1
- 篩選出其中的偶數(shù)元素
- 將所有篩選到的元素相加
lastObjects.map { element in element + 1 }
.filter { element in element%2 == 0 }
.reduce(0, combine: +)
類似復(fù)雜的應(yīng)用場景,使用泛型函數(shù)編程是不是變得很簡單?以上場景你試試使用 for 循環(huán)?
泛型編程:適配工具的實戰(zhàn)開發(fā)
以上,我們講解了蘋果使用泛型構(gòu)建的函數(shù),接下來我們進(jìn)入一個簡單但特別實用的泛型實戰(zhàn)。
特別廣泛的應(yīng)用場景
我們顯示到界面上的元素:圖片、文字,很多時候需要在不同尺寸的設(shè)備上呈現(xiàn)不同的姿態(tài)(大小、位置、樣式),這個時候我們該怎么辦?仔細(xì)一想,其實這個還是有挺多種情況的,可能也會造成很多功能性冗余代碼塊,該怎么辦?
使用泛型編程恰好可解決了這些問題。
屬性定義
定義屏幕類型(iPhone/iPad),而每種類型,都有不同尺寸的屏幕大小:
enum DeviceType<T> {
case iPhone(inch35: T, inch40: T, inch47: T, inch55: T)
case iPad(common: T, pro: T)
}
定義屏幕的尺寸系數(shù)及當(dāng)前屏幕尺寸,目的是讓外界可以通過該屬性直接知道當(dāng)前是那種尺寸的屏幕:
struct DeviceDiaonal {
static let iPhone4: Double = 3.5
static let iPhoneSE: Double = 4.0
static let iPhone6: Double = 4.7
static let iPhone6Plus: Double = 5.5
}
// 當(dāng)前屏幕尺寸
var currentDiaonal: Double = DeviceDiaonal.iPhone6
定義屏幕的規(guī)格及當(dāng)前屏幕規(guī)格,目的是讓外界可以通過該屬性直接知道當(dāng)前是那種屏幕規(guī)格的:
// 屏幕規(guī)格
enum ScreenSpecs {
enum PhoneInch {
case inch35, inch40, inch47, inch55
}
enum PadInch {
case common, pro
}
case iPhone(PhoneInch), iPad(PadInch)
}
// 當(dāng)前屏幕規(guī)格
var screenSpecs: ScreenSpecs = .iPhone(.inch47)
初始化構(gòu)造器的構(gòu)建
因為當(dāng)前工具類是一個處理類,所以我們可將其定義為單例類,而其初始化構(gòu)造器僅限于被單例調(diào)用。那么,我們需要在初始化構(gòu)造器初始化什么屬性呢?因為這是一個屏幕特性的單例類,毋庸置疑,我們可以直接通過該類,就可以拿到當(dāng)前屏幕的所有特性,因此在初始化構(gòu)造器中,我們需要對當(dāng)前一些屏幕特性進(jìn)行初始化
// 構(gòu)造單例(調(diào)用 init 構(gòu)造函數(shù))
static let shared = ScreenFeatureManager()
fileprivate init() {
let screenWidth = UIScreen.main.bounds.width
switch screenWidth {
case 320:
if screenHeight <= 480 {
currentDiaonal = DeviceDiaonal.iPhone4
screenSpecs = .iPhone(.inch35)
} else {
currentDiaonal = DeviceDiaonal.iPhoneSE
screenSpecs = .iPhone(.inch40)
}
case 375:
currentDiaonal = DeviceDiaonal.iPhone6
screenSpecs = .iPhone(.inch47)
case 414:
currentDiaonal = DeviceDiaonal.iPhone6Plus
screenSpecs = .iPhone(.inch55)
case 768:
screenSpecs = .iPad(.common)
case 1024:
screenSpecs = .iPad(.pro)
default:
break
}
}
至此,我們初始化了一些屏幕特性,接下來,我們將這些屏幕特性加到正菜中!
使用泛型類構(gòu)造適配函數(shù)
利用前面定義好的 DeviceType 類型,對于這個類型,我們可以根據(jù)不同的類型輸入不同的泛型值(T),然后在函數(shù)內(nèi),拿到上一步就處理好的屏幕特性,結(jié)合輸入值進(jìn)行判斷處理,不同的屏幕會映射會出不同的泛型值(T),并拿到該映射下的泛型值輸出返回。
func adapt<T>(toDevice type: DeviceType<T>) -> T {
// 多個輸入值,判斷處理之后,輸出一個單一的泛型值(多對一的映射)
switch type {
case let .iPhone(inch35, inch40, inch47, inch55):
switch screenSpecs {
case .iPhone(.inch35):
return inch35
case .iPhone(.inch40):
return inch40
case .iPhone(.inch47):
return inch47
case .iPhone(.inch55):
return inch55
default:
return inch47
}
case let .iPad(common, pro):
switch screenSpecs {
case .iPad(.common):
return common
case .iPad(.pro):
return pro
default:
return common
}
}
}
應(yīng)用
以上泛型函數(shù)構(gòu)造好之后,適配工作就變得特別簡單。
例如有三個適配點:
- 不同設(shè)備,擁有不同的封面圖
- 不同設(shè)備,封面圖的 size 是不一樣的
- 不同設(shè)備,其標(biāo)題顏色、樣式、大小都不一樣
我們該如何適配以上的需求呢?
fileprivate func adaptiveConfiguration() {
//適配封面圖的寬度(在 storyBoard 中寬度與高度成一比例,適配了寬度,高度也會跟著變化)
coverImageViewWidthConstraint.constant = ScreenFeatureManager.shared.adapt(toDevice: .iPhone(inch35: 150, inch40: 250, inch47: 350, inch55: 420))
// 適配不同的設(shè)備不同的封面圖
coverImageView.image = ScreenFeatureManager.shared.adapt(toDevice: .iPhone(inch35: UIImage(named: "home_adapt_inch35"), inch40: UIImage(named: "home_adapt_inch40"), inch47: UIImage(named: "home_adapt_inch47"), inch55: UIImage(named: "home_adapt_inch55")))
//適配主題標(biāo)題內(nèi)容
themeTitleLabel.text = ScreenFeatureManager.shared.adapt(toDevice: .iPhone(inch35: "杳無音迅(inch35)", inch40: "杳無音迅(inch40)", inch47: "杳無音迅(inch47)", inch55: "杳無音迅(inch55)"))
//適配主題標(biāo)題的字體樣式
themeTitleLabel.font = ScreenFeatureManager.shared.adapt(toDevice: .iPhone(inch35: UIFont.boldSystemFont(ofSize: 15), inch40: UIFont.boldSystemFont(ofSize: 18), inch47: UIFont.boldSystemFont(ofSize: 21), inch55: UIFont.boldSystemFont(ofSize: 25)))
//適配主題標(biāo)題的字體顏色
themeTitleLabel.textColor = ScreenFeatureManager.shared.adapt(toDevice: .iPhone(inch35: UIColor.black, inch40: UIColor.gray, inch47: UIColor.lightGray, inch55: UIColor.green))
}
至此,適配工具已開發(fā)完成,如果你是使用 Swift 開發(fā),那么可以直接將其引入你的項目使用。如果有更好的實現(xiàn)方式,期待你評論告知。
Demo: https://github.com/iJudson/ScreenFeature
歡迎 stars
Thanks:多謝觀看,歡迎收藏文章,歡迎關(guān)注、交流...