文字っぽいの。

文字を書いています。写真も混ざります。

Swiftで大量のメンバ変数があるclassやstructのinitを自動生成する。

環境

課題

開発をしていると、こんな感じでメンバ変数がたっぷりあるclassができます。この例ではclassですが、structも同様ですね。

class SampleClass: Codable {
    public let id: Int
    public let title: String
    public let body: String
    public let thumbnailUrl: URL
    public let tags: [String]
    public let categories: [String]
    public let createdAt: Date
    public let updatedAt: Date
    public let comment: [String]
    public let isFavorited: Bool
    public let isBookmarked: Bool
    public let url: URL
}

Swiftのstructではinitが自動生成されますが、Embedded Frameworkを活用してModelをアプリ外に出しているとその恩恵を受けることができません。Modelを外に出している場合は、Codableに準拠しておくだけで済むことが多いのですが、たまにinitが必要になります。

さて、このメンバ変数に対応したinitを書くと、

init(id: Int, title: String, body: String, thumbnailUrl: URL, tags: [String], categories: [String], createdAt: Date, updatedAt: Date, comment: [String], isFavorited: Bool, isBookmarked: Bool, url: URL) {
    self.id = id
    self.title = title
    self.body = body
    self.thumbnailUrl = thumbnailUrl
    self.tags = tags
    self.categories = categories
    self.createdAt = createdAt
    self.updatedAt = updatedAt
    self.comment = comment
    self.isFavorited = isFavorited
    self.isBookmarked = isBookmarked
    self.url = url
}

と、結構な量を書く必要があり面倒です。実際のプロダクトコードでは、もっと多くのメンバ変数を持つ場合もあります。

解決法

XcodeのRefactor機能を使って自動生成します。

  1. class名 or struct名を右クリックする
  2. "Refactor"メニューを選ぶ
  3. "Generate Memberwise Initializer"をクリック

f:id:FromAtom:20210106000215g:plain

これで簡単にinitが生成できます。めでたしめでたし。

Xcode12時代のライブラリ管理ツール選定

背景

iOSDC 2020でMint及びiOSアプリ開発時のライブラリ管理について執筆し、同様の内容をZennでも本として販売した。

zenn.dev

しかし、Xcode12の正式版がリリース後に、執筆時からライブラリ管理を取り巻く状況が大きく変わってきたため、ここに筆を執った次第である。

はじめに

この記事はXcode12を利用してiOSアプリ開発を行う前提で執筆されている。また、情報は2020年12月25日現在のものとなる。

iOSアプリ開発を行う場合、ライブラリ管理には

  • CocoaPods
  • Carthage
  • Swift Package Manager(以下 SwiftPM)
  • Manual

が選択肢として挙げられる。Manualは管理といえるのか怪しいため今回は省略する。なお筆者は、

  • 本業:Carthage, CocoaPods, Mint 1
  • 副業:SwiftPM, Mint
  • 個人開発1:SwiftPM, Mint
  • 個人開発2:CocoaPods, cocoapods-binary

という構成で開発を行っている。

以下、CocoaPods、Carthage、SwiftPMのそれぞれでライブラリ管理をする場合のメリットとデメリットを述べる。

CocoaPods

■メリット

CocoaPodsは2011年に作られたツールで、すでに9年以上の歴史がある。Objective-CからSwiftへの移行も乗り越えた歴戦の戦士であり、十分に長い年月利用されているため、とてもよく枯れている。

また、CocoaPodsにはPluginの仕組みがあり、便利なサブコマンドを追加することができる。有名なところではcocoapods-keyscocoapods-binaryがある。これらPluginもGemとして提供されているので、Gemfile に追記して bundle install して Podfile に追記するだけで利用することができる。

■デメリット

Ruby Gemなので、Rubyが必要になる。Rubyは別に嫌いではないし、個人OSSでも利用している。ただ、こちらとしてはiOSアプリ開発をしたいのであって、Rubyでコードを書くわけではない。SwiftでiOSアプリを書きたいだけなのに、どうしてrbenvでRubyを入れ、Bundlerを入れ、Gemfileを書いてCocoaPodsを入れてバージョン管理をしなければならないのだろうか。Fastlaneも同様にGemなので許容範囲とも考えられるが、正直管理するものが多くて面倒である。

