2018年2月28日水曜日

SpriteKit で UserDefaults に独自に定義したクラスをセットする方法

概要

過去に SpriteKit 上で UserDefaults を使う方法を紹介しました
UserDefaults は Objective-c の機能になり NSString や NSInteger 型のデータははそのまま使うことができるのですが Swift で定義した独自のクラスのオブジェクトなどを set/get する場合には少し工夫が必要でした
今回はその方法を紹介します

環境

  • macOS 10.13.3
  • Xcode 9.2 (9C40b)

宣言する独自クラスに NSObject と NSCoding を継承する

例えば以下のような感じです

import Foundation

class GameConfig: NSObject, NSCoding {
...
}

Foundation がないと NSObject が継承できないので追加します
またただ継承しただけではダメで NSCoding 用のメソッドを定義しなければなりません

init と encode メソッドを追加する

先ほど継承したクラスを実装すると以下のようになります

import Foundation

class GameConfig: NSObject, NSCoding {
    var roomId = ""
    var player = ""
    var name = ""
    var msg = ""

    init(roomId: String) {
        self.roomId = roomId
        self.msg = "The room was lost..."
    }

    required convenience init(coder aDecoder: NSCoder) {
        let roomId = aDecoder.decodeObject(forKey: "roomId") as! String
        self.init(roomId: roomId)
        let player = aDecoder.decodeObject(forKey: "player") as! String
        let name = aDecoder.decodeObject(forKey: "name") as! String
        self.player = player
        self.name = name
    }

    func encode(with aCoder: NSCoder) {
        aCoder.encode(roomId, forKey: "roomId")
        aCoder.encode(player, forKey: "player")
        aCoder.encode(name, forKey: "name")
        aCoder.encode(msg, forKey: "msg")
    }
}

重要なのは required convenience initencode メソッドになります
どちらも UserDefaults の set/get をするときに自動で呼ばれるのですがこれがないと「Use of undeclared type 'NSObject' in Xcode」や「class 'GameConfig' does not implement methodSignatureForSelector」などのエラーが発生します

また required convenience init でエンコードしたデータをフィールドにセットする場合に既存のコンストラクタで全フィールドに対して値をセットできない場合は、その後でちゃんと各フィールドに対して値とセットしないと取り出したときに値がない状態になってしまいます

UserDefaults にセットする

あとは独自クラスから作成したオブジェクトをセットするだけですがここでも少し工夫が必要です

let defaults = UserDefaults.standard
defaults.set(NSKeyedArchiver.archivedData(withRootObject: config), forKey: "GameConfig")

config は上記で説明した GameConfig クラスのオブジェクトになります
NSKeyedArchiver.archivedData を使ってセットすることでデータを NSData 側に変更しています
こうすることで独自で定義したクラスを UserDefaults にセットできるようにしています

UserDefaults から取り出す

取り出すときも少し工夫が必要です
例えば以下のように取り出します

let defaults = UserDefaults.standard
// UserDefaults のデータがある場合
if let data = defaults.value(forKey: "GameConfig") as? NSData {
    // NSData から GameConfig に変更
    let config = NSKeyedUnarchiver.unarchiveObject(with: data as Data) as! GameConfig
    // これで参照できる
    print(config.name)
}

セットしたデータが存在するかしないかを確認してもし存在するならば NSKeyedUnarchiver.unarchiveObject を使ってデータを取り出し GameConfig クラスにキャストしてあげます
こうすることで元のクラスのオブジェクトとして扱うことができます

最後に

SpriteKit + UserDefaults で独自のクラスのオブジェクトを扱う方法を紹介しました
独自のクラスになったとたん急に複雑になってしまいます
これが面倒であればクラスの特定のフィールドだけを String などで共有する方が簡単なケースもあるかもしれません

そもそも SpriteKit で UserDefaults を使うケースですが自分は SKScene 側で生成したデータを AppDelegate.swift の applicationWillTerminate で扱うために UserDefaults を使いました
UserDefaults は永続的なデータの保存にはあまり向いていないのでアプリのライフサイクル内でのみ共有したいデータがあれば使うようにする感じが良いと思います

2018年2月23日金曜日

Cygwin 上でコマンドを管理者権限で実行する方法

概要

例えば Windows 上の hosts ファイルを Cygwin 上で編集したい場合などに使えます

環境

  • Windows 7 64bit
  • Cygwin 2.9.0

コマンド

  • cygstart.exe --action=runas mv hosts hosts.back

コマンドを実行する際に先頭に cygstart.exe --action=runas を付与するだけで OK です
実行すると Windows のダイアログが表示されて本当に管理者権限で実行していいか確認が表示されます

2018年2月21日水曜日

天空トラップタワーの湧き効率を上げるために頑張ったこと

概要

前回スライムトラップタワーが湧かない問題について考えました
今回は天空トラップタワーでモンスターが湧かない問題を対処してみました

環境

  • macOS X 10.13.2
  • Minecraft 1.12.2

天空トラップタワー

作った天空トラップタワーは水流タイマー式のトラップタワーになります
湧き層は全部で 9 層あり一番下の湧き層が Y 軸 225 の位置にあります
そこから 9 層作成しました
待機場所は Y 軸 184 の場所で湧き層とは 41 も差があります (ここが失敗だったかもしれません、、、)
地上の高さは 56 の位置から開始しています
地面ではなく川の上に作っているので地面の開始位置の高さが若干低くなっています

作成完了後待機場所で待っているのですが湧きが非常に悪い感じです
落下してくるモンスターのタイミングとしては 30 秒の 1 体という感じです
これが良いのか悪いのか何ともわからないのですが、他の紹介動画などを見ているともっと湧いても良い印象です

この湧き効率を何とか上げるべく頑張ってみました

試したこと

以下いろいろと模索したときのメモです

待機場所をいろいろと模索

前回のスライムトラップタワーでもいろいろと模索したので今回もまずは待機場所を模索してみました
はじめは Y 軸 184 の場所でじっと待機していたのですが湧き効率が良くなかったのでいろいろな場所で検証してみました

湧き層から平行方向に 24 マス離れた場所で待機したりあえて湧き層に密着して待機したりしてみましたが劇的に変わるものはありませんでした

