Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
プロダクトグロースと技術のベースアップを両立させるRettyのアプリ開発スタイル ...
Search
Sponsored
·
Ship Features Fearlessly
Turn features on and off without deploys. Used by thousands of Ruby developers.
→
Tomohiro Imaizumi
May 13, 2022
Technology
920
1
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
プロダクトグロースと技術のベースアップを両立させるRettyのアプリ開発スタイル / Achieve Product Growth and Tech Update
iOS Snack bar #1 での発表資料になります
https://ios-snack-bar.connpass.com/event/246443/
Tomohiro Imaizumi
May 13, 2022
More Decks by Tomohiro Imaizumi
See All by Tomohiro Imaizumi
VisionFrameworkで実現する - プライバシーに配慮した「顔ぼかし」機能 / Face blurring with Vision Framework
imaizume
0
400
Feature Flagを使った開発で高速かつストレスフリーなデリバリーを実現する / Fast and stress-free delivery with Feature Flag-based development
imaizume
3
4.9k
git branchを自由に操れるようになろう / Let's Play with Git branch!
imaizume
2
1.3k
スナップショットテスト実戦投入 / Practical Snapshot Testing
imaizume
14
8k
コーディング以外のエンジニアリング / About Engineering Without Coding
imaizume
1
1.9k
Firebase Remote Configの運用で知ったこと・知っておくと良いこと / Things I Learned from Operation of Firebase Remote Config
imaizume
6
4.4k
iOSアプリのテストを書きたいのに書けないあなたへ / How You Should Start to Write Your First Unit Test for iOS
imaizume
6
5.3k
循環的複雑度を上げないためのSwiftプログラミングTips / Tips of Swift Programming to Reduce Code Complexity
imaizume
11
8.7k
シングルトンではじめる状態管理と依存注入 / A way to control state using singleton pattern
imaizume
0
5.2k
Other Decks in Technology
See All in Technology
銀行勘定系システムにおける開発プロセス刷新×AIによる環境モダナイゼーション / Development Process Transformation and AI-Driven Environment Modernization
muit
0
1.6k
絵ではじめるKubernetesセキュリティ
aoi1
3
420
ユーザー価値を届け続けるためにウォンテッドリーが大切にしている文化
kotaminato
0
140
GoCon2026 - Open Source, Open World
sanposhiho
4
3.9k
[2026-09-11]SREは誰のもの?運用エンジニアが始める 「SRE領域への越境」とチームの進化の軌跡 〜Road to NEXT CRE
tosite
0
200
HRC_Frontend_Conference_Fukuoka_2026.pdf
ts020
0
680
アプリログインとWeb認証基盤をつなぐ ASWebAuthenticationSession 作法
shimastripe
1
300
Sigmaで作る業務アプリ
kazushiro_honma
0
130
AI 時代のスタートアップエコシステ厶から考究する技術的負債との向き合い方
m3m0r7
PRO
2
1.4k
多層防御と最⼩権限で実現する、安全なAIエージェント設計パターン
lycorptech_jp
PRO
1
290
2026-09-11 【Snowflake World Tour Tokyo 2026】Snowflakeを起点に、AI Agentが自律稼働し続ける未来へ / Driving AI Agents with Snowflake
civitaspo
0
390
Amazon Quick on DesktopがIAM Identity Centerで動かない理由
yukiogawa
0
190
Featured
See All Featured
Principles of Awesome APIs and How to Build Them.
keavy
128
18k
Conquering PDFs: document understanding beyond plain text
inesmontani
PRO
4
3k
Lightning Talk: Beautiful Slides for Beginners
inesmontani
PRO
2
690
Building Experiences: Design Systems, User Experience, and Full Site Editing
marktimemedia
0
600
16th Malabo Montpellier Forum Presentation
akademiya2063
PRO
0
370
The Anti-SEO Checklist Checklist. Pubcon Cyber Week
ryanjones
0
230
Performance Is Good for Brains [We Love Speed 2024]
tammyeverts
12
1.8k
30 Presentation Tips
portentint
PRO
1
390
技術選定の審美眼(2025年版) / Understanding the Spiral of Technologies 2025 edition
twada
PRO
120
120k
The B2B funnel & how to create a winning content strategy
katarinadahlin
PRO
1
520
SEOcharity - Dark patterns in SEO and UX: How to avoid them and build a more ethical web
sarafernandez
0
280
The Art of Delivering Value - GDevCon NA Keynote
reverentgeek
16
2.2k
Transcript
プロダクトグロースと 技術のベースアップを両立させる Rettyのアプリ開発スタイル Tomohiro Imaizumi @imaizume iOS Snack bar #1
2022/05/13
自己紹介 Tomohiro Imaizumi (@imaizume) • 2019年11月入社 • 担当業務 ◦ iOS
/ Android / アプリ向けサーバー開発 ◦ Scrum Master / 採用育成 / 業務改善 • 今日話せなかったこと・個人的な話はぜひMeetyで!
🔗 全国20ヶ所以上でワーケーションした私と 外で働く楽しさを語りませんか ? https://meety.net/matches/cmGHdDXSJLHC 🔗 User Happyを目指す iOS開発について語りませんか
https://meety.net/matches/wxbcVgDSiJZF
目次 1. Rettyでのアプリ開発での前提 2. 技術的成果 3. プロダクトグロースと技術のベースアップを両立するための具体的な 取り組み プロダクトと技術の成長を 限られたリソースで両立するときの
参考事例にしてください🙏
Rettyのアプリ開発を取り巻く環境
• チーム構成: 技術横断型 (LeSS) ◦ iOS / Android / APIサーバーを5名前後でメンテ
◦ 得意分野をベースに他領域もカバー (非分業) ◦ App単体だけでなくWeb・toB向けも共同で開発 • プロダクトドリブンな開発 ◦ 施策開発が中心 ◦ 「技術は手段」の位置付け ◦ 2年程前から長期戦略に基づく開発 へシフト Rettyのアプリ開発を取り巻く環境 限られたリソースで施策開発と技術向上を両立する必要 短期的施策から長期戦略に基づく施策へ 1つのバックログ・チームの役割
結果 • 過去の「点ベース」施策による技術負債が溜まりがち ◦ メンテしにくいReactNativeを使った画面 ◦ 特定UIや機能を実現するために導入した、メンテが止まったライブラリ ◦ WebViewによる実装 •
技術的改善やベースアップ専用の時間は取りづらい ◦ ライブラリのバージョンアップが追いつかない ◦ warningやdeprecated API使用箇所の増加 • 今後の長期戦略に耐えられる基礎技術の更新がも必要に ◦ UIKitからの脱却 ◦ 自前CIのメンテコストの削減
最近の成果
直近2~3年のプロダクト的成果 (新機能) • Go To Eat / PayPay キャンペーン ◦
プロダクトへの集客や回遊を上げる • 新人気店ラベル ◦ 「似た好みのユーザーさんたちがオススメするお店」を人気店として再定義 • 好きラベル ◦ その人が好き・詳しいジャンルを可視化し、好みの近い「人からお店が探せる」を目指す • マイベストリニューアル ◦ ユーザーさんのベストなお店のシェア体験を改善 • オススメラベル ◦ おすすめしている人の見える化で、人気の根拠・人から価値の信頼性を上がる • プロフィール編集画面ネイティブ化 ◦ プロフィールの表示設定がスムーズに
新機能 Go To Eatキャンペーン 好きジャンル/マイベスト 新人気店・おすすめラベル プロフィール編集
直近2~3年の技術的成果 • ReactNative/UIKitからの脱却 • SwiftUI/Combineを使った宣言的UI化 • Renovate導入によるライブラリ更新の定常化 • swiftlint/dangerでの自動スタイルチェック •
uber/mockoloで自動Mock生成 • ReSwift-Thunkへの移行 • Bitrise / GitHub ActionsでTest/ベータ配信 • Feature Flagsを使った開発の推進
タイムライン ReactNative廃止 2021/06 SwiftUI製画面リリース 2021/03 Bitrise導入 2021/04 Renovate導入 2021/02
Mockolo導入 2020/11 swiftlint/danger導入 2021/11 ReSwift-Thunk移行完了 2022/01 Feature Flags 2021/11 ネイティブプロフィール編集 2021/12 Go To Eat キャンペーン 2020/10 おすすめラベル 2022/04 マイベストリニューアル 2021/12 好きラベル 2022/02 新人気店リリース 2021/11 PayPayボーナスキャンペーン 2021/02
プロダクトグロースと 技術のベースアップを 両立するための具体的な取り組み
両立するためのポイント 1. タイミング: 新規実装や事故をきっかけに 2. 複利的改善: 運用負荷軽減/開発効率向上を重視 3. スリム化: 標準APIで実現可能な仕様にする
1. タイミング (新規実装や事故をきっかけに)
新規実装でのSwiftUI/Combineの導入 • UIKitでの開発・レビューに限界が • 不安はありつつもGlobal検索画面で導入 (2021/03) ◦ 関心高いメンバーが試験的に実装 & チームに普及
• 新規の画面 / Viewに本格導入を開始 (2021/07) ◦ 新規画面 : SwiftUI + Combine製をデフォルトに ◦ 既存実装: リプレースは基本的に無価値のためやらない • iOS 13サポート切りが必要 ◦ 導入当初はiOS13サポートが一番キツかった 😢 ◦ Rettyでは2021年末でサポートを終了 改善を単体で行わず 日々の開発に取り入れる SwiftUIの導入箇所 完全SwiftUI製 部分的SwiftUI製
struct AttributedText: UIViewRepresentable { private let attributedText: NSAttributedString private let
linkTextAttributes: [NSAttributedString.Key: Any] private let onTap: (URL) -> Void @Binding private var height: CGFloat init( _ attributedText: NSAttributedString, linkTextAttributes: [NSAttributedString.Key: Any] = [:], dynamicHeight: Binding<CGFloat>, onTap: @escaping (URL) -> Void = { _ in } ) { _height = dynamicHeight self.attributedText = attributedText self.linkTextAttributes = linkTextAttributes self.onTap = onTap } func makeUIView(context _: Context) -> UITextView { let view = TextView(onTap: onTap) // 独自のTextViewを実装 view.delegate = view view.attributedText = attributedText view.linkTextAttributes = linkTextAttributes } } Extensionの例 : AttributedTextをSwiftUI向けに実装 class TextView: UITextView, UITextViewDelegate { private var onTap: (URL) -> Void = { _ in } init(onTap: @escaping (URL) -> Void) { self.onTap = onTap super.init(frame: .zero, textContainer: nil) } required init?(coder _: NSCoder) { fatalError() } func textView( _: UITextView, shouldInteractWith url: URL, in _: NSRange, interaction _: UITextItemInteraction ) -> Bool { onTap(url) return false } }
public final class UIHostingCell<Content>: UITableViewCell where Content: View { private
let hostingController = FixedSafeAreaInsetsHostingViewController<Content?>(rootView: nil) override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) hostingController.view.backgroundColor = .clear } deinit { removeHostingController() } func configure(_ view: Content, parent: UIViewController) { hostingController.rootView = view hostingController.view.invalidateIntrinsicContentSize() hostingController.view.fillConstraint(to: contentView) … } } Extensionの例 : UIHostingCell https://medium.com/@hongseongho/43321a9e9e90
Renovateの導入 • renovatebot/renovate • インシデントをきかっけに ◦ 未更新ライブラリが原因でインシデント発生 ◦ 更新コスト削減のために導入 •
導入後 ◦ 定常的に更新PRが出ている状態に ◦ ライブラリ起因のインシデントは発生せず ◦ QA項目記載のみで更新可 • 今後 ◦ QAの作業負荷軽減 (UITestの充実など) ◦ SPMへの移行 { "labels": ["renovate"], "extends": ["config:base"], "commitMessagePrefix": "[ci skip]", "packageRules": [ { "groupName": "FBSDK", "managers": ["cocoapods"], "matchPackagePatterns": ["^FBSDK"], "prPriority": 5 }, … ] } renovate.json インシデントの再発防止は 優先度を上げて取り組みやすい
2. 複利的改善 (開発効率向上 / 運用負荷軽減を重視)
SwiftUI統一で技術可用性と採用力強化 • UIKIt + ReactNative ▶ SwiftUIへ一本化 • 新規参入のハードルを下げる ◦
2022新卒もSwiftUI未経験から開始し即戦力に ◦ ペアプロ・レビューも容易でデリバリが高速に ◦ AppCode + Code With Me + johnno1962/InjectionIII • AndroidでもJetpack Composeを導入 ◦ 宣言的UIフレームワークでコードの類似性が高い ◦ Android ⇔ iOS でタスクをシェアしやすくなる ▶ 技術可用性が向上 ◦ 設計・ドメイン用語に一貫性をもたせやすく • 一定基準を満たせばFlutter経験者も採用候補に チーム全体でのアウトプット量を増やす → 新しい事・技術改善がしやすくなる (1.01の法則) ≒
SwiftUIとJetpack Composeの比較 (人気店ラベル) Row( verticalAlignment = Alignment.CenterVertically, modifier = Modifier
.clip(shape = RoundedCornerShape(2.dp)) .background(brush = level.labelBackground), ) { Text( modifier = Modifier .clip(shape = RoundedCornerShape(1.dp)) .background( brush = level.categoryNameBackground ) .padding( horizontal = 4.dp, vertical = size.categoryNameVerticalPaddingSize ), text = "${name}好き", fontSize = size.fontSize, color = level.categoryNameTextColor, fontWeight = FontWeight.Bold, ) Text( modifier = Modifier.padding( horizontal = size.popularityTextHorizontalPaddingSize ), text = "人気店", fontSize = size.fontSize, color = level.popularityTextColor, fontWeight = FontWeight.Bold, ) } HStack(alignment: .center, spacing: 2) { Text("\(name)好き") .foregroundColor(level.categoryNameTextColor) .fontWeight(.bold) .font(.system(size: size.fontSize)) .padding(.horizontal, 4) .padding(.vertical, size.verticalPadding) .background(level.categoryNameBackground) .cornerRadius(1) Text("人気店") .foregroundColor(level.suffixTextColor) .fontWeight(.bold) .font(.system(size: size.fontSize)) .padding(.horizontal, 4) } .padding(2) .background(level.badgeBackground) .cornerRadius(2) SwiftUI (iOS) Jetpack Compose (Android) 人気店ラベル
運用負荷軽減のための自動化・SaaS利用 • マニュアル作業・自前メンテナンスを極力減らす • β版配信 ◦ 自前Macmini + Firebase App
Distribution(FAD) ▶ GitHub Actions + FAD ◦ 配信用スクリプトのメンテナンスを廃止 • バナーの表出制御 ◦ 自前APIサーバー ▶ Firebase Remote Config ◦ エンジニア不要でキャンペーンバナーの表出が可能に • Slack WF ◦ QAからリリースまで関係者とのコミュニケーションを半自動化 継続的/安定的に本質的プロダクト開発ができる体制を構築
オフショアの活用 継続的/安定的に本質的プロダクト開発ができる体制を構築 • 2021年末からはオフショアを活用 • 主な依頼内容 ◦ warningの解消 (294 ▶
38) ◦ ライブラリ更新 (ReSwift-Thunk移行など一定コストがかかる定形作業 ) ◦ 施策開発の一部 (仕様が明確かつ納期がないもの ) • 国内の開発コストを上げないため ◦ コードレビューコストの削減 (SwiftLint / danger導入) ◦ GitHubカンバン • 国内作業はQA項目作成のみ
オフショアや外部サービスの活用 国内エンジニア 外部サービス 国内エンジニア オフショア 外部サービス 施策開発 施策外開発 運用 運用
施策開発 & 技術改善 施策外開発 運用 運用 施策外開発 施策開発 BEFORE AFTER
3. スリム化 (標準APIで実現可能な仕様にする)
サードパーティーライブラリへの依存を増やさない • 導入から削除まで一定コストが発生 ◦ 技術の比較検討 / バージョン更新と追従 / 対応機能の置換 ◦
Rettyでは極力依存を減らすことがコスト減になると判断 • 削除したライブラリたち ◦ siteline/SwiftUI-Introspect ▪ SwiftUI v1で仕様実現のため導入 ▶ 仕様調整 & OS13終了で不要に ◦ andreamazz/AMPopTip ▪ オンボーディング用ツールチップ表示に利用 ▶ 体験上不要と判断し削除 ◦ CEWendel/SWTableViewCell ▪ 横スワイプ可能なセルの実装に使用 ▶ 標準APIで実現可能なため削除 ◦ Alamofire/AlamofireImage ▪ APIクライアントはAlamofire、画像の取得/表示はKingfisherへ ▪ Background実行は自前実装(BackgroundTaskManager) へ 標準APIで実現するのが長期的に高コスパ
• 前提 ◦ 少人数 & プロダクトドリブンのうえで小さく改善を進める ◦ 施策・技術の両面で少しずつ成果が出始めている段階 • 両立するための取り組みポイント
◦ タイミング: 新規実装や事故をきっかけに ◦ 複利的改善: 運用負荷軽減/開発効率向上を重視 ◦ スリム化: 標準APIで実現可能な仕様にする まとめ: プロダクトグロースと 技術のベースアップを両立させるには 限られたリソースでプロダクトと技術の成長を両立する 参考事例になれば幸いです 🙏