2018年3月8日木曜日

Firebase Swift で observe が複数回コールされてしまう場合に確認すること

概要

Firebase からデータを取得する場合 observe or observeSingleEvent を使うと思います
observeSingleEvent の場合は特に問題ないのですがリアルタイムでデータを取得したい場合には observe を使います
その場合に何故か 1 度しかデータを更新していないのに observe が複数回コールされる現象に遭遇したので対策を紹介します

環境

  • macOS 10.13.2
  • Xcode 9.2 (9C40b)
  • Firebase 4.8.2

確認する点

いくつか紹介します

単純にデータを複数回更新していないか

Swift からデータを更新する際には setValue or updateChildValues を使うと思います
単純にこれらが複数回コールされていないか確認しましょう
例えば touchDown などのイベントが発生したら上記メソッドを呼ぶようなケースだとすると連打などの対策をしていないと同じデータが複数回更新される可能性があります

Firebase の場合データが同じであっても更新イベントがあれば observe してしまいます

observe を removeObserver していない

自分はこっちでハマリました
observe の場合 removeObserver をしないといくら View が変わってもデータの監視が続いてしまうようです
これは SpriteKit でも同様で SKScene が変わった時に removeObserver していないと複数回コールされてしまうことがあります

removeObserver は例えば以下のように使います

var bmref = DatabaseReference()
var handler: UInt = 0

bmref = rootRef.child("path/to/data")
handler = bmref.observe(.value, with: { (snapshot) in
    bmref.removeObserver(withHandle: handler)
    move(to: scene)
})

move(to :scene) は適当にこちらで定義したシーンを移動するためのメソッドです
要するにシーンの移動直前に removeObserver を呼ぶようにしましょう
removeObserver の引数には observe を識別するためのハンドラが必要です
これは observe を登録した際の戻り値として取得することができます
ハンドラはインスタンス変数などにして別のメソッドからも参照できるようにしておきましょう

これで observe を削除することができます

observe が複数コールされる場合の影響

自分の場合は SpriteKit 上で使っていたのですが observe が複数回コールされることで fps が低下するという現象が発生しました

具体的に言うと SKAction.move などで動いているノードがある状態で observe が複数回コールされるとノードが一瞬 (0.5 - 1 秒ほど) 停止する状態になります
これが必要最低限のみコールされるようになることで fps が落ちることなくノードが動き続けてくれます

最後に

Swift + Firebase で observe が複数回コールされてしまう場合の対策を紹介しました
observe が削除されていない場合には削除するように修正してみてください

それでも重い、fps が落ちるという場合には単純に Firebase に対してアクセスしすぎていないか確認してみてください (1 秒間に 100 回データを更新するような処理をしていないかなど)

そういう場合にはずっと observe しないで定期的に observeSingleEvent するような仕組みに変更するようにしてみてください

2018年3月7日水曜日

Mac でスクロールの方向をコマンドで変更する方法

概要

普通は「システム環境設定」でマウスから変更します
いちいち開くのが面倒なのでターミナルからコマンドで一発で変更してみます

環境

  • macOS 10.13.2

コマンド

ナチュラル方向にするコマンド

  • defaults write -g com.apple.swipescrolldirection -bool true

逆の方向にするコマンド (Windows マウスなどを使う場合)

  • defaults write -g com.apple.swipescrolldirection -bool false

defaults read -g com.apple.swipescrolldirection で確認すると 0 or 1 なのですがセットするときは bool でセットします

2018年3月6日火曜日

macOS アプリのアイコンは「Icon Set Creator」を使おう

概要

iOS アプリのアイコン作成は MakeAppIcon が最強だと過去に紹介しました
MakeAppIcon だと macOS 用のアイコンが作成できなかったので別のものを探したら「Icon Set Creator」という便利ツールがあったので紹介します

環境

  • macOS 10.13.2
  • Icon Set Creator 1.1.6

インストール

AppStore からインストールします
https://itunes.apple.com/jp/app/icon-set-creator/id939343785?mt=12

インストールできたら開きましょう

アイコンを作成する

使い方は簡単です
アイコンを作成したい元画像をドラッグアンドドロップしましょう
icon_set_creator1.png

