Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
[Do iOS '24] Ship your app on a Friday...and en...
Search
Pol Piella Abadia
November 17, 2024
Programming
1.4k
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
[Do iOS '24] Ship your app on a Friday...and enjoy your weekend!
Pol Piella Abadia
November 17, 2024
More Decks by Pol Piella Abadia
See All by Pol Piella Abadia
[SwiftConf '24] Shipping your apps should be fast and easy
polpielladev
0
2.2k
[Workshop] Ship your apps faster with Xcode Cloud
polpielladev
0
160
[SwiftCraft '24] Back to the Future: Swift 6 Edition!
polpielladev
0
2.3k
[London Tech Leaders x AppCircle] The future of mobile releases
polpielladev
0
2.2k
[Swift Heroes '24] Delightful on-device AI experiences
polpielladev
0
2.3k
[SwiftLeeds '23] Delightful Swift CLI applications
polpielladev
0
2.1k
[iOS Dev UK 23] Making developer tools with Swift
polpielladev
0
2.4k
[NSBarcelona/AppTalks Manchester] - Delightful Swift CLI applications
polpielladev
0
2.1k
[Swift Heroes 2023] Making developer tools with Swift
polpielladev
0
2.3k
Other Decks in Programming
See All in Programming
生成AIで帳票OCRが「簡単に」作れる時代になった?
kon_shou
0
1.1k
仕様書を書く前にハーネスを作る - Agent Native開発は「探索を速く、判定を固く」
gotalab555
4
1.7k
【やさしく解説 設計編・中級 #6】良いアーキテクチャとは ~ 一本の登り道の、行き先 ~
panda728
PRO
0
220
PostgreSQL 18で考えるUUID主キー
kazuhiro1982
0
480
複数の Claude Code が"放置"されてしまう問題をCLI ダッシュボードを自作して解決した話
sumihiro3
1
700
2年かけて Deno に DOMMatrix を実装した話 / How I implemented DOMMatrix in Deno over two years
petamoriken
0
210
komatsuna「分散システムにおけるバグ分析手法」
komatsunaqa
0
270
20260722_microCMSで考える、AI時代のコンテンツ運用設計
yosh1
0
420
Building a Meta Ray-Ban display app
akkeylab
0
160
ここ半年くらいでAIに作らせたR用ツール
eitsupi
0
390
Go言語とトイモデルで学ぶTransformerの気持ち / fukuokago23-transformer
monochromegane
0
180
ルールを書いて終わらせないハーネスエンジニアリング
yug1224
5
1.9k
Featured
See All Featured
The innovator’s Mindset - Leading Through an Era of Exponential Change - McGill University 2025
jdejongh
PRO
1
260
So, you think you're a good person
axbom
PRO
2
2.1k
The Web Performance Landscape in 2024 [PerfNow 2024]
tammyeverts
12
1.3k
Making the Leap to Tech Lead
cromwellryan
135
10k
For a Future-Friendly Web
brad_frost
183
10k
Money Talks: Using Revenue to Get Sh*t Done
nikkihalliwell
0
470
Design in an AI World
tapps
1
280
Design and Strategy: How to Deal with People Who Don’t "Get" Design
morganepeng
133
19k
The browser strikes back
jonoalderson
0
1.5k
Learning to Love Humans: Emotional Interface Design
aarron
275
41k
Digital Ethics as a Driver of Design Innovation
axbom
PRO
1
370
Tips & Tricks on How to Get Your First Job In Tech
honzajavorek
1
700
Transcript
Ship your app on a Friday and enjoy your weekend
@POLPIELLADEV @
[email protected]
! Do iOS 2024 /IN/POLPIELLADEV
Indie developer and content creator based in Barcelona Pol Hi!
I’m Pol
None
polpiella.dev
ioscinewsletter.com
None
Mobile releases are difficult there is no denying that!
Accurate representation of an iOS Engineer being asked if they
want to manage a release
None
None
None
None
None
None
None
None
Mobile releases can be risky. Let’s just not ship them
on a Friday.
Gain confidence in your releases and ship anytime! A few
tips to…
#1 Automate as much as possible
None
Fully automated
desc "Distributes a new build to App Store Connect" lane
:distribute do version_number = git_branch.split("/", -1).last setup_ci xcode_select("/Applications/Xcode_16.app") sync_code_signing( type: "appstore", app_identifier: ["com.appdiggershq.fosi"], readonly: true ) increment_version_number(version_number: version_number, xcodeproj: "Fosi.xcodeproj") app_store_connect_api_key( key_id: ENV["APP_STORE_CONNECT_API_KEY_ID"], issuer_id: ENV["APP_STORE_CONNECT_ISSUER_ID"], key_content: ENV["APP_STORE_CONNECT_KEY_CONTENT"] ) build_number = latest_testflight_build_number + 1 increment_build_number(xcodeproj: "Fosi.xcodeproj", build_number: build_number) build_app(scheme: "Fosi", configuration: 'Release', output_name: "Fosi.ipa") upload_to_app_store(ipa: "Fosi.ipa", precheck_include_in_app_purchases: false) if !git_status(path: "Fosi.xcodeproj/project.pbxproj").empty? git_commit(path: "Fosi.xcodeproj/project.pbxproj", message: "[ci skip] [" release] Updating version") push_to_git_remote(remote: "origin", remote_branch: git_branch) end end
name: Distribute to the App Store Connect concurrency: group: $!"
github.workflow !#-$!" github.ref !# cancel-in-progress: true permissions: contents: write on: push: branches: - 'release!!$' jobs: distribute: runs-on: [macos-14] steps: - uses: actions/checkout@v4 - uses: ruby/setup-ruby@v1 with: bundler-cache: true - run: bundle exec fastlane distribute env: APP_STORE_CONNECT_API_KEY_ID: $!" secrets.APP_STORE_CONNECT_API_KEY_ID !# APP_STORE_CONNECT_ISSUER_ID: $!" secrets.APP_STORE_CONNECT_ISSUER_ID !# APP_STORE_CONNECT_KEY_CONTENT: $!" secrets.APP_STORE_CONNECT_KEY_CONTENT !# MATCH_GIT_BASIC_AUTHORIZATION: $!" secrets.MATCH_GIT_BASIC_AUTHORIZATION !# MATCH_USERNAME: $!" secrets.MATCH_USERNAME !# MATCH_PASSWORD: $!" secrets.MATCH_PASSWORD !#
A comprehensive test suite will drastically improve confidence
A comprehensive test suite will drastically improve confidence* If you
actually remember to run them!
Where should you run tests? PRs to the development branch
Before uploading to ASC On Nightly or Scheduled Workflows
If there is a manual task that causes friction, automate
it!
None
#2 Test your release pipeline
# App Target $ ModuleA.framework $ ModuleB.framework
None
$ ModuleA.framework $ ModuleB.framework # App Target
None
None
lane :nightly do # Let's make some magic happen %
gym( project: "./AwesomeApp.xcodeproj", clean: true, derived_data_path: "./derived-data", output_directory: "./build", output_name: "AwesomeApp.ipa", scheme: "AwesomeApp", export_options: { provisioningProfiles: { "your.bundle.identifier" !% "Your Provisioning Profile Name", } } ) deliver( ipa: "build/AwesomeApp.ipa", verify_only: true ) end
name: Nightly on: schedule: - cron: '0 0 * *
*' jobs: nightly: runs-on: macos-latest steps: - uses: actions/checkout@v2 - name: Run nightly lane run: bundle exec fastlane nightly
None
None
#3 Adopt a release strategy
None
None
None
None
Quality matters WAY MORE than quantity The silver bullet of
release frequency
Release less, more o!en*! Use release trains to… *Always allowing
ample time for beta testing and QA
You’ll unleash these benefits & Reduced risk ' Predictability (
Faster Feedback
None
#4 Use Feature Toggles
You can just turn it off! If something goes wrong
in production…
enum ToggleableFeature: String { case homeSwiftUIRefactor = "homeRefactor" } struct
HomeViewWrapper: View { @Environment(FeatureToggleService.self) var featureToggles var body: some View { if featureToggles.isOn(.homeSwiftUIRefactor) { HomeView() } else { LegacyHomeView() } } }
None
#5 Observe and Monitor
You first need to know that one is happening! To
quickly react to an incident
Crashes & Performance
Crashes & Performance
Crashes & Performance
None
Analytics
Customer Satisfaction
Customer Satisfaction
User Feedback
User Feedback
User Feedback
None
CI/CD Velocity Metrics
None
#6 Do not release to all users at once
None
None
None
None
None
import AWSLambdaRuntime import AWSLambdaEvents import AppStoreConnect_Swift_SDK @main struct PausePhasedReleaseWebhook: SimpleLambdaHandler
{ func handle(_ event: APIGatewayV2Request, context: LambdaContext) async throws !& APIGatewayV2Response { guard let body = event.body else { return .init(statusCode: .badRequest) } !' Read the webhook payload and decide whehter to stop the release or not!!( } }
func handle(_ event: APIGatewayV2Request, context: LambdaContext) async throws !& APIGatewayV2Response
{ }
func handle(_ event: APIGatewayV2Request, context: LambdaContext) async throws !& APIGatewayV2Response
{ !' … !' Get latest live version let endpoint = APIEndpoint .v1 .apps .id("1234567890") .appStoreVersions .get(parameters: .init(filterPlatform: [.ios], filterAppVersionState: [.readyForDistribution])) let response = try await provider.request(endpoint) let allPhasedReleases = response.included? .compactMap { item in if case .appStoreVersionPhasedRelease(let phasedRelease) = item { return phasedRelease } return nil } guard let id = allPhasedReleases!)first!)id else { return .init(statusCode: .notFound) } }
func handle(_ event: APIGatewayV2Request, context: LambdaContext) async throws !& APIGatewayV2Response
{ !' … let attributes = AppStoreVersionPhasedReleaseUpdateRequest.Data.Attributes( phasedReleaseState: .paused ) let data = AppStoreVersionPhasedReleaseUpdateRequest.Data( type: .appStoreVersionPhasedReleases, id: id, attributes: attributes ) let requestBody = AppStoreVersionPhasedReleaseUpdateRequest(data: data) let phasedReleaseEndpoint = APIEndpoint .v1 .appStoreVersionPhasedReleases .id(id) .patch(requestBody) _ = try await provider.request(phasedReleaseEndpoint) return .init(statusCode: .ok) }
*Things that are out of your control and fail oMen
*Your core business logic that your app depends on #7 Move risky* logic to the server!
None
None
None
Ship bug fixes worldwide for all versions of your app
Within seconds…
# Your App • Risky logic • Risky models
$ Risky Models # Your App • Risky logic
$ Risky Models # Your App ⚙ Risky Logic (BE)
None
#8 Roll back to a stable version immediately
1.0.0
1.0.0 1.0.1
1.0.0
1.0.0 1.0.1
1.0.0 1.0.1 1.0.2
1.0.0 1.0.2
None
Gain confidence on your release process! Automate manual processes Use
Feature Toggles Leverage the BE ecosystem Observe and Monitor Ship less, more o!en! Roll back to stable versions of your app How to use CI/CD to ship your mobile apps
And ship your apps on a Friday… or anytime! Gain
confidence in your releases
Find me online! Last tickets!