[追記 2020/12/25 15:45]

今ではSystemのRubyを利用することも可能なようです。

[追記ここまで]

よく知られたデメリットとして、Clean Buildするとライブラリも再ビルドが必要になるためビルド時間が長くなる点がある。これはCocoaPodsの仕組み上なくなることは無いが、最近ではそこまで気にしなくても良いだろう。Swift2や3系の頃のSwiftコンパイラはかなり安全側に倒してあり、差分ビルドよりフルビルドが選ばれがちであった。そのため、1行変更しただけでフルビルドが走り、その度にCocosPodsで導入したライブラリ群もビルドされるため、長時間のビルドを待つ事態が頻発していた。Swift5系の今では、こういった挙動は皆無ではないにせよかなり減った。

SwiftUIで開発していると稀に「Derived Dataを消してXcodeを再起動すると直るエラー」に出会う。これが何を起因として発生しているのか不明なのでなんとも言えず、Xcodeのアップデートによって発生しなくなるかもしれないが、もし頻発するようであればフルビルドによって開発速度が低下することは記しておく。

Carthage

■メリット

導入には.pkg, Homebrew, Mintなどが使える。別のプログラミング言語を準備すること無く導入が可能である。

Carthageで導入されたライブラリは事前にビルドされており、Xcode上からはビルド済みのライブラリを利用するため、Clean Buildをしてもライブラリ群の再ビルドが走らない。これによって、CocoaPodsに比べてビルド時間を短縮することができる。ちなみに、cocoapods-binaryでも同様の状況にはできる。

■デメリット

Mintを利用せず .pkg やHomebrewで導入した場合、Carthage自体のバージョン管理を行うことができない。そのため、複数人で開発をしている場合に、チームメンバーそれぞれの手元で異なるバージョンのCarthageが動くことになる。これはMintを導入することで解決可能なため、些末な問題だとも言える。

ライブラリ群が事前にビルドされることはデメリットにもなり得る。開発環境を整える際に大量のライブラリ群のビルドを待たなければならないし、 git switchした際にライブラリのバージョンが異なっている場合にも、ビルドし直さなければならない。これは、Carthage/Build ディレクトリをGitの管理下に含めることで対処可能ではある。しかし、Carthage/Build のサイズが大きいためGitリポジトリの肥大化が加速してしまい、別の問題に直面することになる。

さて、ここまで色々と書いてきたが最も大きなデメリットは、Xcode12からCarthageがまともに使えなくなってしまったことだ。この問題は公式から案内されているWorkaroundを利用することで対応が可能ではある。

しかし、2020年9月16日にXcode12 GMが提供されてから3ヶ月が経った今でもこの問題は解決されていないため、対応を待ち続けるのは良策ではないだろう。

このWorkaround問題に関係しているが、未だにCarthageはXCFrameworksに対応できていない点もデメリットの一つである。XCFrameworksはWWDC2019で発表された仕組みであり、当時は「これをCarthageがサポートしたら copy-frameworksが不要になる!」と言われていた。優秀なOSSコミッター各位が揃っている中で未だに対応していないということは、非常に大きく困難な壁があることは想像に難くない。なお、CocoaPodsとSwiftPMはXCFrameworksに対応している。

SwiftPM

■メリット

Xcodeをインストールすればすぐに使える。サードパーティツールを導入する必要が無く、Xcodeを起動して待っているだけでコードを書き始められる点が最大のメリットだろう。CLIツールを利用しなければ、セットアップスクリプトも不要になる。

iOSアプリを開発をしたいのにRubyやHomebrewを利用してサードパーティツールを入れ、そのツールをセットアップするために、setup.shMakefileに細々と設定を書く必要がなくなるのは非常に心地が良い。README.md# SETUP 項には

$ xed .

とだけ書けば良い。

■デメリット

SwiftPMではXcodeでProjectが開かれると、管理しているライブラリ群の更新を確認する。これはAndroidのGradleに似た挙動ではあるが、地味に時間がかかるため若干不便である。もちろん利用するライブラリが増えると、この待ち時間は長くなっていく。

