forceUnwrap - forceUnwrap https://forceunwrap.com Lift your Swift skills & career to the next level Thu, 24 Aug 2023 17:18:18 +0000 en-US hourly 1 https://wordpress.org/?v=7.0 https://forceunwrap.com/wp-content/uploads/2023/03/cropped-ForceUnwrap-32x32.jpg forceUnwrap - forceUnwrap https://forceunwrap.com 32 32 The Art of Constructive Code Review: Why “It’s Sh*t Code” Doesn’t Cut ItπŸŽ¨πŸ’»πŸ”πŸš« https://forceunwrap.com/the-art-of-constructive-code-review-why-its-shit-code-doesnt-cut-it/ https://forceunwrap.com/the-art-of-constructive-code-review-why-its-shit-code-doesnt-cut-it/#respond Thu, 24 Aug 2023 17:11:48 +0000 https://forceunwrap.com/?p=464 Embark on a humorous yet insightful journey through the world of code reviews. Discover the good, the bad, and the downright funny feedback developers face, and learn the art of giving constructive comments that elevate code quality and team morale.

The post The Art of Constructive Code Review: Why β€œIt’s Sh*t Code” Doesn’t Cut ItπŸŽ¨πŸ’»πŸ”πŸš« first appeared on forceUnwrap.

]]>
Code reviews: that special time when developers come together, coffee in hand, hoping for the best but expecting the worst. It’s a time of bonding… or breaking. All depends on the feedback!

When Feedback Turns Comical (But not in a good way) πŸ€¦β€β™‚οΈ

We’ve all been there. The long-awaited moment when your code goes under the microscope, and you get gems like:

  • “This is garbage.” πŸ—‘
  • “Sh*t code!” πŸ’©
  • “Do you even know how to code?” 🧐
  • “You must be joking with this pull request.” πŸ˜‚
  • “This looks like spaghetti code thrown against a wall.” 🍝
  • “You’ve just set the project back by weeks.” πŸ“†βͺ

Decoding “It’s Sh*t Code” πŸ”

When faced with such cryptic feedback as “It’s Sh*t Code”, a developer might wonder:

  • Lack of Guidance: This phrase doesn’t illuminate the problem, nor does it suggest ways to rectify it. ❓
  • Morale Impact: Sharp criticisms can sap motivation, leading to dwindling enthusiasm for the task at hand. 😞
  • Clarity Concerns: “Sh*t” refers to…? πŸ€” Without context, it’s hard to pinpoint what “sh*t” refers to. Is it about the structure, the logic, or something else?
  • Professionalism: Using crude language is unprofessional and can degrade the team culture and trust. 🚫

The Gentle Art of “Not Making Your Colleague Cry” 😒➑😊

Ideal feedback during a code review should be:

  • Explicit: Less “What is this mess?” More “This section could use a comment for clarity.” ✍
  • Neutral-Toned: Keep the focus on the code, separating it from the coder. πŸ‘©β€πŸ’»πŸ”
  • Solution-Oriented: Instead of just highlighting a problem, suggest a potential fix. πŸ’‘

The Beauty of Constructive Zingers ✨

Constructive feedback not only points out issues but also guides towards improvement:

  • Instead of: “This code is messy.”
    Suggest: “Consider breaking this function into smaller ones to improve readability and maintainability.” πŸ‘“
  • Instead of: “This section seems off.”
    Suggest: “Maybe we can modularize this part for better clarity.” 🧱
  • Instead of: “The chosen algorithm seems slow.”
    Suggest: “Given our requirements, perhaps [algorithm] might offer faster performance.” πŸš€
  • Instead of: “I can’t follow this logic.” or “This part is confusing.”
    Suggest: “Maybe we can modularize this part for better clarity.” πŸ€·β€β™‚οΈβž‘πŸ€“

Embracing the Benefits of Thoughtful Feedback 🌈

  • Elevated Code Standards: Better feedback, better code, better coffee breaks. β˜•πŸ˜Œ
  • Positive Team Environment: Less side-eye, more high-fives. πŸ™„βž‘πŸ™Œ
  • Personal Growth: We’re all here to level up, right? 🌱➑🌳

Share Your Hall of Fame… or Shame πŸ˜…

