swift - forceUnwrap https://forceunwrap.com Lift your Swift skills & career to the next level Thu, 24 Aug 2023 16:39:19 +0000 en-US hourly 1 https://wordpress.org/?v=7.0 https://forceunwrap.com/wp-content/uploads/2023/03/cropped-ForceUnwrap-32x32.jpg swift - forceUnwrap https://forceunwrap.com 32 32 Swift’s print vs dump: A Deep Dive https://forceunwrap.com/swifts-print-vs-dump-a-deep-dive/ https://forceunwrap.com/swifts-print-vs-dump-a-deep-dive/#respond Thu, 24 Aug 2023 08:09:39 +0000 https://forceunwrap.com/?p=456 Swift's print offers clean, user-friendly output, often needing customization. In contrast, dump provides deep introspection, ideal for debugging.

The post Swift’s print vs dump: A Deep Dive first appeared on forceUnwrap.

]]>
Swift, Apple’s powerful and expressive language, is full of nuances that can either make your life easier or add a layer of complexity. Two seemingly simple yet profoundly distinct functions in Swift are print and dump. At first glance, both appear to display information, but when you delve deeper, you’ll see that they serve very different purposes. Using the provided sample code, let’s explore their differences.

Sample Code Overview

First, let’s dissect the provided sample:

class Person {
    let name: String
    let surname: String
    let age: Int

    init(name: String, surname: String, age: Int) {
        self.name = name
        self.surname = surname
        self.age = age
    }
}

let person = Person(name: "John", surname: "Doe", age: 21)

Here, we have a simple Person class that stores an individual’s name, surname, and age. An instance of this class, person, is initialized with the values “John”, “Doe”, and 21, respectively.

The print Function

Let’s first discuss the print function:

print("--- Print ---")
print(person)

When you run this, you might see something like:

--- Print ---
Person

The print function displays the textual representation of the passed argument. For most Swift standard library types, this function will show a useful and clear representation. For example, if you pass a string or an array to print, it will pretty much show you the content as you’d expect.

However, when you pass a custom object, like our Person instance, you get a default representation (like Person) which isn’t particularly useful.

To make print more informative for custom types, you’d typically have to implement the CustomStringConvertible protocol and provide a description computed property:

extension Person: CustomStringConvertible {
    var description: String {
        return "\(name) \(surname), \(age) years old"
    }
}

With this in place, print(person) would output:

John Doe, 21 years old

The dump Function

Now, let’s see what happens when we use the dump function:

print("--- Dump ---")
dump(person)

You’ll probably get output like this:

--- Dump ---
▿ Person
  - name: John
  - surname: Doe
  - age: 21

The dump function provides a detailed breakdown of the passed argument. In this case, it shows the internal properties of the Person instance.

This function is especially useful for debugging purposes. You don’t need to implement any additional protocols or provide a custom description. It just inspects the object and presents you with a more in-depth view of its contents.

Performance of print vs dump

Another aspect worth examining between print and dump is their performance impact. While for most casual use cases you might not notice a significant difference, when dealing with large datasets or frequently called operations, understanding the relative costs can be beneficial.

print Performance

The print function, especially when you’re using the default or custom string representations, tends to be lighter on performance. It simply takes an object’s representation and writes it to the standard output. When dealing with simple and small data types, the overhead introduced by print is minimal.

However, when you implement the CustomStringConvertible protocol to give a custom representation, there’s potential overhead depending on the complexity of the description computed property. The more operations and concatenations you perform, the slower it can become, especially if called frequently.

dump Performance

The dump function, being more introspective, can be a bit heavier on performance. It delves deep into the object, revealing not just top-level properties but also nested structures. This deeper dive can introduce more computational overhead, especially when dealing with complex and large objects or hierarchies.

In practice, using dump repeatedly on large structures might slow down your debugging sessions or, if mistakenly used in production code, your application’s performance. It’s designed for detailed introspection, and this depth comes at a cost.

dump and String Interpolation

An interesting quirk about the dump function is its behavior with string interpolation. When you attempt to use dump on a string that contains nested objects via interpolation, such as dump("Test: \(person)"), it doesn’t dissect the object within the string as you might expect. Instead, it treats the whole content similarly to how print would, displaying the string as-is without deeper introspection of the nested object. This can be misleading if you’re expecting the detailed breakdown that dump typically offers. When using dump, it’s best to directly pass the object of interest rather than nesting it inside a string to ensure you get the detailed output you’re seeking.

Structs and Readability in Swift

