Detecting first launch, app upgrades and other useful launch informations

One of the first things every iOS developer should add to their project is a way to detect what is happening at launch. It doesn’t have to be complicated, but trust me, this can potentially save you some trouble later on.

I usually create a BaseAppDelegate class that will be responsible for detecting the following:

  • is it the first time the user launch the app?
  • how many times did the user launch count?
  • is this launch the first launch after an app upgrade?
  • when was last time the user launched the app before this launch?

Here is the code I usually put in this BaseAppDelegate class:


//
// BaseAppDelegate.swift
//
// Created by Cyril Chandelier on 21/04/2018.
// Copyright © 2018 Cyril Chandelier. All rights reserved.
//
import UIKit
class BaseAppDelegate: UIResponder, UIApplicationDelegate {
// MARK: – Dependencies
lazy var bundle: Bundle = Bundle.main
lazy var userDefaults: UserDefaults = UserDefaults.standard
// MARK: – UIApplicationDelegate methods
@discardableResult func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey : Any]? = nil) -> Bool {
print("Initializing app")
// Try to detect first launch
if launchCount == 0 {
print("…first launch detected")
applicationFirstLaunch()
}
// Update app launches count
launchCount += 1
print("…\(launchCount) launches")
// Try to detect version upgrade
let currentVersion = bundle.object(forInfoDictionaryKey: "CFBundleShortVersionString")
as! String
if let lastLaunchVersion = lastLaunchVersion, currentVersion != lastLaunchVersion {
print("…upgrade detected")
applicationDidUpgrade(from: lastLaunchVersion, to: currentVersion)
}
// Update last known version if new
if lastLaunchVersion != currentVersion {
print("…saving new version: \(currentVersion)")
lastLaunchVersion = currentVersion
}
// Update last known launch date
print("…saving current launch date")
appLastLaunchDate = Date()
// Make sure user defaults are saved
userDefaults.synchronize()
return true
}
// MARK: – Optional methods to override
func applicationFirstLaunch() {}
func applicationDidUpgrade(from: String, to: String) {}
// MARK: – Utilities
private(set) var launchCount: Int {
get {
return userDefaults.integer(forKey: Keys.appLaunchCount)
}
set {
userDefaults.set(newValue, forKey: Keys.appLaunchCount)
}
}
private(set) var lastLaunchVersion: String? {
get {
return userDefaults.string(forKey: Keys.appLastLaunchVersion)
}
set {
if let newValue = newValue {
userDefaults.set(newValue, forKey: Keys.appLastLaunchVersion)
} else {
userDefaults.removeObject(forKey: Keys.appLastLaunchVersion)
}
}
}
private(set) var appLastLaunchDate: Date? {
get {
return userDefaults.object(forKey: Keys.appLastLaunchDate) as? Date
}
set {
if let newValue = newValue {
userDefaults.set(newValue, forKey: Keys.appLastLaunchDate)
} else {
userDefaults.removeObject(forKey: Keys.appLastLaunchDate)
}
}
}
// MARK: – User default keys
private struct Keys {
static let appLaunchCount = "appLaunchCount"
static let appLastLaunchVersion = "appLastLaunchVersion"
static let appLastLaunchDate = "appLastLaunchDate"
}
}

You just need to make your AppDelegate extends BaseAppDelegate, and call the super method in application(_:didFinishLaunchingWithOptions). Then, you can choose (or not) to override (and therefore implement something for) the applicationFirstLaunch() and/or applicationDidUpgrade(from:to:) methods.

Here are a couple use cases where having this super-class reveals itself handy:

  • I want to know how many times a users came into the app for my analytics
  • I want to know when a users re-open the app after a while, to customize his experience as a returning user
  • I want to show a “What’s new” popup to my returning users who upgraded their app
  • my app was a paid app before, and now it’s free with In-App Purchases to unlock some content, but I don’t want my users who already paid to hit my paywall

Detecting organic vs. local notification vs. remote notification app launches

Since iOS 10 and with the UserNotifications framework, detecting wether the user launched the app organically or from a local/remote notification is a bit different than before.

Let’s say I’ve to send an event to my analytics service when user launches the app, I do this using one of these three calls:

  • Analytics.trackAppLaunched(source: .organic)
  • Analytics.trackAppLaunched(source: .localNotification)
  • Analytics.trackAppLaunched(source: .remoteNotification)

I have a NotificationController class that will be the UNUserNotificationCenter delegate. After experimenting a bit with it, I noticed that userNotificationCenter(:didReceive:withCompletionHandler:) is called after application(:didLaunchWithOptions:) but before applicationDidBecomeActive(_:).

In order to not send the event multiple times, we simply need to set a flag when we handle a notification during launch, I called it handledLaunchAnalytics.


func applicationDidBecomeActive(_ application: UIApplication) {
let notificationController = NotificationController.shared
// Track app launch if the notificationController didn't already do it
if !notificationController.handledLaunchAnalytics {
Analytics.trackAppLaunched(source: .Organic)
}
}


private(set) var handledLaunchAnalytics: Bool = false
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
let atAppLaunch: Bool = (UIApplication.shared.applicationState != .active)
let request: UNNotificationRequest = response.notification.request
if let trigger = request.trigger, trigger is UNPushNotificationTrigger {
// if atAppLaunch == true, call Analytics.trackAppLaunched(source: .remoteNotification)
// and flag handledLaunchAnalytics as true
// then you can handle your remote notification here
} else {
// if atAppLaunch == true, call Analytics.trackAppLaunched(source: .localNotification)
// and flag handledLaunchAnalytics as true
// then you can handle your local notification here
}
completionHandler()
}

