The Pragmatic Ball boy

iOSを中心にやってる万年球拾いの老害エンジニアメモ

Invalid bitcode version (Producer: 'xxx' Reader: 'yyy')になったときの対処方法

iOSアプリのreleaseビルドを行った際にlinkerでinvalid bitcode versionで怒られることがあります。

ld: could not reparse object file in bitcode bundle: 'Invalid bitcode version (Producer: '1103.0.32.62.0_0' Reader: '1100.0.33.17_0')', using libLTO version 'LLVM version 11.0.0, (clang-1100.0.33.17)' for architecture arm64

上記の場合だと、雑に解釈するとclang1100.0.33.17_0使っているんだけどclang1103.0.32.62.0_0でビルドされたbinaryが混じっててbitcodeのバージョン合わないから駄目ですという意味になります。

大抵の場合、原因は使っている3rd partyのライブラリが同梱しているbinaryのビルド時に使ったclangのバージョンが合ってないということが問題です。

対策としては、原因となっているライブラリを以前使っていたバージョンに戻すか、原因となっているライブラリの使っているclangバージョンに合わせてXcodeのバージョンをあげるかになります。

後者の場合、clang1103.0.32.62.0_0と言われてもどのXcode?となってしまいます。

その際は以下のgistにXcodeとclangのバージョンの一覧がのっているのでここでProducerのほうに書かれているバージョンにXcodeを探しましょう

Xcode clang version record · GitHub

npmの特定のパッケージのみ最新にアップデートする

npm i -g npm-check-updatesを入れておくと便利

npm-check-updatesをインストール

$ npm i -g npm-check-updates

特定のパッケージを最新にする

$ncu -u styled-components

これを実行するとpackage.jsonが更新される

--- a/package.json
+++ b/package.json
@@ -18,7 +18,7 @@
     "next": "^9.1.6",
     "react": "^16.12.0",
     "react-dom": "^16.12.0",
