Macアプリ初心者：Realmの導入(CocoaPods利用)


今回はMacアプリでDBを使いたくてRealmについて調べてみました。
[以前に作成したプロジェクト](https://qiita.com/ynozue/items/e839f25426bbd1be313b)に組み込んでみます。

# 完成イメージ
[![完成イメージ](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/203864/ff607b11-4fb1-1299-53b9-856660fceb07.png)](https://youtu.be/MmRJaaHhdPo)


# 環境
* macOS Mojave：10.14.6
* Xcode：10.3
* CocoaPods：1.7.5

# Realm インストール
## Podfile の作成
```sh
$ pod init
$ ls
NSTableViewSample NSTableViewSample.xcodeproj Podfile
```

# Realm インストール
## Podfile に `pod 'RealmSwift'` を追加
```sh
$ vi Podfile
```
```Podfile
# Uncomment the next line to define a global platform for your project
# platform :osx, '10.14'

target 'NSTableViewSample' do
  # Comment the next line if you're not using Swift and don't want to use dynamic frameworks
  use_frameworks!

  # Pods for NSTableViewSample
  pod 'RealmSwift'

end
```

# Realm インストール
```
$ pod install
Analyzing dependencies
Downloading dependencies
Installing Realm (3.17.3)
Installing RealmSwift (3.17.3)
Generating Pods project
Integrating client project
Sending stats
Pod installation complete! There is 1 dependency from the Podfile and 2 total pods installed.

[!] Automatically assigning platform `osx` with version `10.14` on target `NSTableViewSample` because no platform was specified. Please specify a platform for this target in your Podfile. See `https://guides.cocoapods.org/syntax/podfile.html#platform`.
```

# Realm を使う
## `pod install` で作成された workspace を開く
```
$ open NSTableViewSample.xcworkspace
```

## 株式情報保存用のクラス（StockInfo）を作成
Object クラスを継承して作成します。

```swift:StockInfo.swift
import Cocoa
import RealmSwift

class StockInfo: Object {
    @objc dynamic var name = ""
    @objc dynamic var price = -1
    @objc dynamic var status = StockStatus.flat.rawValue
    @objc dynamic var createdAt = Date()
}

extension StockInfo {
    /// 状態をEnum形式で返却
    var stockStatus: StockStatus {
        get {
            return StockStatus(rawValue: self.status) ?? StockStatus.flat
        }
        set {
            self.status = newValue.rawValue
        }
    }
    
    /// StockInfo オブジェクトを作成
    class func create(realm: Realm, name: String, price: Int, status: StockStatus) -> StockInfo {
        let stockInfo = realm.create(StockInfo.self)
        stockInfo.name = name
        stockInfo.price = price
        stockInfo.stockStatus = status
        
        return stockInfo
    }
    
    /// 企業名で部分一致検索（空の場合は全件取得）
    class func objects(searchValue: String = "") -> Results<StockInfo> {
        var result = try! Realm().objects(StockInfo.self)
        let searchValue = searchValue.trimmingCharacters(in: .whitespaces)
        if searchValue != "" {
            result = result.filter("name CONTAINS %@", searchValue)
        }
        
        return result.sorted(byKeyPath: "createdAt")
    }
    
    /// デバッグ用
    class func debugInitData() {
        let realm = try! Realm()
        try! realm.write {
            // データクリア
            realm.deleteAll()
            
            // サンプルデータ作成
            let _ = StockInfo.create(realm: realm, name: "○○株式会社", price: 1000, status: .up)
            let _ = StockInfo.create(realm: realm, name: "○×水産", price: 12345, status: .down)
            let _ = StockInfo.create(realm: realm, name: "株式会社□○", price: 345, status: .flat)
            let _ = StockInfo.create(realm: realm, name: "×△ホールディングス", price: 321, status: .up)
            let _ = StockInfo.create(realm: realm, name: "ABC BANK", price: 20, status: .down)
            let _ = StockInfo.create(realm: realm, name: "▼○重工", price: 98000, status: .up)
            
            // サンプル用に1,000 件データを作成
            for i in 1...1_000 {
                let status = StockStatus.init(rawValue: Int.random(in: 0..<3))!
                let price = Int.random(in: -1..<10_000_000)
                let _ = StockInfo.create(realm: realm, name: "○△×株式会社 \(i)", price: price, status: status)
            }
        }
    }

}
```

## 株価情報の状態を表すEnum(StockStatus)を作成
```swift:StockStatus.swift
import Cocoa

enum StockStatus: Int {
    case flat = 0
    case up
    case down
    
    /// 状態ごとのImageオブジェクト
    var image: NSImage {
        get {
            switch self {
            case .flat:
                return NSImage(named: NSImage.Name("flat"))!
            case .up:
                return NSImage(named: NSImage.Name("up"))!
            case .down:
                return NSImage(named: NSImage.Name("down"))!
            }
        }
    }
    
    /// 状態ごとのカラーオブジェクト
    var color: NSColor {
        get {
            switch self {
            case .flat:
                return .gray
            case .up:
                return .green
            case .down:
                return .red
            }
        }
    }
}
```

## StockInfo クラスを利用して株価情報を表示
細かい説明は省きますが、リファクタリングして多少見やすくなっているはずです。

```swift:ViewController.swift
import Cocoa

class ViewController: NSViewController {

    // MARK: - IBOutlet
    @IBOutlet private weak var tableView: NSTableView! {
        didSet {
            self.tableView.dataSource = self
            self.tableView.delegate = self
            
            self.tableView.headerView = nil
            self.tableView.rowHeight = 88
        }
    }
    @IBOutlet private weak var countTextField: NSTextField!
    
    
    // MARK: - private variables
    private var stockValues = StockInfo.objects()

    
    // MARK: - override func
    override func viewDidLoad() {
        super.viewDidLoad()

        // Do any additional setup after loading the view.
        // デバッグ用にデータ作成
        StockInfo.debugInitData()
        // 株価情報をViewに反映
        self.updateStockValuesCount()
    }
    
    override var representedObject: Any? {
        didSet {
            // Update the view, if already loaded.
        }
    }
    

    // MARK: - private func
    
    private func updateStockValuesCount() {
        let formatter = NumberFormatter()
        formatter.numberStyle = .decimal
        let commaString = formatter.string(from: NSNumber(value: self.stockValues.count))!
        self.countTextField.stringValue = "\(commaString)件"
    }
    

    // MARK: - actions
    
    @IBAction func actionSearchField(_ sender: NSSearchField) {
        self.stockValues = StockInfo.objects(searchValue: sender.stringValue)
        self.tableView.reloadData()
        self.updateStockValuesCount()
    }
    
}

extension ViewController: NSTableViewDataSource, NSTableViewDelegate {
    
    // MARK: - NSTableViewDataSource
    func numberOfRows(in tableView: NSTableView) -> Int {
        return self.stockValues.count
    }
    
    
    // MARK: - NSTableViewDelegate
    func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
        let result = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "MyView"), owner: self) as! CustomNSTableCellView
        
        // Set the stringValue of the cell's text field to the nameArray value at row
        let stock = self.stockValues[row]
        result.image = stock.stockStatus.image
        result.name = stock.name
        result.color = stock.stockStatus.color
        result.price = stock.price

        // Return the result
        return result
    }
}
```