メモ

プログラミングなどの備忘録を書きます

swiftのlinker command failed with exit code 1 (use -v to see invocation)

タイトルのエラーに遭遇したので対処方法をメモする。

linker command failed with exit code 1 (use -v to see invocation)

ググったらたくさん出てくるが、どうやらcocoaPods周りで問題が起きているらしい。
エラーの詳細も調べたかったが、簡単にはわからなそうだったので未調査。
とにかくキャッシュを消しとばして綺麗にすれば直る。
mavenなどでも古いライブラリのキャッシュが残っていてエラーになったりするがその類いなのだろうか。

 # キャッシュを消しとばす
 sudo rm -fr ~/Library/Caches/CocoaPods/
 sudo rm -fr ~/.cocoapods/repos/master/
 pod setup
 #プロジェクト内も同様に
 sudo rm -fr Pods/
 pod update

SwiftのView関係ではまったことメモ

SwiftのView関係で引っかかったことの雑多なメモです

ボタンが押せない

ボタンが反応しない(addTargetした処理に飛ばない)ときは親のViewからはみ出た場所にボタンが配置されているか、他のViewが上から被さっているかの場合が多い。
Viewに色をつけて可視化すれば分かりやすい。
可視化しても問題なさそうならViewのisUserInteractionEnabledがfalseになっている可能性がある。

Viewのサイズが正しく取得できない

viewDidLoad() の時点ではconstraintsによるViewの配置が行われていないため正しい値が取れない。
viewDidLayoutSubviews() で行えばconstraintsが適用された後の値が取得できる。

override func viewDidLoad() {
  super.viewDidLoad()
  // ここだと計算前の値になる
}

override func viewDidLayoutSubviews() {
  super.viewDidLayoutSubviews()
  // ここならok
}

他のViewから戻った時に画面が更新前のまま

viewDidLoadなどに処理が書かれており、前の画面に戻った際に処理が呼ばれないのが原因の可能性が高い。
viewWillAppearに処理を書けば更新される。

swiftのライフサイクルについては下記の記事が参考になった qiita.com

公式のドキュメントはこちら UIViewController - UIKit | Apple Developer Documentation

cornerRadiusが効かない

デフォルトでは子のViewが親Viewを突き破って表示されるため、親ViewのconerRaiusが聞いて いないように見えたのが原因だった。
下記を親viewに設定すれば子が親Viewからはみ出さないようになる。
ただし、masksToBounds = true を設定すると影が出なくなってしまうので注意

parentView.layer.cornerRadius = 5
parentView.layer.masksToBounds = true

Realm Swiftメモ

swiftでエンティティの永続化をするためにRealmを触ってみたのでメモする。
あんまり詳しくないけどios, android共にアプリ内のDBはRealmがデファクトに近いらしい。

インストール

cocoapodsで入れる

初期化
pod init
Podfileを編集
target 'SampleApp' do
  use_frameworks!
  pod 'RealmSwift'
end
インストール
pod install

使い方

※基本チュートリアルに書いてある内容 realm.io

クラスを定義

永続化したいクラスは通常のSwiftのクラスと同じように定義する。
realmで永続化するクラス同士のリレーションを持たせたい時には通常のクラスと同じようにプロパティに追加する。
リレーションはoptionalにしておかないとRLMExceptionが発生し "Item.user" property must be marked as being optional と言われる。

import RealmSwift

final class Item: Object {
    dynamic var id: String
    dynamic var title: String
    dynamic var description: String
    dynamic var user: User?
    
    //primaryKeyを定義
    override static func primaryKey() -> String? {
        return "id"
    }
}

final class User: Object {
    dynamic var id: String
    dynamic var name: String
    
    override static func primaryKey() -> String? {
        return "id"
    }
}
更新系の処理
//インスタンス化
let realm = try! Realm()

//追加
let item = Item(value["id": "a1", "title": "hogehoge", "description": "hugahuga"]
)
item.user = User(value["id": "b1", "title": "piyopiyo"])

try! realm.write() {
    realm.add(item)
}

//更新
try! realm.write() {
   item.title = "hogehoge2"
   item.description = "hugahuga2"
}

