Tips, tricks and favorites

@attheodo

After almost 10 years on the iOS platform

App Delegate

🤪

You can have more than one!

Single responsibility principle

public protocol ApplicationDelegateResponding: UIApplicationDelegate {}

final class LoggerApplicationService: NSObject, ApplicationService {
    
    func application(_ application: UIApplication, 
                     didFinishLaunchingWithOptions launchOptions: ...) -> Bool 
    {
        log("App started")
        return true
    }
    
    func applicationDidEnterBackground(_ application: UIApplication) {
        log("Entered background")
    }

}

Manual Window stack setup

Cause YOU WILL test at some point

func application(:didFinishLaunching) -> Bool {
    if !testing() {
        setupAppCoordinator()
    }
}

fileprivate func setupAppCoordinator() {
        
      if window != nil { return }
      window = UIWindow(frame: UIScreen.main.bounds)
        
      appCoordinator = AppCoordinator(window: window!, services: services)
        
      let appCoordinatorRootVC: UIViewController? = appCoordinator.start()
      guard let initialVC = appCoordinatorRootVC else {
          fatalError("AppCoordinator didn't return a view controller")
      }
        
      window?.rootViewController = initialVC
      window?.makeKeyAndVisible()
        
}

Kill switch

Cause backwards compatibility is not forever

https://github.com/ArtSabintsev/Siren

let siren = Siren.sharedInstance
        
siren.majorUpdateAlertType = .force
siren.minorUpdateAlertType = .option
siren.patchUpdateAlertType = .skip
siren.revisionUpdateAlertType = .none
        
siren.checkVersion(checkType: .immediately)

Project Structure

  • Group by feature (usually by tab)
  • Inside features, group by type

File "structure"

// MARK: - Static
static let cellHeight: CGFloat = 80.0

// MARK: - IBOutlets
@IBOutlet weak var myLabel: UILabel!

// MARK: - IBActions
@IBAction func didTapDoneButton(sender: UIControl)

// MARK: - Public Properties
weak var delegate: MyDelegate?

// MARK: - Private Properties
fileprivate var someProperty: Bool = false

// MARK: - Overrides
var preferredStatusBarStyle: UIStatusBarStyle { return .lightContent }

// MARK: - Lifecycle
func viewDidLoad() {}
deinit {}

// MARK: - Public Methods
open func doSomething()

// MARK: - Private Methods
fileprivate func doSomethingElse()

Targets

  • As many as you see fit:
    • ​Usually Dev, Staging, Production
  • Make sure your go with different `Info.plist` files and not preprocessor macros:
    • ​Info-{dev/staging/prod/...}.plist
    • Different icons
    • Different API endpoints
    • Different 3rd party services keys

Type Safety

  • Too many APIs rely on Strings
    • ​Storyboard Ids
    • Image names
    • Fonts
    • Segues
    • Nibs
    • Storyboard names
  • Strings on APIs = begging for crashes
https://github.com/mac-cain13/R.swift

Type Safety

myImage.image = R.swift.deleteIcon()
label.font = R.font.muli(size: 15.0)
let main = R.storyboards.main.mainViewController()
let cell = table.dequeReusableCell(withIdentifier: R.reuseIdentitifers.myCell)

Type Safety

  • There's another beast...
    • ​NSNotifications
    • userInfo 🤮
https://github.com/artman/Signals
class NetworkLoader {

    // Creates a number of signals that can be subscribed to
    let onData = Signal<(data:NSData, error:NSError)>()
    let onProgress = Signal<Float>()

    ...

    func receivedData(receivedData:NSData, receivedError:NSError) {
        // Whenever appropriate, fire off any of the signals
        self.onProgress.fire(1.0)
        self.onData.fire((data:receivedData, error:receivedError))
    }

}

networkLoader.onProgress.subscribe(with: self) { (progress) in
    print("Loading progress: \(progress*100)%")
}

Safety in general

extension Collection where Indices.Iterator.Element == Index {
    
    subscript (unsafe index: Index) -> Iterator.Element? {
        return indices.contains(index) ? self[index] : nil
    }
    
}

"Base" view controllers


var appDelegate: AppDelegate {
    return UIApplication.shared.delegate as! AppDelegate
}

func handleReachabilityChanges() {
        
    NotificationCenter.default.addObserver(self,
                                           selector: #selector(reachabilityChanged),
                                           name: ReachabilityChangedNotification,
                                           object: nil)
}
       
// meant to be overriden
@objc func didInternetBecomeReachable() {}       
@objc func didInternetBecomeUnreachable() {}

Copy of DI-MVVM-C

By Thanos Theodoridis

Copy of DI-MVVM-C

  • 236