Swift — 使用 Enumeration 重構您的程式碼. ( Use Enumeration to refactor your code. )
讓我們使用 Swift 的 Enumeration 這個強大的類型,來幫助我們的程式碼變得更好吧!
前言:
會想發這篇文章的原因是我個人在開發以及作案子會常用到的一種方式,而這個方式會讓我在處理有 “ 規範的資料結構 ” 時覺得十分好用,運用起來不僅更快速、安全,同時也使我的程式碼更易讀了。而這種東西就是枚舉 Enumeration
,本篇文章會帶領大家使用 Enumeration
為整體架構,來延伸出您的程式碼開發。今天的主題為圍繞在表格元件 ( UITableView
、UICollectionView
) 上。
這邊都是個人開發方式,若有不適請立即停止服用 😷
● 架構分析:
剛好之前有開發過 MOPCON 的 APP,覺得其中 APP 的首頁很適合當這一次用 Enumeration
重構程式碼的範例,因此我們就以它作為範例吧:
我們可以看見這個 APP 首頁的區塊可能分為下列五個區塊:
- Logo 圖片
- 滾動 Banner
- 最新消息
- 功能項目
- 語言選擇
我們也能發現除了「功能項目」 區塊會有多個項目以外,其它區塊看起來都使需要一個項目即可,我們之後會根據這個規範來定出一個架構。
● 使用 Enumeration 驅動表格視圖開發
⚠️ 此範例會使用 CollectionView 作為範例。
這邊我們加入一個 UICollectionView
作為我們的基底開發,而這時候我們需要設定其 DataSource
,告訴它我們的 CollectionView
該返回的 Section
、Row
數目以及 CollectionViewCell
的樣式。
而平常開發的時候,我們可能會像是這樣直接返回表格視圖的 section
以及 row
的數量:
因為我們知道有五個區塊,所以我們在 numberOfSections
返回 5,而因為我們的功能區塊需要多個項目( 來源是一個數組),所以我們在功能區塊的地方( Section(3) )
的地方返回 features
這個數組的數量。
這一切都看似正常。沒錯,是正常的 😏
你會發現這些東西都是建立在「 你知道 」的情況下,所以你會覺得理所當然,但是今天換作是另一個人開發,他的 Section
的切割方式可能跟你不同,他的區塊可能更多或是更少,那麼我們在返回 Section
的時候就會錯了。而當今天既然我們的 Section
數量異動了,那想必我們的 Section(3)
也未必為我們的功能區塊了。
因為並沒有標示哪個區塊是哪個 section,也無從參考。
所以我們上述這些操作在每個人的看法中都不同,因此我們需要一個統一的架構來開發它 🤝。
● 定義 Enumeration 架構:
我們根據前面五個區塊來定義我們的架構,而我們又希望這個架構是有規範的( 只有這五個區塊 ),因此,在這邊我們使用 Enumeration
就是非常好的方式,後面你會更體會到它的強大。
因此我們定義一個名為 HomeSection
的 Enumeration
,並且加入我們架構中的五個區塊:
enum HomeSection {
case logo // Logo 圖片
case banner // 滾動 Banner
case news // 最新消息
case features // 功能項目
case languages // 語言選擇
}
之後我們以這個架構為基礎,來驅動我們的 CollectionView
的開發。
○ 使用 rawValue 初始化 HomeSection 情況
通常大家可能到這邊是有概念的,但不曉得怎麼繼續下去 🤨
這邊先將我們的 HomeSection
加上 Int
類型,因為後面我們可以藉由 section
來變成 HomeSection
類型。如此一來我們的情況也會帶著一個整數隱含值,並且從 0 開始算起:
enum HomeSection: Int {
case logo = 0, banner, news, features, languages
}
接著我們的 HomeSection
就會有一個透過整數的 rawValue
的初始化器 ,之後我們可以透過它來產生 HomeSection
類型。
而另外一個問題來了,我們怎麼知道 HomeSection
裡面有幾個 case
呢?之前有人提過幾種解決方式:
- 在
HomeSection
下宣告一個 static 常數,輸入case
數量:
這種方式不能說不好,在當時是不錯的解決方案,只是對我來說跟直接返回 5 好像是大同小異的操作( 畢竟還是自已輸入,可能會出錯 )。但是在今天 case
新增或減少的時候就需要修改這個值。
- 在
HomeSection
的最後新增一個名為count
的情況,取其rawValue
:
我個人非常不推薦這種做法,因為這樣就破壞了我們所定義 Enumeration
的結構了,這樣在當你進行 switch
語句判斷時,總是需要特地加上一個 count
或是 default
的情況,但實際上你並不需要判斷它,這種做法會使你後面的處理更多此一舉。
那我們要怎麼辦呢? 😭
放心,在 Swift 4.2
時有一個叫做 CaseIterable
的 protocol,遵循這個協定後,我們能透過 allCases
這個陣列屬性來查看所有 case
,這也表示了我們也能透過它來獲得 case
總數量。
詳情可以參考: 【 蘋果官方文件 — CaseIterable 】
這時候我們的程式碼應該會變成下列的樣子:
這時你應該會發現,我們可以直接採用 HomeSection
的結構,將它套用在我們的 CollectionView
上即可,多少 section
可以從 HomeSection.allCases
的 count
中獲取,而功能區塊在我們透過 rawValue
初始化之後,我們只需要透過 .feature
就能調用它,而它為哪個 section
的區塊在我們定義 HomeSection
的時候就決定好了每個區塊的 section
。
而在需要在對於每個區塊進行不同處理的時候也會更清楚,我們先看一個沒有使用 HomeSection
來分類的方式,我們先透過 switch
語法判斷 IndexPath
為哪個 section
,接著返回特定的 CGSize
:
如果這邊我們先將 indexPath.section
藉由 HomeSection
初始化後,這時我們採用這種方式處理就會很漂亮( 易讀、易用),別忘了 Enumeration
的初始化是可能回傳 nil
的,所以這邊我們都會使用 guard
語句來提早退出超出範圍的值:
在操作過程中你可以發現:
- 因為透過
rawValue
初始化HomeSection
後,不管我們在處理哪個區塊都很明顯,.logo
就是 Logo 區塊、.banner
就是滾動 Banner 區塊…以此類推。而如果是單純的數字( 0,1,2…),很難看出來是什麼區塊。 - 因為
HomeSection
是一個Enumeration
的類型,所以當我們有遺漏的情況,Swift 會提示我們遺漏了哪情況,或是我們可以補上default
來涵蓋。而如果是用IndexPath
我們很難去看出哪邊處理過或是還沒處理。 - 透過這種方式,我們不必再去進行
switch
語句去判斷IndexPath
這種沒有範圍的值,我們會藉由Enumeration
的初始化方法過濾超出範圍的情況,接著只要處理範圍內的內容即可。
○ 直接使用 rawValue
而當我們在使用如果不想使用 if
可選綁定或是 guard
語句來取用 HomeSection
的值,我們也能夠直接使用其 rawValue
來處理。
例如我想要 reload
某個區塊的資料時,我也可以透過 HomeSection.Case.rawValue
來直接獲取它的原始值:
● Enumeration 與元件配合
使用 Enumeration
來處理有範圍的結構是非常有用的,大家還記得我們畫面最下方有一個語言選擇的區塊嗎?而這個 UISegmentControl
剛好本身就是一個範圍內的值的元件,如果我們將它配合 Enumeration
使用,那麼程式碼會變得更好利用,同時可讀性也增加了:
這邊我們用定義一個 Language
的結構,並且類型宣告為 Int
,原因是我們可以透過 SegmentControl
的 selectedSegmentIndex
屬性知道我們選到的項目的 Index
,因此我們會使用 Int
作為 Language
的類型,方便實例化。
那麼我們在調整語言的時候就會很清楚、簡潔:
後記:
其實 Enumeration
在 Swift 中真的是很強大的類型,用途真的很廣泛,如果善用 Enumeration
真的能讓你處理程式碼更加安全快速,同時也能讓你程式碼變得更易讀,在交接或是教學時可以讓能更容易理解。
在經歷這麼多學習階段磨練以及接案的焠鍊,還有寫過不少的 糞code
或 操code
後,覺得自己的程式碼的能力有稍稍的上升了一點。每當看見別人寫出我意想不到而且又清晰易讀時,總覺得自己還差那麼一點點,於是不斷的往更好的程式碼之路邁進。
Practice makes perfect 💪🏻
希望我能分享更多技巧給大家。
Demo Github 連結:
https://github.com/JeremyXue77/Enumeration-CollectionView-