もっとも不便さを感じるのは、SwiftPMではライブラリをStatic Frameworkとして扱う点である。このため、例えばライブラリAがアプリ本体とWidgetKitの両方で使われていると、エラーになってビルドができない。この問題を解決するためには、1度自作のFrameworkに包んでDynamic Frameworkの様に振る舞わせる必要がある。

この問題は、Embedded Frameworkでも発生する。なお、筆者は試していないがEmbedded Frameworkをprojectとして管理し、それぞれのproject毎にSwiftPMの設定をすることで回避することが可能かもしれない。

Firebase iOS SDKやRxSwiftを利用した際にも問題がある。Firebase iOS SDKのSwiftPM対応はまだbetaであるため、Workaroundを利用しないとAppStore Connectへのバイナリアップロードができない。

If you're using FirebaseAnalytics, Xcode 12.0, and have an issue with device installation or archive uploading, see the workaround at https://github.com/firebase/firebase-ios-sdk/issues/6472#issuecomment-694449182.

RxSwiftの場合はRxTestを利用している場合にリンクエラーになるバグがある。

[SR-12303] Swift Package Manager package stopped working after Xcode 11.3, Xcode 12, etc. - Swift

これからアプリを書き始めるならCombineを使う手もあるが、すでにRxSwiftとRxTestが導入されているプロジェクトで、Combine移行するコストは無視できない大きさである。

結論

2020年12月25日現在では、CocoaPodsを選択するのがベターだと言える。

しかし、今後CarthageのXCFrameworksサポートや、Firebase iOS SDKのSwiftPM/Carthageサポート完了によって最適解は都度変化するだろう。もし記事公開から数カ月後にこの記事を読んでいる読者がいたら、最新の状況を調べてから判断して欲しい。

CLIツールの管理

話が脇道にそれるが、Swift製のCLIツールのバージョン管理についても書く。CLIツールとしてよく使われるものには、

  • realm/SwiftLint
  • nicklockwood/SwiftFormat
  • mono0926/LicensePlist
  • mac-cain13/R.swift
  • SwiftGen/SwiftGen
  • yonaskolb/XcodeGen

などがある。

まず、Homebrewによるバージョン管理は困難2なため除外する。そのため選択肢としては、SwiftPM, Mint, CocoaPods,cocoapods-binaryがある。

SwiftPMの場合、すでにアプリ側でSwiftPMを利用している場合にダミーファイルを用意する必要があるため若干煩雑である。よって現時点では、Mint, CocoaPods, cocoapods-binaryのいずれかを用いるのがよいだろう。もし、アプリ側のライブラリ管理をCocoaPodsに一元化するのであれば、cocoapods-binaryを利用するのがベターだと考える。

実際にこれらツールを用いてCLIツールを管理する方法については、上述したZennの本でも記載している。ただし、わざわざ購入しなくても検索したほうが無料で最新の情報が得られるだろう。

zenn.dev

感想

私個人としてはCarthage+Mintによって「最初にビルドをしておけば、後は高速にコードを書き続けられる」という状況が得られると考えていたが、昨今の状況を鑑みると難しそうである。


  1. CocoaPodsを引き剥がそうと思いながら作業をしていたら、Xcode12リリース後に状況が一変してしまって厳しい。

  2. HomebrewでもFormula提供者が特別な対応をすることでバージョンを指定した(かのような)インストールが可能だが、全ての過去バージョンがインストールできるかはFormula提供者が対応するかどうかに依存する。

Xcodeのデバッグコンソール画面が勝手に閉じて不便なやつ対策

環境

起こること

ビルドしてシミュレータが起動するたびに、デバッグコンソール画面が閉じてしまう。

f:id:FromAtom:20201217192005p:plain
デバッグコンソールはここのこと

ログを見たいのに、ここが閉じてしまうと見れなくてとても不便だった。

対策

XcodeのPreferencesのBehaviorsタブを開き、Running -> Completesから下記の項目を変更する。

f:id:FromAtom:20201217192207p:plain

変更内容はチェックを外すでも良いし、下記画像のように変更しても良い。

f:id:FromAtom:20201217192900p:plain

もしくはこうすると、毎回勝手にデバッグコンソールが開いてくれる。

f:id:FromAtom:20201222132238p:plain

謝辞

この対応方法は@k_katsumiさんに教えていただきました。ありがとうございました。

