ruby の指定バージョンを利用する方法

rbenv を利用することで、複数のバージョンの ruby を切り替えることが可能。

参考資料

rbenvの使い方と仕組みについて
rbenv rehashをちゃんと理解する
rbenv rehashは何をやっているのか?

コマンド

# Homebrewをアップデート
$ brew update

# rbenvをインストール
$ brew install rbenv
# rbenvでインストール可能なrubyのバージョンを確認
$ rbenv --version
$ brew upgrade rbenv
$ rbenv install --list
# インストールされているrubyのバージョンを確認
$ rbenv versions
# インストールした ruby(”2.5.0”)に切り替える
$ rbenv global 2.5.0
# 必ず実行しないといけないコマンドではないが、問題がある場合は以下のコマンドを試す
$ rbenv rehash

# 指定したバージョンに切り替える
$ rbenv shell --unset コマンド

~/.bash_profile 設定ファイル

eval “$(rbenv init -)” を ~/.bash_profile の末尾に追加する

環境変数(PATH)

export PATH=”/Users/(ユーザ名)/.rbenv/shims:${PATH}”

中古のステップワゴンでiPhoneの音楽を再生する方法

都内では長らく車は不要かと考えていましたが、中古のステップワゴンを購入し、日々の買い物など便利に使用しています。

少しずつ快適に利用するための改善を楽しんでおり、今回は安価にナビゲーション、オーディオ、Youtube再生ができる商品を選んでみました。

車のナビを最新のものにするのでは無く、スマホで代替する際に必要になるのはスマホ スタンドだと思いますが、いざスマホ スタンドを購入するとなると製品の多さに驚かされます。
ですが、購入時には色々迷っても、購入後はあまり気にならないのではないでしょうか?さっさと購入して便利に利用した方が精神衛生上にも良いと思います。

デザイン、取り付け方式は無数にありとても迷いますが、まずは利用してみないと判断ポイントも分からないと思いますので、今回はデザイン的にもすっきりした、【2023年 超軽い】iPhone スマホホルダーを選びました。

スマホナビを利用して気づく点は、やはり充電しながらの利用が必須だということ。
車の配線を変更し、電源を取り出して…とも考えましたが、素人には手間暇がかかること、コストを考えて安価に解決するため、シガーソケットを拡張できて、USB端子が複数ある商品を選ぶこととしました。

シガーソケットを拡張する商品を選んだ理由は、既にドライブレコーダーの電源を利用していたためです。
こちらも商品点数がとても多いですが、USB端子がリバーシブル、2連セパレートの2連セパレートソケット 3リバーシブルUSB自動判定 7.2A カシムラ KX-212を選びました。

シガーソケットを拡張すれば、ドライブレコーダー用電源とFMトランスミッターを利用できるようになり、USB端子からはiPhoneの充電ができます。

FMトランスミッターも商品が沢山ありますが、選ぶポイントは、以下のようです。
ノイズがない
音量が適切

Amazonでは中国製品が多く販売されているようですが、日本製の製品で評価が高いものがあったので、ジャパンアベニュー FMトランスミッター Bluetooth 5.0 ベースサウンド機能を選びました。(3,500円程度)

FMトランスミッター自体は、ノイズは無く、音量も問題がありませんでしたが、
iOSアプリは動作に問題があるようでした。(22年12月時点)