What’s the most “creative” feedback you’ve received? The funnier, the better. Let’s laugh (or cry) together and create a more constructive code review culture. πŸ˜‚πŸ˜­

To Infinity and Beyond πŸš€

Code reviews: the final frontier in a developer’s journey. Let’s make it less about surviving and more about thriving, one constructive comment at a time. 🌟🌌

The post The Art of Constructive Code Review: Why β€œIt’s Sh*t Code” Doesn’t Cut ItπŸŽ¨πŸ’»πŸ”πŸš« first appeared on forceUnwrap.

]]>
https://forceunwrap.com/the-art-of-constructive-code-review-why-its-shit-code-doesnt-cut-it/feed/ 0
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
How to fix committing to the wrong Git branch? https://forceunwrap.com/how-to-fix-committing-to-the-wrong-git-branch/ https://forceunwrap.com/how-to-fix-committing-to-the-wrong-git-branch/#respond Fri, 18 Aug 2023 12:34:01 +0000 https://forceunwrap.com/?p=453 Committing to the wrong Git branch can be a daunting mistake for developers, but fear not! Explore a step-by-step guide to navigate and rectify such mishaps, ensuring your code remains organized and your repository unscathed.

The post How to fix committing to the wrong Git branch? first appeared on forceUnwrap.

]]>
It’s a scenario that most developers face at some point in their careers: you’ve just made a commit, only to realize moments later that you’ve committed to the wrong Git branch. Panic might set in as you worry about how to undo this mistake without causing more confusion or messing up the repository. Fear not, because Git offers tools to help you correct such mistakes. Here’s a step-by-step guide to help you navigate this situation.

Step 1: Safely Undo the Last Commit

First, make sure that you haven’t pushed your changes to a remote repository. If you have, it becomes a bit more complicated, especially if other people have pulled from that branch in the meantime.

If you haven’t pushed the commit, you can use the following command to revert it:

git reset --soft HEAD~1

Here’s what this command does:

  • git reset: This is a command that allows you to reset your current HEAD to a specified state.
  • --soft: This option ensures that the changes in the wrongfully committed files are retained in the index. In other words, they will be staged and ready to be committed again.
  • HEAD~1: This refers to the commit before the latest one. By resetting to this commit, you’re effectively undoing the last commit.

Once executed, your commit will be undone, but the changes will still be there, staged and waiting for a new commit.

Step 2: Switch to the Correct Branch

Before you can commit your changes to the correct branch, you need to check out that branch. Use the following command, replacing correct-branch-name with the name of the branch where you intended to make your commit:

git checkout correct-branch-name

If you don’t already have this branch locally, you can create and switch to it using:

git checkout -b correct-branch-name

Step 3: Commit Your Changes

Now that you’re on the right branch, you can commit your changes. Since they are already staged (thanks to the --soft flag we used earlier), you can simply commit them:

git commit -m "Your commit message here"

Replace “Your commit message here” with an appropriate message for your commit.

Step 4: Verify Everything is Correct

Always double-check your work. Ensure you’re on the right branch and that your commit has been applied correctly:

git log --oneline

This command will display the commit history in a concise format. Your most recent commit should be at the top, and you should be on the correct branch.

Conclusion

Mistakes happen, and committing to the wrong Git branch is a common one. Thankfully, Git is a powerful tool that provides ways to fix such issues with ease. Always remember to double-check your branch before committing, and should you find yourself having committed to the wrong branch, simply follow the steps above to rectify the situation.

The post How to fix committing to the wrong Git branch? first appeared on forceUnwrap.

]]>
https://forceunwrap.com/how-to-fix-committing-to-the-wrong-git-branch/feed/ 0
Xcode 14 not showing errors https://forceunwrap.com/xcode-14-not-showing-errors/ https://forceunwrap.com/xcode-14-not-showing-errors/#respond Fri, 09 Jun 2023 09:26:56 +0000 https://forceunwrap.com/?p=443 As a passionate advocate for Apple products, I am thoroughly convinced that the Macs with the M1 chip have sparked a technological revolution. This is primarily because, for the second time, we have a Mac model that doesn’t necessitate annual replacement with a newer version. The Apple engineering team consistently strives to deliver the best software experience possible. However, my supposition is that they’re likely under pressure from Product teams and the ceaseless demand for rapid feature introductions. Consequently, due to this accelerated pace, we as software users sometimes experience the unintended consequences of features not functioning as optimally as they should.