2020年に新しく始めたこと、諦めたこと。

新しく始めたこと

今年、30歳になりまして、区切りが良いので色々と新しいことを始めることにしました。正直、始めすぎました。

テックリード

昨年末ぐらいからテックリードというのをやっていて、新しい肩書が増え、仕事が増えました。まぁ本業のことはどうでもいいでしょう。

副業

「新しいことにチャレンジしたい!」となると、転職をして職を変える手もあるんですが、個人的には「なんで新しいチャレンジしたいだけなのに、転職なんてクソ面倒な事せなあかんねん。」と思っていました。あと、基本的に自分は飽き性なので、どうせ転職しても次の仕事も2年ぐらいで飽きてしまうはずです。

だったらもう個人事業主として開業して、面白そうなサービスやアプリを作っている会社さんを手伝ったほうが良いじゃんと思って副業をはじめました。 ありがたいことに、手伝いたいと思っていた会社さんと業務委託契約を結ぶことができ、楽しく働くことができています。

本業とは異なったことができるので楽しいです。また「自分、食いっぱぐれることは無さそうだな」という感覚を得られたのが特に良かったです。会社で働いていると「社内では役に立つけど、実は世間では無能なのでは……?」という不安に襲われることがあるのですが、それが解消されて心穏やかに暮らせています。

あと、単純に労働時間が増えるので収入が増えて嬉しい(税金から目をそらす)。

Podcast

よく弟と通話しながらゲームをしているんですが、「この会話を録音したら面白いのでは?」と思い、Podcastをはじめました。

弟とひたすら「これがいい」だの「あれがうまい」だのという話をしています。結構楽しくて地味に続いていますが、最近はGhost of Tsushimaの冥人奇譚で忙しいので、収録できてません。

個人アプリ開発

自分の中で「iOSアプリエンジニアを名乗っているのに、個人でiOSアプリをリリースしていないのはいかがなものか?」みたいなコンプレックスがずっとありました。もちろん他の人にそんなことは思って無くて、自分自身に対してだけです。

エンジニアだからといって、全員がOSS活動をして、本を書き、勉強会に積極的に参加し、アプリやツールをリリースしているわけでないですからね。インターネットを眺めていると、様々な人間の成果が融合されて架空の超人エンジニアが脳内に生まれてしまいがちです。

それはさておき、弊社には個人アプリ開発勢が結構居て、「いいなー、自分も個人アプリ開発者になりたい!」という気持ちが強くなってきたので、アプリを作ってリリースしました。

Memoliaという箇条書きでメモが書きやすいアプリです。実装からデザインまで自分一人でやりました。アプリの開発よりも、Appleへの審査提出周りの方が面倒でした。

使わない人は全然使わないし、使う人はめっちゃ使うアプリかと思いますが、無料なのでぜひ一度お試し下さい。現状、広告もなくて自分は1円も儲からないアプリとなっております。

あきらめたこと

色々始めるということは、なにかを諦めないといけません。1日は24時間ですからね。

丁寧な夕飯

COVID-19の影響でリモートワークになり、毎食自炊ができるようになりました。しかし、夕飯を作るというのは結構時間がかかるものです、買い出しに行って料理をして食べて片付ける。それだけで2時間ぐらいは吹き飛んでしまいます。

なかなかに時間がなくなってしまって困ったので、スーパーのサラダ、サラダチキン、お刺身を日によって違う味のものを買ってきて済ませるようにしました。

料理はもともと好きなので、丁寧な料理は土日に楽しむこととしました。

まとめ

2020年、全世界的に激動な1年となりましたが、個人的にも新しいことを色々と始めて激動な1年となりました。

めちゃくちゃ当然ですが、新しいことを複数一気に始めないほうが良いですね。やりたいことがたくさんあるのに、時間が全く足りなくなって、もやもやした日々を過ごすことになってしまいます。

今年は発散したので、来年は収束させる年にできると良いなぁと思っています。ずっと忙しすぎて記憶が何もねぇ。

SwiftUIで表示したModalをコードからdismissしたい

やりたいこと

f:id:FromAtom:20201107232000g:plain

いわゆる dismiss(animated:completion:) をしたい。

コード

struct ContentView: View {
    @State private var showModal = false

