Unwrapping optionals in dequeueReusableCell & dequeueResuableCellWithIdentifier

There are two unique methods in the dataSource for UITableVIew: dequeueReusableCell(withIdentifier identifier: String, for indexPath: IndexPath) -> UITableViewCell, dequeueReusableCell(withIdentifier identifier: String) -> UITableViewCell?. A subtle difference separates the two: an optional. It is much like the difference between: array.first & array[0]. The former returns an optional, the latter does not. Optionals deal with values at nil. If a value is optional, then the optional must be unwrapped, especially if it is a type of optional UITableViewCell. Forced unwrapping, however, is dangerous but these two methods provide a unique way to deal with unwrapping an instance of type optional UITableViewCell safely.

identical indices return entirely different results w.r.t.

The consensus in the community is that implicitly unwrapped optionals are safe in only but a few places. Apart from reference outlets & actions created in .storyboard or prepare(for:segue), implicitly unwrapped optionals are safe when loading prototype entities from Interface Builder. Implicitly unwrapping a prototype cell created in an instance of UITableView’s view in a .storyboard scence is, for instance, safe within dequeueReusableCell(withIdentifier identifier: String) -> UITableViewCell?. Since the cell is forcibly downcast with as!, the compiler throws an error anytime dequeueReusableCell(withIdentifier identifier: String) returns nil for UITableViewCell?. Dequeueing the cell with dequeueReusableCell(withIdentifier identifier: String, for indexPath: IndexPath) -> UITableViewCell would thus fail to guarantee a reference. Optional binding becomes odious.

There is another case, however, for their usage where dequeueReusableCell(withIdentifier identifier: String, for indexPath: IndexPath) -> UITableViewCell cannot but return an optional of type UITableViewCell. In a scenario where .storyboard is deleted a cell is initialized in one of two different ways: either through registration register(_ cellClass: AnyClass?, forCellReuseIdentifier identifier: String)or else direct initialization init(style: UITableViewCellStyle, reuseIdentifier: String?). These two different methods are unique not so much for optionality but memory usage. Direct initialization of a UITableViewCell results in a consumption of memory greater than that of registration but registration ultimately results in an optional. The reason being that an instance of a custom subclass of UITableViewCell is passed into dequeueReusableCell(withIdentifier identifier: String, for indexPath: IndexPath) -> UITableViewCell so that even though the function returns a non nil value, required downcasting terminates execution in an optional. The non optional returned from the function becomes an optional of type UITableViewCell.

“Object allocation has a performance cost, especially if the allocation has to happen repeatedly over a short period — say, when the user scrolls a table view. If you reuse cells instead of allocating new ones, you greatly enhance table-view performance.”

Source: iOS Reference Library

While conventional wisdom may recommend ‘forced downcasting’ but there is a way to unwrap the optional value safely. The cell may be downcast optionally in a guard or if let statement in such a way that any failure be alternated to the direct initialization of the cell. While the former’s performance in terms of memory allocation is both stabile as well as sparse, the latter is, nonetheless, functional in that the cells still populate the instance of UITableView; any failure now ceases to be unrecoverable error.

guard let cell = tableView.dequeueReusableCell(withIdentifier: CustomUITableViewCell.cellIdentifier, for: indexPath) as? CustomUITableViewCell else {
let cell = CustomUITableViewCell(style: .default, reuseIdentifier: CustomUITableViewCell.cellIdentifier)
     return cell
}

Coupling both registration as well as direct initialization thus provides a layer of paranoic security against any optional failure. If dequeue fails for whatever reasons, direct initialization with downgraded performance proceeds to populate the UITableView with cells.

Ultimately the subtle difference between dequeueReusableCell(withIdentifier identifier: String, for indexPath: IndexPath) -> UITableViewCell& dequeueReusableCell(withIdentifier identifier: String) -> UITableViewCell? evaporates into thin air. No matter which function is used, downcasting results in an optional. While clearly the best choice between the two in a non .storyboard is the former, the problem of optionality persists.