Skip to main content

38 questions tagged with "General"

General tag description

View All Tags

Git Flow vs. Trunk-Based Development

· 2 min read
Szymon Michalak
iOS Developer
Sources & Resources

Main Source: đź”— Git Flow

Additional Sources:

Further Reading:

TL/DR

Git Flow and trunk-based development are two version control strategies with distinct advantages. Git Flow emphasizes structured branching and code reviews, making it ideal for open-source projects or teams with junior developers. Trunk-based development, on the other hand, focuses on continuous integration and rapid deployment, which suits startups and teams with experienced developers.

Concept Overview​

Version control systems are crucial in software development, enabling teams to track changes, collaborate efficiently, and manage project history. Among the various strategies, Git Flow and trunk-based development are widely used but differ in approach and application.

  • Git Flow:
    • Involves creating feature branches off a develop branch.
    • Changes are merged back through pull requests, ensuring code quality through peer reviews.
    • It’s particularly useful in environments where code quality and control are prioritized over development speed.
  • Trunk-Based Development:
    • Developers work directly on a single branch, often the master.
    • Encourages continuous integration and short-lived feature branches.
    • Suited for fast-paced environments where rapid iteration and deployment are crucial.

Real-World Applications​

  • Git Flow is ideal for large projects with a lot of contributors, such as open-source projects, where it’s essential to have controlled and well-reviewed code integration.

  • Trunk-based development works best in startup environments or in teams composed of senior developers who can be trusted to push directly to production, ensuring faster delivery without the bottlenecks of pull requests.

Common Mistakes​

  • Git Flow: Overly complex branch structures can lead to difficult merges and integration challenges, especially in fast-moving projects.

  • Trunk-based Development: Without rigorous testing and discipline, it can lead to unstable builds being pushed to production.

Diagrams/Visual Aids​

  • Git Flow: Git Flow Diagram

  • Trunk-Based Development: Trunk-Based Development Diagram

In Bullets
  • Git Flow: Best for environments needing strict code reviews and controlled integration.
  • Trunk-based Development: Suited for fast-paced, continuous integration environments.
  • Critical Decision Factor: Choose based on the project's need for speed vs. control.

How can we handle changes in our application’s lifecycle state? (SwiftUI)

· 3 min read
Szymon Michalak
iOS Developer
Sources & Resources
TL/DR

In SwiftUI, you manage the application’s lifecycle using the @Environment(\.scenePhase) property, along with onAppear and onDisappear modifiers. The scenePhase property allows you to monitor and respond to changes in the app's lifecycle state.

SwiftUI introduces a declarative way to manage an application's lifecycle without relying on the traditional AppDelegate. Instead, you use the App protocol and the scenePhase environment property to track and respond to changes in the app's state.

Here are the key components:

  • @Environment(\.scenePhase): Provides access to the current lifecycle state of the app, which can be .active, .inactive, or .background.

  • onAppear(perform:): This modifier is used to run code when a view appears on the screen.

  • onDisappear(perform:): This modifier allows you to execute code when a view disappears from the screen.

  • App Lifecycle States:

    • .active: The app is currently active and in the foreground.
    • .inactive: The app is transitioning or is temporarily in an inactive state.
    • .background: The app is running in the background.

Example SwiftUI Code​

import SwiftUI

@main
struct MyApp: App {
@Environment(\.scenePhase) var scenePhase

var body: some Scene {
WindowGroup {
ContentView()
.onAppear {
// Handle view appearance
}
.onDisappear {
// Handle view disappearance
}
}
.onChange(of: scenePhase) { newPhase in
switch newPhase {
case .active:
// The app is active
print("App is active")
case .inactive:
// The app is inactive
print("App is inactive")
case .background:
// The app is in the background
print("App is in the background")
@unknown default:
// Handle unexpected new phases
break
}
}
}
}

Additional Details​

SwiftUI’s declarative lifecycle management allows for a more integrated approach to handling the state of your app, removing the need for a separate AppDelegate in most cases. However, you can still integrate an AppDelegate if needed, using the UIApplicationDelegateAdaptor property wrapper to bridge between UIKit and SwiftUI.

In Bullets
  • Lifecycle Management: In SwiftUI, use the @Environment(\.scenePhase) property to track app state changes.
  • Declarative Modifiers: Use onAppear and onDisappear to manage view-specific lifecycle events.
  • Integrated Handling: Most lifecycle management can be handled within the SwiftUI App protocol, reducing the need for an AppDelegate.
  • State Monitoring: The scenePhase provides a straightforward way to monitor .active, .inactive, and .background states.

