ヤフー株式会社は、2023年10月1日にLINEヤフー株式会社になりました。LINEヤフー株式会社の新しいブログはこちらです。LINEヤフー Tech Blog

テクノロジー

iOS向けSDKの完全Swift対応と配布形態の刷新 〜 Yahoo!広告のモダン化事例

Yahoo! JAPAN Advent Calendar 2022の3日目の記事です。

こんにちは。Yahoo!広告でiOS版SDK開発を担当している青木(@shoaooki)です。
今回は、iOS向けSDKが抱える課題の共有と、Yahoo!広告SDKで行ったSwift化を円滑に進める上での工夫や刷新版をリリースする上で注意した点についてお話しします。

Yahoo!広告SDKとは

Yahoo!広告のディスプレイ広告をネイティブアプリへ簡単に表示できる、Yahoo! JAPANのサービス内および、社外のパートナーネットワークのアプリへ配布しているSDKです。

このSDKは、広告表示に必要なデータをリクエスト〜取得したり、広告枠のUIを提供する機能を持っています。

Yahoo!広告SDKの機能イメージ

iOSのSDK開発に迫る大きな3つの変化

iOSにおけるSDK開発はXcode 11が登場した2019年から大きく変化しました。これらにより、Swift化対応を進めやすい土壌が整いました。

Module StabilityでSwift言語のSDK開発が現実的に

Xcode 11に含まれているSwift 5.1ではModule Stabilityがサポートされました。
それまではSwift言語(Xcode)のバージョンが異なる環境でビルドしたものをインポートすると、エラーとなっていました。

SDKという立場上Swiftバージョンの異なる複数のサービスアプリで導入してもらう必要があり、Swift言語の採用はこれまで断念していました。
Module Stabilityがサポートされた今ではSwift言語でのSDK開発が現実的になっています。

Swift Package ManagerでSDK導入がスムーズに

Xcode 11では “Swift Package Manager” (以下、”SwiftPM”) も登場しました。
Apple純正のパッケージ管理ツールで、XcodeのGUI上で完結してSDKの導入がスムーズに行えます。
以前まではCocoaPodsやCarthageなどサードパーティのツールを利用していましたが、ようやく純正ツールの登場というところで利用者から対応を求められることが予想されます。

XCFrameworkでバイナリ衝突問題を回避できるように

同じく、Xcode 11ではバイナリフレームワーク配布のための新形式 “XCFramework” も登場しました。

社外利用者は、ヤフー社内ネットワークにあるGitHub Enterpriseへアクセスできないなどの理由でSwiftPMを採用できません。そこでファイル操作でインストール可能なビルド済みのフレームワークファイルも用意する必要がありました。
従来のフレームワーク形式では、iOSシミュレーターや実機それぞれのCPUアーキテクチャのためバイナリもそれぞれ提供する必要があり、lipo コマンドでこれらを結合しFat Binary化し1つのフレームワークとして提供していました。

しかし、Xcode12以上からiPhone実機用とApple Silicon用シミュレーターとでいずれもarm64用バイナリを含むようになったことで、重複が発生し、ビルドに失敗するようになりました。

Apple Silicon用シミュレーターに用意されたarm64を除外してビルドを行うワークアラウンドを導入すれば、この問題は一時的に回避できましたが、このままではせっかくApple Silicon搭載のMacが使える環境であっても、Rosetta 2上でのXcode開発を強いることになってしまいます。
arm64のネイティブ環境で開発できれば、2倍以上ものビルド速度が出ると言われています。

XCFrameworkに対応できれば、このarm64用バイナリの衝突問題を回避できます。

Swift版Yahoo!広告SDK開発PJ

私がYahoo!広告SDKのiOS開発担当になったばかりの時点では、『Objective-C言語100%で開発が行われている』『パッケージ配布もCocoaPodsのみ』『社外向けへの配布もXCFramework形式非対応』という状況でした。
これらの課題を一気に解決させるため、”Swift版Yahoo!広告SDK”として別リポジトリにて開発を行う決断をしました。

Yahoo!広告SDKの刷新内容