Let’s just not forget to reset handledLaunchAnalytics when the app is going back to background.

If you know of a better way for doing this, I would be happy to hear about you!

 

Chasing UIViewController retain cycles

A retain cycle is a common memory issue preventing objects to be released. It’s happens when two (or more) objects have a strong reference one to each other. The main object can not be released because the other object has a strong reference to it, and this other object will only be released when the main object is released. Dead end, these two objects will never be released.

This can lead to random crashes due to lack of memory on your users’ phone or weird behaviours in your app due to existing but not visible objects that are interfering with the normal execution.

Do I have a retain cycle?

That’s easy to test, in your class, add a print statement in the deinit method, also add a breakpoint. Assuming that your view controller is part of a navigation controller, run your app and go to the screen then hit back, you should hit the breakpoint and “MyViewController deinit called” should appear in your console. If this is not happening, then you view controller is part of a retain cycle and will never be released.

deinit {
    print("MyViewController deinit called")
}

Find and fix the retain cycles

We now know that a view controller generates retain cycles, let’s try to fix them. There are several reasons why a retain cycle exists.

I passed my view controller to someone else and he is retaining me

Typical example is with delegate. Let’s say we have this class that needs a delegate

MyCustomObject.swift

protocol MyCustomObjectDelegate {
   // some methods to be implemented
}

class MyCustomObject {
    var delegate: MyCustomObjectDelegate?
}

MyViewController.swift

let myCustomObject = MyCustomObject()
myCustomObject.delegate = self

To fix the retain cycle, we need to change this a little bit:

MyCustomObject.swift

protocol MyCustomObjectDelegate: class {
   // some methods to be implemented
}

class MyCustomObject {
    weak var delegate: MyCustomObjectDelegate?
}

Note that MyCustomObjectDelegate is now a class protocol and that the reference to this object is now weak.

Another case, slightly more subtle is possible, in this case, your delegate is not optional because you always need one and it always has to provide you with some values

MyCustomObject.swift

protocol MyCustomObjectDelegate {
   // some methods to be implemented
}

class MyCustomObject {
    private let delegate: MyCustomObjectDelegate

    init(delegate: MyCustomObjectDelegate) {
        self.delegate = delegate
    }
}

MyViewController.swift

let myCustomObject = MyCustomObject(delegate: self)

In that case, we will need to use the unowned keyword instead of the weak keyword:

MyCustomObject.swift
protocol MyCustomObjectDelegate: class {
   // some methods to be implemented
}

class MyCustomObject {
    private unowned let delegate: MyCustomObjectDelegate

    // init remains the same
}

A block / closure is retained

It’s also easy to generate a retain cycle when using closures. In the following example, the addInfiniteScrollWithHandler methods copies my block to eventually use it later. A copy automatically performs a retain on it, and if the block make use of self, then it’s incrementing my view controller retainCount.

tableView.addInfiniteScrollWithHandler { tableView in
    self.nextPageForScrollView(tableView)
}

The following one is similar, except that a method has been passed as the closure, this is nasty because the self is here implicit:

func infiniteScrollHandler(tableView: UITableView) {}
tableView.addInfiniteScrollWithHandler(infiniteScrollHandler)

To fix it, we need to use a safe version of self, using the [weak self] parameters in the closure. From now, all self. occurrences have to be replaced with self?. or self!.:

tableView.addInfiniteScrollWithHandler { [weak self] tableView in
    self?.nextPageForScrollView(tableView)
}

This can happen, but remember that most of the closures you are writing will probably not be retained! It’s not always necessary and therefore considered as overkill to write [weak self] and handle the optional self in all every closures. In the following example, the block will be executed directly and will not be retained, this will not generate a retain cycle.

UIView.animateWithDuration(0.25, animations: {
    self.view.alpha = 1.0
})

Running multiple instances of Parse Server on the same machine

When migrating from Parse.com to Parse Server, I learned the hard way that NodeJS is not multi-threaded.

ParseServer is made in JavaScript with NodeJS and unfortunately, this technology doesn’t take advantage of the multiple cores your server can have.

This lead to a waste of resources: imagine that a running instance of your server is only using 25/30% CPU before latency goes up. At that point, you have to deploy a new instance of the same server to maintain a low latency. Why? Because of the use of a single thread, incoming requests are quickly queued, especially if one the request takes time to build the response.

To take advantage of the multiples cores you may have on your server, you can use a worker manager such as Throng to manage multiple instances of your Parse Server on the same machine:

var throng = require('throng');
var WORKERS = process.env.WEB_CONCURRENCY || 1;
throng({
workers: WORKERS,
lifetime: Infinity,
start: start
});
function start() {
var app = express();
// Serve static assets from the /public folder
app.use('/public', express.static(path.join(__dirname, '/public')));
// Serve the Parse API on the /parse URL prefix
var mountPath = process.env.PARSE_MOUNT || '/parse';
app.use(mountPath, api);
var httpServer = require('http').createServer(app);
var server = httpServer.listen(process.env.PORT || 1337, function() {
console.log('ParseServer running on port %s', server.address().port);
});
}
view raw index.js hosted with ❤ by GitHub

Don’t ask for too many workers. The appropriate number of workers depends of the number of cores your machine has and is usually a number between 1 and (2 x NUMBER_OF_CORES) + 1.

Writing self-documenting code

Great article giving tips on how to write self-documenting good (aka good code), even though this is written for JavaScript, most of the tips can directly be applied to any languages.

15 Ways to Write Self-documenting JavaScript by Jani Hartikainen: https://www.sitepoint.com/self-documenting-javascript/

Note: this definitely has to be ported to Swift, any volunteer?