How can we handle changes in our application’s lifecycle state? (UIKit)

· 3 min read
Ace the iOS Interview
Aryaman Sharda
Sources & Resources
TL/DR

Our application's AppDelegate allows us to handle its lifecycle changes. UIApplication triggers various delegate methods whenever the app's lifecycle status changes.

When building an iOS application, managing its lifecycle is crucial to ensure it behaves correctly during different states such as launching, becoming active, entering the background, and terminating. The UIApplicationDelegate provides several methods to handle these transitions:

  • application(_:willFinishLaunchingWithOptions:): Called when the app is about to launch. This is the app's first opportunity to execute code.

  • application(_:didFinishLaunchingWithOptions:): Called after the app has finished launching and is ready to run. Final initialization tasks should be performed here.

  • applicationDidBecomeActive(_:): Invoked when the app is about to become the foreground app. This is where last-minute preparations can be done.

  • applicationWillResignActive(_:): Notifies that the app is transitioning away from being the foreground app.

  • applicationDidEnterBackground(_:): Indicates that the app is now running in the background and may transition to a suspended state.

  • applicationWillEnterForeground(_:): Indicates that the app is transitioning from the background to the foreground but is not yet active.

  • applicationWillTerminate(_:): Notifies that the app is about to be terminated, providing a chance to perform any necessary cleanup.

These methods allow developers to effectively manage the app's behavior and resources at each stage of its lifecycle.

// Example Swift code
class AppDelegate: UIResponder, UIApplicationDelegate {

func application(_ application: UIApplication, willFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool {
// First chance to execute code
return true
}

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool {
// Final initialization before the app is displayed
return true
}

func applicationDidBecomeActive(_ application: UIApplication) {
// App is becoming the foreground app
}

func applicationWillResignActive(_ application: UIApplication) {
// App is transitioning away from the foreground
}

func applicationDidEnterBackground(_ application: UIApplication) {
// App is now in the background
}

func applicationWillEnterForeground(_ application: UIApplication) {
// App is transitioning to the foreground
}

func applicationWillTerminate(_ application: UIApplication) {
// App is about to be terminated
}
}

Additional Details​

Understanding the importance of these lifecycle methods is crucial for managing resources effectively, ensuring a smooth user experience, and handling tasks such as saving data or releasing resources when the app is not active.

In Bullets
  • Lifecycle Management: Use UIApplicationDelegate methods to manage app behavior during different lifecycle stages.
  • Initialization: Perform initial setup in application(_:willFinishLaunchingWithOptions:) and application(_:didFinishLaunchingWithOptions:).
  • State Transitions: Respond to app state changes using applicationDidBecomeActive(_:), applicationWillResignActive(_:), applicationDidEnterBackground(_:), and applicationWillEnterForeground(_:).
  • Termination: Clean up resources in applicationWillTerminate(_:).

Are closures value or reference types?

· 2 min read
Ace the iOS Interview
Aryaman Sharda
Sources & Resources

Main Source: đź”— Ace the iOS Interview

Additional Sources:

Further Reading:

TL/DR

Closures are reference types.

When we use closures, we want them to be able to reference all of the variables from their surrounding context (like class and local variables). This means when the closure modifies acaptured reference-type variable in its definition, we’re also affecting the variable’s value outside of the closure's scope.

var money = Money(value: 20 , currencyCode: "USD")

//Output: Money(value: 20, currencyCode: "USD")
print(money)

let closure = {
money = Money(value: 200 , currencyCode: "USD")
}

closure()

//Output: Money(value: 200, currencyCode: "USD")
print(money)

If closures were value types, then they would only have access to a copy of the variables in their surrounding context instead of a direct reference to the variables themselves. So, if the value of any of these referenced variables changed, the closure would be none the wiser and would be operating on the now out-of-date values.

Sometimes, we’ll want this behavior though.

We can specify a capture list in our closure’s definition which will create an immutable read-only copy of the variables we’ve listed. This way, changes made within the closure’s definition will not affect the value of the variables outside of the closure.

var money = Money(value: 20 , currencyCode: "USD")

//Output: Money(value: 20, currencyCode: "USD")
print(money)

let closure = { [money] in
print(money)
}

money = Money(value: 200 , currencyCode: "USD")

//Output: Money(value: 20, currencyCode: "USD")
closure()

//Output: Money(value: 200, currencyCode: "USD")
print(money)

In this example, even though we’re modifying the money variable before we execute the closure, the closure only has access to a copy of the variable’s value - not a reference to the variable itself. So, any changes made to money outside of the closure will not affect the value of money the closure operates on.

In Bullets
  • Closures are reference types: They capture and maintain a reference to variables from their surrounding context.

