Swiftのenumが素敵すぎる Part2
前回の投稿で、以下のSwiftの列挙型(enum)がもつ固有の性質のうち、#3と#4を使ってサーチプログラムをリファクタリングした。
今回は、#1と#2を使って、さらにリファクタリングするぜ。
Swiftの列挙型の固有な性質:
1. メンバーに具体的な値をいれる必要はない。メンバーを値として扱える。
2. メンバーに付随する型を設定できる。
3. メンバーに値を入れた場合は、Objective-CのNS_ENUMと同様に扱える。
4. 列挙型自体を値型としてオブジェクトとして扱え、プロパティーやメソッドも定義できる。
問題:
サーチプログラムのコアとなる以下の関数では、サーチ状態を表すのに、大きく3つのパラメタ(isLoading, HasSearched, searchResults)を使っている。なんだけど、本当に興味があるのは、以下の4つの状態のはずで、これらの状態を3つのパラメタ(isLoading, HasSearched, searchResults)の組み合わせで判断する必要がある。コードが美しくないし、わかりにくいので、バグを誘発しそうだ。こういう時に、enumの#1と#2を使ってコードをすっきりできるぜ。
1) サーチ処理実行前
2) サーチ実行中
3) サーチ実行完了かつ該当の項目が一つもない
4) サーチ実行完了かつ該当の項目が一つ以上あり
typealias SearchComplete = (Bool) -> Void func performSearchForText(text: String, category: Category, completion SearchComplete) { if !text.isEmpty { dataTask?.cancel() isLoading = true hasSearched = true searchResults = [SearchResult]() let url = urlWithSearchText(text, category: category) let session = NSURLSession.sharedSession() dataTask = session.dataTaskWithURL(url, completionHandler: { data, response, error in var success = false if let error = error { if error.code == -999 { return } // Search was cancelled } else if let httpResponse = response as? NSHTTPURLResponse { if httpResponse.statusCode == 200 { if let dictionary = self.parseJSON(data) { self.searchResults = self.parseDictionary(dictionary) self.searchResults.sort(<) println("Success! ") self.isLoading = false success = true } } } if (!success) { self.hasSearched = false self.isLoading = false } dispatch_async(dispatch_get_main_queue()) { SearchCompletion(success) } }) dataTask?.resume() } }
素直に、enumを使えば、以下のように定義できる。これらは、先にのべた本当に興味のある状態を表している。
#1の性質にあるように、4つの状態すべてに値を定義しておらず、それぞれのメンバを値として扱える。
また、4つめの.Resultsのケースが特殊で、#2の性質にあるように、Swiftのenumは、メンバーに付随する型を設定できる。ここでは、SearchResultオブジェクトのArrayを設定している。
enum State { case NotSearchedYet // サーチ処理実行前 case Loading //サーチ実行中 case NoResults //サーチ実行完了かつ該当の項目が一つもない case Results([SearchResult]) //サーチ実行完了かつ該当の項目が一つ以上あり }
上記の状態(State)に従って、先のPerformSearchForText関数をリファクタリングをしたのが以下である。
func performSearchForText(text: String, category: Category, completion SearchComplete) { if !text.isEmpty { dataTask?.cancel() state = .Loading //サーチ実行中 let url = urlWithSearchText(text, category: category) let session = NSURLSession.sharedSession() dataTask = session.dataTaskWithURL(url, completionHandler: { data, response, error in self.state = .NotSearchedYet // サーチ処理実行前 var success = false if let error = error { if error.code == -999 { return } // Search was cancelled } else if let httpResponse = response as? NSHTTPURLResponse { if httpResponse.statusCode == 200 { if let dictionary = self.parseJSON(data) { var searchResults = self.parseDictionary(dictionary) if searchResults.isEmpty {  self.state = .NoResults //サーチ実行完了かつ該当の項目が一つもない } else { searchResults.sort(<) self.state = .Results(searchResults) //サーチ実行完了かつ該当の項目が一つ以上あり success = true } } } dispatch_async(dispatch_get_main_queue()) { SearchCompletion(success) } }) dataTask?.resume() } }
このようにenumを使えば、処理状態の管理が容易になり、かつ、ある状態にのみ付随する情報をまとめて設定できるので便利だ。
参考に、上記で、.Resultsに付随する情報として設定したsearchResultsを読み出す処理例として、状態(state)に応じて、tableViewの構成する各Cellを返す関数の例を以下に記載しておく。
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { switch search.state { case .NotSearchedYet: fatalError("Should never get here") case .Loading: let cell = tableView.dequeueReusableCellWithIdentifier(TableViewCellIdentifiers.loadingCell, forIndexPath: indexPath) as UITableViewCell let spinner = cell.viewWithTag(100) as UIActivityIndicatorView spinner.startAnimating() return cell case .NoResults: return tableView.dequeueReusableCellWithIdentifier( TableViewCellIdentifiers.nothingFoundCell, forIndexPath: indexPath) as UITableViewCell case .Results(let list): let cell = tableView.dequeueReusableCellWithIdentifier( TableViewCellIdentifiers.searchResultCell, forIndexPath: indexPath) as SearchResultCell let searchResult = list[indexPath.row] cell.configureForSearchResult(searchResult) return cell } }
U