The Trap of Libraries

One of the worst feelings I can ever have in iOS Software Engineering is cloning an existing project for the first time and looking at the Podfile. I feel it gives me a good sense of how the project is going to go. If there are only 1 or 2 pods, I sigh a breath of relief, if there are more, I start the mourning process.

It's not like having a handful of pods are always a bad thing, but most of the time they definitely are.

Here is a short list of things that I don't mind seeing in a Podfile.

  • HockeyApp
  • Fabric
  • Google Analytics
  • Urban Airship

Libraries like these are helpful, they integrate backend services that might be overwhelming to create from scratch, and allow you to focus more on your app. While they tend to be a little larger than one would like, the companies that maintain them tend to spend time making sure they are low impact, relatively bug-free, and fairly easy to keep updated.

Here is a list of libraries that send a chill down my spine.

  • ReactiveCocoa
  • Mantle
  • AFNetworking
  • Facebook Pop
  • ZXingObjC

There are many more I could list here, but let's get to the point. These libraries are a loan from the bank of tech debt. Using some of these commits large portions of your code to require constant maintainance, and you can only avoid it for so long.

The First Problem

When I tell people my thoughts on these libraries, they always tell me...

They are open source, if there is something you don't like you can always just fix it.

or

If the maintainer goes away or stops supporting it, you can take it up.

Let's run a little thought experiment on this, shall we?

Take AFNetworking for example...

If I am building a library that uses Apple's URLSession to build my network stack, I can be fairly confident that it will be maintained as long as the platform is. It has a lot of features, is relatively bug-free, and I don't have to pour over its source-code with every update to make sure it is following the new best practices.

However, if I use AFNetworking, I potentially have to wait weeks before new language, security, and platform features are integrated. I am not sure if you remember the certificate pinning issue, but the gist of it is that the maintainers identified a security issue introduced in one of their patches and, to their credit, they patched it in about a week. The problem arose in that every developer who used the library didn't follow the news, or had to wait for internal company processes to allow a release, then wait for Apple's (at the time), slow, app review to patch this vulnerability. The effect of this was that over 1000 apps on the app store had this issue.

Now this is a big, frequently used library and the maintainers did the best they could to fix the problem as quickly and as loudly as they could, but can you say that you follow the git issues and blogs related to every single library you use? A small "syntactic sugar" library could easily introduce security issues without your knowing, and if it's not used by A LOT of people, it will fly completely under your radar (I will avoid the obvious pun here).

And what if this library wasn't even well maintained?

Yes, you could fork the library and start maintaining it yourself, but do you think your clients and managers will agree that spending several hours a week maintaining a library is a good thing? How would they feel if you are only using a couple functions? If they are ok with it, you probably have a great job with very understanding colleagues and you are probably on retainer, or work in a big corporation. Consulting/Agency work doesn't usually afford this luxury.

Even if you only use popular, well-maintained libraries, you can still be hit by...

The Second Problem

In the beginning of this article, I talked about the dread I feel opening up an existing project's Podfile for the first time, the worst thing you can see is at the top.

platform :ios, '6.0'

This means one or two of the following.

  1. This project hasn't been updated in a LONG time.
  2. This project relies on a library that no longer exists.

If the issue is the former, you will probably have well over 100 random deprecated API warnings, and if you are lucky, only a couple hard errors. If it is the latter, get prepared to spend a lot of time extracting that pod from the application.

For example, I am looking forward to having to spend quite a few hours extracting Mantle from an application because the app heavily relies on a feature no longer present. This is not even close to the first time I have had to deal with this. Smaller libraries are a bit more loose with redefining their entire API in an update, completely breaking the hosting app, and sometimes introducing new bugs that can take weeks of updating to work out.

It can also be easy for someone working on the project to work around issues and never actually update the library. When it finally comes time that you do run pod update, you might find that you now have a week or more of fixes to get the new library running. Maybe you are the good guy (or gal) who spends time keeping pods updated and therefore you don't have to waste weeks doing the epic rewrite, if you are this person, would you like a job?

Libraries can be useful when a task is too complicated or requires large amounts of infrastructure, but what I see a lot of the time are tiny, single-purpose, libraries that only marginally improve the workflow.

This sort of library can also dictate your entire architecture. They can lock you into something that might be trendy now, but in a year or two, you find yourself scratching your head and wondering how you ever got into this mess in the first place.

The Solution

If you are using a smaller library, why not write it yourself? Autolayout DSL's, JSON parsers, custom animations; these aren't generally very complicated to do on your own and since you wrote it, you should be able to easily fix it when needed. It will reduce app bloat, improve security, and you will get better at the problems these libraries solve.

Reducing boilerplate and simplifying common programming tasks are the siren songs of software engineering, they can be quite attractive, and it is easy to add them to your project. In reality, for larger projects, they are traps that lock you into spending quite a lot of your time putting out the fires they can create.

If you want my opinion, they are best avoided when possible.

Afterthoughts

It was not my intent to spend so much time picking on AFNetworking, it just provided a good example for issues I will encounter. AFNetworking was incredibly useful back before NSURLSession was a thin, but it is so easy to do it right with just foundation libraries these days, I wonder why it is still so popular for simple networking tasks. If you know why, please tweet at me.


Auto-Completion And Organization for NotificationCenter

I may be late to the party, but I just discovered the most amazing trick thanks to this article.

Have you seen this before?


NotificationCenter.default.post(name: NSNotification.Name(rawValue: "didSomething"), object: nil)

... yea, its pretty ugly, not to mention incredibly error prone. If you have to use this same notification in multiple parts of your app, you are very likely to typo that string literal at some point.

