Mitigation • Name some ways to prevent said bad code (coming from you): • Quit and do something else for monies? • have someone else write all your code, basically be a manager or pm? • Actually learn patterns and practices, learn from your mistakes (most of us).
That Single Responsibility thing… • If all you ever get out of the S in SOLID is this, that’s fine. • So much code could be cleaned up and fixed if it followed this rule, of having 1 and only 1 job. • Think of your code when you see it… “YOU HAD ONE JOB CLASS/Function/Module, ONE JOB.” • Even Microsoft and Apple example/starter code violates this.
What we did… • Made the AppDelegate continue to do only 1 thing. • The CoreDataHelper does only 1 thing (provide an interface to the CoreData Stack) and can be accessed from anywhere with sharedInstance. • We also introduced the beginning of a pattern that’s extremely useful in any app architecture… • It’ll be the D in SOLID, but more on that later.
Now we need a data layer • Lets say we want to have a friendly interface to fetching cached data (whether from user input or last web service call, etc). • Put that in the CoreDataHelper? Let’s try it out. • Well, now the CoreDataHelper is promising Weather days? Sounds like that’s not it’s job. • Move it out, create a new job for someone else.
Now the source of Data is a black box • CoreDataHelper returns to doing 1 job. • New WeatherDataSource promises to do 1 job, give back Weather data. It will now use the CoreDataHelper. • The abstraction now lets the ViewController worry about getting actual WeatherData and not manipulating the CD Stack. • Wait… Who is going to give the ViewController the WeatherDataSource?
The D in SOLID • Now we’re skipping around, that’s okay • Dependencies keep the modules/parts safely independent while continuing to do their 1 intended job. • WeatherDataSource is now a dependency of ViewController • CoreDataHelper is now a dependency of WeatherDataSource.
What we did • Injected (or handed) the ViewController an instance of the WeatherDataSource, without having IT instantiate itself. • We also handed WeatherDataSource an instance of CoreDataHelper for the same reasons. • Notice there’s a chain here, of one class handing another the dependencies it needs. • This is called Dependency Injection and it helps insure that classes only worry about what they promise to do (that 1 thing) any other work is done by its dependencies.
DI continued • ViewController got it through Property Injection, the WeatherDataSource through Initializer Injection. • Either is fine, use whatever suits your needs best. • Usually, because of storyboards, ViewControllers will need to get it through property injection, all other classes though that can be initialized, should get it through their inits.
What we did. • Created a client class to use NSURLSession that at some point will return data from a service. • Lets imagine the endpoint isn’t ready yet (cause that’s never happened in a mobile project before). • Best case, and you should do this at the start of EVERY app…
Faking Data • In most instances, you’ll get JSON back, so write some JSON. • Best bet is to keep a file handy in the project with JSON that you believe will come back from your service. • return that JSON as Data() in your fake client.
What we did. • WeatherDataSource will use a WebClient either real or fake to get back Data, from there it’ll parse and form into NSManagedObjects. • The WebClient will be injected into the WeatherDataSource as well. • In doing this the WeatherDataSource will be the glue between CoreDataHelper and the WebClient • From there the ViewController will get back first a cached set of data (from the stack), then when the completion block completes, it’ll get updated data
A few more things… • Notice the AppDelegate is now pretty much handling the injections of dependencies two levels beneath it. • Also notice we’re hardcoding the dependency to be a FakeWebClient, not a common client that could be either real or fake. • I know the urge… subclass, but don’t, just don’t. avoid Subclassing with this pattern at ALL COSTS.
What should we do then? • Notice the FakeWebClient and the WebClient adhere to a common interface: • Here’s our I in SOLID, we need an Inteface. • Of course in Swift and Objective-C they’re protocols.
Done. • Now the webClients adhere to the Client protocol. • The protocol/interface can be injected instead, increasing flexibility • This makes it easier to swap out the fake from the real client by changing 1 line.
• This is the selling point of Dependency Injection, it’s easy to swap out pieces for completely different behaviors (even at runtime) with minimal effort.
But wait, we’re not done. • CoreData is a hefty stack. • What if for faster prototyping we need something simpler, like NSUserDefaults (UserDefaults now in Swift 3) • Hmm… sounds like a familiar situation.
What we did. • WeatherDataSource still uses a Client interface, but now uses a new interface to persisted data. • The Persistence protocol will be injected into the WeatherDataSource instead of the specific CoreDataHelper. • In doing this the WeatherDataSource no longer needs to know about the CoreData stack, just a layer persisting to disk. • Again, we’re following the rules of modules knowing less and less and doing only what they need to do.
Exactly what we did • We extended functionality of the app, instead of changing it. • By using protocols, new implementations like UserDefaults instead of CoreData could be swapped out (again at runtime if need be). • The app didn’t change, it was just enhanced with more capabilities for doing the same task (persisting data, or fetching JSON)
Bonus Round • AppDelegate shouldn’t worry about injecting, that should be a job for someone else. • In comes a Dependency Resolver. • A class that simply registers protocols with implementations (with a Dictionary). • Even better, create Xcode build schemes, one for fakes, one for reals, where certain lines of code in the Resolver are run for each.
Summary • We created a better App stack. • Again, imagine Lego pieces and not lines of code. • Use Design Patterns only when it suits the situation. • The Dependency Resolver is a good example of the Factory Pattern.