Advanced iOS App Architecture Flashcards

1
Q

Pragmatic 7 steps you can take to ensure your architecture is effective:

A

Understand the current state of your codebase.
Identify problems you’d like to solve or code you’d like to improve.
Evaluate different architecture patterns.
Try a couple patterns on for size before committing to one.
Draw a line in the sand and define your app’s baseline architecture.
Look back and determine if your architecture is effectively addressing the problems you want to solve.
Iterate and evolve your app’s architecture over time.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
2
Q

The reality is that selecting an architecture pattern is less important than

A

understanding the the problems you’re trying to solve using architectural patterns.

Taking the time to understand the problems you want to solve allows you to focus on the few aspects of architecture that really make a difference. While many problems will be specific to your project, there are several general problems you can solve through good architecture.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
3
Q

Here are several problems that, when present, lead to slow velocity and fragile code quality:
My app’s codebase is hard to understand.
Changing my app’s codebase sometimes causes regressions.
My app exhibits fragile behavior when running.
My code is hard to re-use.
Changes require large code refactors.
My teammates step on each other’s toes.
My app’s codebase is hard to unit test.
My team has a hard time breaking user stories into tasks.
My app takes a long time to compile.

A

Understanding root causes

Each of these problems can be caused by two fundamental root causes: highly interdependent code and large types.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
4
Q

Explain Highly interdependent code problem

A

Properly encapsulating your code

Code becomes highly interdependent when code in one type reaches out to other concrete, i.e., non-protocol, types. Making one part of your code depend on another is extremely easy. This is especially true when a codebase has a lot of visible global objects.

Without properly encapsulating your code, your interdependencies can run rampant! The more you tightly couple parts of your codebase, the more likely something unexpectedly breaks when making code changes. This is further complicated by large teams with multiple developers because everyone needs to fully understand the interdependencies, which on very large teams may be an impossible task.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
5
Q

Explain large types problem

A

breaking large types into smaller types is a great way to improve your codebase’s architecture.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
6
Q

When creating a new type, you have to think about so many things:

A

What should the type be responsible for?
How long should instances of this type live for?
Should any existing code be moved to this type?
What if this new type needs access to state held by another type?

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
7
Q

Explain My app’s codebase is hard to understand problem and questing to solve this problem

A

How long are your class implementations?

A good architecture breaks large chunks of code into small, modular pieces that are easy to read and understand. The more an architecture encourages locally encapsulated behavior and state, the easier the code will be to read.

How many global variables does your codebase have, and how many objects are instantiated directly in another object?

The more your objects directly depend on each other and the more your objects depend on global state, the less information a developer will have when reading a single file.

Carefully managing dependencies is a universal aspect that can be applied to any architecture pattern.

How differently are your view controllers implemented across your app’s codebase?

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
8
Q

be able to easily reason about how the current file you’re editing is connected to the rest of your codebase - the best way to do it?

A

is to limit object dependencies and to make the required dependencies obvious and visible.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
9
Q

The structure of a codebase determines how much

A

code you can re-use

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
10
Q

Large types can prevent your code from

A

being reusable

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
11
Q

This is important: Reusability is not just about being able to re-use code. It’s also

A

about being able to move code around when making changes to your app. The more reusable everything is, the easier it is to shuffle code around without needing to do risky refactors.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
12
Q

what makes code hard to replace?

A

large types and highly interdependent code.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
13
Q

To summarize, you’ll be able to build features much faster if your app’s architecture allows

A

your team to easily parallelize work by loosely coupling layers and features that make up your codebase.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
14
Q

If your unit tests require a ton of set up, or if your unit tests perform uncontrollable side effects such as networking and persistence, then

A

your app’s codebase could benefit from an architectural refactor.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
15
Q

Here are some problems that can be solved with architecture in order to increase your
code’s agility

A

I find myself locked into a technology.
I’m forced to make big decisions early in a project.
Adding feature flags is difficult.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
16
Q

Clean Architecture and Ports & Adapters patterns fit really well into apps that have

A

a lot of local business logic. If your app is presentation heavy and doesn’t have a whole lot of local business logic, these patterns might not work well for you.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
17
Q

which pattern you select is less important

A

than how you put the pattern to practice.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
18
Q

The best way to decide which pattern to use is to

A

try a couple of the patterns in your codebase.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
19
Q

don’t forget to consider the human aspects

A

How big is your team? How experienced are your teammates? What patterns are your teammates most familiar with? How tight are your deadlines? And of course, consider the technical aspects such as, what constraints are you looking to design into your codebase?

20
Q

All this to say, architecture is more of an art than science

A

Go experiment, learn and be creative. There’s no right way to do it. Just remember, there are many good ways to architect and there are many not-so-great ways to architect — but not a single right way.

21
Q

Loosely coupled parts when creating architecture

A

Whether you’re using MVC, MVVM, Redux, VIPER, etc. make sure your code is broken down into small loosely coupled parts.

22
Q

Cohesive types in architecture

A

Make sure your types exhibit high cohesion, i.e., the properties and methods that make up each type belong together. If you have small types that have very focused responsibilities, your types probably exhibit high cohesion.

23
Q

Multi-module apps

A

Make sure your app is broken down into several Swift modules.

24
Q

Object dependencies

A

Make sure you’re managing object dependencies using patterns such as Dependency Injection containers and Service Locators.

25
Q

Key points architecture

A

There’s no such thing as a perfect universal app architecture.

You can use architecture to boost your team’s velocity, to strengthen your code’s quality and to increase your code’s agility.

Selecting the “right” architecture pattern won’t guarantee your codebase will be well- architected. Which pattern you select is less important than how you put the pattern to practice.