-    "styled-components": "^4.4.1"
+    "styled-components": "^5.0.0"
   },
   "devDependencies": {
     "@types/node": "^13.1.2",

あとはnpm installするだけ

2019振り返り

2019年の振り返りです。

仕事

去年の5月からコネヒトに出向していましたが、今年の5月にSupershipからコネヒトに転籍となりました。

アウトプット

まだちゃんとつくりきってはいないんですが、変更のあったファイルにのみlintとかformatterを適用するSwiftのライブラリを一応公開しました。

GitHub - yanamura/lint-staged

久しぶりに外部の勉強会でLTしました

コネヒトの開発者ブログを書いたり

tech.connehito.com

tech.connehito.com

tech.connehito.com

tech.connehito.com

コネヒトのアドベントカレンダーを書いたりしました。

yanamura.hatenablog.com

しょぼい変更ですが、OSSにコントリビュートしました

インプット

11月にScrum Inc.認定資格スクラムマスター研修を受けて試験もパスして、Licensed Scrum Masterになりました。

今年は人材育成やチームリーダーのロールもあったりしたのでマネジメント系が多かったです。

CAREER SKILLS ソフトウェア開発者の完全キャリアガイド

HIGH OUTPUT MANAGEMENT(ハイアウトプット マネジメント) 人を育て、成果を最大にするマネジメント

OKR(オーケーアール)

エラスティックリーダーシップ ―自己組織化チームの育て方

読んだ中でもエラスティックリーダーシップが自分の思っていたあるべきリーダー像に近くてとても共感がわきました。

久しぶりにリーダー的なことやプロジェクトのマネジメントに近い感じのこともしていたので、以前読んだ本を読み返したりと復習も多かったなと思いました。一回読んだら忘れない技術を身に着けたいです。

健康

納豆を食べるのを習慣としたのと、食べても太らない体質なのでこれはもしかしたら栄養の吸収がよくないのではないかという仮説のもとサプリメントを摂取してみたところ、全然風邪をひかなくなりました。 ただ年初にインフルエンザの家庭内パンデミックに巻き込まれてインフルエンザにはかかってしまいました。

プライベート

子供が三歳になりました。 相変わらず子育てが最優先で対応中です。

まとめ

振り返ってみると微妙な一年だったので今年はちゃんとOKRたててやりたいなとおもいます

iOS開発の年末大掃除

この記事はこの記事はコネヒト Advent Calendar 2019 18日目の記事です。

開発を重ねるとプロジェクト内についつい使っていないコードやリソースが残ってしまったり、開発環境にゴミが残ったりします。 今回はそれらの掃除に役立つツールを紹介します。

f:id:yanamura:20191217160324p:plain

使っていないコード検出ツール

unused.rb

unused.rb

rubyのscriptを実行するだけで使ってない変数、関数を検出できます。

  • 利点
    • 実行が速い。お手軽
  • 欠点
    • 除外したいディレクトリを選んだりといった細かい設定ができない
    • 文字列を検索して使われているかどうかチェックしているだけなので、delegateなどは誤検出される。

periphery

periphery

Swift製の使ってないコードを検出するツール

  • 利点
    • ただ文字列を検索してるのではなくSourceKitを使ってるのでご検出が少なさそう
  • 欠点
    • 結構遅い
    • TypeAliasなどが誤検出されたりとまだ発展途上
    • Xcode11対応版がリリースされていないので今年の大掃除に使うのは厳しそう(xcode11というブランチをとってきて自力でビルドすると一応動きはしますが誤検出しまくるのでオススメできません!)

使っていない画像、リソース検出ツール

FengNiao

FengNiao

CLIで使っていない画像などのリソースを検出ツール

  • 利点
    • optionで色々指定できるので快適に使える

LSUnusedResources

LSUnusedResources

GUIで使っていない画像などのリソースを検出ツール

  • 利点
    • GUIでポチポチするだけなので誰でも使える

開発環境のゴミ掃除

DevCleaner

xcode-dev-cleaner

Xcodeの不要なデータを消すGUIのツール

  • 利点
    • XcodeのDevice Support, Derived Data, Archive, Logを一気に消すのに便利
    • GUIでポチポチするだけなので誰でも使える

手動だけどやっておきたい

おまけでお掃除ついでにやっておきたいことも書いておきます!

warningの修正

Xcodeのissue navigatorに出てるwarningを直しましょう!

f:id:yanamura:20191216192445p:plain

並び替え

ファイルが順番に並んでない場合はSort by Nameしてやると見やすくなって良いかなと思います。

f:id:yanamura:20191216192430p:plain

typoの検出

Xcode11からSpell Check機能がついてるのでONにしておくとよいです。 ただ、どこでひっかかってるかは一括で見れたりはしないです。

f:id:yanamura:20191216192338p:plain

一括でやりたい場合は有料ですがAppCodeのinspect機能を使うとよいです。

最後に

もうすぐAppStoreの審査は23~27まで休みの期間でiOSエンジニアも開発に一区切りつきやすい時期ですね。 こういった時期にこれらのツールをつかってお掃除してみてください!

そしてコネヒトではエンジニアを募集しておりますので少しでも興味のある人はお話だけでも聞きにきてください! www.wantedly.com

peripheryの特定ブランチをビルドしてつかう

Swiftの使ってないコードを消すperipheryとツールがありますが、 Xcode11の対応版がまだマージされていなくて使えません。

xcode11というブランチがあってそれを使うと一応使うことができます。ただビルドしないといけないのでそのビルド方法です。

  1. peripheryをgit cloneしてくる
  2. cloneしてきたリポジトリのrootディレクトリでmake
  3. .build/x86_64-apple-macosx 以下にperipheryというbinaryができるのでこれを使う

というふうにただmakeするだけでした。

MacBook Proの電源アダプタ

MBPを会社と家で使っている場合に、ただでさえMBPが重いのに電源アダプタまで持ち帰りたくないですよね。

ですが、純正の電源アダプタ(85W)を買うと8000円もするわけです・・

そこで色々探してよさげだったのがAnker PowerPort Speed PD 60です。

出力は60Wと私の使っているMBP15インチでは少々足りないですがこれを購入して使っています。

3ヶ月ほどMBP15インチ 2018のフルスペックのマシンでつかっていますが、これといった不満はありません。 当方はiOSエンジニアなのでXcodeでCPUぶん回してビルドとかしてますが、60Wでも充電されます。

値段も純正の半額以下とお手軽なのでおすすめです

SwiftUIのViewで条件によってViewを出し分ける方法

potatotips #64でLTした内容です。


SwiftUIのViewで条件によってViewをだしわけたいことがたまにあるかと思います。

SwiftUIでこのように条件によってViewを出し分けるようなコードを書いてみます。

var body: some View {
    if imageName.isEmpty {
        return Text("no image")
    } else {
        return Image(imageName)
    }
}

そうするとこのようなコンパイルエラーになってしまいます。

! Function declares an opaque return type, but the return statements in its body do not have matching underlying types

なぜかというとbodyの戻り値の型がOpaque Result Typeなので、条件によってTextを返したりImageを返すといった複数の型を返すことができないからです。

ちなみにどちらの条件でも同じ型を返す場合はOKです。

var body: some View {
    if imageName.isEmpty {
        return Text("no image")
    } else {
        return Text(imageName)
    }
}

解決方法

解決方法はざっくり3つあります

1. Group / HStack / VStack / ZStack

1つはGroupやZStackなどをつかってwrapしてやる方法

var body: some View {
    Group {
        if imageName.isEmpty {
            Text("no image")
        } else {
            Image(imageName)
        }
    }
}

2. @ViewBuilder

もう1つは@ViewBuilderアノテーションをつける方法

@ViewBuilder
var body: some View {
    if imageName.isEmpty {
        Text("no image")
    } else {
        Image(imageName)
    }
}

3. ViewBuilder.buildEither()

最後はViewBuilder.buildEither()を使う方法です。(こちらの場合は3項演算子にする必要があります)

var body: some View {
    imageName.isEmpty
        ? ViewBuilder.buildEither(first: Text("no image"))
        : ViewBuilder.buildEither(second: Image(imageName))
}

※この3つの中でどれが一番よいかは最後に紹介します

以上3つの書き方を紹介しましたが、どれもViewBuilderの力によるものです。

なぜViewBuilderを使うと条件分岐が使えるのでしょうか?

ViewBuilderを使うと条件分岐が使える理由

ViewBuilderの仕組みを説明すると ViewBuilderはfunction builderを用いて実装されています。

このfunction builderはコンパイル時に構文解析をしてif文のchainだったらbuildEitherというfunctionを使うというふうになっています。

つまりどういうことかというと

こういうコードをコンパイルすると

@ViewBuilder
var body: some View {
    if imageName.isEmpty {
        Text("Hello")
    } else {
        Image(imageName)
    }
}

このような感じにコンパイル時に解釈されるということです。

var body: some View {
    imageName.isEmpty
        ? ViewBuilder.buildEither(first: Text("no image"))
        : ViewBuilder.buildEither(second: Image(imageName))
}

ここでViewBuilderのbuildEitherの定義がどうなっているか確認してみます

extension ViewBuilder {
    public static func buildEither<TrueContent, FalseContent>(first: TrueContent) -> _ConditionalContent<TrueContent, FalseContent> where TrueContent : View, FalseContent : View

    public static func buildEither<TrueContent, FalseContent>(second: FalseContent) -> _ConditionalContent<TrueContent, FalseContent> where TrueContent : View, FalseContent : View
}

このように_ConditionalContent<TrueContent, FalseContent> を返すようになっていました。

確認のためにこのようなコードを書いて実行してみると

@ViewBuilder
var body: some View {
    if imageName.isEmpty {
        Text("Hello")
    } else {
        Image(imageName)
    }
}

戻り値の型は_ConditionalContent<Text, Image> になっていました。

このようにViewBuilderが条件分岐を_ConditionalContent<,>に変換してくれていたので、bodyの戻り値がopaque result typeであってもコンパイルが通るようになっていたわけです。

現時点で解決できない条件分岐

しかし、現時点(2019年8月)ではViewBuilderでは解決できない問題があります。

このようにswitch文やif let, if caseのようなif else以外の条件分岐を使うとコンパイルに通りません。(ちなみに@ViewBuilderをつかうとコンパイルは通りますがちゃんと動きません)

var body: some View {
    Group {
        switch foo {
        case a:
            Text("a")
        case b:
            Text("b")
        case c:
            Text("c")
        }
    }
}

これを解決するには面倒ですがif elseに書き換えましょう

var body: some View {
    Group {
        if foo == .a {
            Text("a")
        } else if foo == .b {
            Text("b")
        } else if foo == .c {
            Text("c")
        }
    }
}

ですが、どうしてもif else 以外で実装したい場合はAnyViewで、返すViewをwrapしてやると動かすことができます。

var body: some View {
    switch foo {
    case .a:
        return AnyView(Text("a"))
    case .b:
        return AnyView(Text("b"))
    case .c:
        return AnyView(Text("c"))
    }
}

まとめ

ViewBuilderのおかげでView内で条件分岐が使えるということがわかりました。

ただし、現在はif elseのみ対応していて、それ以外のswitch文などは使えません。

しかし、最悪AnyViewを使って型を消してやればif else以外の条件もつかうことができます。

最後に、条件分岐を使う場合は紹介した3つのやり方の中だとGroupやZStackなどでwrapする方法が一番オススメです。理由は@ViewBuilderを使うと利用できないswitch文などを使ってもコンパイルに通ってしまうので間違いに気づきにくいためです。buildEitherは直接使う用途ではないと思うのであえて使わなくて良いかなと思います。

アプリのDark ModeをOFFにする

既存のアプリでいきなりダークモードに対応するのは大変です。 しかしXcode11でビルドするとデフォルトでダークモードが適用されてしまいます。

もちろんダークモードをOFFにすることは可能となっています。

アプリ全体でダークモードをOFFにする方法

Info.plistのUIUserInterfaceStyleLight にしてやります。

 <key>UIUserInterfaceStyle</key>
    <string>Light</string>

UIで設定したい場合はこうします

f:id:yanamura:20190814181522p:plain

デバッグ

ダークモードのデバックはXcodeで簡単に切り替えて確認できるようになっています。

デバッグツールバーにEnvironment Overridesというボタン(トグルスイッチみたいなボタン)が新たに追加されていてこれを使います

f:id:yanamura:20190814182229p:plain

このボタンを押すとこのような画面が表示されます

f:id:yanamura:20190814182458p:plain

Interface StyleのところのトグルスイッチをONにして Dark のほうを選択するとダークモードになります。

f:id:yanamura:20190814182619p:plain

Nimbleでtupleの比較

Nimbleでtupleをequal()で比較してもコンパイルに通りません。

let tuple = (1, 2)
expect(tuple).to(equal((1, 2)))

原因

これはtupleがEquatableではないからです

対策

これはどうしようもないので以下のようにequalを使わずに回避するしかなさそうです

expect(tuple == (1, 2)).to(beTrue())

Add ability to compare tuples · Issue #227 · Quick/Nimble · GitHub

ERROR ITMS-90784

2019/6/28ころから急にAppStoreConnectにipaをアップロードするときに以下のエラーがでるようになった

ERROR ITMS-90784: "Missing bundle name. The Info.plist key CFBundleName is missing or has an empty value in the bundle with bundle identifier ''

読んでそのとおり、info.plistのBundle name(CFBundleName)が未設定だったり空の文字列だった場合に発生するので、info.plistのBundle name(CFBundleName)を設定すればエラーは解消します。

flutter doctorのエラーの解決方法 Verify that all connected devices have been paired with this computer in Xcode

このようなエラーがでてbrew xxxをしても解決しない場合

[!] iOS toolchain - develop for iOS devices (Xcode 10.2.1)
    ✗ Verify that all connected devices have been paired with this computer in Xcode.
      If all devices have been paired, libimobiledevice and ideviceinstaller may require updating.
      To update with Brew, run:
        brew update
        brew uninstall --ignore-dependencies libimobiledevice
        brew uninstall --ignore-dependencies usbmuxd
        brew install --HEAD usbmuxd
        brew unlink usbmuxd
        brew link usbmuxd
        brew install --HEAD libimobiledevice
        brew install ideviceinstaller

MaciPhoneを接続してから、flutter doctorを実行すると消えました

@propertyDelegateと@propertyWrapperの違い

2019/06/25 現在の情報

Swift EvolutionのProperty Wrappersを見ると@propertyWrapper ですが、

AppleのドキュメントのStateを見ると @propertyDelegateになっています。

f:id:yanamura:20190625135622p:plain
state

原因

仕様策定段階で名前が変わった

詳細

ここに書いてあるように最初はproperty delegatesだったのがproperty wrappersに名前が変わったそうです。

The name of the feature has been changed from "property delegates" to "property wrappers" to better communicate how they work and avoid the existing uses of the term "delegate" in the Apple developer community

AppleWWDCに間に合わせるために未fixの段階で実装していれこんじゃったために古い名前になっているようです。

Renamed the value property to wrappedValue to avoid conflicts.

こちらのvalueがwrappedValueに名前が変わる変更もbeta2の段階では反映されておらず、valueにしないとコンパイルに通りません。

6.5インチ用のスクリーンショットの必須化

2019年3月27日からは6.5インチ用のスクリーンショットも必須になったようで、AppStoreConnectで申請時に設定しないと審査に出すときにエラーになるようになっていました。

iOS 12.2 SDKを含むXcode 10.2にアップデートして、Appをビルドしてください。2019年3月27日以降、App Storeに提出されるすべてのiOS Appは、iOS 12.1以降のSDKでビルドされ、iPhone XS Maxまたは12.9インチiPad Pro(第3世代)のオールスクリーンのデザインをサポートする必要があります。

App StoreにiOS Appを提出する - Apple Developer

6.5インチのスクリーンショットは以下のサイズになります。

1242 x 2688 ピクセル(縦向き) 2688 x 1242 ピクセル(横向き)

https://help.apple.com/app-store-connect/?lang=ja-jp#/devd274dd925

DispatchQueue.main.asyncAfter(deadline: .now() + 1.0)はなぜnanosecondではなくsecondになるか

あんまり気にしてる人はいないかもしれないですが、普段何も考えずに DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) を使っているとdeadline: DispatchTime はsecond単位っぽく感じるかもしれないですが、nanosecondです。

