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
ドリフトを絶対に許さない(?)CDK運用 / CDK Ops with Zero Tolerance for Drifts (?)
akihisaikeda
1
200
楽しそうなつよつよエンジニアと目が死んでる僕/A brilliant engineer having a blast, and dead-eyed me.
3l4l5
2
190
AI時代のPHPer生存戦略 ~「言語、もうなんでもよくない?」に本気で向き合う~
vivion
0
810
Built Our Own Background Agent at LayerX
layerx
PRO
10
5.7k
今さら聞けない .NET CLI
htkym
0
200
Augmenting AI with the Power of Jakarta EE
ivargrimstad
0
630
Claude CodeとAgentCore Gatewayを繋ぐ際の認証認可 / Authentication and authorization when connecting Claude Code with AgentCore Gateway
har1101
2
300
S3 を使うアプリケーションをローカル完結で動かすことに全力を注いでみた / Running S3 Apps Offline
contour_gara
0
530
5分で問診!Composer セキュリティ健康診断
codmoninc
0
1.1k
Go 1.27 における memory allocation の高速化
andpad
0
250
生成AIで帳票OCRが「簡単に」作れる時代になった?
kon_shou
0
1.1k
KotlinConf Extended South Korea 2026 Keynote
l2hyunwoo
0
110
Featured
See All Featured
Navigating Weather and Climate Data
rabernat
0
480
Public Speaking Without Barfing On Your Shoes - THAT 2023
reverentgeek
1
540
Ten Tips & Tricks for a 🌱 transition
stuffmc
0
170
Product Roadmaps are Hard
iamctodd
55
12k
svc-hook: hooking system calls on ARM64 by binary rewriting
retrage
2
490
Efficient Content Optimization with Google Search Console & Apps Script
katarinadahlin
PRO
1
800
Bootstrapping a Software Product
garrettdimon
PRO
307
120k
Optimising Largest Contentful Paint
csswizardry
37
3.9k
Agile that works and the tools we love
rasmusluckow
331
22k
The innovator’s Mindset - Leading Through an Era of Exponential Change - McGill University 2025
jdejongh
PRO
1
260
First, design no harm
axbom
PRO
2
1.3k
How to Ace a Technical Interview
jacobian
281
24k
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!