Feel free to mix and match different architecture patterns.
Architecture is more of an art than a science. Go experiment, learn and be creative.

26
Q

Goals of dependency injection patterns

A

Maintainability: The ability to easily change a code-base without introducing defects, i.e., the ability to reimplement part of a code-base without adversely affecting the rest of the code-base.

Testability: Deterministic unit and UI tests, i.e., tests that don’t rely on things you can’t control, such as the network.

Substitutability: The ability to substitute the implementation of a dependency at compile-time and at runtime. This quality is useful for A/B testing, for gating features using feature flags, for replacing side-effect implementations with fake implementations during a test, for temporarily swapping in a diagnostic version of an object during development and more.

Deferability: Having the ability to defer big decisions such as selecting a database technology.

Parallel work streams: Being able to have multiple developers work independently on the same feature at the same time without stepping on each others toes.

Control during development: A code-base that developers can quickly iterate on by controlling build and run behavior, e.g., switching from a keychain-based credential store to a fake in-memory credential store so that you don’t need to sign in and out over and over again while working on a sign-in screen.

Minimizing object lifetimes: For any given app, the less state a developer has to manage at once, the more predictably an app behaves. Therefore, you want to have the least amount of objects in-memory at once.

Reusability: Building a code-base out of components that can be easily reused across multiple features and multiple apps.

27
Q

Accessing dependencies from inside

A

From the inside:

Global property: The object-under-construction can simply access any visible global property.

Instantiation: If a dependency is ephemeral, i.e. the dependency doesn’t need to live longer than the object-under-construction, the object-under-construction can instantiate the dependency.

28
Q

Accessing dependencies from outside

A

From the outside:

Initializer argument: A dependency can be provided to the object-under- construction as an initializer argument.

Mutable stored-property: A dependency can be provided to an already created object-under-construction by setting a visible mutable stored-property on the object-under-construction.

Method: Dependencies can be provided to the object-under-construction through visible methods of the object-under-construction.

29
Q

The basic idea of DI pattern is

A

The main goal of Dependency Injection is to provide dependencies to the object- under-construction from the outside of the object-under-construction as opposed to querying for dependencies from within the object-under-construction. Dependencies are “injected” into the object-under-construction.

30
Q

Dependency patterns

A

Dependency Injection

Service Locator: A Service Locator

Environment

Protocol Extension

31
Q

By externalizing dependencies, you can easily see

A

what dependencies an object has by looking at the object’s public API

32
Q

Types of injection

A

Initializer

Property

Method

33
Q

Initializer type of injection

A

Initializer: The consumer provides dependencies to the object-under-construction’s initializer when instantiating the object-under-construction. To enable this, you add dependencies to the object-under-construction’s initializer parameter list. This is the best injection type because the object-under-construction can store the dependency in an immutable stored-property. The object-under-construction doesn’t need to handle the case in which dependencies are nil and doesn’t have to handle the case in which dependencies change. Initializer injection isn’t always an option, so that’s when you would use property injection, the next injection type.

34
Q

Property type of injection

A

After instantiating the object-under-construction, the consumer provides a dependency to the object-under-construction by setting a stored-property on the object-under-construction with the dependency. If you don’t have a default implementation for a property-injected-dependency, then you’ll need to make the property type Optional. This injection type is usually used in Interface Builder- backed view controllers because you don’t have control over which initializer UIKit uses to create Interface Builder-backed view controllers.

35
Q

Method type of injection

A

Method: The consumer provides dependencies to the object-under-construction when calling a method on the object-under-construction. Method injection is rarely used; however, it’s another option at your disposal. If a dependency is only used within a single method, then you could use method injection to provide the dependency. This way, the object-under-construction doesn’t need to hold onto the dependency. Remember, the less state an object has, the better. The shorter an object’s lifetime, the better.

36
Q

A good rule of thumb for choosing a type of injection

A

When the object-under-construction cannot function without a dependency, use initializer injection. If the object-under-construction can function without a dependency, you can use any type of injection, preferably initializer injection.

37
Q

Circular dependencies

A

because you cannot initialize both objects with each other; you have to create one first and then create the second object with the first object via initializer injection, then set a property on the first object with the second. Also, remember to avoid retain cycles by making one reference weak or unowned.

38
Q

Therefore, injection alone does not enable substitutability. To enable substitutability:

A

you need to define protocols for dependencies so the consumer can inject different classes that conform to the dependency’s protocol. When designing an object-under- construction, use protocol types for dependencies.

39
Q

Compile-time substitution

A

Once you add custom identifiers to the active compilation condition’s build setting, you use the identifiers in #if and #elseif compilation directives.

40
Q

Runtime substitution

A

you can use a remote-feature flag service, or you can key off local values, such as the app’s version number.

41
Q

Dependency Injection approaches:

A

On-demand

Factories

Single container

Container hierarchy

42
Q

Dependency Injection On-demand approach

A

In this approach, you create dependency graphs when needed in a decentralized fashion. This approach is simple yet not very practical. You can use this approach to solidify your understanding of the fundamentals and to feel some of the pain addressed by more advanced approaches.

43
Q

Dependency Injection Factories approach

A

Here, you begin to centralize initialization logic. This approach is also fairly simple and is designed to help you learn the fundamentals.

44
Q

Dependency Injection Single container approach

A

This approach packages all the initialization logic together into one container. Since there’s state involved, it’s a bit more difficult to put into practice than the previous two approaches.

45
Q

Dependency Injection Container hierarchy approach

A

One of the problems with centralizing all the initialization logic is you end up with one massive class. You can break a single container down into a hierarchy of containers. That’s what this approach is all about.

46
Q
A