そしてスライムトラップタワーのときにも試したトロッコで動き続ける待機場所も作成してみたのですがこれも劇的に改善することはありませんでした

結局落ち着いたのは待機場所 Y 軸 184 の場所から 13 ブロックおいた Y 軸 197 の場所で待機する方法でした
これがとりわけ一番効率が良かったわけではないですが「何となく湧き続けている感」があったのでここにしています
それでも 10 分間で 10 - 30 アイテムほどしかドロップしません

描画距離を調整

描画距離が短すぎると 128 ブロック離れた mob がデスポーンしないという情報があったので試しに描画距離を 18 チャンクに伸ばしてみたのですが特に変わりませんでした

トラップドアを使ってクリーパーのみ湧くようにした

もうひとつ検証として試してみたのが湧き層の天井にトラップドアを設置してクリーパーのみスポーンするようにしてみたことです
しかもこれを 9 層いきなりやるのではなく下位層から徐々に設置していきどの層になったときにクリーパーのみになるかを調査してみました

というのもいろいろと調べてみると「高度によってスポーン効率が悪くなる」という情報を見たので、それが本当かどうか試してみました
ようするにクリーパー以外も湧いているのであればその層の Y 軸では mob がスポーンするということになり、逆にクリーパーのみ湧くようになったのであればその層以降の Y 軸では mob が湧かないということになります

で結果としてどうだったかというと

  • 10 層全部スポーンしているのが確認できた

でした
根拠は下から 9 層はトラップドラを設置して最後の上部 1 層だけはトラップドアを設置しない状態で湧きを確認したのですが、クリーパー以外も湧いたからです
ただ、この検証で明確になっていないのは

  • 上位の 10 層や 9, 8, 7 層での湧き効率が 1 層に比べて良いかまではわからない

という点があります
厳密にやればわかるかもしれませんが、自分は検証しきれていません

ここまで検証して自分の考察としては 255 でもおそらく mob は湧くが地上や地下洞窟に比べて湧き効率が非常に悪いと推測しました
なので湧き効率を上げる 1 つの方法としてもっと低いところに湧き層を作るというのが解決策になるかもしれません

ただ、湧き層を低い位置に作るとその分地上 or 地下の湧き潰しが必要になるのでもしそれが面倒なのであればやはり無いもない海の上を待機場所にしてそこから 128 離れたあたり (63 + 128 = 191) を湧き層にするのが良いかもしれません
それでも海の下にもし洞窟があれば結局そこが影響して湧きが悪くなるというのはあります

レッドストーン回路の変更

今回参考にしたサイトのタイマー回路を見るとタイマーから出た信号をラッチ回路で単発の信号にしてディスペンサーから水を出し、その後レッドストーンリピーターの遅延を使って水を回収するという仕組みにしているようでした

これをそのまんまやってみたところうまく動きませんでした
具体的にうまく動かなかったのは水をまく初めの信号が 1 層目の信号を受け取る 1 つ目の部分にしか伝わっていませんでした
本来であればブロックを経由して伝わってきた信号がトーチタワーと伝わって上位層にも行くのですが行っていませんでした
つまり本来水を回収する信号で水をまいてしまっており、水を回収するタイミングが次のタイマーでの信号になるので、湧き層に水がまかれている時間が長くなってしまい、その分湧きが悪くなっているということに気が付きました

なので回路を以下のように変更して水をまくための信号がしっかり全てのトーチタワーに伝わるようにしてみました
具体的にはラッチ回路で単発の信号を作る部分を手前に持ってきてそこから水をまく信号と水を回収する信号を分岐するようにしています
minecraft_tenku_tt1.png

あともう一つ改善してみたのがタイマーの切り替えのタイミングです
紹介サイトではアイテムを 21 個入れていたのですがこれを 32 個に増やしてみました
要するにこれも水をまいていない時間を増やすことで mob の湧き効率を上げようという作戦になります

で、この回路の変更で結局どうなったかというとこれもそこまで湧き効率が改善した感じはありませんでした、、、

待機場所の足場を極力なくす

スポーンするブロックを探す段階で足場が少ないほど湧きやすくなると思い待機場所の足場をほぼなくしました
が、結果は変わらずでした

湧きの挙動がおかしい

待機場所ではただただ立っているだけなのですが、湧きの挙動も少しおかしいので記載しておきます
どうおかしいかと言うと時間が経過すると全く湧かなくなるということです
待機し始めは湧きが悪いものの 2, 3 匹の mob が落下するのを確認することができます
が、1, 2 時間すると落下する mob がほぼいなくなります
試しに 8 時間ほど放置してみたところやはり、ある時間から全く湧いていないことがわかりました
湧いていないということは何かしらのスポーン条件に当てはまっていない可能性があるとは思いますが、その原因はさっぱりわかりません

最後に

マインクラフト v1.12.2 で天空トラップタワーの湧き効率を何とかあげてみようと模索してみました
結果的にはほぼ上げることができませんでした

今回紹介した中でまだ試していない施策として

  • 湧き層を下に増やす
  • 地上の湧き潰しをしてみる (たぶん意味なし)
  • 別のタイプの天空トラップタワーを作ってみる

があるかなーと思います
正直天空トラップタワーを作るのにはかなり苦労するのでまずは湧き層を増やすことをやってみようかなと思います
ただ、これもすでに処理層などがあるのを考えるとそれを一度崩す必要が出てくるので大変かなと思います

あと今回の作業で一番なぞだったのが「Y 軸 200 以上の高度での mob のスポーン効率」です
もしこれが本当にそうなのであればそもそも湧き層を作る位置をもっと下げないといけなくなります
いろいろと調べたのですが明確な情報が出てこなかったので詳細は不明です

2018年2月20日火曜日

docker-compose で load-balancer を使う方法

概要

docker が公式で load-balancer 用のイメージを出しているのでそれを使います
dockercloud-haproxy というやつです

環境

  • macOS 10.13.2
  • Docker version 18.02.0-ce, build fc4de44
  • docker-compose version 1.19.0, build 9e633ef

docker-compose.yml

