Testing App Launch Time for React Native Sample App – Expo
Create Sample App using Expo
When you create an app using Expo, there are no iOS and Android folder.
Create iOS and Android folder in Expo Sample Project
npx expo prebuild
Setup iOS App Launch Tests
Add UI Testing Bundle and write a test cases
@MainActor
func testExample() throws {
// UI tests must launch the application that they test.
let app = XCUIApplication()
app.launch()
// Use XCTAssert and related functions to verify your tests produce the correct results.
}
@MainActor
func testLaunchPerformance() throws {
if #available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 7.0, *) {
// This measures how long it takes to launch your application.
let measureOptions = XCTMeasureOptions()
measureOptions.iterationCount = 10
measure(metrics: [XCTApplicationLaunchMetric()], options: measureOptions) {
XCUIApplication().launch()
}
}
}
Results
I ran 10 times and it’s average launch time is 0.157s (157ms).
Setting GitHub actions is a headache, and it takes time to run correctly. I’ll share what actions I daily use.
How to add github actions?
Add github actions on your branch
.github/workflows/xxxx.yml
Trigger github action by leaving a comment on PR
Scenario 1: share new build to someone
You can define commands to run action
For example, If you want to share something to test, use this action.
share is command
test is userId
And you can leave a comment using github.issues.createComment
This action is very useful. You can integrate third-party services like Slack, WhatsApp, or Linear. Listening share command and trigger action whatever you want to share it.
name: Comment Action
on:
issue_comment:
types: [created]
jobs:
comment_job:
runs-on: ubuntu-latest
steps:
- name: Check Comment Type
id: check_comment
uses: actions/github-script@v4
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const comment = context.payload.comment.body.trim();
console.log(`Received comment: ${comment}`);
const issueOrPrNumber = context.payload.issue?.number || context.payload.pull_request?.number;
if (!issueOrPrNumber) {
throw new Error("Could not determine issue or PR number.");
}
let action = '';
if (comment.startsWith('share')) {
action = 'share';
}
core.setOutput('action', action);
core.setOutput('comment', comment);
core.setOutput('issue_or_pr_number', issueOrPrNumber);
- name: Handle Share Command
if: steps.check_comment.outputs.action == 'share'
uses: actions/github-script@v4
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const comment = '${{ steps.check_comment.outputs.comment }}';
const issueOrPrNumber = '${{ steps.check_comment.outputs.issue_or_pr_number }}';
if (!issueOrPrNumber) {
throw new Error("Could not determine the issue or PR number.");
}
const splitComment = comment.split(' ');
if (splitComment.length < 2) {
throw new Error("Invalid 'share' command format. Expected: 'share <id>'");
}
const recipientId = splitComment[1]; // Extract ID after "share"
const commentBody = `Sharing this with: ${recipientId}`;
console.log(`Adding a share comment for ID: ${recipientId}`);
await github.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issueOrPrNumber,
body: commentBody
});
scenario 2: rebuild when the build failed
There are 2 yml files in workflows
When you get a build failed message, you can rebuild it by leaving a comment.
To install IPA file on iPhone device, use Apple Configurator app
How to use it?
Connect your iPhone into Macbook. In the Apple Configurator app, you may see your connected iPhone screen. Drag IPA file into the your iPhone. That’s it
You will see iPA is adding on your iPhone. (Installing)
Security topics are common interview question. I summarized what is Certificate, Provisioning, Code Signing, App Transport Security and CryptoKit.
Overview
Use the Security framework to protect information, establish trust, and control access to software. Broadly, security services support these goals:
Establish a user’s identity (authentication) and then selectively grant access to resources (authorization).
Secure data, both on disk and in motion across a network connection.
Ensure the validity of code to be executed for a particular purpose.
As shown in the image below, you can also use lower level cryptographic resources to create new secure services. Cryptography is difficult and the cost of bugs typically so high that it’s rarely a good idea to implement your own cryptography solution. Rely on the Security framework when you need cryptography in your app.
Digital certificates can be used to securely identify a client or server, and to encrypt the communication between them using the public and private key pair.
A certificate contains a public key, information about the client (or server), and is signed (verified) by a CA.
A certificate and its associated private key are known as an identity. Certificates can be freely distributed, but identities must be kept secure. The freely distributed certificate, and especially its public key, are used for encryption that can be decrypted only by the matching private key. The private key part of an identity is stored as a PKCS #12 identity certificate (.p12) file and encrypted with another key that’s protected by a passphrase. An identity can be used for authentication (such as 802.1X EAP-TLS), signing, or encryption (such as S/MIME).
The certificate and identity formats Apple devices support are:
Certificate: .cer, .crt, .der, X.509 certificates with RSA keys
If a certificate has been issued from a CA whose root isn’t in the list of trusted root certificates, iOS, iPadOS, macOS, or visionOS won’t trust the certificate. This is often the case with enterprise-issuing CAs. To establish trust, use the method described in certificate deployment. This sets the trust anchor at the certificate being deployed. For multitiered public key infrastructures, it may be necessary to establish trust not only with the root certificate, but also with any intermediates in the chain. Often, enterprise trust is configured in a single configuration profile that can be updated with your MDM solution as needed without affecting other services on the device.
Root certificates on iPhone, iPad, and Apple Vision Pro
Root certificates installed manually on an unsupervised iPhone, iPad, or Apple Vision Pro through a profile display the following warning, “Installing the certificate “name of certificate” adds it to the list of trusted certificates on your iPhone or iPad. This certificate won’t be trusted for websites until you enable it in Certificate Trust Settings.”
The user can then trust the certificate on the device by going to Settings > General > About > Certificate Trust Settings.
Note: Root certificates installed by an MDM solution or on supervised devices disable the option to change the trust settings.
A development provisioning profileallows your app to launch on devices and use certain app services during development. For an individual, a development provisioning profile allows apps signed by you to run on your registered devices. For an organization, a development provisioning profile allows apps developed by a team to be signed by any member of the team and installed on their devices.
The development provisioning profile contains:
A wildcard App ID that matches all your team’s apps or an explicit App ID that matches a single app
Specified devices associated with the team
Specified development certificates associated with the team
distribution provisioning profile
A distribution provisioning profile is a provisioning profile that authorizes your app to use certain app services and ensures that you are a known developer distributing or uploading your app. A distribution provisioning profile contains a single App ID that matches one or more of your apps and a distribution certificate. You configure the App ID indirectly through Xcode to use certain app services. Xcode enables and configures app services by setting entitlements and performing other configuration steps. Some entitlements are enabled for an App ID (stored in your developer account) and others are set in the Xcode project. When you export or upload your app, Xcode signs the app bundle with the distribution certificate referenced in the distribution provisioning profile.
Code signing is a macOS security technology that you use to certify that an app was created by you. Once an app is signed, the system can detect any change to the app—whether the change is introduced accidentally or by malicious code.
You participate in code signing as a developer when you obtain a signing identity and apply your signature to apps that you ship. A certificate authority (often Apple) vouches for your signing identity.
Note: In most cases, you can rely on Xcode’s automatic code signing, which requires only that you specify a code signing identity in the build settings for your project. This document is for readers who must go beyond automatic code signing—perhaps to troubleshoot an unusual problem, or to incorporate the codesign(1) tool into a build system.
Benefits of Code Signing
After installing a new version of a code-signed app, a user is not bothered with alerts asking again for permission to access the keychain or similar resources. As long as the new version uses the same digital signature, macOS can treat the new app exactly as it treated the previous one.
Other macOS security features, such as App Sandbox and parental controls, also depend on code signing. Specifically, code signing allows the operating system to:
Ensure that a piece of code has not been altered since it was signed. The system can detect even the smallest change, whether it was intentional (by a malicious attacker, for example) or accidental (as when a file gets corrupted). When a code signature is intact, the system can be sure the code is as the signer intended.
Identify code as coming from a specific source (a developer or signer). The code signature includes cryptographic information that unambiguously points to a particular author.
Determine whether code is trustworthy for a specific purpose. Among other things, a developer can use a code signature to state that an updated version of an app should be considered by the system to be the same app as the previous version.
Limitations of Code Signing
Code signing is one component of a complete security solution, working in concert with other technologies and techniques. It does not address every possible security issue. For example, code signing does not:
Guarantee that a piece of code is free of security vulnerabilities.
Guarantee that an app will not load unsafe or altered code—such as untrusted plug-ins—during execution.
Provide digital rights management (DRM) or copy protection technology. Code signing does not in any way hide or obscure the content of the signed code.
See Also
Read Security Overview to understand the place of code signing in the macOS security picture.
For descriptions of the command-line tools for performing code signing, see the codesign and csreq man pages.
App Transport Security
On Apple platforms, a networking security feature called App Transport Security (ATS) improves privacy and data integrity for all apps and app extensions. It does this by requiring that network connections made by your app are secured by the Transport Layer Security (TLS) protocol using reliable certificates and ciphers. ATS blocks connections that don’t meet minimum security requirements.
Use Apple CryptoKit to perform common cryptographic operations:
Compute and compare cryptographically secure digests.
Use public-key cryptography to create and evaluate digital signatures, and to perform key exchange. In addition to working with keys stored in memory, you can also use private keys stored in and managed by the Secure Enclave.
Generate symmetric keys, and use them in operations like message authentication and encryption.
Prefer CryptoKit over lower-level interfaces. CryptoKit frees your app from managing raw pointers, and automatically handles tasks that make your app more secure, like overwriting sensitive data during memory deallocation.
In this post, I’ll share how to build socket application.
Project Structure
I created a workspace and put the Client (SwiftUI) and Server (Vapor)
Server (Vapor)
Server code is very simple. It is only 32 lines of code.
import Vapor
func routes(_ app: Application) throws {
//Endpoint - ws://127.0.0.1:8080/echo
app.webSocket("echo") { req, ws in
// Connected WebSocket.
//Send Ping to Client
ws.sendPing()
ws.onPong { ws, data in
// Pong was received from client
print("Connection alive, Received Pong")
}
ws.onText { ws, text in
// String received by this WebSocket.
ws.send("👋 Sent Message: \(text) from Server")
}
ws.onBinary { ws, binary in
// [UInt8] received by this WebSocket.
print(binary)
ws.send("Received Data: \(binary.readableBytes)")
}
ws.onPing { ws, data in
// Ping was received.
print("Received Ping")
}
}
}
Both client and server can send a ping. When received the ping message then receiver send a pong message automatically. It’s a way to check status of connections.
If you want to more details about WebSocket, check my post
Choosing a server can be challenging, especially if you’re not a backend engineer but still need one for your iOS app. You may find yourself in this situation.
So, Why did I consider CloudKit?
Well, first off, it’s maintained by Apple and has been around since its introduction at the 2014 WWDC.
Apple actively uses it for various apps like Photos and Notes, which gives me confidence in its longevity and reliability. Plus, I expect it to receive regular updates from Apple.
My experience
Parse.com
I used Parse (Acquired by Facebook) and contributed to the Parse iOS SDK. I liked it, but Facebook announced its discontinuation.
Consequently, I had to migrate to alternatives like back4app or Sashido.io, which serve as alternatives to parse.com. While they were good, I encountered issues with the lack of active updates to the iOS SDK. This led me to contribute to its development, but I felt it was a waste of time. I wanted to focus on my app rather than on open-source projects.
I like Vapor because I can create a server using Swift, which is a significant benefit as an iOS Engineer. Additionally, there are numerous helpful resources available, such as books, YouTube videos, and Udemy courses.
But why did I consider CloudKit?
Unlike CloudKit, I would need to implement server logic from scratch, including authentication, deployment, database setup, migration, and data sharing between users.
My approach
Context
I’ve already implemented a server using Vapor to handle authentication. While working on sharing data between users, I researched an easy way to implement the invite/accept feature and found that CloudKit fully supports it.
The server I implemented using Vapor is deployed on AWS, using ECS with a Load Balancer. Its primary role is handling SignIn/SignUp and supporting user-related features such as changing passwords and usernames. All user information is saved in PostgreSQL.
For data synchronization across user devices, I utilize NSPersistentCloudKitContainer, which syncs data between CoreData and CloudKit seamlessly. This integration is very convenient and eliminates the need for manual synchronization logic.
CloudKit also supports invite and accept functionality for sharing data between users, eliminating the need to implement server-side logic for this feature.
I can access and edit CloudKit’s data using CloudKit Web Service. NSPersistentCloudKitContainer use a special zone called com.apple.coredata.cloudkit.zone. If you’re interested how to access it using CloudKit Web Service, you can check out Reading CloudKit Records for Core Data.
Public Data
As for public data storage, I haven’t decided yet where to store it. I’ll update you once I make a decision.
How about CloudKit costs?
Currently, the pricing seems like a black box. I can’t find pricing information on the Apple Developer website. That’s why I can’t decide where I should store public data.
If you want to know AWS costs, see my previous post.
Here is a simple swift code for checking execution time. (main.swift)
import Foundation
func sum(from: Int, to: Int) -> Int {
var result = 0
for i in from...to {
result += i
}
return result
}
let startTime = Date()
let result = sum(from: 1, to: 10000000)
let endTime = Date()
print("Result: \(result)")
print("Calculation Time: \(endTime.timeIntervalSince(startTime)) seconds")
swiftc -O main.swift is generating executable file. It is not a human readable file. The optimization process transforms the source code into machine code, which is the binary code that can be executed directly by the computer.
Do you want to see the intermediate representation of the Swift code generated during the optimization process?
You can use the -emit-sil flag with the swiftc command. This will output the SIL (Swift Intermediate Language) code generated by the compiler, which is a human-readable representation of the Swift code that has been optimized
swiftc -O -emit-sil main.swift -o main.sil
swiftc -Onone -emit-sil main.swift -o main.sil
You can see the main.sil file. Let’s open it using TextEdit
Now we can see the details by checking sil file. If you want to learn more about it visit here
Conclusion
I wanted to test how Swift’s optimization levels work, so I used the -emit-sil option to generate an SIL (Swift Intermediate Language) file. This file serves as a bridge between the Swift source code and the machine code, and allows the Swift compiler to perform optimizations such as dead code elimination, constant folding, inlining, and loop unrolling. By inspecting and analyzing the SIL file, we can better understand how the Swift compiler is optimizing our code. Additionally, SIL can be used for debugging purposes since it provides a more detailed and low-level view of the generated code compared to the original Swift source
Notes This blog post has been edited using ChatGPT to refine some of the sentences
Our UI creation rule was creating a UI programmatically. But recently, we changed it.
Now our iOS team can create UI depending on their preferred approaches.
The reason is that there are really good things if you use Storyboard
Less Code
Compile Time
Preview
Layout warnings
Among them, the main reason for making a decision is Compile time.
Compile Time: Swift vs Storyboard
Imagine if you need to change the spacing in UIStackView.
The left one is programmatic UI. If you change just one line of code then the swift compiler detects source file changes and will compile it. It may take more time if the source file dependent on other source files.
The right one is used Storyboard. If you change the spacing in StackView on Storyboard then ibtool compile a storyboard into the multiple nibs(stands for NextStep Interface Build file) at compile time.
Results: Storyboard compile-time 6.5x faster than Swift
Swift: 46.8 seconds
Storyboard: 7.2 seconds
As you can see from the results, compile-time when using the storyboard is much faster than swift file changes.
WWDC2018, Behind the Scenes of the Xcode Build Process
If you want to know ibtool’s compile options then check man ibtool in /Applications/Xcode.app/Contents/Developer/usr/bin.
/Applications/Xcode.app/Contents/Developer/usr/bin/You can check the Interface Builder document.
Although there were pros we’ve had some concerns about using Storyboard like below
PR Review
Conflicts Storyboard/Xib files
To address these concerns, we need to know about the xib(stands for XML Interface Builder) format.
xib format
Storyboard/Xib is a XML format. It means that Human Readable format. I sometimes heard that from iOS Engineers Storyboard/Xib files are hard to review. It’s true compared to reviewing swift code.
But once familiar with that format You may feel It is easy to review it. Because It looks like HTML format.
The storyboard is an XML document.
The numbers above the image mean like below
toolVersion
It means Interface builder tool’s version. It may be changed when you update Xcode. I highly recommend all team members use the latest Xcode version to avoid merge conflicts.
initialViewController
It indicates the initial view controller’s object id in Storyboard. So If you called an instantiateInitialViewController() then It returns that viewController.
device
You can use it to preview the layout on Storyboard. Anyone can change it but after checking the layout then I recommend resetting it as your team’s default device (e.g. iPhone X) to avoid merge conflicts.
scenes
A storyboard can have multiple scenes. A Scene has a ViewController.
segue
segue is a connection between ViewControllers. It has an id and a destination’s id. If the destination’s id has changed then we need to review it carefully. If there is no ViewController that has the destination’s id then It might be caused a crash at runtime.
PR review
I’ll show two examples.
Minor changes
Major changes (e.g. Layout, objectId, segue, etc)
Minor Changes
These changes normally don’t affect your UI. So You can easily review and approve it. It should be fine.
toolsVersion / plugin version can be changed automatically when you use a different Xcode version and edited the Interface Builder.
rect can be changed when you change the device or move a subview. These small changes should be fine because It uses autolayout.
propertyAccessControl(Locking View) is controlled by the developer. I’ll explain it next section.
Major Changes
Major Changes will affect layout issues. I’ll show you some examples below.
Embedded in UIStackView
If you embedded labels in UIStackView then you have to double-check label’s id is in stackView or not.
Layout Constraint Issue
An ambiguous=”YES” indicates us there is Layout Issue! So It should be fixed.
Pull Request Template
I highly recommend adding a screenshot of the layout in PR. To encourage your teammate to add a screenshot, set a Pull Request Template.
File changes cause merge conflicts. You can avoid it by dealing with file changes carefully.
By following this rule, merge conflicts are avoided as much as possible.
use the latest Xcode.
don’t commit the changes if you are not working on that View
locking Views in Storyboard and XIB Files you are working on
If your team is working on the same Storyboard, I highly recommend locking views to prevent unwanted changes.
Show the Identity Inspector -> Lock
You can set a lock on any UI Components(Whole Storyboard, UIViewController, UIView, Segue, any UI-related objects like a Constraint) you will be working on. As I mentioned above, propertyAccessControl will be added to the storyboard/xib file when you set a lock.
UIViewController is locked
Lock Image will be shown when you try to change on Locking View.
Conclusion
There are cons and pros to using Storyboard/Xib. Productivity is also different depending on the iOS developer’s preferred UI creation approach. But We made a decision to use both. Because we can save compile-time and found a good solution to resolve concerns for using it.
I was asked What is Struct and Class? Could you explain it?
It’s the most common interview question.
I summarized the differences between classes and structure when to use it.
Struct versus Class
Structure and Enum are value types.
It is copied when it’s assigned to a variable or constant, or it’s passed to a function.
Classes are reference types.
Reference types are not copied when they’re assigned to a variable or constant or when they’re passed to a function. Rather than a copy, a reference to the same existing instance is used.
Structure and Class both can
define properties to store values
define methods to provide functionality
define subscripts to provide access to their values using subscript syntax
define initializers to set up their initial state
be extended to expand their functionality beyond a default implementation
conform to protocols to provide standard functionality of a certain kind
Include computed properties.
Classes have additional capabilities that structures don’t have.
Inheritance enables one Class to inherit the characteristics of another.
Typecasting enables you to check and interpret the type of a class instance at runtime.
Deinitializers enable an instance of a class to free up any resources it has assigned.
Referencecounting allows more than one reference to a class instance.
Identity operators to check whether two constants or variables refer to the same single instance.
Identical to (===)
Not identical to (!==)
The structure has additional capabilities that classes don’t have.
Memberwise initializers
You can use it to initialize the member properties of the new structure instance.
Choosing between Structures and Classes
Decide how to store data and model behavior.
When designing a type, we have to think about whether ownership of a particular instance of this type “has to be shared among different parts of our program, or if multiple instances can be used interchangeably as long as they represent the same value. To share ownership of a particular instance, we have to use a class. Otherwise, we can use a struct.
Chris Eidhof. “Advanced Swift.”
Apple’s guide
Use structures by default
Use structures along with protocols to adopt behavior by sharing implementations.
Use classes when you need Objective-C interoperability
Use classes when you need to control the identity of the data you’re modeling
Choose Structures
It makes it easier to reason about a portion of your code without needing to consider the whole state of your app. Because structures are valued types-unlike classes-local changes to a structure aren’t visible to the rest of your app unless you intentionally communicate those changes as part of the flow of your app.
when you don’t control identity
Use structure when you’re modeling data that contains information about an entity with an identity that you don’t control.
Local changes to model types like PenPalRecord are useful. For example, an app might recommend multiple different penpals in response to user feedback. Because the PenPalRecord structure doesn’t control the identity of the underlying database records, there’s no risk that the changes made to local PenPalRecord instances accidentally change values in the database.
If another part of the app changes my nickname and submits a change request back to the server, the most recently rejected change won’t mistakenly pick up penpal recommendation. Because the myId property is declared as a constant, it can’t change locally. As a result, requests to the database won’t accidentally change the wrong record.
struct PenPalRecord {
let myID: Int
var myNickname: String
var recommendedPenPalID: Int
}
var myRecord = try JSONDecoder().decode(PenPalRecord.self, from: jsonResponse)
Use structures and protocols to model inheritance and share behavior.
Structure can’t inherit from classes. However, the kinds of inheritance hierarchies you can build with class inheritance can also be modeled using protocol inheritance and structures.
Protocols permit classes, structures, and enumerations to participate in inheritance, while class inheritance is only compatible with other classes. When you are choosing how to model your data, try building the hierarchy of data types using protocol inheritance first, then adopt those protocols in your structures.
Choose Classes
when you need to control identity (===)
Common use cases are file handles, network connections, and shared hardware intermediaries like CBCentralManager.
If you share a class instance across your app, changes you make to that instance are visible to every part of your code that holds a reference to that instance.
You must be logged in to post a comment.