ファイルを開いたら左下の Platform で「macOS」を選択しましょう
icon_set_creator2.png

あとは「Go!」を選択するだけです
アイコンを出力するフォルダを選択しましょう
icon_set_creator3.png

すると「macOS」というフォルダが作成されます
中を見ると以下のような感じです
icon_set_creator4.png

これを xcode で作成したプロジェクトにコピーするだけです

AppIcon.appiconset をコピーする

あとは丸ごとコピーするだけです

  • cd /path/to/xcode-workspace/test-proj/test-proj/Assets.xcassets
  • cp ~/Downloads/icons/macOS/AppIcon.appiconset/* .

これで Xcode から Assets.xcassets を見ると以下のようになっています
icon_set_creator5.png

最後に

Icon Set Creator を使って macOS 用のアイコンを作成してみました
できたファイルたちをコピーするだけなので iOS のときとほぼ同じ感じでできます
Web ではなくアプリとして動くのでそこだけ違う感じでしょうか

2018年3月5日月曜日

time コマンドの format を使ってみた

概要

time コマンドと言っても format が使えるのは /usr/bin/time のほうになります
サンプルを紹介します

環境

  • macOS 10.13.2
  • Ruby 2.4.1p111
  • Sinatra 2.0.0

サンプルコマンド

(/usr/bin/time --format="{\"real\":%e,\"sys\":%S,\"user\":%U,\"time\":`date +"%Y%m%d%H%M%S"`}" curl -sL 192.168.0.2 > /dev/null) &>> log

こんな感じ
上記は curl を実行した時間を計測しています
JSON で出力して結果を log ファイルにリダイレクトしています
format ないでは eval することもできるので普通のコマンドの結果も含めることができます

2018年3月3日土曜日

ESXi から紐付いている vCenter を見つける方法

概要

vCenter に登録されているホストは簡単に見つけることができます
逆に ESXi から登録されている vCenter を知りたいケースもあると思います
そんな場合に役に立つ方法を紹介します

環境

  • Ubuntu 16.04
  • Ruby 2.3.1p112
  • rbvmomi 1.11.3

サンプルコード

rbvmomi を使って取得しています
moref を使って取得しているだけなので直接 SOAP をコールしても問題ありません

require 'rbvmomi'

vim = RbVmomi::VIM.connect(
  host: 'your-esxi-ip-or-hostname',
  user: 'esxi-username',
  password: 'esxi-password',
  insecure: true
)

vim.serviceContent.rootFolder.childEntity.each { |dc|
  dc.hostFolder.childEntity.each { |compute|
    compute.host.each { |host|
      puts host.summary.managementServerIp
    }
  }
}

こんな感じです
moref で見るとこの階層です
esxi_belong_vcenter1.jpg

ID/PW が同じであれば、ESXi の情報をリストにして順番に検索すれば vCenter にぶら下がっているかどうかわかります

2018年3月2日金曜日

Swift でネットワークに接続されているか確認できる ReachabilitySwift をさらっと試してみた

概要

Swift でネットワークに接続しているかどうか確認するのに Reachability.swift というライブラリがあったので使ってみました
サンプルが動作するか確認したレベルです

環境

  • macOS 10.13.3
  • Xcode 9.2 (9C40b)
  • ReachabilitySwift 4.1.0

ライブラリインストール

  • pod init
  • vim Podfile
pod 'ReachabilitySwift'
  • pod install

サンプルコード

  • vim ViewController.swift
import UIKit
import Reachability

class ViewController: UIViewController {
    let reachability = Reachability()!
    override func viewDidLoad() {
        super.viewDidLoad()
        reachability.whenReachable = { reachability in
            if reachability.connection == .wifi {
                print("Reachable via WiFi")
            } else {
                print("Reachable via Cellular")
            }
        }
        reachability.whenUnreachable = { _ in
            print("Not reachable")
        }
        do {
            try reachability.startNotifier()
        } catch {
            print("Unable to start notifier")
        }
    }
}

let reachability = Reachability()! はインスタンス変数として定義するようにしてください
ローカル変数だと初めの 1 回しか監視できません

closure でリスナーを登録してネットワークの状態が変化したら呼ばれるようにします
リスナーを登録したら reachability.startNotifier() でネットワークの監視を初めます
基本的には必要なときに start して終了したら stop したほうが良いと思います
このリスナーの中でフラグを ON/OFF するような仕組みにすればアプリがネットワークにつながっているかチャックできると思います

ちなみに実機でテストして Wifi などを切ったりすると実際にコールされるリスナーの動きを確認することができると思います

参考サイト

2018年3月1日木曜日

SwiftRealm で update 処理

概要

前回、基本的な操作方法を紹介しました
今回は更新系の処理を試してみました

環境

  • macOS 10.13.3
  • Xcode 9.2 (9C40b)
  • RealmSwift 3.1.1

クラス定義

まずはクラスを定義しましょう
シンプルに id 属性と name 属性を持つクラスを定義します

import RealmSwift

class User: Object {
    @objc dynamic var id = 0
    @objc dynamic var name: String? = nil

    override static func primaryKey() -> String? {
        return "id"
    }

    static func insertTestData() {
        let names = ["bob", "tom", "jessie"]
        var users: [User] = []
        for (id, name) in names.enumerated() {
            let user: User = User()
            user.name = name
            user.id = id
            users.append(user)
        }
        let realm = try! Realm()
        try! realm.write {
            realm.add(users)
        }
    }

    static func debug() {
        let realm = try! Realm()
        let user = realm.objects(User.self)
        for u in user {
            print(u.id)
            print(u.name!)
        }
    }
}

そしていきなりポイントですが id 属性を Primary Key として登録しています
更新系の処理を行う場合はこの定義がないとエラーとなります

あとテストデータを挿入するためのメソッドと挿入したデータを表示するだけのメソッドを追加しています

一旦実行する

今回はシングルビューアプリケーションにしたので ViewController 側で定義したクラスを呼び出します

override func viewDidLoad() {
    super.viewDidLoad()
    User.insertTestData()
    User.debug()
}

実行結果は以下の通りです

0
bob
1
tom
2
jessie

更新するためのメソッドを追加する

先ほどの User クラスに以下のメソッドを追加しましょう

static func update(id: Int, name: String) {
    let realm = try! Realm()
    let user = User()
    user.id = id
    user.name = name
    try! realm.write {
        realm.add(user, update: true)
    }
}

id と name を指定して該当する id のレコードを更新します
ポイントは add を引数付きでコールする点で update: true を付与すると Primary Key で該当するレコードがあるとそのレコードを更新してくれます
実行してみましょう

override func viewDidLoad() {
    super.viewDidLoad()
    User.update(id: 0, name: "holly")
    User.debug()
}

新規でレコードを追加せず指定した id のレコードが更新されているのがわかります

0
holly
1
tom
2
jessie

該当する id がない場合の挙動

例えば今回の場合 User.update(id: 3, name: "holly") という感じでコールすると以下のようになりました

0
holly
1
tom
2
jessie
3
holly

所謂 upsert の挙動になります
該当するレコードがある場合は更新し、ない場合は新規のレコードとして追加します

ちなみに update: false にして同じように id = 3 のユーザを登録しようとするとプライマリキーが重複しているよーというエラーになります

Terminating app due to uncaught exception 'RLMException', reason: 'Attempting to create an object of type 'User' with an existing primary key value '3'.'

該当するレコードがない場合に追加しないようにするには

それ専用のメソッドがないようなので条件に合うレコードがある場合は更新してない場合は何もしないというメソッドを定義します

static func updateOnly(id: Int, name: String) {
    let realm = try! Realm()
    let user = realm.objects(User.self).filter("id == \(id)")
    if !user.isEmpty {
        try! realm.write {
            user[0].name = name
        }
    }
 }

あとは以下のような感じでコールすれば id = 10 の場合にエラーとならずかつ新規で追加もされません

override func viewDidLoad() {
    super.viewDidLoad()
    User.updateOnly(id: 3, name: "jobs")
    User.updateOnly(id: 10, name: "none")
    User.debug()
}

最後に

RealmSwift で update 処理を試してみました
ポイントは Primary Key を定義する点かなと思います