    var body: some View {
        Button("モーダルを開く") {
            showModal.toggle()
        }.sheet(isPresented: $showModal) {
            ModalView()
        }
    }
}

struct ModalView: View {
    @Environment(\.presentationMode) private var presentationMode

    var body: some View {
        Button("閉じる") {
            presentationMode.wrappedValue.dismiss()
        }
    }
}

カービィカフェ TOKYOに行ってきた。

f:id:FromAtom:20201102131619j:plain

ソラマチにあるカービィカフェ TOKYOに行ってきた。

店内の様子

店内に入ると、でっかいウィスピーウッズがいる。

f:id:FromAtom:20201102133610j:plain

ウィスピーウッズの根本には、所々にカービィの置物が設置されていて可愛かった。

f:id:FromAtom:20201102133622j:plainf:id:FromAtom:20201102143041j:plain

店内もおしゃれに可愛くカービィ要素で飾り付けてあって大変いい。

f:id:FromAtom:20201102144531j:plain

ちょこんとカービィがいる。

f:id:FromAtom:20201102142926j:plainf:id:FromAtom:20201102142933j:plain

完全予約制かつ飛び石連休の平日部分で行ったので、結構空いていた。

ご飯の様子

カービィバーガー&ミートパスタ 温野菜のせ』を食べた。こういったコラボカフェ系なご飯、スーベニア(お皿とかコースターとか)が主で食物は添え物になって味とか二の次なイメージだったけど、普通に美味しくて良かった。

f:id:FromAtom:20201102134121j:plain

パスタもアルデンテな感じだし、ハンバーガーのパティもペラペラしておらず「ハンバーグやん」って厚みで食べごたえがあった。

ワドルディがすやりしているオムライスも可愛い美味しかった。チーズの上にシートが載ってるタイプじゃないので、顔がフニャクチャにならなくて良い。

f:id:FromAtom:20201103172854j:plain

勢いでピザも頼んだ。このピザは生地がパリモチで、下手なデリバリーピザより格段に美味しい。おまけつきピザを頼むと、この木のプレートももらえるけど、家では使わなさそうだったのでやめておいた。

f:id:FromAtom:20201103172949j:plain

ドリンクたち。アートオレは滲んでしまうので、素早く写真を撮らないといけない。

f:id:FromAtom:20201102134048j:plainf:id:FromAtom:20201103180403j:plain

これも、カフェオレとして普通に美味しくてよかった。カービィカフェ第2章の時に、コーヒーがコンセプトだったのでその成果で美味しいのだと思う。

料理、すごい待たされるということもなく、普通の提供時間で出てくるし、追加注文も気軽にできてよかった。

グッズ

左下のお皿と右上のマグカップとコースターは、料理についてきたもの。

f:id:FromAtom:20201102191317j:plain

うっかり大量に散財してしまったけど、かわいいからいいや。

まとめ

ごはんも美味しかったし、グッズもいい感じのものが多くて満足した。超カービィファンというわけじゃないけれど、良いクリエイティブを見るとアガるので、良い体験ができた。

"dyld: Symbol not found: _OBJC_CLASS_$_RPSystemBroadcastPickerView" 対策

環境

  • Xcode 12.0.1
  • Apple Swift version 5.3 (swiftlang-1200.0.29.2 clang-1200.0.30.1)
  • iOS Deployment Target 12.0

状況

  • Deployment Targetを11.0から12.0にあげた
  • ビルドは通る
  • iOS 12系のシミュレータで起動するとアプリ起動時にクラッシュする
  • iOS 14系のシミュレータだと起動する
  • dyld: Symbol not found: _OBJC_CLASS_$_RPSystemBroadcastPickerView と表示される
  • RPSystemBroadcastPickerView はReplayKitに含まれるもの
  • RPSystemBroadcastPickerView を使っている箇所を全てコメントアウトするとクラッシュしない

対策

  • Generalタブを開く
  • Frameworks, Libraries, and Embedded ContentにReplayKitが無いなら追加する
    • Do No Embedにする
  • Build Phasesタブを開く
  • Link Binary With Librariesを開く
  • ReplayKitを "Optional" にする

Extensionもある場合は、同様の作業をExtension側でも行う。