In Swift, structs enjoy a level of readability that often surpasses that of classes, especially when it comes to debugging and logging. Due to the way Swift’s compiler auto-synthesizes certain functionalities for structs, when you print a struct instance (even when nested inside a string), you usually get a clear and readable output without needing the introspective depth of dump. For example, executing print("Test: \(person)") with a struct version of our Person would yield a detailed output, showcasing all its properties. This inherent readability of structs provides an advantage in clarity, often rendering the use of dump unnecessary.

Recommendations

For general logging and display purposes in production, stick to print. If you need detailed introspection for debugging, use dump but be wary of its performance impact on larger structures. Always test performance in scenarios that mirror real-world usage, especially if logging or data display operations are frequent.

In conclusion, while both print and dump have their unique strengths, it’s essential to be mindful of their performance implications in specific scenarios.

Conclusion

While both print and dump allow you to display information about objects and data in Swift, they serve slightly different purposes:

  • print is for presenting a clean, human-readable representation, often requiring some customization for custom types.
  • dump is more for debugging and introspection, offering a detailed view of the internals of an object or data structure without any additional work on your part.

By understanding and using these functions appropriately, you can make your Swift development and debugging processes much smoother.

The post Swift’s print vs dump: A Deep Dive first appeared on forceUnwrap.

]]>
https://forceunwrap.com/swifts-print-vs-dump-a-deep-dive/feed/ 0
Swift multiple trackers architecture with SOLID – Part 2 https://forceunwrap.com/swift-multiple-trackers-architecture-with-solid-part-2/ https://forceunwrap.com/swift-multiple-trackers-architecture-with-solid-part-2/#respond Sun, 19 Mar 2023 18:06:12 +0000 https://forceunwrap.com/?p=371 In part 1, we developed code to implement an infrastructure that enables multiple trackers. In part 2, we will refactor our code based on SOLID principles to improve its design.

SOLID principles consist of five guidelines that help developers create code that is easy to maintain, extend and test. These principles include:

  • Single Responsibility Principle (SRP): Our code follows SRP as the TrackerStore class is responsible for storing and registering trackers, while the Tracker class is responsible for implementing tracking functions.
  • Open/Closed Principle (OCP): Our code follows OCP by allowing the system to be extended without modifying existing code, thanks to the TrackerConfigurable protocol that enables/disables trackers through configuration.
  • Liskov Substitution Principle (LSP): Our code follows LSP as the AdobeTracker, BrazeTracker, and SumoTracker classes implement their respective protocols without violating the behavior expected by their supertypes.
  • Interface Segregation Principle (ISP): Our code follows ISP by using separate protocols for each set of related functions (PlaybackTrackable, NavigationTrackable, OptionalNavigationTrackable) instead of one large protocol.
  • Dependency Inversion Principle (DIP): Our code follows DIP as the Tracker class depends on abstractions (protocols) rather than concrete types, allowing for easier testing and swapping of implementations.
enum TrackerType {
    case braze, adobe, sumo
}

extension TrackerType {
    var isEnabled: Bool {
        return TrackerConfig.enabledTrackers.contains(self)
    }
}

// DIP
// Client depends on abstractions
protocol Trackable {
    var type: TrackerType { get }
}

// OCP
// Enable/Disable trackers through configuration
protocol TrackerConfigurable {
    static var enabledTrackers: [TrackerType] { get }
}

struct TrackerConfig: TrackerConfigurable {
    static let enabledTrackers = [TrackerType.braze, TrackerType.adobe]
}


// SRP
// Tracker class is responsible for storing and registering trackers
final class TrackerStore {
    private(set) var trackers = [Trackable]()

    func registerTracker(tracker: Trackable) {
        if !trackers.contains(where: { $0.type == tracker.type }) {
            trackers.append(tracker)
        } else {
            print("🔥🔥🔥 We don't want duplicates 🔥🔥🔥")
        }
    }
}

// Tracker class is responsible for implementing tracking functions
class Tracker {
    private let trackerStore: TrackerStore

    init(trackerStore: TrackerStore) {
        self.trackerStore = trackerStore
    }
}

extension Tracker: PlaybackTrackable {
    private var playbackTrackers: [PlaybackTrackable] {
        trackerStore.trackers
            .filter { $0.type.isEnabled }
            .compactMap { $0 as? PlaybackTrackable }
    }

    func trackPlay() {
        playbackTrackers
            .forEach { $0.trackPlay() }
    }

    func trackPause() {
        playbackTrackers
            .forEach { $0.trackPause() }
    }

    func trackStop() {
        playbackTrackers
            .forEach { $0.trackStop() }
    }

    func trackNext() {
        playbackTrackers
            .forEach { $0.trackNext() }
    }

