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
使い方
クラスを定義
永続化したいクラスは通常の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で株価の取得開始日を指定できなくなったのが辛かった。
実行
起動する前にslackのconfigurationでhubot用のトークンを発行して環境変数に設定しておく。
export HUBOT_SLACK_TOKEN=<hubot token>
bin/hubot --adapter slack
こんな感じの結果が返る
所感
ものすごく久しぶりに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の時の処理 } })