version: '2'
services:
  web:
    image: kakakikikeke/debug_container
    ports:
      - 4567
  lb:
    image: dockercloud/haproxy
    ports:
      - 80:80
    links:
      - web
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
  • docker-compose up -d
  • curl -s localhost | jq '.hostname'

こんな感じでアクセスするとコンテナ ID が取得できます

スケールしてみる

  • docker-compose scale web=3

でスケールできます
これで何度か curl を叩いてみましょう

  • for i inseq 1 10; do curl -s localhost | jq '.hostname'; done
"7a6fd1982372"
"d35f25b865a1"
"57236b7f536d"
"7a6fd1982372"
"d35f25b865a1"
"57236b7f536d"
"7a6fd1982372"
"d35f25b865a1"
"57236b7f536d"
"7a6fd1982372"

こんな感じで別のコンテナにアクセスしてくれていることがわかると思います
ローカルで動作させる場合には volumes で指定している /var/run/docker.sock を haproxy にマウントしていないとちゃんとバランシングしてくれないので注意してください

最後に

dockercloud-haproxy を使ってコンテナのロードバランシングをしてみました
scale するだけでバランシングしてくれるので haproxy の再起動が不要なのが嬉しい点です
haproxy 自体の多重構成もできるはずです
AWS などには ALB など専用のロードバランサがありますが、そこまでアクセスがないのであれば、コンテナで簡単に LB を立てるのもありかなと思います
本番運用するのであれば Swarm でクラスタ化してちゃんと affinity ルールを考える必要はありそうですが

参考サイト

2018年2月19日月曜日

docker 17.06 で swarm 機能を試してみた

概要

docker 17.06 で swarm の使い方が変わっていたので試してみました
かなり簡単に使える印象です
Manager と Worker 2 つのノードを作成してどちらも dockerd を起動しておきましょう
ついでなので overlay ネットワークも試してみました

環境

  • Ubuntu 16.04
  • Docker version 17.06.2-ce, build cec0b72

Manager となるノードで初期化する

  • docker swarm init

以下で実行する join 用のコマンドが表示されます

Worker となるノードで join する

  • docker swarm join --token SWMTKN-1-4n6elansbxnaz6iubmy2i171dptprfpflykqn3vjx53ef7kivy-65jhododu1mnkps3s95jfl86j 192.168.100.20:2377

Manager ノードで overlay ネットワークを作成する

  • docker network create -d overlay --attachable overlay1

attachable を指定すると作成したネットワークを手動でアタッチすることができるようになります

  • docker network ls

で作成した overlay ネットワークを確認しましょう
Manager では表示されると思います
Worker 側では表示されません、が普通に使うことができるので試してみます

ちなみに ingress というデフォルトで作成されるネットワークは Manager にも Worker 側にもあります

overlay ネットワークを試してみる

Manager 側でコンテナを起動します

  • docker run -d --net overlay1 -p 80:80 --name nginx nginx

これで overlay ネットワーク上で起動するコンテナが 1 台作成されました
続いて Worker 側のホストでコンテナを起動してみます

  • docker run --rm --net overlay1 --link nginx alpine ping -c 5 nginx

これで実行すると Manager 側のコンテナにアクセスできると思います

Swarm 状態を解除する

まず Worker 側で

  • docker swarm leave

します
Manager 側で

  • docker node ls

を実行してみて leave した Worker 側のホストのステータスが Down になるのを待ちます
Down になったら Manager 側で

docker node rm 7jug2spgk3c1i335shlnmz885

することで Worker を切り離すことができます
Worker 側で docker info を実行すると Swarm の状態が表示されなくなります

最後に Manager 1 台になったら Manager で以下のコマンドを実行すると Manager を切り離せます

  • docker swarm leave --force

ちなみに swarm 状態が解除されると overlay ネットワークも勝手に削除してくれました

最後に

比較的新しめの docker で Swarm 機能を試してみました
かなり簡単に使えるようになっている印象です
昔は Swarm を管理する agent コンテナが起動する必要があったのですがそれはなくても動作するみたいです
KVS を管理する必要があると思うのでどこかで管理しているとは思いますが

2018年2月17日土曜日

マイクラを DropBox を使ってロールバックする方法

概要

放置プレイ中に予期せぬ自体で死んでしまいアイテムをロストするなんてことがあると思います
そんな場合にセーブデータをロールバックする方法を紹介します

環境

  • macOS 10.13.2
  • Minecraft 1.12.2

やり方

前提としてセーブデータを Dropbox で管理している必要があります
DropBox でなくても OK ですがバージョン管理できていないとダメです
今回は DropBox でロールバックする方法を紹介します
マイクラを起動している場合は停止しましょう

まず DropbBox にログインしてセーブデータ配下のディレクトリに移動します
そして level.dat のバージョン履歴を開きます
minecraft_rollback1.png

level.dat は現在のキャラクターの状態を保存しているファイルです
ほぼ毎分更新履歴があるので死ぬ前、ロストする前の時間に遡って復元を選択します
minecraft_rollback2.png

これで状態が戻りました
再度マイクラを開いて見ると死ぬ前の状態になっていると思います
もし、戻りすぎた or まだ戻っていない場合は再度マイクラを停止して復元する箇所を変えてみてください

同じようなファイル名で _old とついたファイルがありますがそいつは特に復元しないで大丈夫です

他にも .json ファイルや .log, .mca ファイルなどが更新されていますがそれも復元しないで OK です

最後に

マイクラのデータをロールバックする方法を紹介しました
前提としてバージョン管理されていることが条件なので、バージョン管理していない場合は諦めてください

Dropbox はファイルに更新があるたびに同期するので自分の好きなタイミングで履歴を取りたい場合などは git などを使ったほうが良いと思います
コミットログも残せるので

DropBox も git もよくわからんという場合はセーブデータのディレクトリを丸ごとコピーするでも良いと思います

2018年2月16日金曜日

framework not found GoogleToolboxForMac 対策

概要

iOS11 の実機 (iPhone6) でビルドすると問題ないのですが iOS10 の実機 (iPhone5) でビルド発生するようになりました
対策したので紹介します