    func trackPrevious() {
        playbackTrackers
            .forEach { $0.trackPrevious() }
    }
}

extension Tracker: NavigationTrackable {
    private var navigationTrackers: [NavigationTrackable] {
        return trackerStore.trackers
            .filter { $0.type.isEnabled }
            .compactMap { $0 as? NavigationTrackable }
    }

    func trackNavigate(toView: String, fromView: String) {
        navigationTrackers
            .forEach { $0.trackNavigate(toView: toView, fromView: fromView) }
    }

    func trackBackTap(fromView: String) {
        navigationTrackers
            .forEach { $0.trackBackTap(fromView: fromView) }
    }

    func trackSettingsOpen(fromView: String) {
        navigationTrackers
            .forEach { $0.trackSettingsOpen(fromView: fromView) }
    }
}

extension Tracker: OptionalNavigationTrackable {
    private var optionalNavigationTrackers: [OptionalNavigationTrackable] {
        return trackerStore.trackers
            .filter { $0.type.isEnabled }
            .compactMap { $0 as? OptionalNavigationTrackable }
    }

    func optionalTrackTap() {
        optionalNavigationTrackers
            .forEach { $0.optionalTrackTap() }
    }
}

protocol PlaybackTrackable {
    func trackPlay()
    func trackPause()
    func trackStop()
    func trackNext()
    func trackPrevious()
}

// ISP
// Separate protocols for each function
protocol NavigationTrackable {
    func trackNavigate(toView: String, fromView: String)
    func trackBackTap(fromView: String)
    func trackSettingsOpen(fromView: String)
}

// LSP
// Separate protocol for optional function
protocol OptionalNavigationTrackable {
    func optionalTrackTap()
}

class AdobeTracker: Trackable {
    let type = TrackerType.adobe
}

extension AdobeTracker: NavigationTrackable {
    func trackNavigate(toView: String, fromView: String) {
        // Specific implementation
    }

    func trackBackTap(fromView: String) {
        // Specific implementation
    }

    func trackSettingsOpen(fromView: String) {
        // Specific implementation
    }
}

extension AdobeTracker: OptionalNavigationTrackable {
    func optionalTrackTap() {
        // Specific implementation
    }
}

class BrazeTracker: Trackable {
    let type = TrackerType.braze
}

extension BrazeTracker: NavigationTrackable {
    func trackNavigate(toView: String, fromView: String) {
        // Specific implementation
    }

    func trackBackTap(fromView: String) {
        // Specific implementation
    }

    func trackSettingsOpen(fromView: String) {
        // Specific implementation
    }
}

class SumoTracker: Trackable {
    let type = TrackerType.sumo
}

extension SumoTracker: OptionalNavigationTrackable {
    func optionalTrackTap() {
        // Specific implementation
    }
}

By following SOLID principles, we have created a well-structured code that is maintainable, extensible, and testable.

The post Swift multiple trackers architecture with SOLID – Part 2 first appeared on forceUnwrap.

]]>
https://forceunwrap.com/swift-multiple-trackers-architecture-with-solid-part-2/feed/ 0
Copy on write iOS developer interview question explained https://forceunwrap.com/copy-on-write-interview-question-explained/ https://forceunwrap.com/copy-on-write-interview-question-explained/#respond Wed, 15 Mar 2023 12:20:53 +0000 https://forceunwrap.com/?p=327
var firstArray = [1, 2, 3, 4, 5]    // [1, 2, 3, 4, 5]
var secondArray = firstArray        // [1, 2, 3, 4, 5]

print(firstArray == secondArray)    // true

secondArray.append(6)               // [1, 2, 3, 4, 5, 6]

print(firstArray.count)             // 5
print(secondArray.count)            // 6

print(firstArray == secondArray)    // false

In Swift, when an array is assigned to another variable, a copy of the array is not created immediately. Instead, the new variable refers to the same memory location as the original array. This means that any changes made to the new variable are reflected in the original array as well.

However, Swift also uses a technique called Copy-On-Write (COW) to optimize memory usage. When a mutable operation is performed on an array (e.g. adding or removing an element), Swift checks whether the array has more than one reference to its memory location. If it does, Swift creates a new copy of the array before performing the mutation, so that the original array remains unchanged.

In the code provided, firstArray is created with the values [1, 2, 3, 4, 5], and then secondArray is assigned to firstArray. Since secondArray is not mutated at this point, it still refers to the same memory location as firstArray.

The first print statement outputs true because firstArray and secondArray both contain the same values.

Next, the value 6 is appended to secondArray. Since secondArray now has more than one reference to its memory location, Swift creates a new copy of the array before appending the value. This means that firstArray remains unchanged, and the print statement that outputs 5 shows that firstArray still has 5 elements.