//削除
try! realm.write() {
    realm.delete(item)
}
参照系の処理
//全件取得
let items = Array(realm.objects(Item.self))   //Result型からArray型にしている

//検索
let items = Array(realm.objects(Item.self).filter("name = hogehoge"))

//関連オブジェクトから検索
let items = Array(realm.objects(Item.self).filter("user.name = piyopiyo"))

変数を埋め込む際は下記のようにする。
普通の文字列に変数を埋め込むやり方をするとエラーになってしまう。(内部でNSPredicateを使用しているためらしい)

//変数を使って検索
let items = Array(realm.objects(Item.self).filter("name = %@", itemName))

//これは失敗する
let items = Array(realm.objects(Item.self).filter("name = \(itemName)"))

マイグレーション

既存のクラスに変更を加えた場合、実行時にエラーが発生する。 解消するにはマイグレーションを行う必要がある。
https://realm.io/jp/docs/swift/latest/#migrations

realmをインスタンス化する際にschemaVersionを更新すればエラーは起きなくなるが、これだけだと既存のデータは修正されない。

let realm = try! Realm(configuration: Realm.Configuration(schemaVersion: 2))

データを格納したファイルを削除することでも対応できるので、データが消えても問題ない場合はこちらの方が早い。

//ファイルの場所を取得して消す
if let fileURL = Realm.Configuration.defaultConfiguration.fileURL {
    try! FileManager.default.removeItem(at: fileURL)
}

hubotで株価を取得するbotを作る

hubotを使ってSlackのbotを作ってみたので手順を簡単にメモっておく。
今回は簡単なサンプルとしてgoogle financeのAPIから株価を取得して、前日との差分を教えてくれるbotを作成した。

インストール

npmでyeoman、generator-hubotをインストール

npm install -g yo generator-hubot

ディレクトリを作って初期化する

mkdir mybot
cd mybot
yo hubot

色々質問されるので適当に入力しておく。adapterはslackを選択する。

コード

自動生成されたscriptというディレクトリにコードを書いていく。
デフォルトではcoffee scriptが置いてあるけど普通にJavascriptでも書ける。

google financeのAPIを叩くのにfetchを使用する。

npm install node-fetch --save

コードはこんな感じ。最新の株価と前日との差分を返す。
tsで株価の取得開始日を指定できなくなったのが辛かった。

gist.github.com

実行

起動する前にslackのconfigurationでhubot用のトークンを発行して環境変数に設定しておく。

export HUBOT_SLACK_TOKEN=<hubot token>
bin/hubot --adapter slack

こんな感じの結果が返る
f:id:ymtttk:20170916173429p:plain:w300

所感

ものすごく久しぶりにJavascriptを書いたけど、fetchとpromiseすごく便利だった。
一方で日付とか値の文字列とかの挙動は相変わらず扱いにくかった。

現状証券コード問い合わせる仕様になっているが、証券コードとか覚えてられないので会社の名前で問い合わせられるようにしたい。あと、急騰や急落を通知してくれる機能なんかを実装したい。

vagrantメモ

久しぶりにvagrantでローカル環境を作ってみたが、結構使い方を忘れてしまっていた。 毎度ググらなくて済むようにメモを残しておく。

box追加

box名、boxのurlを指定してインストールする。
公式が提供しているboxを利用する場合は飛ばせる。(初回起動時にインストールされる)

vagrant box add Ubuntu14.04 https://cloud-images.ubuntu.com/vagrant/trusty/current/trusty-server-cloudimg-i386-vagrant-disk1.box
初期化

boxを指定してカレントディレクトリを初期化する。Vagrantfileが作られる。

mkdir myvagrant
cd myvagrant
vagrant init Ubuntu14.04
起動
vagrant up
ssh接続
vagrant ssh
プロビジョニング

Chef, ansible, シェルスクリプトなど様々な方法を選択することができる。 自分は良くansibleを利用するのでansibleのサンプルを書いておく。
下記はlocal provisionerを使用する例。local provisionerを使用する場合、ホストにansibleがインストールされ、ホスト上でプロビジョニングが行われる。