環境

  • macOS 10.13.2
  • Xcode 9.2 (9C40b)

Build Active Architecture Only を NO にする

プロジェクトの Build Settings と Pods の Build Settings の Build Active Architecture Only をすべて NO にすることで解決しました

Targets の Build Settings
not_found_framework3.png

Projects の Build Settings
not_found_framework2.png

Pods の Build Settings
not_found_framework1.png

一度 Clean してから iPhone5 上でビルドしてみてください
当然ですが Deployment Target は iOS10 以上を指定するようにしてください

参考サイト

2018年2月14日水曜日

Firebase でリアルタイムデータベースを使ってみた

概要

過去にデータの保存と取得を試しました
リアルタイムなやり取りはやっていなかったので試してみました
また今回は SpriteKit を使っています

環境

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

Firebase にアプリを追加

コンソール画面から追加しましょう
追加したら
GoogleService-Info.plist をプロジェクトに追加します

ライブラリインストール

  • pod init
  • vim Podfile
# Uncomment the next line to define a global platform for your project
# platform :ios, '9.0'

target 'test-proj' do
  # Comment the next line if you're not using Swift and don't want to use dynamic frameworks
  use_frameworks!

  # Pods for test-proj
  pod 'Firebase/Core'
  pod 'Firebase/Database'
  pod 'Firebase/AdMob'

  target 'test-projTests' do
    inherit! :search_paths
    # Pods for testing
  end

  target 'test-projUITests' do
    inherit! :search_paths
    # Pods for testing
  end

end
  • pod install

初期化

  • AppDelegate.swift
import UIKit
import Firebase

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        FirebaseApp.configure()
        return true
    }
}

didFinishLaunchingWithOptions 内で FirebaseApp.configure() を実行しているだけです

リアルタイムにデータを取得する

  • GameScene.swift
import SpriteKit
import FirebaseDatabase

class GameScene: BaseScene {

    let rootRef = Database.database().reference()

    override func didMove(to view: SKView) {
        let bmref = rootRef.child("test-proj")
        bmref.observe(.value, with: { (snapshot) in
            let value = snapshot.value as? NSDictionary
            let room = value!["rooms"] as? NSDictionary
            print(room!["id"] as! Int)
        })
    }
}

observe を登録することで、そのコールバック内でデータが更新されたときの挙動を処理することができます
今回は階層的なデータを登録してそのデータを表示するだけです

動作確認

動作確認の前にシミュレータでアプリを起動しておきます
今回はコンソールにデバッグ表示するだけです
.sks ファイルには Hello World のラベルが定義されていますが気にしません

認証をオフにする

とりあえず今回はテストなので前許可にしちゃいます
コンソールから設定しましょう

Database -> ルール

firebase_realtime1.png

ちなみに認証をオンにして使いたい場合は Anonymous 認証ですが、こちらを御覧ください

テストデータを登録する

コンソールからデータを登録してみます

Database -> データ

firebase_realtime2.png

すると登録した瞬間コンソールに登録した数字が表示されると思います
firebase_realtime3.png

最後に

Firebase でリアルタイムデータベースを使って挙動を確認してみました
今回の場合構造的なデータを登録したので構造が間違っているとキャストに失敗して Exception が発生します

データの登録に関しては setValue をコールする感じです

今回はシミュレータ+管理コンソールでやりましたが次回は実機+シミュレータでやってみたいと思います 

参考サイト

2018年2月10日土曜日

Ruby からサラッとツイートする

概要

Ruby からツイートしてみました

環境

  • macOS 10.13.2
  • Ruby 2.4.1p111
  • twitter 6.2.0

ライブラリインストール

  • bundle init
  • vim Gemfile
gem "twitter"
  • bundle install --path vendor

サンプルコード

  • tweet.rb
require 'twitter'

client = Twitter::REST::Client.new do |config|
  config.consumer_key        = ""
  config.consumer_secret     = ""
  config.access_token        = ""
  config.access_token_secret = ""
end

client.update("I'm tweeting with ruby")
  • bundle exec ruby tweet.rb

各種キー情報は Twitter Application Management から取得してください
最近だと電話番号の登録が必要だとか、、、

2018年2月9日金曜日

selenium-docker を使ってみた

概要

前回 Ruby で Selenium を使って firefox の自動操作をしてみました
その際にローカル上に selenium-server を立てて geckodriver で操作しました
ローカルで実行する場合いろいろとセットアップが面倒です
そこで selenium docker を使うとその辺の面倒なセットアップが不要になります

環境

  • macOS 10.13.2
  • Ruby 2.4.1p111
  • selenium-webdriver 3.9.0
  • docker 18.02.0-ce-rc2
  • docker-compose 1.19.0-rc2

selenium docker の起動

これだけで selenium server が立ちます

  • docker run -d -p 4444:4444 --shm-size 2g selenium/standalone-firefox:3.8.1-francium

4444 ポートで LISTEN していることを確認してください

  • docker ps
CONTAINER ID        IMAGE                                        COMMAND                  CREATED             STATUS              PORTS                    NAMES
b54c3c9b5497        selenium/standalone-firefox:3.8.1-francium   "/opt/bin/entry_poin…"   55 seconds ago      Up About a minute   0.0.0.0:4444->4444/tcp   modest_bhaskara

あとはここに対して selenium を使ったテストコードを書くだけです

テストコード

前回とほぼ同じです
:remote, url: "http://localhost:4444/wd/hub" の指定が増えています

  • vim access_google.rb
require "selenium-webdriver"

driver = Selenium::WebDriver.for :remote, url: "http://localhost:4444/wd/hub", desired_capabilities: :firefox
driver.navigate.to "http://google.com"

element = driver.find_element(name: 'q')
element.send_keys "Hello WebDriver!"
element.submit

sleep 10

puts driver.title

driver.quit

こんな感じです

  • bundle exec ruby access_google.rb

ブラウザがコンテナ内で立ち上がっているのでローカル側では立ち上がらないと思います

  • docker logs -f modest_bhaskara

で selenium server のログを確認していると流れると思います

テストも docker 内で実行する

こうなるとテストも docker 内で実行したくなります
やってみましょう

  • vim Dockerfile
