20200809のiOSに関する記事は12件です。

【入門】iOS アプリ開発 #1

はじめに

新型コロナウィルス(COVID-19)の影響により、外出自粛が続いている。
どこへも出かけない夏休み、この機会に Mac を購入して iOS のプログラミングを勉強してみようと思う。
iPhone のアプリは Swift という言語で作られるようだ。

アップルは Swift を「モダン、安全、高速、インタラクティブ」を特徴として謳っている。
これだけでは全くイメージつかないが、さぞかし簡単にアプリが開発できるに違いない。

昔のゲームぐらいならば、さくっと作れて、軽快にサクサク動作するのであろう。
さらにタッチパネルやセンサーで操作できると面白そうだ。

プログラミングすることよりも、何を作るか?どんな仕様するか?、企画や上流設計は悩みどころだ。
またキャラクターのデザインやサウンドなどを作るのも骨が折れる。

色々調べてみるとパックマンの仕様書が公開されている。
これをもとに作ればプログラミングに専念できそうだ。

パックマンのゲーム仕様書

下記の人工知能学会誌に仕様書が公開されている。

Img0001.png

一度、何かを作ってしまえばノウハウが溜まり、自分が作りたいものが簡単にできるはずなので、
まずはパックマンを作ってみようと思う。

仕様書をよく読むと、思ったよりも複雑な仕様になっているが、
高級言語 Swift で書けば、1万行もコーディングせずに出来るのではないか。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Flutter の iOS 向けビルドでハマったメモ

Flutter の iOS 向けビルド備忘録

とりあえず解決したことを列挙するだけの備忘録

Undefined symbols for architecture arm64

Create Bridging Header で解決した

domain/default pair of ... does not exist

プロジェクトのルートにアセットを置いているとビルドできない模様。

hoge を assets/hoge に移動して。pubspec.yaml の該当箇所も直せばOK。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[SwiftUI]EnvironmentObjectの罠

EnvironmentObjectを使っていたところ、以下のようなエラーに遭遇しました。

Fatal error: No ObservableObject of type **** found.
A View.environmentObject(_:) for * may be missing as an ancestor of this view.

これは、直前に

}
.sheet(isPresented: $isPresented) {
     ModalView(showingModal: self.$isPresented)
}

という操作を行っていました。ModalViewの中では呼び出し元と同じEnvironmentObjectを宣言していました。

原因

こちらを参考にしました。

https://stackoverflow.com/questions/58743004/swiftui-environmentobject-error-may-be-missing-as-an-ancestor-of-this-view
https://github.com/peterfriese/Swift-EnvironmentObject-Demo

この記事によると、sheetによるモーダル表示は、rootとなるContentViewとは全く別のツリー構造となっているようでした。

  • ContentView
    • SubView
    • SubView
      • ChildView
  • ModalView

というような感じです。

解決策

