Search
  • Nick Daigler

iOS Lead Essentials Course Learnings - Part 1

If you're reading this, you're probably considering enrolling in the iOS Lead Essentials Course. I enrolled in the course's 10th cohort on December 14th, when enrollment opened.


I couldn't find any public pricing information before enrollment opened. So, if you're searching high and low, look no further: enrollment costs $1,500, if you opt for a one-time payment.

Motivations for documenting my progression through this course

  1. It'll be a good way for me to solidify what I'm learning. The act of writing will force me to reflect on the material more than I otherwise would.

  2. I want others to share what I'm learning with anyone who is interested.

  3. I want others who are considering enrolling in a future cohort to have access to a more detailed account of what the course is like.


Why I enrolled


I want to preface my endorsement of this course by noting that I'm not affiliated with Essential Developer in any way, nor am I making any money off of this.


I've been following Mike and Caio for about two years on the Essential Developer YouTube channel. I had only been coding for around two years at that point and found myself in the deep end when listening to Mike and Caio; many practices they preach were totally arcane to me. At one point I unsubscribed from their channel because I was feeling continually frustrated by the material that wasn't sinking in. I figured the concepts Mike and Caio praised weren't relevant because they weren't being referenced in other videos and articles I'd been learning from.


By wading through many software conference talks, Medium articles, Ray Wenderlich tutorials, and iOS-related podcast episodes, I began realizing that Mike and Caio really know what they're doing. The content and information they're sharing is extremely relevant to achieving technical excellence as a software engineer.


The decision to enroll came down to a couple things:

  1. I trust Mike and Caio because I've benefited from their teachings. I've used many of the learnings from the Essential Developer YouTube channel to produce work that has led me to more, and better, career opportunities.

  2. An investment of $1,500 is nothing in comparison to the return on that investment. If you're worried that the valued offered wouldn't lead to a return that exceeded the cost, there's a thirty day money back guarantee, so you might as well enroll.

Onward to learnings!


The first course module focuses on approaching an application's system design. This included discussions about singletons, testability, compositional design, behavior-driven development, domain-driven design, and strategies for building parts of an app that have not been implemented on the backend.


It's good to keep in mind that there are books on all of the aforementioned buzzwords. So, I've limited the scope of my learnings from this module to single, more sweeping takeaway, in order to keep the length of this post to a reasonable size.


My biggest takeaway from this course module is the notion that dependency injection is an antidote to legacy code.


Concrete examples are great communication tools, so I've opted to convey this takeaway by riffing on one of the course's discussion points from this section.


In the planning and system design segment of the course, Mike and Caio talk about a common idiom shared (pardon the singleton pun) amongst third party frameworks: an API for a shared instance. Regardless of whether the framework actually uses a shared instance under the hood, we need to deal with the API provided by the framework. Referencing this framework's shared instance across a codebase is not a good idea. Below is a pseudo-example of the kind of referencing I'm referring to.


import ThirdParty

class Foo {
    func doThisThing() {
        ThirdParty.shared.doStuff()
    }
}

class Bar {
    func doThatThing() {
        ThirdParty.shared.doStuff()
    }
}

Referencing third party frameworks as seen above pushes a codebase to become ever-more coupled to the particular framework. The classes above have an implicit dependency on this third party framework. If the framework ever became unsuitable for the app's needs, untangling it from the codebase would be a nightmare.


This is where dependency injection — initializer injection, in this case — swoops in to save the day. Rather than referencing the framework's shared instance directly, we can inject the shared instance into objects that rely on the instance's exposed API. It's important to note that we'd like to depend on behavior, rather than on concrete implementations, when we're dealing with a third party dependency.


Notice how both the classes Foo and Bar rely on the existence of the below method:

func doStuff()

This is the behavior we'd like Foo and Bar to depend on. Consider the following code.

protocol DoesStuff {
    func doStuff()
}

extension ThirdParty: DoesStuff {}

class Foo {
    private let doer: DoesStuff
    
    init(doer: DoesStuff) {
        self.doer = doer
    }
    
    func doThisThing() {
        doer.doStuff()
    }
}

class Bar {
    private let doer: DoesStuff
    
    init(doer: DoesStuff) {
        self.doer = doer
    }
    
    func doThatThing() {
        doer.doStuff()
    }
}

let shared = ThirdParty.shared
let foo = Foo(doer: shared)
let bar = Bar(doer: shared)

Perfect! Now both Foo and Bar rely on a protocol; they're dependent on behavior specified by an interface. Alternatively, instead of making Foo and Bar dependent on the type of a particular protocol, we could have just as easily injected a closure.


Using a protocol will allow the type system to enforce conformance at build time. It could also signify greater relative importance, since presumably developers took the time to create the protocol and give it a name. In some figurative sense, the act of providing a type name enforces a more tightly bound contract between the modules. Though, in practice, injecting a protocol with a single method and injecting closures are essentially the same idea. Many times it will come down to preference.


Below is a version of the above code snippet in which closures are injected into Foo's and Bar's initializers.

class Foo {
    private let doer: () -> Void
    
    init(doer: @escaping () -> Void) {
        self.doer = doer
    }
    
    func doThisThing() {
        doer()
    }
}

class Bar {
    private let doer: () -> Void
    
    init(doer: @escaping () -> Void) {
        self.doer = doer
    }
    
    func doThatThing() {
        doer()
    }
}

let shared = ThirdParty.shared
let foo = Foo(doer: { shared.doStuff() })
let bar = Bar(doer: { shared.doStuff() })

Injecting closures can be a strategy to prevent the initialized object from implicitly depending on behavior that is not utilized by the object itself; it can help developers from accidentally violating the Single Responsibility Principle.


But, as I mentioned, most of this decision comes down to personal preference. Either way, the behavioral dependency is being inverted.

If you've read this far, I hoped you learned something and/or were entertained. If so, consider sticking around for future posts. I'll be writing more about my experiences and learnings, as I continue progressing through the course and interacting with the Essential Developer community.



That's all for now; be well.


Nick

54 views0 comments

Recent Posts

See All

Parsing XML with Swift

If you're just here for the code, go here. I recently found myself needing to parse an RSS feed and display a list of podcast episodes in a list. I typed, "xml parser apple docs" into Google and found

iOS @ Pludo: Reactive Components

Reactive programming is a trendy topic these days, and this has proved especially true in the iOS community over the past few years. Combine was introduced at WWDC 2019 and only added fuel to the fire

iOS Lead Essentials Course Learnings - Part 2

I've been chugging through the iOS Lead Essentials Course for about one month, at this point. I'm enjoying how the course doesn't lean heavily into iOS-specific technologies and concepts. Rather, the