config.vm.provision "ansible_local" do |ansible|
  ansible.playbook = "playbook.yml"
  ansible.inventory_path = "hosts"
  ansible.install_mode = "pip"
  ansible.version = "1.9.6"
end
シャットダウン
vagrant halt

RabbitMQでロストしないようにpublish/consumeする

RabbitMQでメッセージをロストせずにpublish/consumeする方法を軽く調べたのでメモしておく。

コードはJavaのクライアントライブラリを使用したものをscalaぽっく書いている。
RabbitMQ - RabbitMQ Java Client Library

consume

メッセージの処理が終わったらacknowledgementを返す。

val connection = factory.newConnection(addresses)
val channel = connection.createChannel()
// noAck=falseに設定
channel.basicConsume(queue, false, consumer)
.
.
.
// メッセージの処理の成否によってAckかNackを返す
if (isSuccess) {
  channel.basicAck(deliveryTag, multiple)
} else {
  channel.basicNack(deliveryTag, multiple, requeue)
}

acknowledgementが返ってくるまでRabbitMQはメッセージを保持するので、consume中にクライアントが死んだりコネクションが切れたりした場合でもメッセージはキューに残る。
multipleは指定したdeliveryTag(キューに入っているメッセージの番号のようなもの)までの全てのメッセージにacknowledgementを返すときtrue、指定したdeliveryTagのメッセージのみにacknowledgementを返す時falseにする。
requeueはnack時にメッセージをキューに詰め直す場合trueに、メッセージを捨てたりdead letterキューに入れたりする場合falseにする。

publish

publishの失敗を検知するには、transactionとconfirmという2通りの方法がある。
普通にbasicPublishするだけだとpublish後の失敗は検知できない。(publish先のExchageやキューが存在しなかったりしても何も言われない。)

transaction

RabbitMQがキューにデータを永続化するまでロックする。
AMQPの仕様ではトランザクションはatomicな処理とされているが、RabbitMQではこの仕様を満たしていないらしい。(失敗したがメッセージのpublishができていることがある?)

val connection = factory.newConnection(addresses)
val channel = connection.createChannel()
channel.basicPublish(exchange, routingKey, mandatory, immediate, properties, body)
channel.txCommit()
channel.close()

mandatoryをtrueにするとメッセージをキューにルーティングできない時にエラーになる。falseの時はバインド先が存在しない場合メッセージが吐き捨てられる。
immediateをtrueにすると、メッセージは送信先のキューからすぐにコンシュームされない場合エラーになる。

transactionはロックを行うため、処理速度がかなり遅くなる。 メッセージをロストしたくないという目的に対してコストが高すぎるため、RabbitMQではconfirmという仕組みが提供されている。(AMQPの仕様には存在しないRabbitMQオリジナルの仕様らしい)
メッセージをロストしたくないだけならconfirmで十分なので、transactionを使用するケースはあんまり多くなさそうに感じる。

confirm

confirmを使うとbrokerがメッセージを処理した段階でpublisherにacknowledgementが返る。
下記のようにするとackknowledgementが帰ってくるまでwaitする。

val connection = factory.newConnection(addresses)
val channel = connection.createChannel()
channel.confirmSelect()
channel.basicPublish(exchange, routingKey, mandatory, immediate, properties, body)
channel.waitForConfirmsOrDie(timeout)
channel.close()

下記のようにListenerを設定することで非同期的にacknowledgementを受け取ることもできる

channel.addConfirmListener(new ConfirmListener {
  override def handleAck(deliveryTag: Long, multiple: Boolean): Unit = {
    //ackの時の処理
  }
  override def handleNack(deliveryTag: Long, multiple: Boolean): Unit = {
    //nackの時の処理
  }
})

pythonのプロジェクト構成

今までちょっとしたスクリプトくらいでしかpython使ってこなかったので、ちゃんとしたプロジェクト構成について知識がなかった。
githubリポジトリを漁って見たが、結構まちまちであまり決まった構成というのはないのかなという印象。

下記のドキュメントが参考になりそうだったのでひとまずこれに従ってやってみようと思う。
python初学者向けのドキュメントで構成についてだけでなく全般的な内容が書かれている。

Structuring Your Project — The Hitchhiker's Guide to Python