読者です 読者をやめる 読者になる 読者になる

A Day In The Life

とあるプログラマの備忘録

ReSwiftのステートをRxSwiftを使って監視する

普段から RxSwift(Reactive ExtensionsのSwift実装)と ReSwift(ReduxのSwift実装)を使っています。RxSwift を通信系の処理だったりデリゲートの処理(例えば StoreKit とか CoreBluetooth)だったりに使って、ReSwiftをアプリ全体で管理しなきゃいけないデータの監視なんかに使ってます。ReSwift をそのまま使ってもあまり困らないのですが、ReSwift のステートを Observable に変換してUI部品とバインドするといろいろと捗るなぁというわけで ReSwift の Rx 用ラッパークラスを作成しました。

ReduxのステートをRx化するクラス

Redux のステートを Rx の Variable に格納して、Redux からステート変更の通知が来たら Variable(エラーの発生しないBehaviorSubject)を更新するクラスを作成します。

import RxSwift
import ReSwift

class RxStore<AppStateType: StateType>: StoreSubscriber {
  // 監視する値
  let state: Variable<AppStateType>
  private let store: Store<AppStateType>
    
  init(store: Store<AppStateType>) {
    self.store = store
    self.state = Variable(store.state)
    // 購読開始
    self.store.subscribe(self)
  }
    
  deinit {
    // 購読終了
    self.store.unsubscribe(self)
  }
    
  func newState(state: AppStateType) {
    // Variableを更新する
    self.state.value = state
  }
}

使い方

Store を生成します。ここは通常の ReSwift の使い方そのままです。

// ストア
struct AppState: StateType {
  var count: Int = 0
}

// アクション
enum AppAction: Action {
  case incAction
}

// Reducer
struct AppReducer: Reducer {
  func handleAction(action: Action, state: AppState?) -> AppState {
    let state = state ?? AppState()
    return AppState(count: state.count + 1)
  }
}

// ストアオブジェクトの生成
let mainStore = Store<AppState>(
  reducer: AppReducer(),
  state: nil
)

ViewController で Rx化したステートを subscribe(購読)する場合は以下のようになります。

import UIKit
import RxSwift
import ReSwift

class ViewController: UIViewController {

  let disposeBag = DisposeBag()
    
  let rxStore = RxStore<AppState>(store: mainStore)

  override func viewDidLoad() {
    super.viewDidLoad()
    rxStore.state.asObservable()
      .subscribe(onNext: { state in
        // 更新された時の処理
        print(state.count)
      }).disposed(by: disposeBag)
  }
}
// ディスパッチ
mainStore.dispatch(AppAction.incAction)
mainStore.dispatch(AppAction.incAction)
mainStore.dispatch(AppAction.incAction)
mainStore.dispatch(AppAction.incAction)

UI部品とバインド

Rx化されて入れば UI 部品と簡単にバインドできます。

class ViewController: UIViewController {

  let disposeBag = DisposeBag()
  @IBOutlet weak var button: UIButton!

  override func viewDidLoad() {
    super.viewDidLoad()
    // カウントが3を越えたらtrueにする
    let appStateValid = rxStore.state.asObservable()
      .map { state -> Bool in
        return state.count > 3
      }.shareReplayLatestWhileConnected()
    // ボタンのEnabledとバインドする
    appStateValid
      .bind(to: button.rx.isEnabled)
      .addDisposableTo(disposeBag)
  }
}

参考記事

enum駆使してKeychainをUserDefaultsっぽく使う

以前、こちらの記事で enum(列挙型)を使って UserDefaults のキー名を管理する方法について紹介しました。この方法を応用(ってほど大袈裟なもんじゃないけど)して Keychain にセキュアなデータを保存してみたいと思います。

Keychainって何?

API アクセスに使う認証用のトークン文字列なんかは UserDefaults じゃなくてもっとセキュアなところに保存する必要があります。そんなときに使うのが Keychain です。詳しい説明は以下の記事が参考になると思いますので以下をご一読ください

Keychainに文字列を保存する

enum に Keychain 用のキー名を列挙してデータ保存と取り出しのためのメソッドを追加します。

import Foundation
import Security

public enum Keychain: String {
  // キー名    
  case accessToken = "accessToken"
  case password = "password"

