Xcode 11で導入されるxcframeworkのディレクトリ構造

WWDC19でお目見えしたXcode 11ですが、ライブラリ・フレームワークの配布形態として .xcframework という新しいフォーマットが導入されています。

https://developer.apple.com/documentation/xcode_release_notes/xcode_11_beta_release_notes/#3318305

XCFrameworks make it possible to bundle a binary framework or library for multiple platforms —including iOS devices, iOS simulators, and UIKit for Mac — into a single distributable .xcframework bundle that your developers can use within their own applications. An .xcframework bundle can be added to an Xcode target’s Link Libraries phase and Xcode uses the right platform’s version of the included framework or library at build time. Creation of XCFrameworks is supported from the command line using xcodebuild -create-xcframework. Frameworks or libraries bundled in an XCFramework should be built with the Build Libraries for Distribution build setting set to YES. (49948269)

このフォーマットの導入は、リリースノートにも書かれているようにUIKit for Mac(Project Catalyst)と関係していそうです。それだけではなく、Swift 5.1でのModule Stability*1サポートによる、Swiftで書かれたフレームワークのバイナリ配布とも関連した動きに見えます。

これまでは、各種SDKなどでiOS用のフレームワーク.framework形式)を配布する場合、iOSシミュレーター用のframeworkと、iOSバイス用のframeworkをそれぞれビルドしてから、frameworkのバンドル内に含まれるバイナリをlipoコマンドで結合して1つのframeworkにする、という手段が取られていました。Carthageでframeworkをビルドする時もこうした処理が行われています。

ただしこの方法には少し問題があり、iOSアプリをApp Storeに提出する際のバリデーションで、アプリに含まれるframeworkのバイナリにシミュレーター用のCPUアーキテクチャーのスライスが含まれているとバリデーションで蹴られてしまいます。そのためストア提出用のリリースビルドを行う際には、lipoコマンドでシミュレーター用のスライスを取り除く必要があります。Carthageではこの問題の対処として、アプリにframeworkを組み込む際にcarthage copy-frameworksコマンドを使うようになっています。

一方、新しい.xcframework形式では、複数の.frameworkを1つのバンドル(ディレクトリ)にまとめる構造となっています。この構造では、アプリをデバイス向けにビルドする時にはxcframework内のデバイス用のframework、シミュレーター向けにビルドする時にはシミュレーター用のframeworkを使うことができるので、ストア提出用のアプリにシミュレーター用のスライスが含まれてしまうということが起きなくなります。Carthageでもxcframeworkがサポートされると、copy-frameworksコマンドを使用する必要がなくなることが期待できます*2

実際に.xcframeworkを生成してディレクトリ構造を確認してみました。シミュレーター用・デバイス用・UIKit for Mac用のframeworkは先にビルドした上で、xcframeworkの生成は次のコマンドで行えます:

$ xcodebuild -create-framework \
    -framework iphonesimulator/Himotoki.framework \
    -framework iphoneos/Himotoki.framework \
    -framework uikitformac/Himotoki.framework \
    -output Himotoki.xcframework

ディレクトリ構造は次のようになっていました。