DispatchTime

DispatchTime represents a point in time relative to the default clock with nanosecond precision.

なんで1.0足してるのに秒になるかというと演算子オーバーロードされてるんですね・・

public func + (time: DispatchTime, seconds: Double) -> DispatchTime
public func - (time: DispatchTime, seconds: Double) -> DispatchTime

理解して使ってればいいんですが、個人的にはちょっと紛らわしいと思うので DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(1)) のほうがいいかなと思ったりします。

2018振り返り

2018年の振り返りです。

仕事

5月からコネヒトに出向することとなり、またiOSエンジニアのカムバックしママリのiOSアプリを開発しています

アウトプット

今年は去年よりは少し子育てに余裕ができたのでアウトプットを少しすることができました。

gitのhookを楽にするhuskyのSwift版を作って公開したり、

GitHub - yanamura/Captain: makes easy to manage git hooks for written in Swift products

コネヒトマルシェというコネヒトでやっている勉強会でLTしたり、

コネヒトのアドベントカレンダーを書いたりしました。

tech.connehito.com

しょぼい変更ですが、久々にOSSにコントリビュートしました

今更ですがQiitaでも書き始めてみました

yanamura - Qiita

インプット

割と今年は読みたいと思った本が多く、どれも良書でした。

ディープラーニングはやろうと思いつつ全く手付かずで意味もわかってなかったのを2冊読んで少し理解が進みました。

寄る年波に負けないように体のマネジメントも今年から頑張り始めました

プライベート

子供が二歳になりました。 相変わらず子育てが最優先で対応中です。

だいぶほっといても大丈夫な感じにはなってきましたが、最近は添い寝しないと寝ないマンになってしまい、添い寝すると自分が寝てしまって自由時間が・・となってしまうのが悩みです。。

まとめ

ここ2年は初めての子育てということでどれくらい時間がさけるか未知数すぎて 目標すらたてれなかったんですが、今年からはなにかしら目標をたててそれに向けて頑張りたいです!