FROM ruby

ADD . /home
WORKDIR /home
RUN bundle install --path vendor

CMD ["bundle", "exec", "ruby", "access_google.rb"]

テストコードも書き換えます
Selenium サーバのホスト名を環境変数で指定できるようにします

  • vim google_access.rb
require "selenium-webdriver"

host = ENV['SELENIUM_SERVER'] || 'localhost'
driver = Selenium::WebDriver.for :remote, url: "http://#{host}:4444/wd/hub", desired_capabilities: :firefox
driver.navigate.to "http://google.com"

element = driver.find_element(name: 'q')
element.send_keys "Hello WebDriver!"
element.submit

sleep 10

puts driver.title

driver.quit

Selenium サーバとテストが同時に上がるように docker-compose を作成します

  • vim docker-compose.yml
version: '3'
services:
  test:
    build: .
    links:
      - server
    environment:
      - SELENIUM_SERVER=server
  server:
    image: selenium/standalone-firefox:3.8.1-francium
    ports:
      - 4444:4444
    shm_size: '2gb'

これで実行してみましょう

  • docker-compose up -d

サーバとテストが同時に立ち上がりテストが実行されます

  • docker-compose logs test

でログを確認すると puts している内容が表示されると思います

Attaching to selenium_test_1
test_1    | Hello WebDriver! - Google 検索

初回 up 時に server 側がまだ起動していなくてエラーになる場合は再度テストだけを実行してみてください

  • docker-compose start test

おまけ: VNC を使ってブラウザの様子を確認する

selenium/standalone-firefox-debug のイメージに切り替えるだけです

  • docker run -d -p 4444:4444 -p 5900:5900 --shm-size 2g selenium/standalone-firefox-debug:3.8.1-francium

立ち上がったら適当な VNC Viewer を使って localhost:5900 に接続します
ユーザ名はなくパスワードに「secret」と入力すれば接続できます

試しにテストを実行すると firefox が問題なく起動するのが確認できました

最後に

selenium docker を使って selenium でテストする環境を docker 上に構築してみました
セットアップが不要なのもありますし、VNC でデバッグもできるのでこれを使うほうが断然良いと思います

参考サイト

2018年2月8日木曜日

Ruby で Selenium を使ってみた

概要

Ruby で Selenium を使ってブラウザ操作をしてみました

環境

  • macOS 10.13.2
  • Ruby 2.4.1p111
  • selenium-webdriver 3.9.0
  • geckodriver 0.19.1

geckodriver のインストール

  • brew install geckodriver

とりあえずサンプルコード

  • vim access_google.rb
require "selenium-webdriver"

driver = Selenium::WebDriver.for :firefox
driver.navigate.to "http://google.com"

element = driver.find_element(name: 'q')
element.send_keys "Hello WebDriver!"
element.submit

sleep 10

puts driver.title

driver.quit

ヘッドレスブラウザっぽいです
検索のテキストボックスに「Hello WebDriver!」と入力し Enter を押して検索しています
検索結果の画面のタイトルバーからタイトルを取得して終了です

基本的には find_element でテキストボックスなりボタンを取得して入力したりクリックしたりすれば操作できると思います

よく使いそうなテクニック

クリックする

element.click

タグ名で検索する

driver.find_element(:tag_name, 'input')

CSS セレクタを直接使う
属性の比較は完全一致や部分一致、前方一致などが使用できる

driver.find_element(:css, 'input[onclick*="552777"]')

おまけ

ブラウザで実際に要素が検索できるかどうか試したい場合があると思います
そんな場合は Chrome の Console 機能などを使うと簡単に検索できます

例えば今回の driver.find_element(name: 'q') は CSS セレクタを使って $$('[name=q]'); という感じで検索できます
class 属性で検索したい場合は $$('.srp'); のようにドットで検索し id 属性を検索した場合は $$('#doc-info'); という感じでシャープを使って検索します
その他のCSS セレクタの検索テクニックはこちらを参考にしてみてください

xpath を使って要素情報を取得することも可能です

最後に

Ruby で Selenium を使ってブラウザ操作をしてみました
とりあえずローカルで動かすのであれば簡単にできます
次回は selenium server を別途構築してそこでブラウザを動かす方法を紹介したいと思います

参考サイト

2018年2月7日水曜日

Ruby を使って fc2 ブログに記事をポストする方法

概要

ruby で XML-RPC API を使って fc2 に記事をポストしてみました
記事の投稿からカテゴリの設定など記事の投稿に必要な最低限の流れを紹介したいと思います

環境

  • macOS 10.13.2
  • Ruby 2.4.1p111
  • xmlrpc 0.3.0

ライブラリインストール

  • bundle init
  • vim Gemfile
gem "xmlrpc"
  • bundle install --path vendor

記事をポストする

metaWeblog.newPost を使います

  • vim post.rb
# coding: utf-8
require 'xmlrpc/client'

client = XMLRPC::Client.new2("http://blog.fc2.com/xmlrpc.php")
blog_id = '' # 空で OK
username = 'fc2_user@your.mail.local' # fc2 の場合 fc2 ID で登録したメールアドレスを指定します
password = 'password'
contents = {
  'title' => 'test',
  'description' => 'test post',
  'mt_text_more' => 'more text'
}
publish = 1 # 1 は公開, 0 は非公開

begin
  ret = client.call('metaWeblog.newPost', blog_id, username, password, contents, publish)
  puts ret
rescue XMLRPC::FaultException => e
  puts e
end

基本的な使い方は client.call の第一引数に API を指定します
それに続いて API に必要なパラメータを指定します
metaWeblog.newPost の場合 blog_id, username, password, contents, publish が必要になります
blog_id は fc2 の場合空で OK です
username はメールアドレスを指定します
password は fc2ID で登録したパスワードを指定します
contents はハッシュで指定します
title, description でタイトルと記事の本文を指定します
それ以外にも

  • mt_allow_comments
  • mt_allow_pings
  • mt_convert_breaks
  • mt_text_more
  • mt_excerpt
  • mt_keywords
  • mt_tb_ping_urls

などが指定できます
試しに mt_text_more を指定したところ「続きを読む」の表示が追加されました