$ tree Himotoki.xcframework
Himotoki.xcframework
├── Info.plist
├── ios-armv7_arm64
│   └── Himotoki.framework
│       ├── Headers
│       │   ├── Himotoki-Swift.h
│       │   └── Himotoki.h
│       ├── Himotoki
│       ├── Info.plist
│       └── Modules
│           ├── Himotoki.swiftmodule
│           │   ├── arm.swiftdoc
│           │   ├── arm.swiftinterface
│           │   ├── arm.swiftmodule
│           │   ├── arm64-apple-ios.swiftdoc
│           │   ├── arm64-apple-ios.swiftinterface
│           │   ├── arm64-apple-ios.swiftmodule
│           │   ├── arm64.swiftdoc
│           │   ├── arm64.swiftinterface
│           │   ├── arm64.swiftmodule
│           │   ├── armv7-apple-ios.swiftdoc
│           │   ├── armv7-apple-ios.swiftinterface
│           │   ├── armv7-apple-ios.swiftmodule
│           │   ├── armv7.swiftdoc
│           │   ├── armv7.swiftinterface
│           │   └── armv7.swiftmodule
│           └── module.modulemap
├── ios-i386_x86_64-simulator
│   └── Himotoki.framework
│       ├── Headers
│       │   ├── Himotoki-Swift.h
│       │   └── Himotoki.h
│       ├── Himotoki
│       ├── Info.plist
│       ├── Modules
│       │   ├── Himotoki.swiftmodule
│       │   │   ├── i386-apple-ios-simulator.swiftdoc
│       │   │   ├── i386-apple-ios-simulator.swiftinterface
│       │   │   ├── i386-apple-ios-simulator.swiftmodule
│       │   │   ├── i386.swiftdoc
│       │   │   ├── i386.swiftinterface
│       │   │   ├── i386.swiftmodule
│       │   │   ├── x86_64-apple-ios-simulator.swiftdoc
│       │   │   ├── x86_64-apple-ios-simulator.swiftinterface
│       │   │   ├── x86_64-apple-ios-simulator.swiftmodule
│       │   │   ├── x86_64.swiftdoc
│       │   │   ├── x86_64.swiftinterface
│       │   │   └── x86_64.swiftmodule
│       │   └── module.modulemap
│       └── _CodeSignature
│           └── CodeResources
└── ios-x86_64-uikitformac
    └── Himotoki.framework
        ├── Headers -> Versions/Current/Headers
        ├── Himotoki -> Versions/Current/Himotoki
        ├── Modules -> Versions/Current/Modules
        ├── Resources -> Versions/Current/Resources
        └── Versions
            ├── A
            │   ├── Headers
            │   │   ├── Himotoki-Swift.h
            │   │   └── Himotoki.h
            │   ├── Himotoki
            │   ├── Modules
            │   │   ├── Himotoki.swiftmodule
            │   │   │   ├── x86_64-apple-ios-macabi.swiftdoc
            │   │   │   ├── x86_64-apple-ios-macabi.swiftinterface
            │   │   │   ├── x86_64-apple-ios-macabi.swiftmodule
            │   │   │   ├── x86_64.swiftdoc
            │   │   │   ├── x86_64.swiftinterface
            │   │   │   └── x86_64.swiftmodule
            │   │   └── module.modulemap
            │   └── Resources
            │       └── Info.plist
            └── Current -> A

ios-armv7_arm64にデバイス用のframeworkが、ios-x86_64-simulatorにシミュレーター用のframeworkが、そしてios-x86_64-uikitformacにUIKit for Mac用のframeworkが含まれていることが分かります。

またトップレベルのInfo.plistの内容は次のようになっていました。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>AvailableLibraries</key>
    <array>
        <dict>
            <key>LibraryIdentifier</key>
            <string>ios-x86_64-uikitformac</string>
            <key>LibraryPath</key>
            <string>Himotoki.framework</string>
            <key>SupportedArchitectures</key>
            <array>
                <string>x86_64</string>
            </array>
            <key>SupportedPlatform</key>
            <string>ios</string>
            <key>SupportedPlatformVariant</key>
            <string>uikitformac</string>
        </dict>
        <dict>
            <key>LibraryIdentifier</key>
            <string>ios-armv7_arm64</string>
            <key>LibraryPath</key>
            <string>Himotoki.framework</string>
            <key>SupportedArchitectures</key>
            <array>
                <string>armv7</string>
                <string>arm64</string>
            </array>
            <key>SupportedPlatform</key>
            <string>ios</string>
        </dict>
        <dict>
            <key>LibraryIdentifier</key>
            <string>ios-i386_x86_64-simulator</string>
            <key>LibraryPath</key>
            <string>Himotoki.framework</string>
            <key>SupportedArchitectures</key>
            <array>
                <string>i386</string>
                <string>x86_64</string>
            </array>
            <key>SupportedPlatform</key>
            <string>ios</string>
            <key>SupportedPlatformVariant</key>
            <string>simulator</string>
        </dict>
    </array>
    <key>CFBundlePackageType</key>
    <string>XFWK</string>
    <key>XCFrameworkFormatVersion</key>
    <string>1.0</string>
</dict>
</plist>

xcodebuild -create-xcframeworkコマンドの-frameworkオプションの数を増やすことで、ここにmacOS、tvOS、watchOS用のビルドも追加することができるはずです。

また.frameworkだけでなく、.aのstatic libraryや.dylibのdynamic libraryとそれ用のヘッダーをバンドルすることもできるようになっています。コマンドのヘルプの出力も載せておきます。

$ xcodebuild -create-xcframework -help
OVERVIEW: Utility for packaging multiple build configurations of a given library or framework into a single xcframework.

USAGE:
xcodebuild -create-xcframework -framework <path> [-framework <path>...] -output <path>
xcodebuild -create-xcframework -library <path> [-headers <path>] [-library <path> [-headers <path>]...] -output <path>

OPTIONS:
-framework <path>         Adds a framework from the given <path>.
-library <path>           Adds a static or dynamic library from the given <path>.
-headers <path>           Adds the headers from the given <path>. Only applicable with -library.
-output <path>            The <path> to write the xcframework to.
-help                     Show this help content.

今後の各種ツールやSDKでのサポートに期待したいですね。

EpoxyでのモデルのID重複時の挙動

LinuxのSwift 5.1で、NSNumberなどがComparableになってDarwinとソース互換がなくなる事例