ここのModalViewでも同じEnvironmentObjectを使うためには、以下のようにして渡します。

}
.sheet(isPresented: $isPresented) {
     ModalView(showingModal: self.$isPresented)
        .environmentObject(someObject)
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

不要になったUserDefaultsキーの管理方法

UserDefaultsについて

「ある画面を表示したかどうか」などの情報をアプリに保存する場合、UserDefaultsを利用する機会が多いと思います。
UserDefaultsを利用して値を保存することに目を向けがちですが、アプリ開発では機能追加だけでなく、利用しなくなった機能を削除することももちろんあります。
その際に機能を削除する際に不要となるUserDefaultsのキーを適切に扱わなければ、同一のキーを利用して意図しない動作につながってしまうケースがあります

この記事では既存の不要となったキーの管理方法についてまとめ、新しい管理方法についてまとめたものとなります。

既存の管理方法

1. キーごと削除する

不要となったキーを削除するパターンです。もし別の機能開発で全く同じキー名が利用されるケースがあった場合、未然に防ぐことができなくなるためあまりおすすめはできません。

2. prefix / suffix

利用していないキー名にprefixやsuffixを加えることで管理するパターンです。
利用されていないキーが明示的にはなりますが、キー名が増えてくると行数が増え見通しが悪くなります。
また、Active Compilation Conditionsを利用して、Releaseビルドでは含めないなどを設定しないとdeprecatedなキーが成果物に含まれてしまいます。

static let isHoge = "isHoge"

// deprecated keys
static let deprecated_isFoo = "isFoo"
.
.
.

3. コメント化

使用されなくなったキーをコメントで管理するパターンです。
筆者はこのパターン方法で管理したことはないですが、コメントで管理することで視認性が落ちるのが気になります。

static let isHoge = "isHoge"

// deprecated keys
// static let isFoo = "isFoo"
.
.
.

他にもこんな風に管理しているなどがあれば教えていただけるとありがたいです:bow:

どのような管理方法が理想か

上記のような管理方法を洗い出し、以下の用件が満たせていれば理想だと考えました。

  1. どのキーが使用されなくなったかがわかる状態
  2. 使用されいているキーと不要なキーの管理が分離されている
  3. 既に利用していないキー名を再度利用しようとした場合、ビルド時にエラー検知

新しい管理方法

ドットファイルとスクリプトで管理するパターンを考えてみました。

  • 使用しなくなったキー名を.unused_userdefaults_keyで管理
  • buildPhaseでスクリプトを実行し、使用していないキーを利用する場合エラーを出力

1. ドットファイルを用意

使用しなくなったキー名をまとめた.unused_userdefaults_keyを用意します

is_hoge
is_foo

2. スクリプトファイルを用意

キーをまとめたswiftファイルを引数として、.unused_userdefaults_keyと一致するものが含まれていればエラーを出力するスクリプトを用意します

unused_userdefaults_key.sh
#!/bin/sh
file_path=$1
index_array=`cat -n ${file_path}| nl -nln | grep -f .unused_userdefaults_key | cut -d " " -f1`

if (( ${#index_array[@]} )); then
  for i in ${index_array[@]}
  do
    echo "${file_path}:${i}:0: error: It's a key name that's already in use."
  done  
  exit 1
fi

3. Build Phasesに組み込み

unused_userdefaults_key.shをBuildPhasesで実行するようにします
Screen Shot 2020-08-09 at 16.27.46.png

動作GIF

SwiftyUserDefaultsを利用した場合の動作GIFです。
demo.gif

GitHubにもプロジェクトを公開しているので、興味がある人は確認してみてください。
GitHub: https://github.com/funzin/UnusedUserDefaultsKeyExmaple

まとめ

不要となったUserDefaultsキーの管理方法についてまとめてみました。
削除したキーが復活して事故を防ぐには有効だと思いますので、ぜひ参考にしてもらえるとうれしいです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Swift】deinitではwillSet/didSetが呼ばれないというお話

結論

init/deinit内でプロパティに代入を行っても、willSet/didSetは呼ばれないので注意しましょう。
ただし、スーパークラスのプロパティのwillSet/didSetは呼ばれるようです。

公式ガイドの注意書き

公式ガイド(The Swift Programming LanguageのProperties)に、以下のような注意書きがあります。

NOTE
The willSet and didSet observers of superclass properties are called when a property is set in a subclass initializer, after the superclass initializer has been called. They are not called while a class is setting its own properties, before the superclass initializer has been called.1

注意
サブクラスのイニシャライザ内で、スーパークラスのイニシャライザが呼ばれた後に、プロパティがセットされたとき、スーパークラスのプロパティのwillSetとdidSetのオブザーバーが呼ばれます。スーパークラスのイニシャライザが呼ばれる以前に、自身のプロパティをセットしている間は呼ばれません。2

なるほど。スーパークラスのプロパティのdidSet/willSetについては、サブクラス内のイニシャライザでスーパクラスのイニシャライザを呼んだ後であれば、機能するということらしいです。
クラス自身で定義したプロパティのdidSet/willSetについては、イニシャライザで呼ばれないのは当然だろということか、記述が見当たりませんでした。

コードで確認

確かにスーパークラスのプロパティのdidSetについては呼ばれました。

class Animal {
    var age: Int {
        didSet {
            print(#function, age, oldValue)
        }
    }

    init(age: Int) {
        self.age = age // -> 呼ばれない
    }
}

class Tiger: Animal {
    var family: String {
        didSet {
            print(#function, family, oldValue)
        }
    }

    init(age: Int, family: String) {
        self.family = family  // -> 呼ばれない
        super.init(age: age) // -> 呼ばれない

        self.age += 0  // -> didSetが呼ばれる!!
        self.family += "" // -> 呼ばれない
    }

    deinit {
        print(#function)
        age = 0 // -> 呼ばれる
        family = "" // -> 呼ばれない
    }
}

どういうときに注意すべき?

例えば、Timerをdeinitでinvalidateしたいときに、以下のような書き方をすると呼ばれないので注意しましょう。

class ViewController2: UIViewController {
    var timer: Timer? {
        willSet {
            timer?.invalidate()
        }
    }

    deinit {
        timer = nil // nilを代入してもwillSetは呼ばれない!
        // -> timer?.invalidate()
    }
    ...
}

参考

Swiftでdeinit時にメンバ変数(property)のdidSetが呼ばれない気がした
Can I use didSet in deinit?


  1. [The Swift Programming Language - Properties](https://docs.swift.org/swift-book/LanguageGuide/Properties 

  2. 太字による強調は訳者による 

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Functionに関して、まとめた。【Swift】

 Functionの初歩。

func(関数)を学んだので、簡単におさらい。

 Functionを何故使うのか。

amazonのような買い物アプリにて、
ユーザーの買い物カゴの合計金額を計算する箇所が5つあるとき、

  • 5箇所で同じコードを書くのは無駄。
  • コード量が増えて、プログラム自体が複雑で読みにくくなる。

処理が1回しかないものでも、functionには利点あり。

  • functionにしてマトめると、コード量がかなり多いとき読みやすい。
  • function名を適切につけて、判別しやすい。

 パラメータと引数の違い

意思疎通にはそれほど困らないけど・・・
「引数 == パラメータ」ではない

  • パラメータ (仮引数)は、関数に受け渡されるものの宣言
  • 引数は、関数に渡した実際の
// Funtion with parameters

func declare(name: String) {
    print(name)
}

declare(name: "Shimura") // 呼び出し
declare(name: "Ken") // 呼び出し

「志村」と「けん」が引数で、nameがパラメータ
パラメータと引数の違い

因みに、「ひきすう」と読みます。

 関数3パターン、おさらい。

 with no parameters?

func declareName() {
    print("MyName")
}
declareName() // 呼び出し

 with parameters?

func declare(name: String) {
    print(name)
}
declare(name: "Shimura") // 呼び出し
declare(name: "Ken") // 呼び出し

 with a return value?

func 一日の秒数() -> Int {
    return 24 * 60 * 60
}

let 秒数 = 一日の秒数() // 呼び出し & 代入 (=インスタンス化)
print("一日は\(秒数)秒!")  // 一日は86400秒!

 with parameters and a return value?

func createFullName(firstName: String, lastName: String) -> String {
    return firstName + " " + lastName
}

//let fullName =  createFullName(firstName: String, lastName: String)
let fullName = createFullName(firstName: "Suzuki", lastName: "Ichiro") // 呼び出し & 代入 (=インスタンス化)
print(fullName) // Suzuki Ichiro

 returnを使った関数

一方的にただ呼び出す関数も便利ですが、(上記2つのコード??)

関数の中でいろいろな処理をさせて、その「結果」を貰いたいときがあります。
要するに、呼び出しに対する「返事」が欲しいときです。

 with a return value?

swift6_04_01.jpg

 with parameters and a return value?

swift6_04_07.jpg

戻り値を持つ関数
書き方は、こんな感じ。

func 関数の名前() -> 戻り値の型 {
    // 実行する処理
    return 戻り値
}

具体例。

func 一日の秒数() -> Int {
    return 24 * 60 * 60
}

でも、上記コードだけでは実行されない。
「関数の呼び出し」を、変数or定数に代入(=インスタンス化

let seconds = 一日の秒数() // インスタンス化
print("一日は\(seconds)秒!")  // 一日は86400秒!

  • \()」の中に変数を入れると、その内容が埋め込まれます。

  • Swiftでは『戻り値』のデータ型を指定する 必要があります。
    -> データ型』で指定。これがないとエラー。

  • {}内でreturnが実行されると 関数内の処理は終了なので、
    関数{}の中の、一番最後に書く。

func 一日の秒数() -> Int {   // 今回はInt型。(Integer: 整数)
    return 24 * 60 * 60
    print("Hello World")         // エラー。 Code after 'return' will never be executed
}

let seconds= 一日の秒数() 
print("一日は\(seconds)秒!")  // 『\()』を使って、変数secondsを埋め込み。

おしまい。

 参考サイト

[Swift初心者向け] function(メソッド)の使い方

Swiftの関数、引数、戻り値の基本的な書き方と使い方

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Functionに関して、まとめた。【Swift】

 Functionの初歩。

func(関数)を学んだので、簡単におさらい。

 Functionを何故使うのか。

amazonのような買い物アプリにて、
ユーザーの買い物カゴの合計金額を計算する箇所が5つあるとき、

  • 5箇所で同じコードを書くのは無駄。
  • コード量が増えて、プログラム自体が複雑で読みにくくなる。

処理が1回しかないものでも、functionには利点あり。

  • functionにしてマトめると、コード量がかなり多いとき読みやすい。
  • function名を適切につけて、判別しやすい。

 パラメータと引数の違い

意思疎通にはそれほど困らないけど・・・
「引数 == パラメータ」ではない

  • パラメータ (仮引数)は、関数に受け渡されるものの宣言
  • 引数は、関数に渡した実際の
// Funtion with parameters

func declare(name: String) {
    print(name)
}

declare(name: "Shimura") // 呼び出し
declare(name: "Ken") // 呼び出し

「志村」と「けん」が引数で、nameがパラメータ
パラメータと引数の違い

因みに、「ひきすう」と読みます。

 関数3パターン、おさらい。

 with no parameters?

func declareName() {
    print("MyName")
}
declareName() // 呼び出し

 with parameters?

func declare(name: String) {
    print(name)
}
declare(name: "Shimura") // 呼び出し
declare(name: "Ken") // 呼び出し

 with a return value?

func 一日の秒数() -> Int {
    return 24 * 60 * 60
}

let 秒数 = 一日の秒数() // 呼び出し & 代入
print("一日は\(秒数)秒!")  // 一日は86400秒!

 with parameters and a return value?

func createFullName(firstName: String, lastName: String) -> String {
    return firstName + " " + lastName
}

//let fullName =  createFullName(firstName: String, lastName: String)
let fullName = createFullName(firstName: "Suzuki", lastName: "Ichiro") // 呼び出し & 代入
print(fullName) // Suzuki Ichiro

 returnを使った関数

一方的にただ呼び出す関数も便利ですが、(上記2つのコード??)

関数の中でいろいろな処理をさせて、その「結果」を貰いたいときがあります。
要するに、呼び出しに対する「返事」が欲しいときです。

 with a return value?

swift6_04_01.jpg

 with parameters and a return value?

swift6_04_07.jpg

戻り値を持つ関数
書き方は、こんな感じ。

func 関数の名前() -> 戻り値の型 {
    // 実行する処理
    return 戻り値
}

具体例。

func 一日の秒数() -> Int {
    return 24 * 60 * 60
}

でも、上記コードだけでは実行されない。
「関数の呼び出し」を、変数or定数に代入

let seconds = 一日の秒数() // 呼び出し & 代入
print("一日は\(seconds)秒!")  // 一日は86400秒!

  • \()」の中に変数を入れると、その内容が埋め込まれます。

  • Swiftでは『戻り値』のデータ型を指定する 必要があります。
    -> データ型』で指定。これがないとエラー。

  • {}内でreturnが実行されると 関数内の処理は終了なので、
    関数{}の中の、一番最後に書く。

func 一日の秒数() -> Int {   // 今回はInt型。(Integer: 整数)
    return 24 * 60 * 60
    print("Hello")         // エラー。 Code after 'return' will never be executed
}

let seconds= 一日の秒数() 
print("一日は\(seconds)秒!")  // 『\()』を使って、変数secondsを埋め込み。

おしまい。

 参考サイト

[Swift初心者向け] function(メソッド)の使い方

Swiftの関数、引数、戻り値の基本的な書き方と使い方

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[SwiftUI]EnvironmentObjectとBinding

EnvironmentObjectを使って他のViewを更新しようとしましたが中々できなく、数時間ハマってしまいました。
解決方法を見つけたので書いてみました。

どのような場合か

親View・子View・もう一つのViewの構成で、もう一つのViewから子Viewを更新したい時。

始めにやっていたこと

  • 親View:EnvironmentObjectを持つ
  • 子View:Stateを持つ
  • もう一つのView:EnvironmentObject
  • EnvironmentObjectとしてObservableObjectを継承したクラスを使っていた

→もう一つのViewで値を変更しても、子Viewの値が更新されませんでした

解決策

  • 親View:EnvironmentObjectを持つ
  • 子View:Bindingを持つ
  • もう一つのView:EnvironmentObject
  • EnvironmentObjectとしてObservableObjectを継承したクラス

→子ViewのStateをBindingに変更しました

実装

親View

ContentView.swift
struct ContentView: View {

    @EnvironmentObject var countSetting: CountSetting

    var body: some View {
        NavigationView {
            List {
                NavigationLink.init(destination: AnotherView()) {
                    ChildView(value: Binding.init(get: { self.countSetting.count }, set: { _ in }))
                }
            }
        }
    }
}

ポイントはBindingでChildViewに値を渡しているところです。

子View

ChildView.swift
struct ChildView: View {    
    @Binding var value: Int

EnvironmentObjectにするクラス

CountSetting.swift
class CountSetting: ObservableObject {
    @Published var count: Int = 0
}

ソース

ソースはこちら
https://gist.github.com/usk2000/95fdcd327c5fe22aa892d615721de0d9

スクショ

親View もう一つのView
スクリーンショット 2020-08-09 16.02.38.png スクリーンショット 2020-08-09 16.03.01.png

もう一つのViewの「増やす」を押すと親Viewの「4」のところが変わります。

参考

https://qiita.com/shiz/items/6eaf87fa79499623306a#binding
https://stackoverflow.com/questions/59259921/binding-value-from-an-observableobject

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Immutable value "number" was never used;

 たまに見るやつ。

たまに見る警告。そして、自然と消えるやつ。⚠️
いちいち投稿するまでも無いかもだけど、一応まとめておきます。

 Immutable value "number" was never used;

for構文とかで、まだ中身書いてない時に、
一時的に発生する警告。

Immutable value "number" was never used; consider replacing with "_" or removing it
for number in 0...4 { // Immutable value "number" was never used; consider replacing with "_" or removing it
    // まだ中身書いてない。
}

「"number"は使われてないから、"_"に置き換える、もしくは削除することも考え直したらどう?」

と、言われています。

 「 _ 」 とは?

「その値は使用しない」 という意味らしいです。

Swiftでとまどった「_」(アンダースコア)

for number in 0...4 {
    print(number)
}

中身で”number”を使えば、警告は消えます。

おしまい。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Immutable value "number" was never used; consider replacing with "_" or removing it

 たまに見るやつ。

たまに見る警告。そして、自然と消えるやつ。⚠️
いちいち投稿するまでも無いかもだけど、一応まとめておきます。

 Immutable value "number" was never used;

for構文とかで、まだ中身書いてない時に、
一時的に発生する警告。

Immutable value "number" was never used; consider replacing with "_" or removing it
for number in 0...4 { // Immutable value "number" was never used; consider replacing with "_" or removing it
    // まだ中身書いてない。
}

「"number"は使われてないから、"_"に置き換える、もしくは削除することも考え直したらどう?」

と、言われています。

 「 _ 」 とは?

「その値は使用しない」 という意味らしいです。

Swiftでとまどった「_」(アンダースコア)

for number in 0...4 {
    print(number)
}

中身で”number”を使えば、警告は消えます。

おしまい。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

自作ライブラリ Carthage CocoaPods 公開を CI を利用して自動化

前提

  • Release の作成 (= tag 作成) は手作業で行うものとします。
    この記事で行う自動化は、Release 作成をトリガーとして行うものです。

  • https://github.com/yuki0n0/WaveSlider/
    解説するものは、こちらのライブラリを作成したときの情報に基づいています。
    よかったら GitHub で Star ★ つけてね。

  • https://qiita.com/yuki0n0/items/290c6ab753634e7395f5
    ライブラリ公開の手続きなどはこちらで解説しております。

  • CI は GitHub Actions を利用しています。
    大したことはやってないのでお好きな CI で読み替えてください

CocoaPods

今回やることは下記です。

  • pod trunk push を CI で自動的に実行
  • push が行えるように認証情報を用意する
  • バージョン情報を tag から取得

1. トークンを取得

登録 (もしくはログイン) します。メールが来たらアクティベートしましょう。

# 登録済みの場合は Name は必要ありません
pod trunk register mail@example.com Name

~/.netrc に認証情報があるため、 password を確認します。

~/.netrc
machine trunk.cocoapods.org
  login mail@example.com
  password 0000000a000000aaaaaaa0000aa00a0a

この password を GitHub Actions から利用できるように Secrets にセットします。
ここでは COCOAPODS_TRUNK_TOKEN としておきます。
GitHub Actions Secrets

2. バージョンを環境変数から取れるようにしておく

.podspec ファイルでバージョンを指定しますが、ここの値を外部から指定できるようにしておきます。
環境変数 LIBRARY_VERSION1.0.0 のような値が入っていることを想定します。

LibraryName.podspec
# コードは一例です。お好きな方法で外部の値をとってこれるようにしましょう。
version = ENV["LIBRARY_VERSION"]
exit 1 if version.to_s.empty?

Pod::Spec.new do |spec|
  spec.name         = "LibraryName"
  spec.version      = version
  spec.source       = { :git => "https://...git", :tag => spec.version }
  ...
end

今回でいう外部は、 git の tag 名に基づいて指定したいということです。
つまり、必然的に tag 名は 1.0.0 というような CocoaPods のバージョン名と一致することが求められます。

3. GitHub Actions の Yaml ファイル

最低限の Yaml を下記に載せました。ポイントをまとめます。

  • CocoaPods のトークン (password) を env として定義し忘れない
  • env LIBRARY_VERSION に tag 名を渡す
  • github.event.release.tag_name でタグ名を取れる! (これにたどり着くのに時間かかった)
GitHub Actions の Yaml ファイル
on:
  release:
    types: [published]

jobs:
  job-name:
    runs-on: macos-10.15

    env:
      COCOAPODS_TRUNK_TOKEN: ${{ secrets.COCOAPODS_TRUNK_TOKEN }}
      LIBRARY_VERSION: ${{ github.event.release.tag_name }}

    steps:
    - uses: actions/checkout@v2
    - run: pod trunk push LibraryName.podspec

ソースコードはこちら
https://github.com/yuki0n0/WaveSlider/blob/1.0.2/.github/workflows/release_published.yaml

Carthage

Carthage は公開を自動化するというより、
オプションとして行う「バイナリのアップロード」を自動化します。

1. 前提知識

Release の Assets

https://docs.github.com/ja/github/administering-a-repository/about-releases
GitHub の Release には、ファイルを Assets として置いておける場所があります。
ReleaseのAssets
そして、ここにブラウザ上から手動でファイルをアップロードすることもできます。
Releaseにアップロード
このアップロードを自動化します。

アップロードしたいバイナリ

下記コマンドを実行すると、 LibraryName.framework.zip を作成できます。

carthage build --archive

バイナリを Assets に置いておくメリット

ビルド済みのバイナリを置いておくことにより、ライブラリ利用者はこれを使用できます。
通常はソースコードを各利用者の環境でビルドするので、このビルド時間を省略1できます。

2. バイナリを自動アップロード

最小限のコードを、コメントと共に掲載しておきます。

GitHub Actions の Yaml ファイル
on:
  release:
    types: [published]

jobs:
  job-name:
    runs-on: macos-10.15

    env:
      GITHUB_TOKEN: ${{ github.token }}

    steps:
    - uses: actions/checkout@v2

    # LibraryName.framework.zip を作成
    - run: carthage build --archive 

    # upload_url を取得するために使用させていただく
    - uses: bruceadams/get-release@v1.2.0
      id: get

    # Release の Assets にファイルをアップロード
    - uses: actions/upload-release-asset@v1.0.2
      with:
        upload_url: ${{ steps.get.outputs.upload_url }}
        asset_path: ./LibraryName.framework.zip
        asset_name: LibraryName.framework.zip
        asset_content_type: application/zip

ソースコードはこちら
https://github.com/yuki0n0/WaveSlider/blob/1.0.2/.github/workflows/release_published.yaml

参考


  1. 一方で、このバイナリを「Swiftのバージョンが異なる」等の理由で利用できない場合があります。
    その場合は Carthage 側が自動的に判断2して、バイナリは利用せずに通常通りソースコードからビルドして利用します。
    どんな状況でもバイナリを使用したくない場合は --no-use-binaries オプションを付与してコマンドを実行します。 

  2. Carthage 0.20.0 以降 

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

自作ライブラリ Carthage CocoaPods 公開を CI で自動化

GitHub Actions を利用し、Release 作成タイミングで、ライブラリ公開作業を自動化しましょう!

前提

  • Release の作成 (= tag 作成) は手作業で行うものとします。
    この記事で行う自動化は、Release 作成をトリガーとして行うものです。

  • https://github.com/yuki0n0/WaveSlider/
    解説するものは、こちらのライブラリを作成したときの情報に基づいています。
    よかったら GitHub で Star ★ つけてね。

  • https://qiita.com/yuki0n0/items/290c6ab753634e7395f5
    ライブラリ公開の手続きなどはこちらで解説しております。

  • CI は GitHub Actions を利用しています。
    大したことはやってないのでお好きな CI で読み替えてください

CocoaPods

今回やることは下記です。

  • pod trunk push を CI で自動的に実行
  • push が行えるように認証情報を用意する
  • バージョン情報を tag から取得

1. トークンを取得

登録 (もしくはログイン) します。メールが来たらアクティベートしましょう。

# 登録済みの場合は Name は必要ありません
pod trunk register mail@example.com Name

~/.netrc に認証情報があるため、 password を確認します。

~/.netrc
machine trunk.cocoapods.org
  login mail@example.com
  password 0000000a000000aaaaaaa0000aa00a0a

この password を GitHub Actions から利用できるように Secrets にセットします。
ここでは COCOAPODS_TRUNK_TOKEN としておきます。
GitHub Actions Secrets

2. バージョンを環境変数から取れるようにしておく

.podspec ファイルでバージョンを指定しますが、ここの値を外部から指定できるようにしておきます。
環境変数 LIBRARY_VERSION1.0.0 のような値が入っていることを想定します。

LibraryName.podspec
# コードは一例です。お好きな方法で外部の値をとってこれるようにしましょう。
version = ENV["LIBRARY_VERSION"]
exit 1 if version.to_s.empty?

Pod::Spec.new do |spec|
  spec.name         = "LibraryName"
  spec.version      = version
  spec.source       = { :git => "https://...git", :tag => spec.version }
  ...
end

今回でいう外部は、 git の tag 名に基づいて指定したいということです。
つまり、必然的に tag 名は 1.0.0 というような CocoaPods のバージョン名と一致することが求められます。

3. GitHub Actions の Yaml ファイル

最低限の Yaml を下記に載せました。ポイントをまとめます。

  • CocoaPods のトークン (password) を env として定義し忘れない
  • env LIBRARY_VERSION に tag 名を渡す
  • github.event.release.tag_name でタグ名を取れる! (これにたどり着くのに時間かかった)
GitHub Actions の Yaml ファイル
on:
  release:
    types: [published]

jobs:
  job-name:
    runs-on: macos-10.15

    env:
      COCOAPODS_TRUNK_TOKEN: ${{ secrets.COCOAPODS_TRUNK_TOKEN }}
      LIBRARY_VERSION: ${{ github.event.release.tag_name }}

    steps:
    - uses: actions/checkout@v2
    - run: pod trunk push LibraryName.podspec

ソースコードはこちら
https://github.com/yuki0n0/WaveSlider/blob/1.0.2/.github/workflows/release_published.yaml

Carthage

Carthage は公開を自動化するというより、
オプションとして行う「バイナリのアップロード」を自動化します。

1. 前提知識

Release の Assets

https://docs.github.com/ja/github/administering-a-repository/about-releases
GitHub の Release には、ファイルを Assets として置いておける場所があります。
ReleaseのAssets
そして、ここにブラウザ上から手動でファイルをアップロードすることもできます。
Releaseにアップロード
このアップロードを自動化します。

アップロードしたいバイナリ

下記コマンドを実行すると、 LibraryName.framework.zip を作成できます。

carthage build --archive

バイナリを Assets に置いておくメリット

ビルド済みのバイナリを置いておくことにより、ライブラリ利用者はこれを使用できます。
通常はソースコードを各利用者の環境でビルドするので、このビルド時間を省略1できます。

2. バイナリを自動アップロード

最小限のコードを、コメントと共に掲載しておきます。

GitHub Actions の Yaml ファイル
on:
  release:
    types: [published]

jobs:
  job-name:
    runs-on: macos-10.15

    env:
      GITHUB_TOKEN: ${{ github.token }}

    steps:
    - uses: actions/checkout@v2

    # LibraryName.framework.zip を作成
    - run: carthage build --archive 

    # upload_url を取得するために使用させていただく
    - uses: bruceadams/get-release@v1.2.0
      id: get

    # Release の Assets にファイルをアップロード
    - uses: actions/upload-release-asset@v1.0.2
      with:
        upload_url: ${{ steps.get.outputs.upload_url }}
        asset_path: ./LibraryName.framework.zip
        asset_name: LibraryName.framework.zip
        asset_content_type: application/zip

ソースコードはこちら
https://github.com/yuki0n0/WaveSlider/blob/1.0.2/.github/workflows/release_published.yaml

参考


  1. 一方で、このバイナリを「Swiftのバージョンが異なる」等の理由で利用できない場合があります。
    その場合は Carthage 側が自動的に判断2して、バイナリは利用せずに通常通りソースコードからビルドして利用します。
    どんな状況でもバイナリを使用したくない場合は --no-use-binaries オプションを付与してコマンドを実行します。 

  2. Carthage 0.20.0 以降 

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む