  // データの保存
  public func set(_ value: String) {
    let query: [String: AnyObject] = [
      kSecClass as String: kSecClassGenericPassword,
      kSecAttrAccount as String: self.rawValue as AnyObject,
      kSecValueData as String: value.data(using: .utf8)! as AnyObject
    ]
    SecItemDelete(query as CFDictionary)
    // エラー処理サボったのでこのへんはよしなに直してください
    SecItemAdd(query as CFDictionary, nil)
  }
  // データの取り出し
  public func value() -> String? {
    let query: [String: AnyObject] = [
      kSecClass as String: kSecClassGenericPassword,
      kSecAttrAccount as String: self.rawValue as AnyObject,
      kSecReturnData as String: kCFBooleanTrue,
      kSecMatchLimit as String: kSecMatchLimitOne
    ]
    var result: AnyObject?
    let status = SecItemCopyMatching(query as CFDictionary, &result)   
    guard status == noErr else {
      return nil
    }
    guard let data = result as? Data else {
      return nil
    }
    return String(data: data, encoding: .utf8)
  }
}

使い方

使い方は以下のようになります。

// データの保存
Keychain.accessToken.set("xxxxxx")
// データの取り出し
if let value = Keychain.accessToken.value() {
  print(value) // xxxxxxと出力される
}

ユニットテスト時の注意点

こちらのコードを XCTest でテストする時は Host Application を指定するのを忘れないでください。 f:id:glass-_-onion:20170201151902p:plain Host Application ってなんだそれって方は以下の記事を参考にしてください

参考記事

関連記事

Realmを前提としたリポジトリパターンの実装

いろんなところで Realm が良いと聞くようになり最近やっと Realm を使い始めました。実際 Realm を使ってみると予想していたよりも使い勝手がよく Core Data には戻れそうにないです…。ソースコードは Swift3 に対応しています。

リポジトリパターンの実装

Core Data に比べてお作法的なものが少ないとはいえ、いくつかのお決まりのパターンがあるので DDD のリポジトリパターンを適用してみました。クリーンアーキテクチャをまったく意識してないお気軽実装です。

import Foundation
import RealmSwift

class Repository<DomainType: Object> {
    
  var realm: Realm
    
  init() {
    self.realm = try! Realm()
  }
  // PK検索
  func find(primaryKey: String) -> DomainType? {
    return realm.objects(DomainType.self).filter("id == %@", primaryKey).first
  }
  // 全部取ってくる
  func findAll() -> [DomainType] {
    return realm.objects(DomainType.self).map({$0})
  }
  // 条件指定
  func find(predicate: NSPredicate) -> Results<DomainType> {
    return realm.objects(DomainType.self).filter(predicate)
  }
  // データ追加と更新
  func add(domains: [DomainType]) {
    try! realm.write {
      realm.add(domains, update: true)
    }
  }
  // データ削除
  func delete(domains: [DomainType]) {
    try! realm.write {
      realm.delete(domains)
    }
  }
  // トランザクション
  func transaction(_ transactionBlock: () -> Void) {
    try! realm.write {
      transactionBlock()
    }
  }
  // スレッドをまたいだオブジェクトの解決
  func resolve<Confined: ThreadConfined>(_ reference: ThreadSafeReference<Confined>) -> Confined? {
    return self.realm.resolve(reference)
  }
}

一つのリポジトリオブジェクトが一つの Realm オブジェクトを保持するようにしました。リポジトリオブジェクトをスレッドをまたいで使わない想定です。

リポジトリクラスの使い方

以下の Person クラスを例にリポジトリクラスの使い方をみてみましょう。

class Person: Object {
  dynamic var id = UUID().uuidString
  dynamic var name = ""
  // プライマリーキーを設定
  override static func primaryKey() -> String? {
    return "id"
  }
}

Person オブジェクトの生成から削除まで一通りやってみます。

// パーソンオブジェクト生成
let glassonion = Person()
glassonion.name = "glassonion"
let jude = Person()
jude.name = "jude"
// パーソンリポジトリ生成
let repo = Repository<Person>()
// データ追加
repo.add(domains: [glassonion, jude])
// 全件取得
let allPeople = repo.findAll()
// 条件を指定して取得
let predicate = NSPredicate(format: "name CONTAINS %@", "gl")
let people = repo.find(predicate: predicate)
// データの更新
repo.transaction {
  allPeople[0].name = "hoge"
}
// 削除
repo.delete(domains: allPeople)