The post Copy on write iOS developer interview question explained first appeared on forceUnwrap.

]]>
https://forceunwrap.com/copy-on-write-interview-question-explained/feed/ 0
Probably the best guide to Swift Timer https://forceunwrap.com/probably-the-best-guide-to-swift-timer/ https://forceunwrap.com/probably-the-best-guide-to-swift-timer/#respond Tue, 30 Aug 2022 13:20:18 +0000 https://forceunwrap.com/?p=254 Swift’s Timer class allows us to schedule work to be repeated or run once. In SwiftUI, we can use the Timer.publish method to create a timer that runs every second and updates a state variable. Once the timer has run 10 times, we cancel it using the timer’s upstream connection.

import SwiftUI

struct TimersSwiftUIPlayground: View {
    private let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
    
    @State private var timerRunCount = 0
    
    var body: some View {
        ScrollView {
            Text(String(timerRunCount))
                .onReceive(timer) { _ in
                    timerRunCount += 1
                    
                    guard timerRunCount > 9 else { return }
                    timer.upstream.connect().cancel()
                }
        }
    }
}

Non-repeating Timer

For non-repeating timers, we can use either a closure or a method selector to define the code to be executed. Similarly, we can create repeating timers using the scheduledTimer method and specifying the time interval between each execution.

// Timer with closure
let timer1 = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: false) { timer in
    print("forceUnwrap.com - have a nice day")
}

// Timer calling method
let timer2 = Timer.scheduledTimer(
    timeInterval: 1.0,
    target: self,
    selector: #selector(fireTimer),
    userInfo: nil,
    repeats: false
)

@objc
func fireTimer() {
    print("forceUnwrap.com - you will learn something new today")
}

Repeating Timer

// Timer with closure
let timer1 = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { timer in
    print("forceUnwrap.com - You are beautiful")
}

// Timer calling method
let timer2 = Timer.scheduledTimer(
    timeInterval: 1.0,
    target: self,
    selector: #selector(fireTimer),
    userInfo: nil,
    repeats: true
)

@objc
func fireTimer() {
    print("forceUnwrap.com - You will learn something new today")
}

Termination

It’s a good practice to store timers in properties so we can terminate them when needed. We can do this by calling the timer’s invalidate method or setting its value to nil. Additionally, we can use the tolerance property to optimize for power savings and responsiveness.

// Timer with closure
var timerRunCount = 0

Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { timer in
    print("forceUnwrap.com -> You can do this ")
    timerRunCount += 1
    
    guard timerRunCount == 5 else { return }
    timer.invalidate()
}

// Timer calling method
var timer: Timer?
var timerRunCount = 0

let timer = Timer.scheduledTimer(
    timeInterval: 1.0,
    target: self,
    selector: #selector(invalidateTimerIfNeeded),
    userInfo: nil,
    repeats: true
)

@objc
private func invalidateTimerIfNeeded() {
    print("forceUnwrap.com -> You can do this ")
    timerRunCount += 1
    
    guard timerRunCount == 5 else { return }
    timer?.invalidate()
}

Tolerance

Tolerance helps the system to optimize for increased power savings and responsiveness. Apple documentation.

let timer = Timer.scheduledTimer(
    timeInterval: 1.0,
    target: self,
    selector: #selector(fireTimer),
    userInfo: nil,
    repeats: true
)
timer.tolerance = 0.3

@objc
private func fireTimer() {
    print("forceUnwrap.com")
}

Run loops

When creating timers on the main thread, we may encounter issues with user interaction and UI updates. To avoid these problems, we can use the RunLoop.current method to add the timer to the main thread’s run loop.

Timer issue with user interaction
let timer = Timer.scheduledTimer(
    timeInterval: 1.0,
    target: self,
    selector: #selector(fireTimer),
    userInfo: nil,
    repeats: true
)
RunLoop.current.add(timer, forMode: .common)

@objc
private func fireTimer() {
    print("forceUnwrap.com")
}
Timer solution

Sync with screen update

Alternatively, we can sync the timer with the display refresh rate using the CADisplayLink class.

let displayLink = CADisplayLink(target: self, selector: #selector(update))
displayLink.add(to: .current, forMode: .default)

@objc
private func update() {
    print("forceUnwrap.com")
}

Overall, Timer is a useful class for scheduling and executing code on a periodic basis in Swift.

The post Probably the best guide to Swift Timer first appeared on forceUnwrap.

]]>
https://forceunwrap.com/probably-the-best-guide-to-swift-timer/feed/ 0
Pass some View as parameter to another View https://forceunwrap.com/pass-some-view-as-parameter-to-another-view/ https://forceunwrap.com/pass-some-view-as-parameter-to-another-view/#respond Fri, 26 Aug 2022 10:53:43 +0000 https://forceunwrap.com/?p=237
import SwiftUI

