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
Sponsored
·
Ship Features Fearlessly
Turn features on and off without deploys. Used by thousands of Ruby developers.
→
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
150
[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
琵琶湖の水は止められてもNet--HTTPのリトライは止められない / You might be able to stop the water flow of Lake Biwa but you can't stop Net::HTTP retries
luccafort
PRO
0
380
Embedded SREと共に達成した会員管理システムのAWS移行 - SRE NEXT 2026 ランチスポンサーセッション
niftycorp
PRO
1
2.5k
Terraform標準の組織で AWS CDKをどう使うか
mu7889yoon
0
140
Claude Team Plan導入・ガイド
tk3fftk
0
200
Performance Engineering for Everyone
elenatanasoiu
0
270
Apache Hive: Toward a Cloud Native Lakehouse
okumin
0
120
【SRE NEXT 2026 Lunch Session】一人目専任SREの立ち上げを加速する ― AIと進めたオンボーディングで2分を0.04秒にした話
pkshadeck
PRO
0
2.6k
Laravel Boostに学ぶ、AIにPHPを書かせる技術 〜OSSの実装から蒸留するエージェント制御の王道〜
kentaroutakeda
3
410
共通化で考えるべきは、実装より公開する型だった
codeegg
0
240
なぜ型を書くのか? TSKaigi2026で改めて考える #tskaigi_smarthr
kajitack
0
370
コーディングルールの鮮度を保ちたい for SRE NEXT 2026 / keep-fresh-go-internal-conventions-sre-next-2026
handlename
0
140
フィードバックで育てるAI開発
kotaminato
1
120
Featured
See All Featured
The Language of Interfaces
destraynor
162
27k
Ecommerce SEO: The Keys for Success Now & Beyond - #SERPConf2024
aleyda
1
2.1k
Organizational Design Perspectives: An Ontology of Organizational Design Elements
kimpetersen
PRO
1
770
Become a Pro
speakerdeck
PRO
31
6k
How to Build an AI Search Optimization Roadmap - Criteria and Steps to Take #SEOIRL
aleyda
1
2.1k
Conquering PDFs: document understanding beyond plain text
inesmontani
PRO
4
2.9k
Documentation Writing (for coders)
carmenintech
77
5.4k
Designing for humans not robots
tammielis
254
26k
Money Talks: Using Revenue to Get Sh*t Done
nikkihalliwell
0
380
Design in an AI World
tapps
1
260
Utilizing Notion as your number one productivity tool
mfonobong
4
420
Effective software design: The role of men in debugging patriarchy in IT @ Voxxed Days AMS
baasie
0
450
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!