コード内の一部をSwift言語にし、徐々に移行することも考えられましたが、言語が混ざることでimport周りの混乱やSwiftの言語機能がフル活用できない懸念もあり断念しました。
また、コード量がそこまで膨大ではなかったり依存するSDKも少ないという背景も踏まえての決定でした。

移植ルールの作成、依存関係の視覚化で一気に100%Swift化

開発仕様の詳細ドキュメントが不足している状況だったため、今あるコードや動作を正として進める必要がありました。
そこで『Objective−C版のロジックをそのままに、Swift言語のいいとこ取りをしながらも、無理なリファクタリングはしない』という方針を立てました。
具体的には、内部のクラス構成や大掛かりなI/F変更は禁止としました。

さらに、Swift言語に慣れていないメンバーも居たため、「どこまで変更すればいいのか?」「何がNGなのか?」を明確にするために、移植例やNG集を作りました。

移植ルール(一部)

これらのルールがあったからこそ、迷いなくスピード感を持って移植を進めることができました。
しかし、ルールは多すぎるとレビューが大変なので、SwiftLint や SwiftFormat といった静的解析ツールも活用し都度ルールを見直しつつ進めました。

また、移植する順番は他クラスへの依存がないクラスから行っていきました。
Graphviz というツールを使って、依存関係を視覚的に表しながら移植担当の割り振りも決めていきました。
戦況がわかりやすく、モチベーションも保ちながら進めることができました。

クラス依存関係図

fastlane上でXCFrameworkを生成できるようにする

XCFrameworkに対応するには、該当のターゲットの Build Options にて Build Libraries for DistributionYes にします。

次に xcodebuild archive を実行し、実機用フレームワークとシミュレーター用フレームワークを作成します。

 xcodebuild archive -archivePath "build/ios.xcarchive" -scheme "YJADSDK" -destination "generic/platform=iOS" -configuration "Release" -workspace "YJADSDK.xcworkspace"
 xcodebuild archive -archivePath "build/ios-simulator.xcarchive" -scheme "YJADSDK" -destination "generic/platform=iOS Simulator" -configuration "Release" -workspace "YJADSDK.xcworkspace"

lipo コマンドでの結合は不要で、代わりに xcodebuild -create-xcframework を実行します。
-framework で先程生成したフレームワークファイルをそれぞれ指定します。
dSYMファイル(デバッグ情報ファイル)を含める場合は、続けて -debug-symbols で指定を追加します。

 xcodebuild -create-xcframework \
 -framework 'build/ios.xcarchive/Products/Library/Frameworks/YJADSDK.framework' \
 -debug-symbols 'build/ios.xcarchive/dSYMs/YJADSDK.framework.dSYM' \
 -framework 'build/ios-simulator.xcarchive/Products/Library/Frameworks/YJADSDK.framework' \
 -debug-symbols 'build/ios-simulator.xcarchive/dSYMs/YJADSDK.framework.dSYM' \
 -output 'YJADSDK.xcframework'

ビルドツールである fastlane を利用している場合、上記を実行する create_xcframework アクションが用意されています。dSYMファイルも引数で含めることができます。
Yahoo!広告SDKではCI上でfastlaneを動かしビルドの自動化を行っているため、このようなアクションが存在する場合は積極的に活用しています。

 create_xcframework(
   frameworks_with_dsyms: {
     'build/ios.xcarchive/Products/Library/Frameworks/YJADSDK.framework' => {
       dsyms: 'build/ios.xcarchive/dSYMs/YJADSDK.framework.dSYM'
     },
     'build/ios-simulator.xcarchive/Products/Library/Frameworks/YJADSDK.framework' => {
       dsyms: 'build/ios-simulator.xcarchive/dSYMs/YJADSDK.framework.dSYM'
     }
   },
   output: 'YJADSDK.xcframework'
 )

依存するSDKを含めたSwiftPM対応

SwiftPMに対応するには、Package.swift を用意し、GitHubのリポジトリ上のルートに設置します。