struct CustomNavView<Content: View>: View {
    let content: Content
    let title: String
    
    init(title: String, @ViewBuilder content: () -> Content) {
        self.content = content()
        self.title = title
    }
    
    var body: some View {
        NavigationLink(destination: self.content) {
            Text(title)
        }
        .padding()
    }
}

Usage:

CustomNavView(title: "Picker sample in SwiftUI") {
    EmptyView()
}

Changelog:

// Good stuff. 🙇‍♂
git commit -m "Update to trailing closure as head_of_roses recommends" 

The post Pass some View as parameter to another View first appeared on forceUnwrap.

]]>
https://forceunwrap.com/pass-some-view-as-parameter-to-another-view/feed/ 0
Picker using struct in SwiftUI https://forceunwrap.com/picker-using-struct-in-swiftui/ https://forceunwrap.com/picker-using-struct-in-swiftui/#comments Sun, 07 Aug 2022 11:50:51 +0000 https://forceunwrap.com/?p=212 I have observed that there are numerous tutorials online that explain how to set up Picker in SwiftUI. However, most of them utilize a simple string array for data, which is not sufficient for more complex scenarios. Instead, you should use a struct or class. In this example, I will demonstrate how to use an array of custom structs.

Here is a sample code that shows how to create a Picker with custom struct data:

import SwiftUI

struct ContentView: View {
    private var cars = [
        Car(brand: "BMW", plates: "AAA111"),
        Car(brand: "Toyota", plates: "BBB222"),
        Car(brand: "Kia", plates: "CCC222")
    ]

    @State private var selectedCar = ""

    var body: some View {
        VStack {
            Picker("Car", selection: $selectedCar) {
                ForEach(cars, id: \.brand) {
                    Text("\($0.brand) \($0.plates)")
                }
            }
            Text("You selected: \(selectedCar)")
        }
    }
}

struct Car: Hashable, Identifiable {
    var id: String {
        return brand + plates
    }
    
    let brand: String
    let plates: String
}

In this example, the Car struct is used as the data source for the Picker. The selectedCar variable is now of type Car? to hold the selected car, instead of a String. Also, the id property has been removed from the Car struct because it is no longer necessary.

The accessibility modifier is used to provide an invisible label to the Picker for VoiceOver users. This ensures that the Picker is accessible to all users.

Finally, I removed the id argument in the ForEach because the Car struct is now identifiable by default. Therefore, you can use car directly instead of car.brand to reference the selected Car object.

            Picker("Car", selection: $selectedCar) {
                ForEach(cars) {
                    Text("\($0.brand) \($0.plates)")
                }
            }

The post Picker using struct in SwiftUI first appeared on forceUnwrap.

]]>
https://forceunwrap.com/picker-using-struct-in-swiftui/feed/ 2
Line breaks – multiple guard statements https://forceunwrap.com/line-breaks-multiple-guard/ https://forceunwrap.com/line-breaks-multiple-guard/#respond Fri, 17 Jun 2022 08:55:12 +0000 https://forceunwrap.com/?p=171 Proper line breaking makes a difference. Don’t put everything in one line. The usual character limit is 120 symbols per line. Put each statement in new line. else could be on a separate line.

Pro tip:
I prefer to put in on a new line for better readability. In my experience I have noticed, that putting else on a new line, helps spoting start and end of guard without reading the context inside the guard.

// Don't
guard
		let clientID = Bundle.main.object(forInfoDictionaryKey: "ApiClientID") as? String,
		let serverClientID = Bundle.main.object(forInfoDictionaryKey: "ApiServerClientID") as? String
else {
		return
}

// Don't
guard let clientID = Bundle.main.object(forInfoDictionaryKey: "ApiClientID") as? String, let serverClientID = Bundle.main.object(forInfoDictionaryKey: "GoogleServerClientID") as? String else { return }

// Do
guard let clientId = Bundle.main.object(forInfoDictionaryKey: "ApiClientID") as? String,
			let serverClientId = Bundle.main.object(forInfoDictionaryKey: "ApiServerClientID") as? String
else {
		return
}

How do you line break guard with multiple statements? Share in the comments below.

The post Line breaks – multiple guard statements first appeared on forceUnwrap.

]]>
https://forceunwrap.com/line-breaks-multiple-guard/feed/ 0