2017/5/23追記
もともとのコードだと、トランザクションを意識しにくかったので、最近はこんなの作ってやってます
//
// 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 でデータを保存してみる
2つめの記事 → Realm × Swift2 でidでデータを管理する
の続きです。(最終のコードに含まれているだけで、読む必要はありません)
1つめの記事で述べたとおり、現在のバージョン(0.96.0)では、
String, NSString
Int
Float
Double
Bool
NSDate
NSData
RealmOptional for optional numeric properties
Object subclasses for to-one relationships
List for to-many relationships
しか保存できない。すなわち、その他の型を持つプロパティは、
存在するデータ形式に変換する必要がある。
今回の記事では、UIImageをNSDataとして保存し、取り出してUIImageにすることを目的とする。
Object
Realmで保存可能なオブジェクトとしては、Objectクラスを継承しなければならない。
前回同様、Userクラスを用いる。今回は、更にimageを加えたものにする。
class User: Object {
dynamic var id = 1
dynamic var name = ""
dynamic var image: UIImage? = nil
}
これを保存したい。
UIImage -> NSData
標準メソッドを使うだけで、JPEG形式またはPNG形式として、NSDataに変換できる。
JPEGでは画質が荒れてしまうので、PNGを用いる。
let data = UIImagePNGRepresentation(image)
NSData -> UIImage
dataからのUIImageの生成も、標準で用意されている。
let image = UIImage(data: data)
実装
NSDataを保存したいしたいので、これをUserクラスに加える。
dynamic private var imageData: NSData? = nil
ただ、imageをセットするたびに変換してセットするのは面倒なので、setter/getterを用いて自動的に行うため、以下のような実装にした。
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は値を保存しない。
すなわち、
dynamic private var imageData: NSData? {
return UIImagePNGRepresentation(self.image)
}
では保存されない。
ignoredProperties
Realmでは、保存しないプロパティを設定することができる。imageや_imageは保存しないので、ignoreさせておく。
override static func ignoredProperties() -> [String] {
return ["image", "_image"]
}
完成品
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)
}
}
}
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)
}
}
}