API の一覧を取得する

mt.supportedMethods を使います

  • vim methods.rb
# coding: utf-8
require 'xmlrpc/client'

client = XMLRPC::Client.new2("http://blog.fc2.com/xmlrpc.php")
blog_id = '' # 空で OK
username = 'fc2_user@your.mail.local' # fc2 の場合 fc2 ID で登録したメールアドレスを指定します
password = 'password'

begin
  ret = client.call('mt.supportedMethods', blog_id, username, password)
  puts ret
rescue XMLRPC::FaultException => e
  puts e
end

以下の API 一覧が取得できました

  • blogger.getUsersBlogs
  • blogger.getUserInfo
  • blogger.newPost
  • blogger.editPost
  • blogger.deletePost
  • blogger.getRecentPosts
  • metaWeblog.newPost
  • metaWeblog.editPost
  • metaWeblog.getPost
  • metaWeblog.getRecentPosts
  • metaWeblog.newMediaObject
  • metaWeblog.getCategories
  • mt.getRecentPostTitles
  • mt.getCategoryList
  • mt.getPostCategories
  • mt.setPostCategories
  • mt.supportedMethods
  • mt.supportedTextFilters
  • mt.getTrackbackPings
  • mt.publishPost
  • mt.setNextScheduledPost

どうやら blogger API もサポートしているようです

カテゴリを登録する

これは残念ながら API がないので諦めて管理画面から登録しましょう

カテゴリを取得する

mt.getCategoryList を使います

  • vim get_category.rb
# coding: utf-8
require 'xmlrpc/client'

client = XMLRPC::Client.new2("http://blog.fc2.com/xmlrpc.php")
blog_id = '' # 空で OK
username = 'fc2_user@your.mail.local' # fc2 の場合 fc2 ID で登録したメールアドレスを指定します
password = 'password'

begin
  ret = client.call('mt.getCategoryList', blog_id, username, password)
  puts ret
rescue XMLRPC::FaultException => e
  puts e
end

先ほど手動で登録したカテゴリ (test) も取得できました
未分類には categoryId がないようです

{"categoryId"=>"0", "categoryName"=>"未分類"}
{"categoryId"=>"1", "categoryName"=>"test"}

カテゴリをセットする

新規で投稿した記事には未分類のカテゴリがセットされています
自分で作成したカテゴリをセットするには mt.setPostCategories を使います

  • vim set_category.rb
# coding: utf-8
require 'xmlrpc/client'

client = XMLRPC::Client.new2("http://blog.fc2.com/xmlrpc.php")
post_id = '1'
username = 'fc2_user@your.mail.local' # fc2 の場合 fc2 ID で登録したメールアドレスを指定します
password = 'password'
categories = [
  {'categoryId' => '1', 'isPrimary' => true}, {'categoryId' => '0', 'isPrimary' => false} # 手動で登録したカテゴリの ID
]

begin
  ret = client.call('mt.setPostCategories', post_id, username, password, categories)
  puts ret
rescue XMLRPC::FaultException => e
  puts e
end

categories 内で categoryId を指定することでセットすることができます
isPrimary は複数カテゴリを指定した場合、第一カテゴリにセットしたいカテゴリに対して true をセットします
ただ、fc2 の場合カテゴリを 1 つしかセットできないので、配列の一番初めにセットしたカテゴリが適用されます

最新の記事を取得する

  • vim get_current_posts.rb
# coding: utf-8
require 'xmlrpc/client'

client = XMLRPC::Client.new2("http://blog.fc2.com/xmlrpc.php")
blog_id = '' # 空で OK
username = 'fc2_user@your.mail.local' # fc2 の場合 fc2 ID で登録したメールアドレスを指定します
password = 'password'
num = 10

begin
  ret = client.call('mt.getRecentPostTitles', blog_id, username, password, num)
  puts ret
rescue XMLRPC::FaultException => e
  puts e
end

上記の場合 num = 10 で最新の 10 件を取得できます
num がどこまで指定可能なのか調べたところ 7,000 件以上投稿してから num = 10,000 で取得したところ問題なく全件取得できました
もしかすると無限に指定できるのかもしれません (もしくは Integer の最大値かもしれません)

最後に

XML-RPC API を使って fc2 ブログに記事をポストしてみました
記事を投稿する際の最低限の流れは紹介できたかなと思います

fc2 ブログの無料プランでは 1 日最大 10 件しか投稿できないので注意してください
テストであれば削除してから再度ポストすれば何回でもテストできます

今回は metaWeblog API と MovableType API を使って記事を投稿してみましたが一部 blogger API もサポートしているようなのでそれを使って良いと思います

参考サイト

2018年2月6日火曜日

gitlab でトークン認証を使って git clone する方法

概要

gitlab で https での clone にトークンを使った認証が使えます
その方法を紹介します

環境

  • Gitlab 9.5.9
  • git on Cygwin 2.14.1

トークンの取得

まずはトークンを取得します

右上アカウントのアイコン -> Settings

でアカウントの設定画面を開きます

「Access Tokens」タブがあるので選択します
Name と Scopes で ACL を設定しますgitlab_token_auth1.jpg

トークンはユーザごとに発行することができます
トークンごとに簡単な ACL を設定できます

トークンが作成できたら忘れずにメモしておきましょう
あとで確認することはできないので忘れたら再作成になります

git clone する

トークンを使って https で clone することができます
こんな感じです

  • git clone https://oauth2:aBcwkfuekAuGn3FQ_gzv@your.gitlab.local/project/repo.git

ポイントは oauth2 ユーザを使って認証する点です

push する

push する場合は pull した段階で .git/config に認証情報が記載されるので普通に git push すれば OK です

最後に

gitlab でトークンを使って pull/push する方法を紹介しました
ssh 認証の場合は鍵の登録や .ssh/config の設定が必要ですが https のトークン認証の場合は不要になります

2018年2月5日月曜日

Swift4 マイグレーション対応メモ

概要

Swift4 へマイグレーションしたときに出たエラーや警告への対応をメモしておきます

環境

  • macOS 10.13.2
  • Xcode 9.2 (9C40b)
  • Cocoapod 1.3.1