Yahoo!広告SDKの場合、ローカルにあるフレームワークへの依存も存在したため、dependencies に名前を書き、binaryTarget にファイルパスを指定する必要がありました。
また、こちらのフレームワークファイルはXCFrameworkでのみ対応しているようで、通常のフレームワークファイルだと unsupported extension for binary target エラーとなります。

 import PackageDescription

 let package = Package(
     name: "YJADSDK",
     platforms: [.iOS(.v12)],
     products: [
         .library(name: "YJADSDK", targets: ["YJADSDK"])
     ],
     targets: [
         .target(
             name: "YJADSDK",
             dependencies: ["DependentSDK"],
             path: "YJADSDK/Sources"
         ),
         .binaryTarget(
             name: "DependentSDK",
             path: "YJADSDK/Frameworks/DependentSDK.xcframework"
         )
     ]
 )

クラスやメソッドごとでなく、提供機能単位でテストケースを整理

前述の通り、ドキュメントが不足していたり、自分も含めメンバーの入れ替わりがあったこともあり、仕様理解が必要でした。
そこで、全てのコードを移植・レビューするとともに同時進行でテストケースも整理していきました。
具体的には、「このSDKには何という機能を持っていて、どのような動作が備わっているべきか」「それはどのOSバージョンでサポートしているのか」をまとめた資料を改めて作成しました。

テストケース資料(イメージ)

単に今あるコードのクラスやメソッドごとに説明をする内容ではないところがポイントで、提供機能単位で書くことを意識し「結局何を実現すべきか?」チーム内の共通認識を合わせる目的があります。

これらのテストケースを元に、SDK本体とは別で開発しているサンプルアプリを利用して、チーム内で問題がないことを確認し、SDKのリリースを行いました。
当時は手動で検証を行っていましたが、現在はUIテストやスナップショットテストでの自動化にも取り組んでいます。

協力部署と連携してサービスアプリへ組み込み〜リリース

Yahoo!広告には、われわれが所属する開発チームとは別にサービスと広告導入に関するコミュニケーションをとってくれる部署が存在します。
サービスがObjective-C版からSwift版への移行開発を行うにあたってスケジュール調整などを行っていただきました。
広告関連以外にも非常に多くの案件を抱えているサービスもある中、SDK側都合の案件も差し込んでいただけて非常に良い協力体制だと感じます。
われわれのチームとしても、サービス側の開発を円滑に進めていただくため、Swift版への移行手順書を用意したり、Slackのチャンネルにて積極的にサポートを行いました。

最後に、品質を管理するチームが、Yahoo!広告SDKを実際に組み込んだアプリを使ってビジネス目線で独自に準備していただいたテストケースに従ってQAを行います。
加えて、モンキーテストも行っていただけるので、不具合はもちろん仕様が明確に決まっていない部分の指摘もここで多く見つかったりして、非常に助かっています。

アプリリリース後約1週間は対象のアプリバージョンに対し、クラッシュ数はもちろんリクエスト数や各種KPIに変化がないかを注視しました。

得られた成果

足かけ2年ほどかけましたが、コード量が27%もカットされ、Swift版Yahoo!広告SDK起因の事故は0件で完全移行を達成することができました!
現在では無事Objective-C版のEOLも完了し、メンバー一同がスピード感を持って開発が進められるようになりました!

また、副次効果として改めてサービスとの結合テストを行うことで、今回の対応とは関係のない部分での不具合や連携ミスも発見でき、仕様やドキュメントを見直す良い機会となりました。

おわりに

ヤフーの収入源で大きなウェイトを占めている広告として、売上の低下や利用者にご迷惑がかかる事故は起こせないプレッシャーもあり、一括移植は勇気が必要ではありましたが、慎重を重ねつつもこれらをやり遂げられたのは良かったです!

Yahoo!広告SDKでは現在Swift言語の特性をフルに生かし、攻めの新広告商品開発と守りのテスト開発をどんどん行うフェーズに突入しています。Android版も既にKotlinへの移行が100%完了しています。
興味を持っていただいた方はぜひ採用ページからエントリーをお待ちしております!

こちらの記事のご感想を聞かせください。

  • 学びがある
  • わかりやすい
  • 新しい視点

ご感想ありがとうございました


青木 翔
iOSエンジニア
Yahoo!広告に関連するモバイルアプリ向けSDKの開発を担当しています。ヤフーアプリを横断でサポートするチーム"アプリWG"にも所属しています。

このページの先頭へ