Realm × Swift2 でシームレスに画像を保存する


## 2017/5/23追記

もともとのコードだと、トランザクションを意識しにくかったので、最近はこんなの作ってやってます

```swift
//
//  RealmExtension.swift
//  Musubi
//
//  Created by はるふ on 2016/10/27.
//  Copyright © 2016年 はるふ. All rights reserved.
//

import RealmSwift
import Realm
import UIKit

//
// MARK: protocols
//

protocol RealmEncodable {
    associatedtype EncodedType: Object
    associatedtype DecodedType: Self // DecodedTypeじゃなくてSelfにしたいけどno-finalクラスを束縛できない
    func realmEncode() -> EncodedType
    static func realmDecode(value: EncodedType) -> DecodedType
}

//
// MARK: Encodable Objects
//

class StringObject: Object {
    dynamic fileprivate var string = ""
}

extension String: RealmEncodable {
    typealias EncodedType = StringObject
    typealias DecodedType = String
    
    func realmEncode() -> StringObject {
        let o = StringObject()
        o.string = self
        return o
    }
    
    static func realmDecode(value: StringObject) -> String {
        return value.string
    }
}


class UIImageObject: Object {
    typealias DecodedType = UIImage
    dynamic fileprivate var data: Data!
}

extension UIImage: RealmEncodable {
    typealias EncodedType = UIImageObject
    typealias DecodedType = UIImage
    
    func realmEncode() -> UIImageObject {
        let o = UIImageObject()
        o.data = UIImagePNGRepresentation(self)
        return o
    }
    
    static func realmDecode(value: UIImageObject) -> UIImage {
        return UIImage(data: value.data)!
    }
}

//
// MARK: extention methods
//

extension Sequence where Iterator.Element: RealmEncodable, Iterator.Element.EncodedType: Object {
    typealias EncodedType = List<Iterator.Element.EncodedType>
    typealias DecodedType = Array<Iterator.Element.DecodedType>
    
    func realmEncode() -> EncodedType {
        return List<Iterator.Element.EncodedType>(self.map { $0.realmEncode() })
    }
    
    static func realmDecode(value: EncodedType) -> DecodedType {
        return value.map { Iterator.Element.realmDecode(value: $0) }
    }
}

extension Optional where Wrapped: RealmOptionalType {
    typealias DecodedType = Wrapped
    typealias EncodedType = RealmOptional<Wrapped>

    func realmEncode() -> RealmOptional<Wrapped> {
        return RealmOptional<Wrapped>(self)
    }
    
    static func realmDecode(value: RealmOptional<Wrapped>) -> Optional<Wrapped> {
        return value.value
    }
}
```

## はじめに

1つめの記事 → [Realm × Swift2 でデータを保存してみる](http://qiita.com/_ha1f/items/08bbed8b3dc6f9f6cadd)
2つめの記事 → [Realm × Swift2 でidでデータを管理する](http://qiita.com/_ha1f/items/f6318e326434dbf83037)
の続きです。(最終のコードに含まれているだけで、読む必要はありません)

1つめの記事で述べたとおり、現在のバージョン(0.96.0)では、


>String, NSString
Int
Float
Double
Bool
NSDate
NSData
RealmOptional<T> for optional numeric properties
Object subclasses for to-one relationships
List<T: Object> for to-many relationships

しか保存できない。すなわち、その他の型を持つプロパティは、
存在するデータ形式に変換する必要がある。

今回の記事では、UIImageをNSDataとして保存し、取り出してUIImageにすることを目的とする。

## Object
Realmで保存可能なオブジェクトとしては、Objectクラスを継承しなければならない。
前回同様、Userクラスを用いる。今回は、更にimageを加えたものにする。

```swift:User.swift
class User: Object {
    dynamic var id = 1
    dynamic var name = ""
    dynamic var image: UIImage? = nil
}
```

これを保存したい。

## UIImage -> NSData
標準メソッドを使うだけで、JPEG形式またはPNG形式として、NSDataに変換できる。
JPEGでは画質が荒れてしまうので、PNGを用いる。

```swift
let data = UIImagePNGRepresentation(image)
```

## NSData -> UIImage
dataからのUIImageの生成も、標準で用意されている。

```swift
let image = UIImage(data: data)
```


## 実装

NSDataを保存したいしたいので、これをUserクラスに加える。

```swift
dynamic private var imageData: NSData? = nil
```

ただ、imageをセットするたびに変換してセットするのは面倒なので、setter/getterを用いて自動的に行うため、以下のような実装にした。

```swift
dynamic private var _image: UIImage? = nil
dynamic var image: UIImage? {
    set{
        self._image = newValue
        if let value = newValue {
            self.imageData = UIImagePNGRepresentation(value)
        }
    }
    get{
        if let image = self._image {
            return image
        }
        if let data = self.imageData {
            self._image = UIImage(data: data)
            return self._image
        }
        return nil
    }
}
dynamic private var imageData: NSData? = nil
```

### set
imageにsetすると、自動的に_imageに値が保持され、imageDataにも変換&setされる。

### get
imageからgetすると、_imageがnilでなければ_imageを返す。
nilならimageDataから生成して_imageにセット＆返す。
これにより、Realmはロード時にimageDataにしかsetしないが、imageからgetするだけでデータを利用することができる。

### 注釈
setter/getterで書いても、Realmは値を保存しない。
すなわち、

```swift
dynamic private var imageData: NSData? {
    return UIImagePNGRepresentation(self.image)
}
```

では保存されない。

## ignoredProperties
Realmでは、保存しないプロパティを設定することができる。imageや_imageは保存しないので、ignoreさせておく。

```swift
override static func ignoredProperties() -> [String] {
    return ["image", "_image"]
}
```

## 完成品

```swift:User.swift
import UIKit
import RealmSwift

class User: Object {
    static let realm = try! Realm()
    
    dynamic private var id = 0
    dynamic var name = ""
    dynamic private var _image: UIImage? = nil
    dynamic var image: UIImage? {
        set{
            self._image = newValue
            if let value = newValue {
                self.imageData = UIImagePNGRepresentation(value)
            }
        }
        get{
            if let image = self._image {
                return image
            }
            if let data = self.imageData {
                self._image = UIImage(data: data)
                return self._image
            }
            return nil
        }
    }
    dynamic private var imageData: NSData? = nil
    
    override static func primaryKey() -> String? {
        return "id"
    }
    
    override static func ignoredProperties() -> [String] {
        return ["image", "_image"]
    }
    
    static func create() -> User {
        let user = User()
        user.id = lastId()
        return user
    }
    
    static func loadAll() -> [User] {
        let users = realm.objects(User).sorted("id", ascending: false)
        var ret: [User] = []
        for user in users {
            ret.append(user)
        }
        return ret
    }
    
    static func lastId() -> Int {
        if let user = realm.objects(User).last {
            return user.id + 1
        } else {
            return 1
        }
    }
    
    func save() {
        try! User.realm.write {
            User.realm.add(self)
        }
    }
}

```

```swift:ViewController.swift
import UIKit
import RealmSwift

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        
        let a = User.create()
        a.name = "name"
        a.image = UIImage(named: "icon1.png")
        a.save()
        
        let b = User.create()
        b.name = "name"
        b.image = UIImage(named: "icon2.png")
        b.save()
        
        
        let users = User.loadAll()
        for (i, user) in users.enumerate() {
            let imageView = UIImageView()
            imageView.frame = CGRectMake(0,CGFloat(100*i),100,100)
            imageView.image = user.image
            self.view.addSubview(imageView)
        }
    }
}


```