というバグレポートを起票しました。

原因となったswift-corelibs-foundationのPRがこちらです。

例として、現在のDarwinmacOSiOSなど)でのNSNumberのProtocol準拠は次のとおりです。

NSNumber - Relationships
NSNumber - Relationships

どちらかと言うと、NSNumberNSStringなどがComparableになるのはポジティブなので、DarwinのFoundationでもComparable準拠になると嬉しいですね。

Java Cryptography Extension(JCE)と、AndroidのBouncy Castleと、Spongy Castle

社内のSlackでSpongy Castleという名前を聞いて、知らなかったので少し調べてみました。

  • AndroidJavaJava Cryptography Extension(JCE)の仕組みに則っている
  • AndroidではJCEのプロバイダーとしてBouncy Castleが提供されている
  • Androidに乗っているBouncy Castleは、スリムダウン・カスタムしたバージョンが乗っていて名前空間はそのまま
  • なので本来のBouncy Castleの新バージョンなどを入れようとすると衝突してしまって問題になる
  • なので中身はBouncy Castleだけど、名前空間を分けたのがSpongy Castle

この辺を見ました:

CocoaPods 1.7.0からSpecsのソースにCDNを使えるようになります

先日リリースされたCocoaPods 1.7.0.beta.1で、実験的機能としてSpecsのソース(パッケージ定義であるpodspecファイルのセントラルリポジトリ)にCDNを利用できるようになりました。

今までは、ソースとして https://github.com/CocoaPods/Specs のGitリポジトリが使用されており、初期セットアップ時のクローンや、日々のアップデートに結構な時間が掛かっていましたが、ソースのCDN化によりこうした時間が削減できるようになります(必要なデータだけダウンロードされてローカルにキャッシュもされる)。

また、ライブラリの最新バージョンを使おうとしたらローカルのSpecsが更新されていなくて、その都度$ pod repo update(もしくは$ pod install --repo-updateのオプション付き実行)をしないといけない、というのもなくなるはずです。

Beta期間中の結果によっては、1.7.0の正式リリース時点でCDNソースがデフォルトになるようです。

Depending on the results and stability, we hope that starting with 1.7.0, CocoaPods will no longer require users to clone the master specs repo in order to get started.

この機能を有効にするには、1.7.0.beta.1にアップデートし、プロジェクトのPodfileの先頭に次の1行を足すだけです。

# source 'https://github.com/CocoaPods/Specs' 既存のこの行があれば削除かコメントアウトしましょう
source 'https://cdn.jsdelivr.net/cocoa/'

ということでどんどん試していきましょう!💪

RawValueがIntのenumを何かのインデックスとして使いたい時はhashValueじゃなくてrawValueを使いましょう

そうそう遭遇はしないと思いつつ、Swift 4.2になるとハッシュシードのランダム化により問題が起き得ます。

  • RawValueがIntのenumであっても、rawValueがそのままhashValueではなくなる
    • Swift 4.1までは、rawValue == hashValueという挙動だった(実装依存
  • 実行ごとにハッシュ値は変わる

hashValueは「ダメ。ゼッタイ。

The standard library now uses a high-quality, randomly seeded, universal hash function, represented by the new Hasher struct. Random seeding varies the result of hashValue on each execution of a program, improving the reliability of the standard library's hashed collections, Set and Dictionary. In particular, random seeding enables better protection against (accidental or deliberate) hash-flooding attacks.

https://developer.apple.com/documentation/xcode_release_notes/xcode_10_release_notes/swift_4_2_release_notes_for_xcode_10

CocoaPodsにtest_specというのがあるのを今更ながら知った

CocoaPods 1.6.0 Betaのブログ記事(2018年8月)を今更ながら読んでいたら、s.test_specという定義ができるのを遅まきながら知った。

Pod::Spec.new do |s|
  # ... rest of root spec entries go here

  # Unit Test Sources - Those do not require an app host to run. 
  # They also require 'OCMock' dependency.
  s.test_spec 'Tests' do |test_spec|
    test_spec.source_files = 'Tests/**/*.{h,m}'
    test_spec.dependency 'OCMock'
  end

  # SnapShot Tests Sources - Those *do* require an app host to run.
  s.test_spec 'SnapshotTests' do |test_spec|
    test_spec.requires_app_host = true
    test_spec.source_files = 'SnapshotTests/**/*.{h,m}'
  end

おまけで、cocoapods-generateという、podspecからXcodeのプロジェクトを生成してくれるプラグインも紹介されていてよさそうだった。SwiftPM(Swift Package Manager)の$ swift package generate-xcodeprojと一緒の感じですね。

Xcodeのプロジェクト生成だとXcodeGenという、YamlJSONの定義から生成するツールもある。