A Day In The Life

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

オブジェクト指向の次に来るもの

自分が駆け出しのプログラマだった頃(10年以上前ですかね)、必死でオブジェクト指向プログラミングの勉強をしました。最近だとオブジェクト指向プログラミングなんて当たり前すぎて技術とすら言えないくらいになったと感じてます。 エンジニアが生き残るために次に必要な技術は何かここ数年いろいろ考えてきましたが、次は間違いなくリアクティブプログラミングだと予想しています。1年半ほど前、UniRxを使ったのをきっかけに現在もRxSwiftを使って日々リアクティブプログラミングと接しています。そんな大事な技術ですがあまり専門書がないのが困ったところです。そんな折に見つけたのが本書です。

厳密にはリアクティブプログラミング(RP)について書かれた本ではなく関数型リアクティブプログラミング(FRP)について書かれた本です。関数型リアクティブプログラミングはこの本によると「関数型プログラミング(FP)とリアクティブプログラミングの交わる部分」だそうです。 FRPの考え方はRx(リアクティブプログラミング)にも応用可能ということなんでこの本を読んで知見をためたいと思います(まだ全部読んでません)。

面倒な課金処理をRx化するRxStoreKitを作りました

StoreKit の SKProductsRequest と SKPaymentQueue を Rx化して課金処理を簡単に書けるライブラリを GitHub にて公開しました。 github.com RxStoreKit を使うと課金処理を以下のような感じで書けます。

let productRequest = SKProductsRequest(productIdentifiers: Set(["xxx.xxx.xxx"]))
productRequest.rx.productsRequest
    .flatMap { response -> Observable<SKProduct> in
        return Observable.from(response.products)
    }
    .flatMap { product -> Observable<SKPaymentTransaction> in
        return SKPaymentQueue.default().rx.add(product: product)
    }
    .subscribe(onNext: { transition in
        print(transition)
    }).disposed(by: disposeBag)
productRequest.start()

Rx以前の課金処理

だいぶ前の記事ですが iOS アプリの課金処理まわりの記事を書きました。 glassonion.hatenablog.com こちら見ていただくとわかると思いますが StoreKit(In-App Purchase) のプログラムはものすごく複雑です。中でも SKPaymentTransactionObserver の処理が特に大変です。StoreKit のような非同期コールバック地獄みたいなプログラムは RxSwift とRxCocoa を使って Rx化するとものすごく簡略化できます。というわけで RxStoreKit を宜しくお願いします。

関連記事

失敗しない iOS In-App Purchase プログラミング - A Day In The Life
RxSwift DelegateProxy with required methods – LazySequence<UnsafePointer>

Swift3でMIDIファイルをパースする方法

Swift3 で MIDI ファイルのパースをしようとしたら意外に方法が見つからなくて苦労したのでメモです。 MusicEventIteratorGetEventInfo 関数で取得したデータを MIDINoteMessage オブジェクトに変換する方法がわからなくてハマりました。

import AudioToolbox

public class MIDIParser {
    
  public static func parse(url midiFileUrl: URL) {
        
    var musicSequence: MusicSequence?
    var result = OSStatus(noErr)
    result = NewMusicSequence(&musicSequence)
    guard let sequence = musicSequence else {
      print("error creating sequence : \(result)")
      return
    }
        
    // MIDIファイルの読み込み
    MusicSequenceFileLoad(sequence, midiFileUrl as CFURL, .midiType, MusicSequenceLoadFlags.smf_ChannelsToTracks)
        
    var musicTrack: MusicTrack? = nil
    var sequenceLength: MusicTimeStamp = 0
    var trackCount: UInt32 = 0
    MusicSequenceGetTrackCount(sequence, &trackCount)
    for i in 0 ..< trackCount {
      var trackLength: MusicTimeStamp = 0
      var trackLengthSize: UInt32 = 0
            
      MusicSequenceGetIndTrack(sequence, i, & musicTrack)
      guard let track = musicTrack else {
        continue
      }
            
      MusicTrackGetProperty(track, kSequenceTrackProperty_TrackLength, &trackLength, &trackLengthSize)
            
      if sequenceLength < trackLength {
        sequenceLength = trackLength
      }
            
      var tmpIterator: MusicEventIterator?
      NewMusicEventIterator(track, &tmpIterator)
      guard let iterator = tempIterator else {
        continue
      }
            
      var hasNext: DarwinBoolean = false
      MusicEventIteratorHasCurrentEvent(iterator, &hasNext)
            
      var type: MusicEventType = 0
      var stamp: MusicTimeStamp = -1
      var data: UnsafeRawPointer?
      var size: UInt32 = 0
            
      while hasNext.boolValue {
        MusicEventIteratorGetEventInfo(iterator, &stamp, &type, &data, &size)
        if type == kMusicEventType_MIDINoteMessage {
          let messagePtr = UnsafePointer<MIDINoteMessage>(data?.assumingMemoryBound(to: MIDINoteMessage.self))
                    
          guard let channel = messagePtr?.pointee.channel,
            let note = messagePtr?.pointee.note,
            let velocity = messagePtr?.pointee.velocity,
            let duration = messagePtr?.pointee.duration else {
              continue
          }
            
          let message = MIDINoteMessage(channel: channel,
              note: note,
              velocity: velocity,
              releaseVelocity: 0,
              duration: duration)
          print(message)
        }
                
        MusicEventIteratorNextEvent(iterator)
        MusicEventIteratorHasCurrentEvent(iterator, &hasNext)
      }
      DisposeMusicEventIterator(iterator)
      MusicSequenceDisposeTrack(sequence, track)
    }
    DisposeMusicSequence(sequence)
  }
}

参考

Objc や Swift2の情報はそこそこありました。

Swift3 のポインタ型についてちゃんと理解してればハマらなかったのかなと反省してます。

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

参考