But, I just discovered some syntax sugar to help clean that up.


extension NSNotification.Name {
    static let appDidSomething = NSNotification.Name(rawValue: "didSomething")
}

With that in place, you can start adjusting your call-sites like this.


NotificationCenter.default.post(name: .appDidSomething, object: nil)

How glorious is that?

In retrospect, it seems a bit obvious, but the best tricks usually are.


Self-Explained Swift #2


// Self-Explained Swift #2
// Tools to make our code easier to manage and read.

import UIKit
import PlaygroundSupport

// Welcome to my second Self-Explained Swift. In this playground, we are starting with
// the code from the previous post but with some new changes and the old comments removed.
// If you feel like you don't understand something, go back and check it out.

// The idea I want to convey in this post is "Tool Creation". You can create many tools
// that can be reused throughout your app that will help cut down on coding mundane tasks
// such as view creation and common layout constraints.

// The "Tools" in this instance will be extensions. If you are not familiar with them,
// extensions allow you to bolt new functions on to existing types. Below I have added
// a few functions to assist us in initializing common UI elements, and generating common
// view layouts.

extension UIView { // Layout extension

    // This function will encapsulate the process of adding a view, enabling autolayout,
    // and configuring the common constraints
    func constrainTo(view: UIView) {

        // Turn on autolayout
        view.translatesAutoresizingMaskIntoConstraints = false

        // Because of the way we worded the function, view will be the parent and self,
        // the child. It might look a little strange here, but as you will see below, it
        // reads quite nicely in the call-sites.
        view.addSubview(self)

        // I was notified about the new NSLayoutAnchor system since my last post, this
        // makes alot nicer constraint building, so we use that here.
        view.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
        view.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
        view.leftAnchor.constraint(equalTo: self.leftAnchor).isActive = true
        view.rightAnchor.constraint(equalTo: self.rightAnchor).isActive = true

    }

}

extension UIStackView {

    // UIStackView has alot of things that are frequently changed from the defaults. This
    // new init overload will allow us to one-line most of it.
    convenience init(arrangedSubviews: [UIView],
                     axis: UILayoutConstraintAxis,
                     distribution: UIStackViewDistribution,
                     alignment: UIStackViewAlignment) {

        // Chain to the original initializer.
        self.init(arrangedSubviews: arrangedSubviews)

        // Set our custom properties here.
        self.axis = axis
        self.distribution = distribution
        self.alignment = alignment

        // Again, it's nice to hide this away, since we will always need it off anyways.
        self.translatesAutoresizingMaskIntoConstraints = false

    }

}

// Here we are going to make class functions to help create a sort of "theme" for
// our app.

// For the most part, this is just our previous button code, refactored into a
// class function. We provide function parameters to set things likely to be
// different per instance.

// We also want to set translatesAutoresizingMaskIntoConstraints, so we can completely
// remove that from our view controller code.

extension UIButton {

    class func standardAwesomeButton(title: String) -> UIButton {

        let button = UIButton()

        button.setTitle(title, for: .normal)
        button.translatesAutoresizingMaskIntoConstraints = false

        return button
    }

}

extension UILabel {

    class func standardAwesomeLabel(title: String) -> UILabel {

        let label = UILabel()

        label.font = UIFont(name: "Menlo", size: 14)
        label.textColor = .white
        label.text = title
        label.textAlignment = .center
        label.translatesAutoresizingMaskIntoConstraints = false

        return label
    }

}

class OurAwesomeViewController: UIViewController {

    lazy var titleLabel: UILabel = {
        return UILabel.standardAwesomeLabel(title: "Awesome")
    }()

    lazy var button: UIButton = {

        let button = UIButton.standardAwesomeButton(title: "Press Me")
        button.addTarget(self,
                         action: #selector(OurAwesomeViewController.buttonTest),
                         for: .touchUpInside)

        return button
    }()

    override func loadView() {

        super.loadView()

        view.backgroundColor = .blue

        // We are using our custom UIStackView Initializer, This will reduce quite
        // a bit of the duplicated code and make your call-sites much easier to read.
        let verticalLayout = UIStackView(arrangedSubviews: [titleLabel, button],
                                         axis: .vertical,
                                         distribution: .fill,
                                         alignment: .fill)

        verticalLayout.isLayoutMarginsRelativeArrangement = true
        verticalLayout.layoutMargins = UIEdgeInsets(top: 20, left: 20, bottom: 20, right: 20)

        // Call our new layout function, this encapsulates and simplifies the common
        // task of adding views and setting their constraints.
        verticalLayout.constrainTo(view: view)

    }

    func buttonTest(sender: UIButton) {
        view.backgroundColor = .red
    }

}

// Fire up our awesome view controller in a playground.
PlaygroundPage.current.liveView = OurAwesomeViewController()
PlaygroundPage.current.needsIndefiniteExecution = true

// As you can see, this greatly cleans up our layout code and makes it easier to
// manage. The View Controller is now ~43 lines of code and centralizes our styling.
// One forgotten property or function call could have caused your view to not render,
// but with this new setup, that code is now shared among other views and should be
// much easier to diagnose, and less likely to happen in the first place.

// Using these techniques, you can make your view controllers smaller, and simply
// theme creation. You could (if you wanted to) make several extensions for different
// styles of buttons, labels, or any sort of UI element. A change in any one would
// instantly be reflected across your app, with the only downside being the initial
// one-time setup.

// That's it for #2, next time, we will adjust the architecture to move your app-logic
// out of the ViewControllers as well.

Download This Playground

Check out the previous post: Self-Explained Swift #1