購入に際してはメーカーHP( https://japan-ave.com )で商品ラインナップを把握したかったのですが、ラインナップが多く、またモデルごとの違いが分からず大変困りましたが、製品型番から後発モデルと判断できるものを選びました。

費用は、総額7,000円程度で快適にスマホナビ、オーディオが利用できるようになったので、良い投資であったと思います。

いざ購入に際してはあまりの商品点数の多さに辟易しましたが、購入の一助になれば幸いです。

今回購入した商品は以下のものです。
ジャパンアベニュー FMトランスミッター Bluetooth 5.0 ベースサウンド機能
【2023年 超軽い】iPhone スマホホルダー
2連セパレートソケット 3リバーシブルUSB自動判定 7.2A カシムラ KX-212

エキスプレスカードが反応せず、メインカードが反応する場合、確認すること

iPhoneのウォレットに楽天カード(JCB)、Suica(定期券)、dCARD(mastercard)、VIEW CARD、MERCARI(mastercard)を登録し、支払いのメインカードは、楽天カードを利用して、エキスプレスカードは、Suica(定期券)を利用しています。

Suica(定期券)は、定期券有効期間外のものにチャージを行い、Suica支払いを利用しています。
ですが、JRの改札を通過する際にエキスプレスカードに設定しているSuica(定期券)が支払いに利用されず、支払いのメインカードである楽天カードが反応してしまい、改札で支払いが出来ない事象が頻発して困っていました。

この事象は回避できないものと諦めていたのですが、最近、以下の設定で様子を見ています。
検証期間は約一ヶ月程度ですが、JR改札でSuica(定期券)が反応しないという問題は起きていないので、改善があったのかもしれません。
同様の事象でお困りの方は、一度試されてはいかがでしょうか?

【設定内容】
「定期券有効期間外のSF利用」設定で、「SFを利用する」に設定する
使用期間後の定期券情報が残るSuicaは設定が必要とのこと。

【設定手順】

  1. Suicaアプリケーションを起動し、ログインする
  2. Suica一覧画面で「チケット購入・Suica管理」タブを選択する
  3. 「その他の設定」を選択する
  4. 「定期券有効期間外のSF利用」を「SFを利用する」

JRの改札で自動で支払いをSuicaにするには、エキスプレスカード設定を行っておくと言うのは理解できるのですが、上記の「定期券有効期間外のSF利用」設定は、誰を救うための機能なのでしょう?

また、このような事象を回避する情報をお持ちの方は是非お知らせ下さい。
では!

SwiftUIの個人的なメモ

SwiftUIもそろそろ業務で利用されるかと思い、少しずつ情報を整理しています。
というわけで個人的なメモを以下に記します。

2022年秋には、iOS16が提供されることにより市場のアプリのサポートバージョンは、iOS14、15、16が一般的になると思います。結果、SwiftUIもそろそろ積極的に採用されるとの判断です。
実際調べてみると、SwiftUIはiOSバージョンにより挙動の差異があるようです。
この点は、以下のページで詳しく報告されています。

iOS バージョン による SwiftUI の機能差分・制限まとめ

SwiftUIを採用するということは、UIKitからの移行ということだと改めて認識しました。現行のままでは、移行しきれないUIデザインというのもあるのでしょうね。
UIKitのあのコントロールは、SwiftUIではどのコントロールに該当するのかというのは、以下のページが参考になりました。

[Swift] SwiftUIのチートシート

SwiftUIサンプルコードを見ていて、新しく見つけたProperty Wrapperは以下の通り。

@mainエントリポイント
@PublishedCombineフレームワークが提供。Publisherを生成する。
$を付加して、Publisherにアクセスできる。
@ObservedObjectSwiftUIの監視対象となり、参照しているViewが自動的に再描画される。
@State変数の変更が監視され、変更時にViewが再描画される。
アクセスは宣言されたView内のみとなり、変更時には$を付与する必要がある。
@StateObject再描画されても値は保持される。
@EnvironmentObjectアプリ内でデータを共有する。
@Environment環境値を取得する。
@FocusStateiOS15で導入。表示要素のフォーカス制御を行う。
SwiftUI Property Wrapper

リアクティブプログラミング手法では、UIとモデルのバインディングがどのように行うのか?ということが一つのテーマであったと思うが、SwiftUIはProperty Wrapperという形で提供している。その点も踏まえて、今後はSwiftUIと相性の良いアーキテクチャについても考えたい。

リアクティブプログラミング、Combine の個人的なメモ

時間があるとリアクティブプログラミングの調査は行っているのですが、毎回まとめる前に忘れてしまうということを繰り返していたので、今回はまとめてみた。
業務でいつも最新の手法のみを使っているわけではないので、習得してもまとめておかないと忘れちゃうんですよね。(笑)

リアクティブプログラミングの定義

非同期イベントの処理を宣言的な記述で書くプログラミング。

もう少しイメージしやすい表現だと、データ流れをイベントのストリームとして表現し、ストリームに応じた振る舞いをさせる考え方。

Combine概要

Combine が登場したのは 2019 年 6 月の WWDC。
iOS 13.0以上で使用できる。
オブジェクト間でイベントを伝える仕組みを提供する。

主要な要素と役割は、以下の通り。

  • Publisher – 時間の経過に沿ってイベントを伝達する役割
  • Operator – イベントを作成、もしくは処理する役割
  • Subscriber – Publisher から送信されたイベントを処理する役割
  • Cancellable – イベントをキャンセルできることを表現する役割
  • Subscription – Publisher と Subscriber の購読を表現する役割

subjectに対して、複数のsubscribeを行うことができる。
複数のsubscriptionを扱う場合、storeメソッドでまとめて保持する。
Publisherは、sendメソッドでイベントをpublishする。

参考: Apple Developer Documentation | Combine

Publisher イベントを発行する

Publisher を subscribe することでイベントを受信できる。
PassthroughSubject クラス
CurrentValueSubject クラス

Justとは 値を1回だけ出力するPublisher。
Futureは、通信処理などで利用していたコールバック処理をCombineで実装する場合に使用する。

Subscriber イベントを購読する

イベントを受信する(購読する)subscribe処理
assignメソッド
sinkメソッド

var subscriptions = Set()

Operator 流れてくる値を加工する

map
filter
compactMap   
combineLatest など

APIリクエストを行い、結果集合をメンバ変数に格納するサンプル

import Foundation
import Combine

final class SearchUserViewModel: ObservableObject {
    @Published var text = ""
    @Published var users = [User]()
    private lazy var subject = PassthroughSubject<Void, Never>()
    private var cancellables = Set<AnyCancellable>()
    private let searchUserModel: SearchUserModelProtocol
    
    init(searchUserModel: SearchUserModelProtocol) {
        self.searchUserModel = searchUserModel
        
        subject.sink(receiveValue: { _ in
            searchUserModel.fetchUser(query: self.text)
                .receive(on: RunLoop.main)
                .sink(receiveCompletion: { completion in
                    switch completion {
                    case .finished:
                        break
                    case .failure(let error):
                        print(error)
                    }
                }, receiveValue: {
                    self.users = $0
                })
                .store(in: &self.cancellables)
            })
            .store(in: &self.cancellables)
    }
    
    func searchButtonTapped() {
        subject.send()
    }
}

参考:【Combine】APIとの通信処理にCombineを取り入れる

Macに外部マイクを接続する場合、注意すること

Macでビデオ会議に参加する際などにAirPodsProもしくは、iPhoneに付属するヘッドホンを利用しています。利便性だけを考えればそれで良いのですが、今回は動画のアフレコを行うため、外部マイクを接続しようとして、認識されず、少しハマったのでメモしておきます。

まず最初に、Macのオーディオ端子は、4極(CTIA)という規格であるようです。

私が使用している「iMac (Retina 5K, 27-inch, Late 2014)」の仕様ページを参照しても「ヘッドフォンポート」としか記載されておらず、適当に4極プラグを購入したのですが、結果、4極(OMTP)であったため、認識されなかったようです。
4極プラグには2種類の規格があり、製品によっては規格が明記されていなかったり、外観上の違いがないことが混乱の原因のようです。

参考: iMac (Retina 5K, 27-inch, Late 2014) – 技術仕様

iMac (Retina 5K, 27-inch, Late 2014) と接続できたケーブル、変換アダプターは、以下の製品です。

  • YOUZIPPER ユージッパー
    P-34G [変換アダプター 3極→4極 150mm]
  • エレコム ELECOM
    DH-MMIP10WH [AUDIOケーブル 直径3.5ステレオミニ 1.0m ホワイト]
  • Ulanzi VM-Q1 ビデオマイク

プラグの規格については以下のページで解説されています。
参考: MacBookでイヤホンジャックにさすタイプの外付けマイクを使う際の注意点

ブログシステムを判断する方法

ブログシステムを判定するには、Chrome拡張を利用する

「Wappalyzer」などの拡張機能を利用する。

Word Pressテーマの確認方法は?

ページのソースを「wp-content/themes/」で検索し、テーマを判断する。
「wp-content/themes/○○/←がテーマの名前になる。

参考: 他サイトがどのWordPressテーマを使っているか調べる方法【最も簡単な調べ方・調査方法】

非同期処理で待ち合わせ制御を行っている場合、自由にキャンセル処理を行うにはNSOperationを選択した方がよい

APIリクエスト処理の不具合に対処していて、非同期処理について調べたのでメモしておきます。

iOSアプリをSwiftで実装している場合、いくつかの実装方法があり、お手軽に対処するためにGCD(Grand Central Dispatch)、DispatchGroup、DispatchSemaphoreを選択したのですが、以下のような【非同期処理の直列化】を行おうとした場合、処理途中で自由に処理をキャンセルできず(割り込めない)、結局、Foundation.Operation(NSOperation)で実装することとなりました。
キャンセル処理が出来ないことなんてあるのか?と思いますが、実際にキャンセル処理は処理完了時にしか出来ないようでした。

【非同期処理】
タスク1 --->
タスク2 --->
タスク3 --->
後処理  --->

【非同期処理の直列化】
タスク1 --->
タスク2     --->
タスク3         --->
後処理              --->

※ 後処理は全タスクの終了を待ち合わせてから処理する

参考にしたURLは以下の通り。

【参考URL】
Foundation.Operationの並列オペレーションがよくわからない人向けの説明
【Swift】非同期処理「Operation」について
swift で直列に非同期処理

Swift5 で文字列検索

久しぶりに Swift で文字列検索処理を書こうとして、 index(of:) を使おうとしたら、思うような結果が得られず、ネットを検索しても新旧の情報が入り交じっていて、よく分からなかったので、まとめた。

配列の最初に一致したインデックス番号を取得

index(of:) → firstIndex(of:)
メソッド名が変更されているようだが、結果を利用することができなかった。
文字列から文字を検索して、単純にインデックス番号が取得したかったのだが、そういうものではないらしい。 😂
Index とか Range とかの意味を整理する必要があって、数分で内容を理解して使用することができなかった。

文字列から文字を検索できるけど…

let str = "abcabc"
let firstIndex = str.firstIndex(of: "c")
print(type(of: firstIndex), firstIndex ?? 0)

出力結果 :
Optional Index(_rawBits: 131329)

Index型は、文字列の範囲指定に使用するものらしい…

文字列から文字を検索して、出現するインデックスを取得

以下の方法であれば、インデックス番号が取得できる。

let str = "abcabc"
let indexes = str.enumerated().compactMap { $0.element == "c" ? $0.offset : nil }
print(type(of: indexes), indexes)
// compactMap は配列のデータから nil を取り除きたい場合に使用する関数

出力結果 :
Array [2, 5]

NSRange で文字列中で指定した文字列が最初に出現する範囲を検索して取得

NSRange で取得するのが便利そう。

var str = "abcdef"
let range = NSString(string: str).range(of: "cde")
print("location:", range.location, "length:", range.length)

出力結果 :
location: 2 length: 3

文字の出現回数を正規表現で取得

こちらも便利です。

let string = "abcdefgabcdefg"
let pattern = NSRegularExpression.escapedPattern(for: "a")
let regex = try! NSRegularExpression(pattern: pattern, options: .caseInsensitive)
print(regex.numberOfMatches(in: string, range: NSRange(0..<string.utf16.count)))

出力結果 :
2

Swiftの範囲についてもよく知らなかったので、まとめた。

これだけ知っていれば良いと思う。

Range0..<30 ~ 2
ClosedRange0…30 ~ 3
let range1 = 0..<3
print("type: \(type(of: range1))")
print("lower:\(range1.lowerBound) upper:\(range1.upperBound) count:\(range1.count)")
print("range1.contains(0) \(range1.contains(0))")
print("range1.contains(1) \(range1.contains(1))")
print("range1.contains(2) \(range1.contains(2))")
print("range1.contains(3) \(range1.contains(3))")
print("range1.contains(4) \(range1.contains(4))")

出力結果 :
type: Range<Int>
lower:0 upper:3 count:3
range1.contains(0) true
range1.contains(1) true
range1.contains(2) true
range1.contains(3) false
range1.contains(4) false
let range2 = 0...3
print("type: \(type(of: range2))")
print("lower:\(range2.lowerBound) upper:\(range2.upperBound) count:\(range2.count)")
print("range2.contains(0) \(range2.contains(0))")
print("range2.contains(1) \(range2.contains(1))")
print("range2.contains(2) \(range2.contains(2))")
print("range2.contains(3) \(range2.contains(3))")
print("range2.contains(4) \(range2.contains(4))")

出力結果 :
type: ClosedRange<Int>
lower:0 upper:3 count:4
range2.contains(0) true
range2.contains(1) true
range2.contains(2) true
range2.contains(3) true
range2.contains(4) false

R1000の走行動画に後方カメラを追加した件

趣味としてサーキット走行時の動画を撮影し、映像編集を楽しんでいるのですが、映像の画角が一つではどうしても飽きてくるので、バイク後方にもカメラを追加マウントすることにしました。
これで、MotoGPのようなアングルの撮影を実現することができます。
まずは、GoPro Hero Sessionを標準マウントで取り付けて撮影してみました。
(脱落防止ワイヤー付き)

意外に振動の影響を受けることなく、視聴に耐えうる動画が撮影できることが分かりました。
実際にバイクにカメラをマウントして撮影してみると、走行時の振動が影響して、視聴に耐えない場合もあるのですが、Sessionの軽さが功を奏したのか、このマウント方法でも綺麗に動画が撮影できるようです。

二つの映像のタイミングを同期したり、録音した音の処理など考慮すべき点はありますが、個人の趣味でもMotoGPのような映像編集が容易に行えるのは大変楽しいですね。