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

A Day In The Life

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

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

swift ios

以前、こちらの記事で 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を前提としたリポジトリパターンの実装

ios swift 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構造体の使い方レシピ

swift ios

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なしでオプショナルメソッドを定義する

ios swift

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で管理する

swift ios

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のテキストを点滅させてみる

c# unity .net ゲーム

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

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

AVAudioSequencerでMIDIファイルを再生して曲の終わりにコールバックを設定する方法

ios swift 音楽 c

AVAudioSequencerでMIDIファイルを再生して曲の終わりにコールバックを設定する方法です。AVAudioSequencer 自体にそのようなコールバックがないので CoreMIDI のコールバックを使います。CoreMIDIはSwiftのブロックではなくてC言語の関数ポインタでコールバックを取らないといけないケースがあって大変です。

public class Sequencer {

  let callBack: @convention(c) (UnsafeMutablePointer<Void>, MusicSequence, MusicTrack, MusicTimeStamp, UnsafePointer<MusicEventUserData>, MusicTimeStamp, MusicTimeStamp) -> Void = {
    (obj, seq, mt, timestamp, userData, timestamp2, timestamp3) in
    // Cタイプ関数なのでselfを使えません
    let mySelf: Sequencer = unsafeBitCast(obj, Sequencer.self)
    :
    : 曲の終了後に行う処理
    :
  }
    
  var sequencer: AVAudioSequencer

  public func playWithMidiURL(midiFileUrl: NSURL, audioFiles: [NSURL]) {
    // サンプラーとオーディオエンジンの初期化
    let audioEngine = AVAudioEngine()
    let samplerNode = AVAudioUnitSampler()

    // サンプラーとオーディオエンジンをつなげる
    audioEngine.attachNode(samplerNode)
    audioEngine.connect(samplerNode,
      to: audioEngine.mainMixerNode,
      format: samplerNode.outputFormatForBus(0))

    // オーディオエンジンをスタートする
    do {
      try samplerNode.loadAudioFilesAtURLs(audioFiles)
      try audioEngine.start()
    } catch {
            
    }

    // シーケンサーの初期化
    self.sequencer = AVAudioSequencer(audioEngine: audioEngine)
    let musicSequence = audioEngine.musicSequence
        
    // MIDIファイル読み込み
    do {
      try sequencer.loadFromURL(midiFileUrl, options: .SMF_ChannelsToTracks)
    } catch {
      print("Error load MIDI file")
    }
    // シーケンサスタート
    do {
      sequencer.prepareToPlay()
      try sequencer.start()
    } catch {
      print("Error play MIDI file")
    }
        
    // 曲の長さを取得
    var musicLengthInBeats: NSTimeInterval = 0.0
    for track in sequencer.tracks {
      let lengthInBeats = track.lengthInBeats
      if musicLengthInBeats < lengthInBeats {
        musicLengthInBeats = lengthInBeats
      }
    }
    // 曲の最後にコールバックを仕込む
    MusicSequenceSetUserCallback(musicSequence, callBack, unsafeBitCast(self, UnsafeMutablePointer<Void>.self))
    var musicTrack: MusicTrack = nil
    MusicSequenceGetIndTrack(musicSequence, 0, &musicTrack)
    let userData: UnsafeMutablePointer<MusicEventUserData> = UnsafeMutablePointer.alloc(1)
    MusicTrackNewUserEvent(musicTrack, ceil(musicLengthInBeats), userData)
  }
}

曲の長さを調べてタイマーでやれば良いと思われるかもしれませんが、それだと曲のポーズに対応できないのでこのような方法が必要になります。

参考記事

ソースコードはこちら

動作確認済みのソースコードはこちらに置いてあります。 - glassonion1/R9MIDISequencer