Case in point, Xcode versions 14.3 and 14.3.1 appear to have compromised live issue functionality. This results in false errors being displayed, while genuine errors flicker briefly before vanishing. Unfortunately, I haven’t found the ideal solution for this, but I have discovered a viable temporary fix, at least until Apple addresses the issue.

The immediate workaround involves disabling the “Show live issues” feature.

Here’s how to do it:
– navigate to Xcode,
– select Settings,
– go to General, and then
– deselect the “Show live issues” checkbox.

This should serve as a provisional remedy, pending a permanent fix from Apple.

The post Xcode 14 not showing errors first appeared on forceUnwrap.

]]>
https://forceunwrap.com/xcode-14-not-showing-errors/feed/ 0
Undo the most recent local commits in Git https://forceunwrap.com/undo-the-most-recent-local-commits-in-git/ https://forceunwrap.com/undo-the-most-recent-local-commits-in-git/#respond Fri, 21 Apr 2023 12:03:29 +0000 https://forceunwrap.com/?p=402 In software development, Git is a vital tool for tracking changes, collaborating with others, and managing code versions. Maintaining good coding practices is crucial to ensuring that the codebase remains organized and updated. However, developers sometimes forget to rebase their feature branch with the main/develop branch, resulting in multiple conflicts during code integration. This article will guide you through the steps to rebase your feature branch and squash all the changes into a single commit, simplifying the rebase process and avoiding the need to resolve conflicts in every conflicting commit.

The post Undo the most recent local commits in Git first appeared on forceUnwrap.

]]>
Git is an essential tool for software development, allowing developers to track changes, collaborate with others, and manage code versions. One of the critical practices in Git is regularly updating your feature branch with the changes made in the main/develop branch. Doing so minimizes conflicts during code integration and ensures that your code stays up-to-date with the latest changes.

However, it’s not uncommon for developers to forget to rebase their feature branch and end up with multiple conflicts. So, what should you do in such a situation?

Here are the steps to rebase and skip conflict-solving in every conflicting commit:

Step 1: Ensure Your Local Develop Branch is Up-to-Date

Before starting, make sure that your local develop branch is up-to-date with the remote repository’s develop branch by using the following commands:

git checkout develop
git pull

This ensures that you have the latest changes made to the main/develop branch on your local machine.

Step 2: Reset the Feature Branch

Next, you need to reset your feature branch to a single commit, which will make the rebase process simpler. You can use the following command to reset your feature branch:

git checkout feature/newFeature
git reset --soft HEAD~<n>

Here, <n> is the number of commits you want to squash into a single commit. For example, if you want to squash the last 21 commits into a single commit, you can use git reset --soft HEAD~21.

Step 3: Create a New Commit

After resetting the branch, you can now create a new commit that represents all the changes made in the previous commits. You can use the following command to create a new commit:

git commit -m "New feature implementation"

Here, the commit message can be anything that describes the changes made in the feature branch.

Step 4: Rebase the Feature Branch

Now that you have a single commit representing all the changes made in the feature branch, you can rebase it onto the main/develop branch. Use the following command to do this:

git rebase develop

This will apply the changes made in the main/develop branch onto your feature branch.

Step 5: Resolve Conflicts

If there are any conflicts during the rebase process, Git will pause and prompt you to resolve them. However, since you have squashed all the changes into a single commit, you will only need to resolve conflicts once, instead of resolving conflicts in every conflicting commit.

Once you have resolved the conflicts, use the following command to continue the rebase process:

git rebase --continue

Step 6: Push Changes

After completing the rebase process, you can now push your changes to the remote repository. Use the following command to do this:

git push --force

The --force flag is required because you have rewritten the history of your feature branch.

In conclusion, by following these steps, you can rebase your feature branch and squash all the changes into a single commit, which will simplify the rebase process and help you avoid resolving conflicts in every conflicting commit. Remember to regularly update your feature branch with the changes made in the main/develop branch to.