The use of Swift 3 @objc inference in Swift 4 mode is deprecated.

警告の詳細は以下の通り

The use of Swift 3 @objc inference in Swift 4 mode is deprecated. Please address deprecated @objc inference warnings, test your code with “Use of deprecated Swift 3 @objc inference” logging enabled, and then disable inference by changing the "Swift 3 @objc Inference" build setting to "Default" for the "ios-fndb" target.

Targets のビルドの設定を変更すれば OK です
migrate_swift4_1.png

Build Settings を選択して、All と Combined にし Swift3 という単語で検索すると「Swift 3 @objc Inference」という項目が出てくるのでこれを Default に変更しましょう

ちなみに xxxTests と xxxUITests のターゲットでも同じ項目があり警告が出ると思うのでそちらの設定も変更してあげましょう

could not build Objective-C module ‘libxml2’

以下のエラーも同時に出ました

'libxml/xmlversion.h' file not found

これは単純にキャッシュがおかしくなっただけでした
Alt + Shift + Cmd + k ですべてのキャッシュを削除したあと再度ビルドするとエラーはなくなりました

pod update の実施

Swift4 対応しているライブラリがある場合は実施しましょう

最後に

Xcode の機能を使ってプロジェクトを Swift4 にマイグレートしてみました
他にも警告が多数でましたが、あとはポチポチやるだけで全て解決しました

主には duplicated になってしまったメソッドの変更ですが、特に手動でやる作業は自分は発生しませんでした
あまり大きなプロジェクトではなかったので、それのせいかもしれません

2018年2月4日日曜日

iPhone5 のバッテリー交換を自力でやってみた

概要

前回フロントのタッチパネルを交換してみました
今度はバッテリーを交換してみました

環境

  • iPhone5

購入したバッテリー

Amazon で調べるとたくさん出てきます
前回液晶を修理した時に工具はそろっていたので今回はバッテリーだけを購入しました

まずパッケージはこんな感じです
iphone5_battery_change1.jpg

開けるとバッテリーと取扱説明書、保証書が入っています
バッテリーはプラケースで保護されているので外しましょう
またコネクタが壊れないようにバッテリー自体にもテープが巻かれているので取っちゃいましょう
iphone5_battery_change2.jpg

交換する

早速修理していきます
前回同様まずは吸盤を使って液晶パネルをパカっと外してください
手前が少し空いたらマイナスドライバを突っ込んで奥に奥に開けていくほうが安全です

まずバッテリーのコネクタを保護しているパネルを取り外しましょう
ネジ 2 つで止まっている写真の下のやつです
iphone5_battery_change3.jpg

パネルを外してコネクタも外しましょう
こんな感じで外れます
iphone5_battery_change4.jpg

で後はバッテリー外すだけです
外す際に iPhone の左側に隙間があるのでそこにマイナスドライバを突っ込んでコテの原理で持ち上げましょう
どうやらデフォルトだとバッテリーが両面テープでしっかりつ接着されてしまっており結構力を入れて剥がさないとダメでした
ベリベリベリと結構な音を立てて剥がれていくので頑張って剥がしましょう
バッテリー右側にもテープで止まっている部分があるのでうまく剥がれない場合は右側からもやってみてください
で、取れたバッテリーが以下の通り
予想通りパンパンに膨れ上がっておりこれが iPhone5 の液晶パネルが浮いてしまう原因でした
iphone5_battery_change5.jpg

で交換後のバッテリーがこんな感じです
同じようにコネクタを接続して保護パネルをネジで止めれば OK です
コネクタはしっかりハマるとパチっと音がするので、音がするまでしっかりとはめてください
iphone5_battery_change6.jpg

あとはケースを被せれば終わりです
ネット上の紹介動画だと液晶も外して本体側だけで作業する動画が多かったのですが自分は面倒だったので液晶のコネクタは接続したままやりました
単純にコネクタが切れたりしないようにするためなので、安全を期す場合には必ず液晶コネクタを外して作業してください

最後に

iPhone5 のバッテリーを交換してみました
液晶に比べて遥かに簡単でした

半分くらいまで充電されているバッテリーだったので交換してすぐに電源 ON にしてみましたが問題なく動作しました
1, 000円ちょっとで交換できるので手先が器用で知識のある方であれば自分で交換する方が安く済むと思います

参考サイト

2018年2月3日土曜日

iPhone5 のタッチパネルを交換してみた

概要

画面が真っ暗になりかつタッチ操作もできなくなりました
ただ QuickTime でミラーリングすると映るのとホームボタンや電源ボタンは動作することからタッチパネルの故障だと思いおもいきって自分で交換してみました

環境

  • iPhone5

交換方法

基本はこの動画を参考にやりました
一応自分も写真を取りながら作業したのでポイントを少し説明したいと思います

まず今回購入した交換用のタッチパネルは以下になります
工具も付いているのおすすめです

こんな感じで箱に入っています
fix_iphone5_1.jpg

入っているものはこんな感じ
ドライバが 2 種類入っていてプラスドライバとマイクロドライバが付いておりマグネットで先端を交換することができます
青いやつは先端がフックみたいに引っ掛けられるようになっており、そこを使ってコネクタなどを外します
吸盤は液晶を引っ張って外ためのものです
あと一つあるおにぎりみたいな形のやつはよくわかりませでした
fix_iphone5_2.jpg

さっそく作業履歴ですがまず充電ケーブルがあるところのネジを 2 つ外します
いきなりポイントですがここを外すのだけマイクロドライバでやりましょう
間違ってプラスドライバでグリグリやるといきなり山が潰れて詰むので注意してください
fix_iphone5_3.jpg

吸盤を貼り付けて引っ張り上げます
このとき手前側に吸盤を付けるようにします
奥にはケーブルがあるので奥から引っ張り上げるとケーブルが切れて詰みます
fix_iphone5_4.jpg

ぱかっと開けてまずは奥のカバーから取り外します
fix_iphone5_5.jpg

