Swift — 使用 Enumeration 重構您的程式碼. ( Use Enumeration to refactor your code. )

讓我們使用 Swift 的 Enumeration 這個強大的類型,來幫助我們的程式碼變得更好吧!

Jeremy Xue
Jeremy Xue ‘s Blog
9 min readFeb 19, 2019

--

Photo by Aleks Dahlberg on Unsplash

前言:

會想發這篇文章的原因是我個人在開發以及作案子會常用到的一種方式,而這個方式會讓我在處理有 “ 規範的資料結構 ” 時覺得十分好用,運用起來不僅更快速、安全,同時也使我的程式碼更易讀了。而這種東西就是枚舉 Enumeration ,本篇文章會帶領大家使用 Enumeration 為整體架構,來延伸出您的程式碼開發。今天的主題為圍繞在表格元件 ( UITableViewUICollectionView ) 上。

這邊都是個人開發方式,若有不適請立即停止服用 😷

● 架構分析:

有興趣的可以下載下來看看,但是部分內容可能移除或過時了。

剛好之前有開發過 MOPCON 的 APP,覺得其中 APP 的首頁很適合當這一次用 Enumeration 重構程式碼的範例,因此我們就以它作為範例吧:

我們可以看見這個 APP 首頁的區塊可能分為下列五個區塊:

  1. Logo 圖片
  2. 滾動 Banner
  3. 最新消息
  4. 功能項目
  5. 語言選擇

我們也能發現除了「功能項目」 區塊會有多個項目以外,其它區塊看起來都使需要一個項目即可,我們之後會根據這個規範來定出一個架構。

● 使用 Enumeration 驅動表格視圖開發

⚠️ 此範例會使用 CollectionView 作為範例。

這邊我們加入一個 UICollectionView 作為我們的基底開發,而這時候我們需要設定其 DataSource,告訴它我們的 CollectionView 該返回的 SectionRow 數目以及 CollectionViewCell 的樣式。

而平常開發的時候,我們可能會像是這樣直接返回表格視圖的 section 以及 row 的數量:

因為我們知道有五個區塊,所以我們在 numberOfSections 返回 5,而因為我們的功能區塊需要多個項目( 來源是一個數組),所以我們在功能區塊的地方( Section(3) )的地方返回 features 這個數組的數量。

這一切都看似正常。沒錯,是正常的 😏

你會發現這些東西都是建立在「 你知道 」的情況下,所以你會覺得理所當然,但是今天換作是另一個人開發,他的 Section 的切割方式可能跟你不同,他的區塊可能更多或是更少,那麼我們在返回 Section 的時候就會錯了。而當今天既然我們的 Section 數量異動了,那想必我們的 Section(3) 也未必為我們的功能區塊了。

因為並沒有標示哪個區塊是哪個 section,也無從參考。

所以我們上述這些操作在每個人的看法中都不同,因此我們需要一個統一的架構來開發它 🤝。

● 定義 Enumeration 架構:

我們根據前面五個區塊來定義我們的架構,而我們又希望這個架構是有規範的( 只有這五個區塊 ),因此,在這邊我們使用 Enumeration 就是非常好的方式,後面你會更體會到它的強大。

因此我們定義一個名為 HomeSectionEnumeration ,並且加入我們架構中的五個區塊:

之後我們以這個架構為基礎,來驅動我們的 CollectionView 的開發。

○ 使用 rawValue 初始化 HomeSection 情況

通常大家可能到這邊是有概念的,但不曉得怎麼繼續下去 🤨

這邊先將我們的 HomeSection 加上 Int 類型,因為後面我們可以藉由 section 來變成 HomeSection 類型。如此一來我們的情況也會帶著一個整數隱含值,並且從 0 開始算起:

接著我們的 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.allCasescount 中獲取,而功能區塊在我們透過 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 ,原因是我們可以透過 SegmentControlselectedSegmentIndex 屬性知道我們選到的項目的 Index ,因此我們會使用 Int 作為 Language 的類型,方便實例化。

那麼我們在調整語言的時候就會很清楚、簡潔:

後記:

其實 Enumeration 在 Swift 中真的是很強大的類型,用途真的很廣泛,如果善用 Enumeration 真的能讓你處理程式碼更加安全快速,同時也能讓你程式碼變得更易讀,在交接或是教學時可以讓能更容易理解。

在經歷這麼多學習階段磨練以及接案的焠鍊,還有寫過不少的 糞code操code 後,覺得自己的程式碼的能力有稍稍的上升了一點。每當看見別人寫出我意想不到而且又清晰易讀時,總覺得自己還差那麼一點點,於是不斷的往更好的程式碼之路邁進。

Practice makes perfect 💪🏻

希望我能分享更多技巧給大家。

Demo Github 連結:
https://github.com/JeremyXue77/Enumeration-CollectionView-

--

--

Jeremy Xue
Jeremy Xue ‘s Blog

Hi, I’m Jeremy. [好想工作室 — iOS Developer]