The post Undo the most recent local commits in Git first appeared on forceUnwrap.

]]>
https://forceunwrap.com/undo-the-most-recent-local-commits-in-git/feed/ 0
Apple MacBook Pro 16-inch M1 Max: A Game Changer for Developers https://forceunwrap.com/apple-macbook-pro-16-inch-m1-max-a-game-changer-for-developers/ https://forceunwrap.com/apple-macbook-pro-16-inch-m1-max-a-game-changer-for-developers/#respond Wed, 22 Mar 2023 20:15:23 +0000 https://forceunwrap.com/?p=381 Introduction:

The Apple MacBook Pro 16-inch with the M1 Max chip has been turning heads since its release in October 2021. With its powerful performance, impressive efficiency, and sleek design, this laptop has quickly become a favorite among professionals and creatives alike. As a programmer who has been using MacBook Pros since 2011 and this specific MacBook Pro since December 2021, I can confidently say it has lived up to the hype and exceeded my expectations. In this review, I’ll share my personal experience with this powerful machine and how it has impacted my workflow as a developer.

Performance:

The M1 Max chip in the MacBook Pro 16-inch model is truly a game changer. With a 10-core CPU, 32-core GPU, and 32GB of unified memory, this laptop can handle the most demanding tasks with ease. During my time using this MacBook Pro, I have never experienced any slowdowns, even when exporting videos, building Xcode projects, or running multiple virtual machines.

One of the most impressive aspects of the M1 Max MacBook Pro is its thermal management. Despite pushing the laptop to its limits, I have never managed to turn on the fans. The advanced cooling system keeps the laptop cool and quiet, allowing me to focus on my work without any distractions.

Battery Life:

The MacBook Pro 16-inch M1 Max boasts an impressive battery life, which is perfect for professionals on the go. With up to 21 hours of battery life when watching video or up to 14 hours of web browsing, I’ve been able to work for extended periods without worrying about finding a power outlet. Even during heavy code building sessions, the battery can last an entire day without requiring a power cord, increasing my productivity and reducing the need for frequent charging.

Display and Design:

Having used MacBook Pros since 2011, I’ve witnessed how the displays have only gotten better, clearer, and brighter over the years. The MacBook Pro 16-inch features a stunning Liquid Retina XDR display, with a resolution of 3456 x 2234 pixels and support for ProMotion technology. The display is incredibly sharp and color accurate, making it perfect for photo and video editing, as well as providing an immersive experience when watching movies or playing games. The large screen size also allows me to comfortably view code and browse Stack Overflow simultaneously, enhancing my workflow as a developer.

In terms of design, the MacBook Pro 16-inch M1 Max maintains the sleek and premium aesthetics that Apple is known for. The laptop is slightly thicker and heavier than its predecessor, but this trade-off is well worth it considering the significant performance improvements and additional ports, such as an HDMI port, SDXC card slot, and three Thunderbolt 4 ports. The embossed “MacBook Pro” on the bottom adds an ultra-cool touch to the overall design.

MagSafe and Ports:

The return of the MagSafe charger in the MacBook Pro 16-inch is a welcome addition, showcasing Apple’s willingness to listen to user feedback. The MagSafe charger offers a convenient and secure way to charge the laptop while minimizing the risk of accidental disconnections or damage to the device.

In addition to the MagSafe charger, Apple has brought back the HDMI port and SDXC card reader, eliminating the need for multiple dongles and adapters. Previously, users had to purchase and carry around USB-C dongles to connect their devices, which were not only expensive but also prone to breakage. The inclusion of these ports simplifies the user experience and streamlines connectivity for professionals who rely on a variety of peripherals for their work.

M1 Pro vs M1 Max:

I have also used the MacBook Pro with the M1 Pro chip, and while it is a bit slower than the M1 Max, the difference is not substantial for developers or regular users. Both chips offer massive performance gains compared to Intel-based Macs, but the main differences between the M1 Pro and M1 Max are the GPU core count and memory bandwidth speed, which is doubled on the M1 Max. This is why the M1 Pro might feel slightly slower after using the M1 Max, but for most tasks, it is still a fantastic choice.

With the recent release of the MacBook Pro with the M2 chip, some users might be considering upgrading. However, I don’t believe the gains in performance are significant enough to justify the additional cost, especially for developers. The M1 Max is probably more oriented towards videographers who can benefit from the increased GPU capabilities and memory bandwidth.

Here’s a comparison table of the M1 Pro and M1 Max:

FeatureM1 ProM1 Max
CPU Cores10 cores (8 performance cores, 2 efficiency cores)10 cores (8 performance cores, 2 efficiency cores)
GPU Cores16 or 32 cores32 cores
Memory Bandwidth200 GB/s400 GB/s
Unified MemoryUp to 32GBUp to 64GB
Comparison table of the M1 Pro and M1 Max

Regarding the pricing point, the larger memory options may not be worth the upgrade for developers, as it is cheaper to invest in an iCloud storage plan to back up code, photos, and other important documents.

In conclusion, I recommend the M1 Pro as the best value for your money, offering an excellent balance of performance and cost for developers and regular users. If you want the ultimate experience or work extensively with video editing, the M1 Max may be the better choice. However, for most users, the M1 Pro will provide more than enough power and capabilities to meet their needs.

Apple MacBook Pro 16-inch M1 Max Specifications

Space graySilver
Best value

16″ Macbook Pro
M1 Pro
16GB RAM
512GB SSD
Top performer

16″ Macbook Pro
M1 Max
32GB RAM
1TB SSD

Affiliate links

At ForceUnwrap.com, we are dedicated to providing valuable and insightful content to our readers. To ensure that we can continue creating and sharing high-quality content, we sometimes include affiliate links in our articles. These links direct you to products and services we genuinely believe in and recommend. When you make a purchase through one of these links, we may earn a small commission at no additional cost to you. This helps us cover the expenses associated with running the website, researching, and producing content. By using our affiliate links, you are directly supporting our efforts to bring you the best resources and information in the tech industry. We appreciate your trust in our recommendations and your support in helping us maintain the quality of our content.

The post Apple MacBook Pro 16-inch M1 Max: A Game Changer for Developers first appeared on forceUnwrap.

]]>
https://forceunwrap.com/apple-macbook-pro-16-inch-m1-max-a-game-changer-for-developers/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
Swift multiple trackers architecture – part 1 https://forceunwrap.com/swift-multiple-trackers-architecture/ https://forceunwrap.com/swift-multiple-trackers-architecture/#comments Fri, 17 Feb 2023 10:29:48 +0000 https://forceunwrap.com/?p=287
Overview

Tracking is an essential part of app development. It allows us to measure the performance, behavior and preferences of our users and optimize our app accordingly. However, tracking can also be a challenge when we have to deal with multiple trackers from different providers. How do we keep our code clean, maintainable and testable when we have to integrate and update various trackers in our app?

In this article, I will show you how to use Swift Package Manager (SPM) to modularize your tracking code and make it easier to manage multiple trackers in your app. SPM is a tool that helps us create and distribute Swift packages that contain source code and resources for our projects. By using SPM, we can separate our tracking code into different packages that can be reused, tested and updated independently.

We will start by creating an abstract tracker class that will be responsible for managing all the trackers in our app. Then we will create different tracker implementations that conform to a common protocol and register them with the abstract tracker class. Finally, we will see how to use SPM to create and import these tracker packages into our main app project.

Let’s get started!

The idea

Let’s create a general Tracker class responsible for managing all trackers, without containing any specific tracker implementation. The Tracker class will allow us to register a Trackable tracker, which will then be added to the trackers array. Ideally, the trackers array should not be accessible from outside the class, but due to the need for using extensions for simpler code management in different files, we are forced to use private(set). This allows us to read the array, but we cannot write to it outside the scope of the Tracker class. If you have any suggestions on how to improve this, please let me know in the comments below. We can all benefit from the knowledge since this is one of the known drawbacks at the moment.

protocol Tracking {
    func registerTracker(tracker: Trackable)
}

// General implementation
public class Tracker: Tracking {
    // Array of our trackers
    // Is there a way to store objects faster and without duplicates?
    private(set) var trackers = [Trackable]()

    // A way to register a Trackable, without duplicates
    public func registerTracker(tracker: Trackable) {
        if !trackers.contains(where: { $0.type == tracker.type }) {
            trackers.append(tracker)
        } else {
            print("🔥🔥🔥 We don't want duplicates 🔥🔥🔥")
        }
    }
}