スレッドをまたいだモデルオブジェクトのあつかいかた

スレッドをまたいでデータの更新をする場合は以下のようにします。

let repo = Repository<Person>()
let person = repo.findAll().first!
// スレッドセーフなオブジェクトに変換
let personRef = ThreadSafeReference(to: person)
// スレッド生成
DispatchQueue(label: "io.realm.RealmTasks.bg").async {
  // 新たにリポジトリ生成
  let repo = Repository<Person>()
  // スレッドの違うリポジトリと関連付ける
  if let person = repo.resolve(personRef)!
  // データ更新
  repo.transaction {
    person.name = "Michelle"
  }
}

参考記事

参考書籍

Swift3で追加されたData構造体の使い方レシピ

Swift3 から Data 構造体が追加され Objective-C 時代の NSData クラスはお役御免になりました。この Data 構造体は NSData の置き換えなので、できることに違いはほとんどありません。ただしメソッドに互換性がないため NSData を使ったコードを Data に置き換えるのが大変です。というわけで Data 構造体の使い方をまとめてみました。

文字列を Data オブジェクトに変換する

文字列を Data オブジェクトに変換するには以下のように String 構造体の data メソッドを使います。

let str = "abcdefghijk"
let data = str.data(using: .utf8)!

Data型のオブジェクトを文字列に変換する

反対に Data オブジェクトを文字列に変換するには String 構造体のイニシャライザを使って以下のように変換します。

let str = String(data: data, encoding: .utf8)!

ただしこの方法は Data オブジェクトの中が文字列で構成されている場合しか使えません。 Data オブジェクトの中が文字列以外の時は、次の方法で変換します。

Data オブジェクトを16進数文字列に変換する

print 関数に Data オブジェクトを渡してもバイト数しか出力されません。

let data = Data(bytes: [0, 1, 127, 128, 255])
// 出力結果は 5 bytes
print(data)
// NSData だとちゃんと内容が出力されるのに…
let nsdata = NSData(data: data)
// 出力結果は <00017f80 ff>
print(nsdata) 

こんなときは Data 構造体の map メソッドを使って以下のように1バイトづつ16進数文字に変換します。

let data = Data(bytes: [0, 1, 127, 128, 255])
let hexStr = data.map {
  String(format: "%.2hhx", $0)
}.joined()
// 出力結果は 00017f80ff
print(hexStr)

ワンライナーで書くと以下のようになります。

print(data.map { String(format: "%.2hhx", $0) }.joined())

デバッグで出力を確認するだけであれば以下の方法もあります。

// 出力結果は<61626364 65666768 696a6b>
print(String(format: "%@", data as CVarArg))

Data オブジェクトを固定長の型(UInt8とかDoubleなど)のオブジェクトに変換する

Data オブジェクトを UInt8 や Double など固定長の型のオブジェクトに変換するには Data 構造体の withUnsafeBytes メソッドを使います。以下は Data オブジェクトを Double オブジェクトに変換する例です。

let data = Data(bytes: [0x71, 0x3d, 0x0a, 0xd7, 0xa3, 0x10, 0x45, 0x40])
let value = data.withUnsafeBytes { (ptr: UnsafePointer<Double>) -> Double in
  return ptr.pointee
}
// 出力結果は42.13
print(value)

上記の方法は文字列など可変長のデータには使えませんのでご注意下さい。

バイトデータにアクセスしたい

CommonCrypto ライブラリを使ってデータをハッシュ化したい時なんかにバイトデータにアクセスしなきゃいけない場面があります。Swift2.xでは以下のように書けました。

let data = NSData(bytes: bytes, length: 15)
let dataBytes = UnsafePointer<CUnsignedChar>(data.bytes)

Swift3では UnsafePointer の仕様が変わったので Data オブジェクトを NSData に変換してから上記の方法を使ってもコンパイルエラーになります。少し面倒ですが UnsafeRawPointer を使って以下のように処理します。

