- 投稿日:2020-02-25T20:33:38+09:00
RealmのschemaVersionの管理方法について考える
RealmのschemaVersion
Realmのドキュメントのマイグレーション項目で言及されている通り、Realmを扱う際には
Realm.Configuration
の引数であるschemaVersion
にモデルにアップデートがあった際に以前のバージョンよりも大きい値を設定する必要があります。なお、この値はマイグレーションなどの処理を行う際に必要となり、Realm内部でoldSchemaVersion
(初期値0)よりも最新のschemaVersion
が大きいか否かで自動的にマイグレーションされます。(データ移行の際はmigrationBlock
で移行処理を記述する)schemaVersionをどのように管理すべきか
方法1. モデルのアップデートを行う際に手元でschemaVersionをインクリメントする
個人的にオススメしません。複数人で開発していた際に同時にアップデートすることもあるかと思います。その際にコンフリクトしたり、配信されていると
schemaVersion
が正しくインクリメントされておらずクラッシュも起きうると考えられます。方法2. ビルド番号を直接入れる
アプリバージョンに関わらず、ビルド番号をGitのコミット数などでインクリメントしている場合、ビルド番号を
schemaVersion
に入れることも方法の1つかと思います。この場合だと実装者に関わらず常にインクリメントされていくのでschemaVersion
も自動で上がってくれます。この方法がお手軽ですが、モデルにアップデートがないのにも関わらずインクリメントされるのは違和感に感じます。また、ビルド番号をどのように管理しているかにも依存するので開発方針に左右されます。以上の方法を踏まえて実現したい理想
schemaVersion
はモデルのアップデートが行われた際のみインクリメントしたい。- 複数人開発においても正しく管理されるようにGitに依存した値を注入したい
モデルのディレクトリ以下のgit logからschemaVersionを設定する
最終的にfastlaneのlaneとして定義できるようにします。
Step1. 特定のディレクトリ以下のマージコミットを取得する
git log --oneline --merges --first-parent master -- DIR_PATH | wc -l上記のコマンドで
DIR_PATH
上の変更に対するマージコミットのカウントが取れます。
DIR_PATHはモデルが存在するディレクトリです。最終的にはこの数を最新のschemaVersion
として設定します。Step2. 現在のブランチの最新マージコミット(コミットハッシュ)を取得する
git log --oneline --merges --pretty=format:"%h" -1取得したコミットハッシュを
HASH_STEP2
とします。Step3. 現在のブランチの最新コミット(コミットハッシュ)を取得する
git log --oneline --pretty=format:"%h" -1取得したコミットハッシュを
HASH_STEP3
とします。Step4. 最新のマージコミットと現在のコミット間で特定ディレクトリ上の変更のコミット数を取得する
git log --oneline HASH_STEP2..HASH_STEP3 -- DIR_PATH | wc -lこれによって
DIR_PATH
において現在、変更を行ったかが0かどうかで判断することができます。Step5. Step1~4を用いてlaneを組む
lane :bump_schema_version do latest_merges_commit_hash = sh(%Q[git log --oneline --merges --pretty=format:"%h" -1]) latest_commit_hash = sh(%Q[git log --oneline --pretty=format:"%h" -1]) latest_model_commit_count = sh(%Q[git log --oneline #{latest_merges_commit_hash}..#{latest_commit_hash} -- DIR_PATH | wc -l]).strip!.to_i latest_schema_version = sh("git log --oneline --merges --first-parent master -- DIR_PATH | wc -l").strip!.to_i if latest_model_commit_count > 0 latest_schema_version += 1 end set_info_plist_value(path: PLIST_PATH, key: "schemaVersion", value: latest_schema_version) end
set_info_plist_value
でInfo.plist内のschemaVersion
に書き込み、コードから取得することでRealm.Configuration
に渡すことができます。このlaneを実行することで現在のブランチで変更があるかを取得することができます。
latest_schema_version
をmasterへのマージコミット数としているのはCI上で実行したときにはモデルのディレクトリにおける変更のmasterマージコミット数をschemaVersion
とするためです。
まだマージしていないブランチ上で実行したときにはStep4で行っている現在変更しているかを加味してlatest_schema_version
がインクリメントされます。前者と後者の値は一致するので「手元で実行してPR作成によってインクリメントする」or「CI上でフックして実行、PR作成する」などインクリメントする方法を選択することができます。
最後に
schemaVersion
の管理方法をどのようにするかはプロジェクトによっても変わりますし、悩ましい部分だと思います。
他の方法もあればぜひ教えてください!
- 投稿日:2020-02-25T19:52:14+09:00
XcodeのXCUITestで実行時に引数を渡す
about
こちらの記事でUITest実行時以外の引数の渡し方について解説しているので
基本はこちらを参考にしてください。Xcodeに引数を渡す LaunchArg & Env Variables
ここではUITest実行時の引数の受け取り方について触れます。
Bitriseの環境変数を渡す方法についても少し触れます。
環境
- Xcode11.x
- Swift4.x
設定方法
対象Scheme -> Edit Scheme -> Run -> Arguments
それぞれチェックの入っているものをAppendしてみました。
2種類の入力が可能なので以下でみていきます。
- Arguments Passed On Launch
- Environment Variables
Arguments Passed On Launch
型:[String]
取得方法
class SampleTests: XCTestCase { override func setUp() { if ProcessInfo.processInfo.arguments.contains("isTestMode") { print("test mode enable") } } }注意点
似た要素に
XCUIApplication().launchArguments
がありますが、こちらはコード内で設定したい場合に利用すればOKだと思います。class SampleTests: XCTestCase { let app = XCUIApplication() override func setUp() { app.launchArguments.append("isTestMode") if app.launchArguments.contains("isTestMode") {} } }Environment Variables
型:[String : String]
取得方法
スクショでの引数を受け取る方法は以下
class SampleTests: XCTestCase { override func setUp() { print(ProcessInfo.processInfo.environment["mode"]) //develop } }注意点
似た要素に
XCUIApplication().launchEnvironment
がありますが、こちらはコード内で設定したい場合に利用すればOKだと思います。class SampleTests: XCTestCase { let app = XCUIApplication() override func setUp() { app.launchEnvironment = ["mode": "develop"] switch app.launchEnvironment["mode"] { case "develop": print("develop env") default: print("default env") } } }Bitriseから環境変数を設定してコードから取得したい場合
Schemeファイルは以下のようなファイルになりますが、Bitriseのビルド時に書き換えるのはdiffを見れば分かりますが脆いのでplistを利用するのが良いと思います。
TargetProject.xcodeproj/xcshareddata/xcschemes/TargetUITests.xcscheme
そちらは別記事にて書きたいと思います。
- 投稿日:2020-02-25T15:45:07+09:00
Xcodeに引数を渡す LaunchArg & Env Variables
about
ネットには古いXcodeの情報しかなく、使い分けやコツをまとめた記事がなかったので記載します。
Xcodeでは2種類の方法で実行引数を受け取ることができます。
- Arguments Passed On Launch
- Environment Variables
Swiftのコード内で引数を使う方法を解説します。
環境
- Xcode11.x
- Swift4.x
設定方法
対象Scheme -> Edit Scheme -> Run -> Arguments
それぞれスクショのようにAppendしてみます。
2種類の入力が可能なので以下でみていきます。
- Arguments Passed On Launch
- Environment Variables
Arguments Passed On Launch
型:[String]
※ KeyとValueを対で渡すことはできません
取得方法
let argv: [String] = ProcessInfo.processInfo.arguments argv[0] : 実行ファイルのパスが入る (ex: /Users/user/Library/Developer/CoreSimulator/Devices/3418A817-3034-4DFC-A030-32C90755376A/data/Containers/Bundle/Application/D6D1533B-B307-4C1B-9DE9-1DDD6744B503/test_develop.app/test_develop) argv[1] : 設定した環境変数 argv[2] : 設定した環境変数2個目 . . .注意点
次で解説するEnvVarのようにkeyを設定できないので、引数が増えたとき番号がずれる可能性があります。
例として、環境変数に
isTestMode
を渡して以下のような実装をしている場合if ProcessInfo.processInfo.arguments[1] == "isTestMode" { print("test mode running") }以下のようにcontainsで含まれているか確認するのが良いでしょう
if arguments.contains("isTestMode") {}Environment Variables
型:[String : String]
Key(Name)とValueを対で設定することができます。
取得方法
スクショでは以下のようになります。
let value = ProcessInfo.processInfo.environment["mode"] print(value) // develop以下のようにswitch文で使うのが良いでしょう
switch ProcessInfo.processInfo.environment["mode"] { case "develop": case "production": defult: }おわり
XCTestでの利用については以下に記載しました!
- 投稿日:2020-02-25T15:26:55+09:00
PusherのChatKitを導入する
はじめに
チャットアプリを作ろうとすると、
Firebase Realtime Databaseというサービスを使うと素早く実装できるという記事がたくさんあるんで、
僕はPusherのChatKitというサービスを使ってチャットアプリを作ってみようかなと思います。
いつも通り元のDocはこちらなぜPusher?
・ある程度なら無料で運用出来る
・有名どころが使用している
・自社サーバーとの連携が可能
主に上記の理由からPusherを選びました。SDKのインストール
cocoapod
gem install cocoapods target '<Your Target Name>' do pod 'PusherChatkit' endpod installCarthage
$ brew update $ brew install carthagegithub "pusher/chatkit-swift"アカウント作成
こちらでアカウント作成
https://dashboard.pusher.com/accounts/sign_upインスタンスを作成
CredentialsでInstace Locatorを取得
認証用の設定
let provider = PCTokenProvider(url: "test用エンドポイント") // ChatManagerの生成 let manager = ChatManager(instanceLocator: ChatKitInfo.instaceLocation, tokenProvider: provider, userID: )Pusherとのコネクションを作る
self.manager.connect(delegate: ChatManagerDelegateImpl(delegate: self)) { (currentUser, error) in if let error = error { print("Error sending message: \(error.localizedDescription)") onError(error) return } self.currentUser = currentUser onSuccess() }チャット部屋の購読
guard let currentUser = self.currentUser else { return } currentUser.subscribeToRoomMultipart(id: id, roomDelegate: self, messageLimit: 0) { error in if let error = error { print("Error subscribing to room: \(error.localizedDescription)") return } print("Successfully subscribed to the room! ?") }メッセージの送信
currentUser.sendSimpleMessage(roomID: room.id, text: message) { (messageId, error) in if let error = error { self.delegate?.onError(error: error) return } self.delegate?.onSuccessSendMessage(id: messageId) }メッセージの受信
extension ChatKitManager: PCRoomDelegate { func onMultipartMessage(_ message: PCMultipartMessage) {} }これで基本的なチャットの機能が使えるようになります。
PCTokenProviderの生成
最後に認証用のプロバイダーですが、この記事ではテスト用のエンドポイントを使用しています。
しかし、実際に使用するときは自分でサーバーを構築して認証用のエンドポイントを作成する必要があります。
その場合の、PCTokenProviderの生成方法以下になります。PCTokenProvider(url: endPoit, requestInjector: { request -> PCTokenProviderRequest in // ヘッダー情報などを設定 request.addHeaders(headers) return request }, retryStrategy: PCDefaultRetryStrategy(maxNumberOfAttempts: 6, maxTimeIntervalBetweenAttempts: 10))Pusherではこれ以外にも既読を点けたり、過去のメッセージを取得したりも出来るのでチャットアプリを作ってみたい方は、Pusherというサービスもあるよとだけ覚えておいていただければと思います。
- 投稿日:2020-02-25T14:29:53+09:00
【Swift】stride()を使ってループを逆に回したり、増分を任意の値にしたりしよう
stride()についての覚書です。
なんとなく使っていたので、改めてちゃんと調べました。ループを逆に回したい!!!!!!!
for i in stride(from: 10, to: 0, by: -1) { print("countdown:", i) } /* 出力 countdown: 10 countdown: 9 countdown: 8 countdown: 7 countdown: 6 countdown: 5 countdown: 4 countdown: 3 countdown: 2 countdown: 1 //0は出力しない */後述しますが、stride(from:to:by:)だと終了条件に指定した数は含まれません。
stride(from:through:by:)を使うと、0まで出せます。増分を任意の値にしたい
for i in stride(from: 0, to: 30, by: 3) { print("3の倍数のときだけ出力します……:", i) } /* 出力 3の倍数のときだけ出力します……: 0 3の倍数のときだけ出力します……: 3 3の倍数のときだけ出力します……: 6 3の倍数のときだけ出力します……: 9 3の倍数のときだけ出力します……: 12 3の倍数のときだけ出力します……: 15 3の倍数のときだけ出力します……: 18 3の倍数のときだけ出力します……: 21 3の倍数のときだけ出力します……: 24 3の倍数のときだけ出力します……: 27 */toとthroughの違い
stride()を使うと、StrideTo/StrideThroughという見慣れないクラスを返します。
イメージ的にはStrideToがS..<Eで、StrideThroughがS...Eです。
ただRangeオブジェクトと違って、Strideableなオブジェクトは増分情報を持っているので、クラスとして完全に別モノとして言語設計されているみたいです。StrideTo/StrideThroughはそれぞれイニシャライザがなく、stride()メソッドを呼び出してインスタンス化するように、とのことです。
また一つSwiftでできることが増えました?
よかったですね。公式
- 投稿日:2020-02-25T02:18:11+09:00
iOSアプリのアイコンをffmpegコマンドを使ってアルファチャンネルなしで各サイズを吐き出す
はじめに
iosアプリのアイコンでは異なるサイズの画像をたくさん作る必要があります。
でこれがめちゃくちゃめんどくさいので、そのためのツールが色々あるのでまずはこれらを試してみることをお勧めします。一番簡単にWebアプリ上できるのがこちらで
appiconMacのアプリでできるのがこちらです
Image2icon遭遇した問題 〜 アルファチャンネルが追加されてしまう 〜
今回自分はillustratorでアイコンを描き画像としてでexportしました。
そして上記のツールでアイコンを作成して、App Store Connectにアップロードしようとすると、以下のようなエラーが出ました。Error ITMS-90717: "Invalid App Store Icon. The App Store Icon in the asset catalog in 'YourApp.app' can't be transparent nor contain an alpha channel."
アルファチャンネルが含まれているから使えないよーとのことですね。
なるほど書き出したアイコンにアルファチャンネルが含まれているのねーということで確認してみると、含まれていません。色々試してみたのですが、どのツールを使っても書き出し後の画像にはアルファチャンネルが含まれてしまいました。
解決法 〜 ffmpegでリサイズする 〜
macのプレビューアプリで1つずつ、アルファチャンネルを除去することもできるのですが、そんなことはめんどくさすぎて絶対したくないので、ffmpegを使ったshell scriptを使います。
# Export ios app icons by ffmpeg scale command # usage: sh export_ios_icons.sh {path_to_your_img} # example: sh export_ios_icons.sh ./app_icon.png # sizes of images # you can get other size images by editing thisarray size=(20 40 60 29 58 87 80 120 180 76 152 167 1024) for i in "${size[@]}" do : ffmpeg -i $1 -vf scale=$i:$i output_$ix$i.png done作りたい画像サイズを配列で保持しておいて、
ここでリサイズした画像を生成します。ffmpeg -i $1 -vf scale=$i:$i output_$ix$i.png # ex) ffmpeg -i $1 -vf scale=1024:1024 output_1024x1024.png実行すると以下のように各画像サイズのアイコンが出力され、アルファチャンネルがない状態も見事キープされていますね!
めでたしめでたし
(あとで気づいたがそもそもjpgでやれば、こんな問題全く起きなかったかも...)
- 投稿日:2020-02-25T00:04:05+09:00
【ARKit】配置した3Dモデル(アニメーションつき)を削除する
はじめに
別記事で書いたアニメーションつきの3DモデルをARKitに追加するのに引き続き、ARKItに追加した3Dモデル(アニメーションつき)を削除するときにも躓いたので、そのメモ。
以下のサイトを参考に、長押しで3Dモデルを消そうとしたが削除できませんでした。このサイトではプリミティブなオブジェクト(cube)を配置しているので、今回使う3Dモデル(アニメーションつき)と何が違うのか?
実行環境
- Xcode 11.2.1
3DモデルはMagicaVoxelで作って、Mixamo でアニメーションをつけたものを使用しました。
- MagicaVoxel 0.99.4
- Mixamo
.scnファイルの構造
.scnファイル(sitting.scn)を開いてみると、直下に「sitting」ノードがあって、その中に3Dモデルとボーンの設定が含まれています。
アニメーション付き3Dモデルの配置時
配置するスクリプトは、直下の「sitting」のノードを取得して、self.mainSceneView.scene.rootNode に追加するようになっていますが、追加するノードに名前がないため、名前をつける処理(node.name = selectedItem)を追加しました。
※削除する際に、どのノードか判断するためです。
アニメーション付き3Dモデルの配置var selectedItem: String? = "sitting" //(中略) /// アイテム配置メソッド func addItem(hitTestResult: ARHitTestResult) { if let selectedItem = self.selectedItem { // .scnファイルから新しい3Dモデルのノードを作成 let scene = SCNScene(named: "art.scnassets/\(selectedItem).scn") let node = (scene?.rootNode.childNode(withName: selectedItem, recursively: false))! // 現実世界の座標を取得 let transform = hitTestResult.worldTransform let thirdColumn = transform.columns.3 // 3Dモデルの配置 node.position = SCNVector3(thirdColumn.x, thirdColumn.y, thirdColumn.z) // 3Dモデルのサイズを変更 node.scale = SCNVector3(0.05, 0.05, 0.05) // 3Dモデルに名前をつける node.name = selectedItem // シーンに追加 self.mainSceneView.scene.rootNode.addChildNode(node) } }アニメーション付き3Dモデルの削除
.scnファイルの構造で見たように、配置したモデルは「sitting」というノード名で、その中に3Dモデル(unamed)とボーン(mixamorig_Hips)が含まれています。
長押しでのオブジェクトが存在するかどうかの判定は、ARSCNView.hitTest(_:types:)で行いますが、このときの検出対象は3Dモデル(unamed)になるため、ノード全体を削除するには親である「sitting」ノードを削除する必要があります。
削除の処理は以下のようになります。
アニメーション付き3Dモデルの削除// ロングプレスイベントハンドラの登録 let longPressGesture = UILongPressGestureRecognizer(target: self, action: #selector(longPressView)) self.mainSceneView.addGestureRecognizer(longPressGesture) //(中略) // 長押しでキャラクタを削除する @objc func longPressView(sender: UILongPressGestureRecognizer) { print("----長押し!") if sender.state == .began { let location = sender.location(in: self.mainSceneView) let hitTest = self.mainSceneView.hitTest(location) if let result = hitTest.first { // 3Dアニメーションモデルは、複数パーツで構成されるため、親ノードの名前で判定・削除する if result.node.parent!.name == selectedItem { result.node.parent!.removeFromParentNode(); } } } }まとめ
ノードを扱う際には、常に階層構造を意識しないとダメってことですね(わかってみれば当然ですが)。
他にもっといい方法があれば、教えてください。