In order to make our tracker masterplan effective, it’s essential to identify the type of tracker we’re working with. Protocols provide a solution to this problem. I’ve named our protocol Trackable, which contains the TrackerType. Having the TrackerType enables us to enable/disable specific trackers or perform other magic tricks with them. It’s like ALOHOMORA, unlocking a whole new level of potential for our trackers!

Alternatively, we could skip the TrackerType and allow specific trackers to simply do their job without worrying about whether they’re enabled or not. This approach may be more straightforward, but it may also limit our ability to customize and fine-tune the behavior of our trackers. Ultimately, the decision of whether to use TrackerType or not will depend on our specific needs and use case.

@objc public protocol Trackable {
    var type: TrackerType { get }
}

@objc public enum TrackerType: Int {
    case general, braze, sumo, adobe

    var isEnabled: Bool {
        switch self {
        case .general, .braze, .adobe:
            return true
        case .sumo:
            return false
        }
    }
}

One potential drawback of having a general TrackerType is that it may feel unnecessary in certain cases. However, this is a matter of debate, and if you have any ideas on how to improve this, let’s have a discussion. The question of whether the Tracker should have a type is an important one that deserves careful consideration. While a TrackerType may provide some benefits, such as enabling us to enable/disable specific trackers or perform other customizations, it may also add unnecessary complexity to our code. Ultimately, the decision of whether to include a TrackerType or not will depend on our specific needs and the goals of our project.

Optional func

// Need the protocol to be @objc, so we could use @objc optional
@objc public protocol TrackableHome {
    // Optional track func, not necessarily implemented by all trackers
    @objc optional func trackHomeTap(extraParam: String)
    func trackNavigate(toView: String, fromView: String)
    func trackLogin()
}

The @objc optional keyword allows us to define functions that may not be implemented by all specific trackers, but only by some of them. This can be very useful when we want to provide flexibility in our tracking system, allowing each tracker to implement only the functions that are relevant to its specific needs. Without @objc optional, we would need to define each function in every tracker, even if it wasn’t used, which could lead to bloated and inefficient code.

Event

struct Event: Eventable {
    let name: String
    let parameters: [String: String]
}

protocol Eventable {
    var name: String { get }
    var parameters: [String: String] { get }
}

Specific tracker protocols

// Need the protocol to be @objc, so we could use @objc optional
@objc public protocol TrackableHome {
    // Optional track func, not necessarily implemented by all trackers
    @objc optional func trackHomeTap(extraParam: String)
    func trackNavigate(toView: String, fromView: String)
    func trackLogin()
}

public protocol TrackablePlayback: Trackable {
    func trackPlay()
    func trackPause()
    func trackNext()
    func trackPrevious()
}

We have a couple of different protocols above. One is for tracking homepage events, other is for tracking playback events. But we could have as many different protocols conforming to Trackable as we want. For each ViewController, feature, service…

Our goal is for our Tracker to support them, so that specific Trackable trackers could implement their specific implementations as well. We extend our abstract Tracker with TrackableHome. Next, we go through each registered tracker, check if it is supported(enabled) and call the same tracking function. The abstract tracker knows nothing about the specific implementations required by each tracker.

// Extension of Tracker, where we see if tracker is enabled and call all the enabled trackers
extension Tracker: TrackableHome {
    public func trackNavigate(toView: String, fromView: String) {
        trackers.forEach { tracker in
            guard tracker.type.isEnabled,
                  let tracker = tracker as? TrackableHome
            else {
                return
            }

            tracker.trackNavigate(toView: toView, fromView: fromView)
        }
    }

    public func trackLogin() {
        trackers.forEach { tracker in
            guard tracker.type.isEnabled,
                  let tracker = tracker as? TrackableHome
            else {
                return
            }
            
            tracker.trackLogin()
        }
    }
}

Specific tracker implementations

Let’s take a closer look at the implementation of a specific tracker. In the BrazeTracker class, our goal is to set up everything we need to run the tracker. By extending the class with Trackable, we gain support for TrackerType, which allows us to easily enable or disable trackers as needed.

To use BrazeTracker with a specific feature or service, we extend it with TrackableHome (or another relevant protocol). The Tracker class does not need to know the specific implementation details required by AppBoy – we can provide all of the necessary extras in the BrazeTracker class. This allows us to customize the specific tracker for our specific needs while still leveraging the benefits of the single Tracker class approach.