let bytes: [UInt8] = [
    0xe3, 0x81, 0x82,
    0xe3, 0x81, 0x84,
    0xe3, 0x81, 0x86,
    0xe3, 0x81, 0x88, 
    0xe3, 0x81, 0x8a
] 
let data = Data(bytes: bytes)
let charPtr = UnsafeMutablePointer<CUnsignedChar>.allocate(capacity: data.count)
charPtr.initialize(from: data)
let dataBytes = UnsafeRawPointer(charPtr)
// メモリを解放する
defer {
  charPtr.deinitialize(count: data.count)
  charPtr.deallocate(capacity: data.count)
}
:
: なんか処理
:

メモリ解放しなきゃいけないのがちょっと辛いです。

メモリ解放を気にせずバイトデータにアクセスしたい場合

「Data オブジェクトを固定長の型に変換する」で使った withUnsafeBytes メソッドを使うとバイトデータにアクセスできます。クロージャの外からはポインタが無効なのでメモリ安全にバイトデータにアクセスできます。

let bytes: [UInt8] = [
    0xe3, 0x81, 0x82,
    0xe3, 0x81, 0x84,
    0xe3, 0x81, 0x86,
    0xe3, 0x81, 0x88, 
    0xe3, 0x81, 0x8a
] 
let data = Data(bytes: bytes)
data.withUnsafeBytes { (ptr: UnsafePointer<CUnsignedChar>) in {
    let dataBytes = UnsafeRawPointer(ptr)
    :
    : なんか処理
    :
}

withUnsafeBytes メソッドに渡すクロージャは任意の戻り値に設定できるので上記のように戻り値なしにもできますし、逆にクロージャ内でハッシュ化の処理をいろいろしてから文字列で返すみたいなこともできます。

参考

Swiftのプロトコルに@objcなしでオプショナルメソッドを定義する

Swift で プロトコルを定義するときにオプショナルなメソッドを定義したい時ってありますよね。そんなときに使える小技です。

@objcを指定するよく知られた方法

Swift のプロトコルObjective-C と違ってオプショナルメソッドを定義することができません。そこで一番お手軽な方法としてはプロトコル宣言するとき、先頭に@objcをつけて Objective-C 互換にしてしまうことです。こんな感じです。

@objc protocol Hoge {
  func foo()
  func bar()
  // オプショナルなメソッド
  optional func buzz()
}

使うときは以下のようになります。

class Fuga: Hoge {
  func foo() {
    // 実装
  }
  func bar() {
    // 実装
  }
}
// メソッドの呼び出し
let fuga = Fuga()
fuga.foo()
fuga.bar()
fuga.buzz?()

Objc 互換にする方法には以下のデメリットがあります。

  • 構造体(struct)や列挙型(enum)で使えない
  • メソッドを呼び出すときに「?」をつけないといけない
  • ジェネリクスが使えない

@objcなしで実装する方法

@objcなしでもっと Swift っぽくオプショナルメソッドを定義する方法があります。それがextensionを使ってメソッドのデフォルト実装を定義する方法です。以下のようになります。

protocol Hoge {
  func foo()
  func bar()
  // オプショナルなメソッド
  func buzz()
}

public extension MIDIMessageListener {
  // デフォルト実装を定義する
  func buzz() {
    print("buzz")
  }
}

使い方は以下のようになります。

class Fuga: Hoge {
  func foo() {
    // 実装
  }
  func bar() {
    // 実装
  }
}
// メソッドの呼び出し
let fuga = Fuga()
fuga.foo()
fuga.bar()
fuga.buzz()

メソッドの呼び出し方も他のメソッドの呼び出し方と同じです。合わせて構造体や列挙型にも適応できます。

UserDefaultsのキー名をenumで管理する

NSUserDefaults あらため UserDefaults のキーを enum で管理する方法(Swift3対応)です。UserDefaults のキーを enum で管理する方法はいくつか見つけたのですが Swift3 に対応しているものが見つからなかったので書きました。

キーに文字列リテラルを直接指定したくない

UserDefaults のキーを文字列リテラルでやり取りするのはタイプミスなどのリスクが高く enum で管理したほうがコンパイルエラーでミスを検知できるのでオススメです。

import Foundation

enum UserSettings: String {
  case autoPlay = "autoPlay"
  case purchasedItems = "purchasedItems"
  :
  : キーをこちらに宣言する
  :
  
  func set(value: Int) {
    UserDefaults.standard.set(value, forKey: self.rawValue)
    UserDefaults.standard.synchronize()
  }
 
  func integer() -> Int {
    return UserDefaults.standard.integer(forKey: self.rawValue)
  }
    
  func set(value: Float) {
    UserDefaults.standard.set(value, forKey: self.rawValue)
    UserDefaults.standard.synchronize()
  }
    
  func float() -> Float {
    return UserDefaults.standard.float(forKey: self.rawValue)
  }
    
  func set(value: Double) {
    UserDefaults.standard.set(value, forKey: self.rawValue)
    UserDefaults.standard.synchronize()
  }
    
  func double() -> Double {
    return UserDefaults.standard.double(forKey: self.rawValue)
  }
    
  func set(value: Bool) {
    UserDefaults.standard.set(value, forKey: self.rawValue)
    UserDefaults.standard.synchronize()
  }
    
  func bool() -> Bool {
    return UserDefaults.standard.bool(forKey: self.rawValue)
  }

  func set(value: Any) {
    UserDefaults.standard.set(value, forKey: self.rawValue)
    UserDefaults.standard.synchronize()
  }
    
  func object() -> Any? {
    return UserDefaults.standard.object(forKey: self.rawValue)
  }
}

Int, Float, Double, Bool はオプショナル型を使いたくなかったのでそれぞれメソッドを定義しました。 使い方はこんな感じになります。

// Bool
UserSettings.autoPlay.set(value: true)
let autoPlay: Bool = UserSettings.autoPlay.bool()

// Array
UserSettings.purchasedItems.set(value: ["item1", "item2", "item3"])
if let items = UserSettings.purchasedItems.object() as? [String] {
  print(items.count)
}      

もう一つのメリット

UserDefaults を使う場所を一元管理できるメリットもあります。アプリのいろんな場所で UserDefaults を使いまくって一体どんなデータが保存されてるかわかりませんってなったことありませんか?(自分はよくあります)

参考記事

uGUIのテキストを点滅させてみる

Unity の uGUI でテキストをこんな感じに点滅させる方法です。

f:id:glass-_-onion:20161027174139g:plain

Update メソッドに処理を書いても良かったのですが個人的な好みで UniRX を使って実装してみました。Mathf.Sin メソッド(三角関数)を使ってアルファ値を0から1に緩やかに変化させています。

using UnityEngine;
using UnityEngine.UI;
using System;
using System.Collections;
using UniRx;

public class FlashingText : MonoBehaviour
{

  [SerializeField]
  private float angularFrequency = 5f;
  // 30FPS
  static readonly float DeltaTime = 0.0333f;
  private Text text;

  void Start()
  {
    float time = 0.0f;
    text = GetComponent<Text>();
    Observable.Interval(TimeSpan.FromSeconds(deltaTime)).Subscribe(_ =>
      {
        time += angularFrequency * deltaTime;
        var color = text.color;
        color.a = Mathf.Sin(time) * 0.5f + 0.5f;
        text.color = color;
      }).AddTo(this);
  }
}

angularFrequency の値をいじることで点滅のタイミングを調整することができます。

なんで Mathf.Sin(time) * 0.5f + 0.5f なのか

Mathf.Sin メソッドの結果に 0.5 をかけたり足したりしているのはアルファの値を0から1の間で緩やかに変化させるためです。

Mathf.Sin(x)

サイン関数を使うと以下のグラフのように、値は-1から1の間で変化します。 f:id:glass-_-onion:20161027171240p:plain

Mathf.Sin(x) * 0.5

それに0.5をかけて値を-0.5から0.5の間で変化するようにしてから f:id:glass-_-onion:20161027171245p:plain

Mathf.Sin(x) * 0.5 + 0.5

0.5を足すことによって0から1の間に値が収まるようになります。 f:id:glass-_-onion:20161027171250p:plain

Mathf.Abs(Mathf.Sin(time)) じゃダメなの?

値を0から1の間で変化させるだけであれば Mathf.Abs(Mathf.Sin(time)) でも問題なさそうですが、値の変化が滑らかになりません。

Mathf.Abs(Mathf.Sin(time))

Mathf.Abs メソッドを使った場合の値の変化は以下のようになります。0付近の値の変化が急な感じになります。 f:id:glass-_-onion:20161027171256p:plain Mathf.Abs メソッドを使って点滅させると以下のような動きになります。

f:id:glass-_-onion:20161027174140g:plain

微妙な違いですが少し違和感がありますね。