iOSエンジニアのつぶやき

毎朝8:30に iOS 関連の技術について1つぶやいています。まれに釣りについてつぶやく可能性があります。

CircleCIでCarthageのビルド済みバイナリをキャッシュする

Carthageのビルド済みバイナリを今までプロジェクトのソース管理に含めてきましたが、Xcodeを移行しづらかったり、プロジェクトのレポジトリが肥大化してクローンにかなり時間がかかったりなど、そろそろ限界を感じてきたので、ついに.gitignoreに追加して Carthage のビルド済みバイナリとお別れしました😇👋

# Carthage
#
# Add this line if you want to avoid checking in source code from Carthage dependencies.
# Carthage/Checkouts
Carthage/Checkouts
Carthage/Build

そんなこんなで、CI側でプロジェクトをビルドする際にCarthageライブラリのbootstrapが必要になりました。ただ、ワークフロー毎に毎回 Carthage ライブラリをフェッチしてビルドし直してたんじゃ、時間がかかりすぎるので、タイトルの通り「キャッシュを利用してプロジェクトをビルドできるようにしようぜ!」という心意気が大事になります。

f:id:yum_fishing:20210319171027p:plain

それではやっていく

やることは至ってシンプルで、Carthageのビルド済みバイナリを取得するjobを追加して、ワークフロー内のビルドを伴うjobの前で実行するようにすれば完了です。 下記が Carthage のキャッシュを取得&保存するためのjobになります。

jobs:
// 省略..
  carthage_dependencies:
    executor: parnovi-macos
    steps:
      - restore_caches
      - run:
          name: Carthage build
          command: make install-carthage
      - save_cache:
          key: carthage-v1-{{ checksum "Cartfile.resolved" }}
          paths:
            - ~/parnovi-ios/Carthage

restore_cachescommandsによってカスタムで設定できるステップで、設定は下記のようになっています。restore_cachesの役割は、プロジェクトのソースファイル・Bundlerライブラリ・Carthageライブラリのキャッシュを取得することで、キャッシュが無い場合や依存関係に変更がある場合は何も行われません。

ちなみにcommands:でステップを定義するには、バージョン2.1が必須になります。

commands:
  save_sha:
    steps:
      - run:
          name: Save commit SHA1 to .sha
          command: echo $CIRCLE_SHA1 > .sha
  restore_caches:
    steps:
      - save_sha
      - restore_cache:
          keys:
            - parnovi-v1-{{ checksum ".sha" }}
      - restore_cache:
          keys:
            - bundle-v1-{{ checksum "Gemfile.lock" }}
      - restore_cache:
          keys:
            - carthage-v1-{{ checksum "Cartfile.resolved" }}

make install-carthageMakefileで定義したCarthagebootstrapを行うコマンドで実態は下記のようになります。carthage.sh になっている理由はこちらをご覧ください。また、Carthageのビルドオプションに--cache-buildsを付けないと、ライブラリがリビルドされてしまい、コンテナでキャッシュしている意味がなくなってしまうので気をつけてください👷‍♀️

$ ./carthage.sh bootstrap --platform iOS --cache-builds  --no-use-binaries

save_cache の役割は特定のファイルをキャッシュすることで、今回の場合はcarthage-v1-{{ checksum "Cartfile.resolved" }}のキーにプロジェクトルートのCarthageディレクトリがキャッシュされます。このcarthage-v1-{{ checksum "Cartfile.resolved" }}キーはchecksumによってCartfile.resolvedの内容をみて値が動的に代わります。つまり、Cartfile.resolvedの内容が変更されると新しいキーでキャッシュが作成されます。 ちなみにここで作成されたキャッシュは、最長で15日間保存されます。手動でキャッシュをクリアしたい場合は、キーの名前を変更する必要があります。今回はxxx-v1-xxxのようなフォーマットにしているのでv1v2などにすればキャッシュがクリアされます。

てな感じ本日も以上になります🍺

参考

circleci.com

circleci.com

その他の記事

yamato8010.hatenablog.com

yamato8010.hatenablog.com

yamato8010.hatenablog.com

【CircleCI】expected type: String, found: Mapping の対処法

凡ミスエラーですが、誰かのためになることを祈ってメモしときます🏃🏻‍♂️

エラー内容はこんな感じ(一部抜粋)。