class BrazeTracker: Trackable {
    private let apikey = "YOUR-API-KEY"

    let type: TrackerType = .braze
//    let appboy = Appboy.sharedInstance()
}

extension BrazeTracker: TrackableHome {
    func trackNavigate(toView: String, fromView: String) {
        // Add specific event params or whatever is needed
        // Can be a specific event as well.
//        let event = Event(
//            name: "navigate",
//            parameters: [
//                "from": fromView,
//                "to": toView
//            ]
//        )
        // Call specific implementation
        // Sumo.track()
    }

    func trackLogin() {}

    func trackHomeTap(extraParam: String) {
        // Implements optional function
    }
}
class AdobeTracker: Trackable {
    var type = TrackerType.adobe
}

extension AdobeTracker: TrackableHome {
    func trackNavigate(toView: String, fromView: String) {
        // Add specific event params or whatever is needed
        // Can be a specific event as well.
//        let event = Event(
//            name: "navigate",
//            parameters: [
//                "from": fromView,
//                "to": toView
//            ]
//        )
        // Call specific implementation
        // Sumo.track()
    }

    func trackLogin() {}

    // Does not implement optional func
    // trackHomeTap(extraParam: String)
}
class SumoTracker: Trackable {
    let type: TrackerType = .sumo
}

extension SumoTracker: TrackableHome {
    func trackNavigate(toView: String, fromView: String) {
        // Add specific event params or whatever is needed
        // Can be a specific event as well.
        let event = Event(
            name: "navigate",
            parameters: [
                "from": fromView,
                "to": toView
            ]
        )
        // Call specific implementation
        // Sumo.track()
    }

    func trackLogin() {}

// Does not implement optional func
// trackHomeTap(extraParam: String)
}

We also have implementations for other specific trackers using this setup, which makes it easy to add, disable, and maintain different trackers as needed. By abstracting away the implementation details and leveraging protocols like Trackable, we can create a flexible and modular tracking system that can be customized to suit the needs of any project.

Protocol naming tip for teams

When it comes to protocol naming in Swift, it’s important to follow some guidelines. The Swift.org documentation suggests using suffixes like -able, -ible, or -ing. However, it’s not always clear which suffix to use for a given protocol.

In some cases, a protocol may have multiple suffixes that sound good, like Eventable, Eventing, or Eventible. This can lead to confusion, especially for non-native English speakers.

One approach to simplify protocol naming is to simply add the word Protocol at the end of the protocol name, like EventProtocol. This approach can be helpful for ensuring consistency across a team and avoiding confusion during code reviews.

However, it’s important to make sure that all team members are on board with this approach and that it fits within the larger project’s naming conventions. Ultimately, the most important thing is to choose a naming convention that is clear, consistent, and easy to understand for everyone working on the project.

SOLID discussion and improvements

Does this code conform to SOLID? How could we improve it? What rules are followed partially? What are adhered?

If you have any suggestions on how to improve this implementation, please share them in the comments below. We can work together to refine the Tracker class and make it even more effective.

Don’t forget to read the follow-up, where we’ll be discussing how we’ve refactored the code according to SOLID principles in part 2.

The post Swift multiple trackers architecture – part 1 first appeared on forceUnwrap.

]]>
https://forceunwrap.com/swift-multiple-trackers-architecture/feed/ 1
Avoid generating unwanted git .orig and .backup files https://forceunwrap.com/avoid-generating-unwanted-git-orig-and-backup-files/ https://forceunwrap.com/avoid-generating-unwanted-git-orig-and-backup-files/#respond Mon, 03 Oct 2022 13:00:26 +0000 https://forceunwrap.com/?p=279 To avoid generating unwanted git .orig and .backup files, you can modify the configuration by executing the following command:

git config --global mergetool.keepBackup false

Typically, when using git mergetool to resolve merge conflicts, the tool will save the conflicted version of the file with a .orig and .backup suffixes. However, if the mergetool.keepBackup configuration option is set to false, these .orig and .backup files will not be preserved. It’s worth noting that the default value for this option is true, meaning that backup files will be kept by default.

The post Avoid generating unwanted git .orig and .backup files first appeared on forceUnwrap.

]]>
https://forceunwrap.com/avoid-generating-unwanted-git-orig-and-backup-files/feed/ 0