ネジが 3 つあるのでプラスドライバを使って取り外します
ちなみにここからはすべてプラスドライバです
中のネジは全部プラスドライバを使うみたいです
下はカバーを外した様子です
ここからコネクタを 3 つ外していきます
それにしても汚い、、、!
fix_iphone5_6.jpg

最後の 3 つ目は隠れています
外すときは青いフックのやつでテコの原理を使って取っていきます
fix_iphone5_7.jpg

本体と液晶が外れました
ここから更に液晶についているパーツを取り外します
fix_iphone5_8.jpg

スピーカーを外します
ネジを外してカバーを外してからです
右側がシールで張り付いているので慎重に剥がしながら取り外してください
fix_iphone5_9.jpg

次はカメラです
fix_iphone5_10.jpg

これも青いやつでサクっと外しましょう
fix_iphone5_11.jpg

外したカメラがこちら
今更ですが細かいネジがたくさん出るのでどこのネジがわかるようにしておきましょう
fix_iphone5_12.jpg

下にいってホームボタンを外します
こちらもカバーから外します
fix_iphone5_13.jpg

外した感じがこちら
後で気づいたんですがホームボタンは少し固定されているようです
取らないといけないので固定部分を取りましたが新品の液晶に取り付ける際固定がなくなりちょっとグラついてしまいます
たぶんこれは仕方ないことだと思います
fix_iphone5_14.jpg

あとはフレームを外していきます
ネジがサイドに 5 箇所くらいあるのですべて取りましょう
するとフレームも外してこれですべてのパーツが液晶から外されました
fix_iphone5_15.jpg

あとはこれを新品の液晶で逆走すれば完成です
逆走時の撮影はしなかったのだけポイントだけ

  • ネジがしまりづらい場合はゆっくり力をいれてやりましょう
  • 液晶のコネクタと本体を接続するときは 3 本あります、どれもパチッと音がなるまではめ込んでください、鳴らないとハマリが悪いか無理に入れようとしています
  • ホームボタンとスピーカーの部分はシールになっているところがあるので粘着が弱まっていてもちゃんと本体に貼りましょう、ちゃんと所定の位置に貼れていないと本体との接触が悪く動作しません
  • 最後の液晶をはめ込むところで自分はなぜか浮いちゃいました、、、もしかするとバッテリーが膨張しているのが原因かもしれません

最後に

iPhone5 の液晶を自力で修理しました
自分でやるのは大変ですが、おもしろいし一回やると慣れて他の修理もできるようになると思います
これを 2,000 円弱でできるのであれば十分安いと思います

修理後に気づいたのですが古い液晶は確かにコネクタが 1 つなくなっていて、これは確かに動かないはずだと言うことがわかりました

感想としては結構たいへんだった印象です
全体的にネジがすべて小さく外すのが大変でした
ネジ山にしっかりハマらないと回ってくれないネジが多かったです
また力を入れないと回らないところもありました

せっかく修理できたので次はバッテリーも交換してみたいと思います

2018年2月1日木曜日

mineo で A プランから D プランに変更してみた

概要

mineo で au プラン (A プラン) を使っていたのですが端末も変わったので docomo プラン (D プラン) に変更してみました
方法を備忘録がてら残しておきます

環境

  • 変更前 mineo A プラン デュアルタイプ3GB (iPhone5)
  • 変更後 mineo D プラン デュアルタイプ3GB (Huawei p9 lite)

PS 2018/03/12 料金が判明しました

特に MNP の転出料金として 1 万なり 2 万なり取られることはありませんでした
手数料で 2,000 円ほどかかるだけです
たぶん mineo から別の MVMO なりに移動する際は取られると思います
mineo_fee1.png

プランの変更を申し込む

mineo のマイページにログインし

ご契約サービスの変更 -> プラン変更(Aプラン⇔Dプラン)

を選択します
ポチポチするだけですが以下自分の設定時のポイントです

  • 番号は引き継ぎにする
  • 端末は新規で購入しない
  • SIM のサイズは nanoSIM にする

あたりです
タイプは A タイプの時と同じデュアルタイプの 3GB です
料金が 1,600 円から 1,700 円になりました
プラン変更の申込みは 2018/01/28 に行いました

SIM カードが届くのを待つ

申し込みが完了してもまだ docomo 回線は使えません
まずは SIM が届くのを待ちます
自分の場合 SIM が到着したのは 2018/01/30 でした
2 営業日で到着しました

郵便で来るのでサインが必要になります
シグネチャなしのポスト投函ではないので気をつけてください

回線切替を行う

SIM が届いたら回線切替を行いましょう
mineo のマイページに移動し

登録情報の変更/サポート > MNP転入切替/回線切替手続き

を選択します
SIM の裏に書かれているバーコード下 4 桁を入力し切替ボタンを押します
回線切替を受け付けたメールが届くのでしばらく待ちましょう

自分は 5 分ほどで切り替わりました
111 のダイヤルで切替完了確認ができます
メールで完了の連絡は来ないので注意してください

これで無事同じ番号で回線切替が完了しました

料金について

月額料金は D プランの料金に切り替わります
それとは別で切替の事務手数料が 2,000 円かかります

あと不確かなのですが MNP 扱いになるので転出費用が 12,400 円かかると思います
自分の場合はこれはあとで気づいたので時既に遅しだったのですがおそらくかかると思います
mineo でかつ同じプランで 1 年以上しようすれば転出費用が 0 になるらしいのですが自分の場合半年しか使っていないのでおそらくかかると思います

ここで「おそらく」という表現を使っているので転出費用について申し込みから切替のどこにも記載がなかったからです
もしかすると mineo 内での回線切替ではかからない可能性もあるからです
完全に別の MVMO なりキャリアに移行する場合には転出費用が発生するのですが mineo 内なのでかからないなんてことがあるのかもしれません

この辺は請求が確定してわかったら追記しようかなと思います (記事上部に追記しました)

最後に

mineo で A プランから D プランに番号そのままで切替えてみました
簡単にできる印象は持ちました

料金に関しては案内が不足している感はありましたが、そこは頑張って自分で調べてやってくださいという感じなのでしょうか

切り替え前の au SIM は使えなくなっているので破棄して OK です
自分は念のため国内キャリア用の端末のアクティベーション用にとっておいていますが