Error: ERROR IN CONFIG FILE:
[#/jobs/checkout_code] 0 subschemas matched instead of one
1. [#/jobs/checkout_code] only 1 subschema matches out of 2
|   1. [#/jobs/checkout_code/steps/2] 0 subschemas matched instead of one
|   |   1. [#/jobs/checkout_code/steps/2] expected type: String, found: Mapping
|   |   |   Shorthand commands, like `checkout`
|   |   |   SCHEMA:
|   |   |     type: string
|   |   |   INPUT:
|   |   |     save_cache:
|   |   |     - key: parnovi-v1-{{ checksum ".circle-sha" }}
|   |   |     - paths:
|   |   |       - ~/parnovi-ios

結論

今回は下記のような config.yml でエラーが発生していました。

jobs:
  checkout_code:
    executor: parnovi-macos
    steps:
      - checkout
      - save_sha
      - save_cache:
          - key: parnovi-v1-{{ checksum ".sha" }}
          - paths:
              - ~/parnovi-ios

そうです。save_cachekeypaths-はいらないのです🧑‍🔧

jobs:
  checkout_code:
    executor: parnovi-macos
    steps:
      - checkout
      - save_sha
      - save_cache:
          key: parnovi-v1-{{ checksum ".sha" }}
          paths:
            - ~/parnovi-ios

みなさんも凡ミスで時間を溶かさないようくれぐれも注意してください🔥

その他の記事

yamato8010.hatenablog.com

yamato8010.hatenablog.com

yamato8010.hatenablog.com

【Swift】makeKeyAndVisibleってなにしてるんだっけ?

結論

makeKeyAndVisible は、windowを key window として設定することで、同一レベル(もしくは同一レベル以下)の windowの最前面に表示するためのメソッドです。

ちなみにレベルには下記のようなものがあり、LevelrawValueとしてCGFloat値を持ちます。

extension UIWindow.Level {

    public static let normal: UIWindow.Level // 0

    public static let alert: UIWindow.Level // 2000

    public static let statusBar: UIWindow.Level // 1000
}

また、OS側で表示されるキーボードも同様にUIWindowViewのルートとなっているので、レベルが自体は設定されますが、UIWindow.Levelとしてのenumでは用意されていません。ちなみにキーボードより上にWindowを表示したい場合は下記が参考になるかと思います👀

qiita.com

てな感じで本日も以上になります🍺

その他の記事

yamato8010.hatenablog.com

yamato8010.hatenablog.com

yamato8010.hatenablog.com

【Javascript】 FirebaseError: Function DocumentReference.set() called with invalid data. Unsupported field value: a custom object の対処法

Firestore のセキュリティールールで使用するテストオブジェクトの GeoPoint を下記のように設定するとエラーが発生していました🤔

const { GeoPoint } = require("@google-cloud/firestore");

function fieldMapData(fieldId) {
    return {
        id: fieldId,
        name: "霞ヶ浦",
        map: new GeoPoint(80, 150), // FirebaseError: Function DocumentReference.set() called with invalid data. Unsupported field value: a custom object
        prefectureName: "茨城県",
        imagePath: "https://homepages.cae.wisc.edu/~ece533/images/airplane.png",
        categoryId: "0"
    };
}

StackOverflowでもGeoPoint で同じような問題が発生している投稿が。

stackoverflow.com

どうやらconst { GeoPoint } = require("@google-cloud/firestore"); で宣言したGeoPointが原因でエラーが発生していたみたいで、下記のようにfirebase.firestoreでアクセスしたGeoPointクラスを使うことで解決することができました🚀

const firebase = require("@firebase/rules-unit-testing");

function fieldMapData(fieldId) {
    return {
        id: fieldId,
        name: "霞ヶ浦",
        map: new firebase.firestore.GeoPoint(80, 150),
        prefectureName: "茨城県",
        imagePath: "https://homepages.cae.wisc.edu/~ece533/images/airplane.png",
        categoryId: "0"
    };
}

その他の記事

yamato8010.hatenablog.com

yamato8010.hatenablog.com

yamato8010.hatenablog.com

【Swift】optional プレフィックスについて

先日、下記の記事を見て 「optional プレフィックスってそうゆう役割だったのか!」ということを知ったので、メモしておきます✍️

qiita.com

ということで、まずは Swift のドキュメントを見てみます。

You can define optional requirements for protocols. These requirements don’t have to be implemented by types that conform to the protocol. Optional requirements are prefixed by the optional modifier as part of the protocol’s definition.

ということで、基本にoptionalProtocolで使用することが可能で、下記のようにClassoptionalなプロパティを宣言しようとするとエラーが発生します。

class Hoge {
    optional var hoge: String // 'optional' can only be applied to protocol members

    init(hoge: String) {
        self.hoge = hoge
    }
}

Optional requirements are available so that you can write code that interoperates with Objective-C. Both the protocol and the optional requirement must be marked with the @objc attribute.

また、optionalで宣言する場合にはObjective-Cと相互運用するコードに記述する必要があるらしく、@objc修飾子をつけなければ下記のようなエラーが発生します。

protocol HogeInterface {
    optional var hoge: String { get set } // 'optional' can only be applied to members of an @objc protocol
}

ちなみに@objcプロトコルは、クラスでのみ採用できるのでStructEnumでは使用できないみたいですね👀

@objc protocol HogeInterface {
    @objc optional var hoge: String { get set }
}

struct Hoge: HogeInterface { // Non-class type 'Hoge' cannot conform to class protocol 'HogeInterface'
}

てな感じで本日も以上になります🍺

その他の記事

yamato8010.hatenablog.com

yamato8010.hatenablog.com

yamato8010.hatenablog.com

【Swift】数値を特定の桁数で表示したい

本日は秒速で終わるSwiftの小ネタです👷‍♀️

結論

「5」を10桁で表示したい場合に、他の桁を「0」で埋めるサンプルが下記になります。

extension Int {
    func filled(digit: Int) -> String {
        return String(format: "%0\(digit)d", self)
    }
}

print(5.filled(digit: 10)) // 0000000005

てな感じで本日も以上になります🍺

その他の記事

yamato8010.hatenablog.com

yamato8010.hatenablog.com

yamato8010.hatenablog.com

JavaScriptでオブジェクトのプロパティを削除

今回もJS初心者なのでメモしました✍️

結論

delete 演算子で下記のように削除ができるようです。

const hoge = {
    width: 100,
    height: 200,
};

delete hoge.width;

console.log(hoge) // Object { height: 200 }

developer.mozilla.org

てな感じで本日も以上になります🌮

その他の記事

yamato8010.hatenablog.com

yamato8010.hatenablog.com

yamato8010.hatenablog.com