  • Effect of Closures: Modifications within a closure affect the original variables outside the closure.

  • Capture List: Use variable in a closure to capture a read-only copy of the variable, preventing modifications outside the closure from affecting it.

How do you create interoperability between Objective-C and Swift?

· 3 min read
Ace the iOS Interview
Aryaman Sharda
Sources & Resources

Main Source: đź”— Ace the iOS Interview

Additional Sources:

Further Reading:

TL/DR

If you’re applying for an older company like YouTube, Facebook, or even Google, you’ll likely have to work with both Objective-C and Swift within the same project.

As part of that workflow, it’s useful to know how to expose variables, classes, and methods declared in Swift to the Objective-C runtime and vice-versa.

Exposing Objective-C to Swift To import a set of Objective-C files into Swift code within the same app target, you rely on an Objective-C Bridging Header file to expose those files to Swift. Xcode offers to create this header for you when you add a Swift file to an existing Objective-C app or an Objective-C file to an existing Swift app.

When you accept, Xcode creates the Bridging Header file and names it by using your product module’s name followed by "-Bridging-Header.h" (e.x.“Facebook-Bridging-Header.h”).

In your newly created Bridging Header file, you can specify all of the Objective-C headers you want to expose to Swift like this:

#import "Properties.h"
#import "Auth.h"

Now, these Objective-C entities are automatically available in any Swift file within the same target without the use of any additional import statements.

Exposing Swift to Objective-C Now, what if you wrote a cool extension in Swift that you want to have access to in Objective-C?

You can work with types declared in Swift from within the Objective-C code in your project by importing an Xcode-generated header file (e.x. “ProductModuleName-Swift.h").This file is an Objective-C header that declares the Swift interfaces in your target.

You don’t need to do anything special to use the generated header - just import it in the Objective-C classes as needed:

#import "ProductModuleName-Swift.h"

This header file is managed behind the scenes. As a result, you don’t need to specify each individual class you want to expose to Objective-C like we did in the previous section.

You can think of it as an umbrella header for all of your Swift code.

By default, the generated header contains interfaces for Swift declarations marked with the public or open modifier. If your app target has an Objective-C bridging header, the generated header also includes interfaces for resources marked with the internal modifier. Declarations marked with the private or fileprivate modifier don'tappear in the generated header and aren't exposed to the Objective-C runtime unless they are explicitly marked with a @IBAction, @IBOutlet, or @objcattribute.

How does code signing work?

· 5 min read
Ace the iOS Interview
Aryaman Sharda
Sources & Resources

Main Source: đź”— Ace the iOS Interview

Additional Sources:

Further Reading:

TL/DR

Code signing is a process that helps you verify the authenticity of the app or software you’re installing and ensures that it hasn’t been tampered with since the developer released it. The code signing process consists of a series of smaller steps: requesting a certificate, receiving a certificate, generating a provisioning profile, and finally signing the application.

Let’s start with requesting a certificate.

To request a certificate, we’ll need to create a certificate signing request - a .csr file. When you first set up your Apple Developer account, you will need to use Keychain Access to request a development certificate from Apple - the certificate authority.

A certificate authority is the entity responsible for issuing digital certificates. Simply put, it’s a trusted organization responsible for verifying the authenticity of software and other digital goods.

Since you have most likely already experienced this certificate request flow, I won't provide a tutorial for that process here. Instead, I'd like to discuss what's happening under the hood.

When you start the process of creating your certificate signing request, Keychain Access will generate a public and private key on your local machine. The public key is eventually attached to the .csr file, but the private key never leaves your machine. After this step, Keychain Access will prompt you to provide some basic metadata like name, country, email, etc. thereby completing the creation of the request.

From here, Apple examines the request's properties and metadata to verify who is requesting the certificate. Upon successful validation, Apple will send you back a certificate, which you should store in your machine's keychain. Certificates usually last for a year before expiring and can come in several varieties; iOS App Development, iOS Distribution, Mac App Distribution, Mac Installer Distribution, etc.

Code signing is driven by asymmetric cryptography, which is what enables communication with Apple and powers the signing process. Any explanation of code signing would be incomplete without an explanation of this encryption, so let's take a quick look at how it works.

To start with, both you and Apple have your own set of public and private keys. Now, these keys are cryptographically linked meaning you can use the private key to decrypt a message encrypted with the public key, but you can’t go in the other direction.

After establishing a connection with Apple, we will exchange public keys. The public keys are intended to be shared, but the private keys need to be protected.

So, when you want to send a message to Apple, you encrypt the message with Apple's public key. Then, when Apple receives the message, they’ll be able to decrypt the message using their private key.

This same process happens in reverse when Apple sends the certificate to us; Apple will encrypt the message using our public key and we’ll use our private key to decrypt the message and retrieve the certificate. This process ensures that our full conversation with Apple from requesting to receiving the certificate is secure.

As a next step, the provisioning profile is created, consisting of a few key components:

  • Team ID: A unique identifier for each developmentteam and can be found in your Apple Developer account.
  • Bundle ID: Every iOS app has a unique bundle identifierwhich allows it to be uniquely identified.
  • App ID: The combination of the Team ID and the BundleID.
  • Device ID: The list of all UDIDs (Unique Device Identifier)that your iOS application is authorized to run on. This is a 40 character alphanumeric identifier.
  • Entitlements: This specifies the permissions and capabilitiesof the app along with which system resources the application has permission to access. For example, services like Push Notifications, Apple Pay, App Sandbox, etc.
  • Certificate: The certificate we received from Applein the previous step (iOS App Development, iOS Distribution, Mac App Distribution, Mac installer Distribution, etc.)

In summary, the provisioning profile is composed of the certificate that verifies the authenticity of the software, the App ID that uniquely identifies the application, and its permissions, entitlements, and the exact list of devices that can run the application. It essentially acts as the middle-man between the end devices and the developer account.

We're finally at the last step - code signing. This step involves downloading the provisioning profile from your developer account and embedding it into your application's bundle. Next, the bundle is signed with the certificate we created earlier and can now run on your device.

Here’s what’s happening under the hood:

  1. The certificate referenced in your provisioning profile is compared against the available certificates in your machine’s keychain.
  2. If a valid certificate is found, it is used to sign the executable.
  3. The UDID of the device you are attempting to run the executable on is compared against the UDIDs listed in the provisioning profile.
  4. The Bundle ID and Entitlements are checked against their respective counterparts in the provisioning profile.
  5. If everything goes well, the app is installed on the device.
In Bullets

How would you debug view layout issues?

· 3 min read
Ace the iOS Interview
Aryaman Sharda
Sources & Resources

Main Source: đź”— Ace the iOS Interview

Additional Sources:

Further Reading:

TL/DR

As an iOS developer, layout problems are an inevitability. From breaking constraints to weird UI issues occurring only at runtime (text truncation, alpha value issues, broken animations, etc.), debugging layout issues in iOS can be tricky.

Therefore, it’s crucial to know all of the different approaches to debugging layout issues in iOS.

This is particularly useful during an interview when time is of the essence. Moreover, it's a great way to demonstrate your familiarity with advanced Xcode features.

Debug View Hierarchy

The Debug View Hierarchy pauses the application in its current state thereby providing the programmer an opportunity to inspect and better understand the UI hierarchy of their app.

This view will not only show you an “exploded” 3D version of your view, but it helps you understand the full hierarchy of the view from the top most view controllers all the way down to individual subviews,UILabels,UIImageViews, etc.Additionally, this tool will also highlight anyUIView’s with runtime constraint errors.

There’s a lot of functionality here, so I’d recommend spending some time playing around with it if you’re unfamiliar. It can help you catch breaking constraints, clipped views, and a variety of other layout issues.

Customizing Constraint Identifiers

Troubleshooting constraint issues is particularly challenging because the error messages are not very user-friendly.

To make things easier, we can leverage theidentifierproperty on a NSLayoutConstraint.

This property is available to use regardless of whether the constraint is defined through a .storyboard, .xib, or programmatically.

var bannerWidthConstraint: NSLayoutConstraint?
bannerWidthConstraint.identifier = "Promotional bannerwidth"

A custom identifier makes it easier for you to distinguish between system-generated and user-generated constraints in Debug Logs.

By leveraging custom identifiers, the Debugger output will now contain clearer error messages which will make it much easier to track down and resolve layout issues.

Without Identifier:

Will attempt to recover by breaking constraint

<NSLayoutConstraint:0x7a87b000 H:[UILabel:0x7a8724b0'Name'(>=400)]>

With Identifier:

Will attempt to recover by breaking constraint

<NSLayoutConstraint:0x7b56d020 'Label Width' H:[UILabel:0x7b58b040'Name'(>=400)]>

As you can see, these identifiers allow you to quickly and easily identify specific constraints in the log output.

exerciseAmbiguityInLayout()

This method randomly changes the frame of a view with an ambiguous layout between its different valid values, causing the view to move in the interface. This makes it easy to visually identify what the different valid frame configurations are and helps the developer understand what constraints need to be added to the layout to correctly and fully specify the layout of the view.

This method should only be used for debugging purposes; no application should ship with calls to this method.

Developer Tool: đź”— https://www.wtfautolayout.com/

If you’re having difficulty making sense of the Debugger’s “breaking constraint” output, you can use this site to help you easily visualize the problem.

Is an enum a value type or a reference type?

· One min read
Ace the iOS Interview
Aryaman Sharda
Sources & Resources

Main Source: đź”— Ace the iOS Interview

Additional Sources:

Further Reading:

TL/DR

In Swift, an enum is a value type (other value types include structs and tuples).

In the following example, you’ll see that changes made to one variable have no effect on the other.

This is because the value is simply copied from one variable to another - they don’t point to the same location in memory.

enum CarBrands {
case porsche
case mercedes
}

var porsche = CarBrands.porsche
var mercedes = porsche

porsche = .mercedes

print(porsche) // Output: mercedes
print(mercedes) // Output: porsche

What are generics and what problem do they solve?

· 3 min read
Ace the iOS Interview
Aryaman Sharda
Sources & Resources

Main Source: đź”— Ace the iOS Interview

Additional Sources:

Further Reading:

TL/DR

Generic classes and functions not only allow you to write more reusable code, but they also help you write less code overall. In simple terms, generics allow you to write the code once, but apply it to a variety of different data types.

While the syntax appears daunting at first, after some practice, generics become much more approachable.

Consider a function,exists(), which simply checksif some specific value exists within an array of elements:

func exists<T: Equatable>(item:T, elements:[T]) -> Bool

This function declaration states that we are going to pass something intoexists()that conforms to Equatable(see <T: Equatable>). We don't know what entity that will be yet, but we promise it will implement the Equatable protocol when the time comes.

We use the variable T to represent this placeholdertype. The method declaration then continues on to say that the remaining parameters will also expect some input matching the type of T.

With this method, we can use an input array of anyEquatabletype and easily check if it contains the target element -item. We are free touse any custom objects, structs, primitive data types, or anything else that implementsEquatable.

Since the items in elements all implement the Equatable protocol, we can simply use == to check for equality:

// Here's our generic exists() function
func exists<T: Equatable>(item:T, elements:[T]) -> Bool {
for element in elements {
if item == element {
return true
}
}

return false
}

// The main takeaway here is that we've written the code once, but

// we can apply it over a wide variety of data types.

// Output: true
exists(item: "1", elements: ["1", "2", "3", "4"]))

// Output: false
exists(item: - 1 , elements: [ 1 , 2 , 3 , 4 ])

// Output: true
exists(item: CGPoint(x: 0 , y: 0 ), elements: [CGPoint(x: 0 , y: 0 ),
CGPoint(x: 0 , y: 1 ),
CGPoint(x: 0 , y: 2 )])

Here's an advanced Swift example:

extension UIView {
// We're saying that `T` is eventually going to be a `UIView`.
//
// It could be a `UIButton`, `UIImageView`, or
// an ordinary `UIView` - it doesn't matter.
//
// Now, we can load any `UIView` or a subclass from a `.nib`.

class func fromNib<T: UIView>() -> T {
return Bundle(for: T.self).loadNibNamed(String(describing:T.self), owner: nil, options: nil)![ 0 ] as! T
}
}

// Usage: let view = RestaurantView.fromNib()
// Usage: let cell = PictureCell.fromNib()
// Usage: let photoGallery = GalleryView.fromNib()

What are our options for storage and persistence?

· One min read
Ace the iOS Interview
Aryaman Sharda
Sources & Resources

Main Source: đź”— Ace the iOS Interview

Additional Sources:

Further Reading:

TL/DR

Some of the options include:

User Defaults

We can useUserDefaultsto store simple key-value pairs in an insecure manner. Typically, you would only use UserDefaults to store something lightweightlike a user setting.

.plist

We can use a .plist to store larger data sets. It’s a really flexible human-readable format.

Keychain

This is the only encrypted persistent storage option available on iOS and is used for storing highly sensitive key-value pairs (primarily credentials).

Disk Storage

We can serialize data, domain models, or other downloaded content and save them directly to disk.

Core Data / SQLite

Useful in cases where we have larger data sets and are interested in making queries on the data.