iOS Development Q & A

2022/04/12

iOS Development Q & A

Below is a summary of notes taken during my Swift/iOS development study.

TOC

Data Structure

Q: What are the differences between struct and class?

A: struct and class have the following features in common:

They also have the following differences:

struct:

class:

Stack and Heap:

Q: What are the common use cases of struct and class?

A:

struct:

class:

Q: What do you need to do, to have a mutable struct?

A: Mark methods that change the internal state as mutating; Also, the variable of this struct type need to be var instead of let.

Q: For Swift enum, how does raw value differ from associated value?

A:

Raw values are constants associated to enum cases, Swift doesn’t allow duplicate values because each enum case must have a unique value. An enum can be converted to its raw value by using the rawValue property, and there’s dedicated initializer to convert a raw value to an enum instance.

Associated values are data associated to enum case, cases can have zero or more such values.

Q: Can you name two benefits of using subclassing instead of enums with associated types?

A: A superclass prevents duplication; no need to declare the same property twice. With subclassing, you can also override existing functionality.

Q: Can you name two benefits of using enums with associated types instead of subclassing?

A: No need to refactor anything if you add another type, whereas with subclassing you risk refactoring a superclass and its existing subclasses. Second, you’re not forced to use classes.

Q: Which raw types are supported by enums?

A: String, character, and integer and floating-point types.

Q: Are an enum’s raw values set at compile time or runtime?

A: Raw type values are determined at compile time.

Q: Are an enum’s associated values set a compile time or runtime?

A: Associated values are set at runtime.

Q: Which types can go inside an associated value for enum?

A: All types fit inside an associated value.

Q: How can enums references themselves as associated value?

A: You can use indirect enum, they are called “indirect” because they modify the way Swift stores them so they can grow to any size. Without the indirection, any enum that referenced itself could potentially become infinitely sized: it could contain itself again and again, which wouldn’t be possible.

Q: How could you preserve a struct’s memberwise initializer while having your custom initializer?

A: Define your custom initializer in an extension.

Q: How could you list all cases of an enum?

A: Swift has a CaseIterable protocol that automatically generates an array property of all cases in an enum. To enable it, all you need to do is make your enum conform to the CaseIterable protocol and at compile time Swift will automatically generate an allCases property that is an array of all your enum’s cases, in the order you defined them.

Q: What is a tuple?

A: A tuple is a data structure like an anonymous struct, it can hold different types of data and it can be created on the fly. It’s commonly used to return multiple values from a function call. Tuples can be accessed using element names if provided, or using a position in the tuple, e.g. 0 and 1. While element names are not mandatory, it’s a good idea to have them, so the code is more readable. Why return a tuple instead of specific struct? When the return values are simple enough and not reused in different places, especially in cases like returning two elements from an array which doesn’t make sense to live in a specific struct.

Back to ToC

Memory Management

Q: What is ARC?

A: Automatic Reference Counting is a memory management method Swift uses to determine when objects should be deallocated. Each object has a reference count, and when the reference count reaches 0 the object is deallocated.

An object’s reference count is increased by 1 when a strong reference is assigned to that object. An object’s reference count is decreased by 1 when a strong reference is removed from that object.

Q: What is retain cycle?

A: It’s possible to write code in which an instance of a class never gets to a point where it has zero strong references. This can happen if two class instances hold a strong reference to each other, such that each instance keeps the other alive. This is known as a strong reference cycle.

Q: What are the difference between weak and unowned references?

A:

A weak reference does not increment or decrement the reference count of an object. Since weak references do not increment the reference count of an object, a weak reference can be nil, meaning the object could be deallocated while the weak reference is still pointing to it.

Like a weak reference, an unowned reference does not increment or decrement the reference count of an object. However, unlike a weak reference, the program guarantees to the Swift compiler that an unowned reference will not be nil when it is accessed.

Q: Do UIView Animation Blocks Require Weak or Unowned Self?

A: Not all blocks require weak or unowned self to prevent retain cycles. UIView animation blocks run only once and are then deallocated. This means even strong references in animation blocks will be released when the block runs.

Q: Are weak and unowned only used with self inside closures?

A: No, you can indicate any property or variable declaration weak or unowned as long as it’s a reference type.

Q: What will the code print?

var thing = "day"

let closure = { [thing] in
  print("To\(thing)")
}

thing = "morrow"

closure()

A:

It’ll print: “Today”.

The capture list creates a copy of thing when you declare the closure. This means that captured value doesn’t change even if you assign a new value to thing.

If you omit the capture list in the closure, then the compiler uses a reference instead of a copy. Therefore, when you invoke the closure, it reflects any change to the variable.

Q: Are closure value or reference types?

A: Closures are reference types. Assigning a closure to a variable, copy the variable into another, it’s still the reference being copie with its capture list.

Q: How would you identify and resolve a retain cycle?

A: There are tools in Instruments that shows memory leaks, which can be a start point for debugging retain cycle; Also, Memory Graph Debugger can be used to visualize memory allocations, which helps identifying retain cycles; Once a retain cycle is identified, we need to decide what should be made weak in order to break the cycle.

Back to ToC

Optionals

Q: What is an optional:

A: Optional is a Swift feature that a variable of any type represent either a value or a lack of value. In Objective-C, similar idea is only available for reference types using the nil special value. Value types, such as int or float, do not have this ability.

Q: How do you assign an optional to a variable with a fallback value if the optional is nil?

A: Use nil-coalescing operator ??, it falls back to a default value, but also unwraps the optional if it does have a value.

Q: What is optional chaining?

A: When you need a value from an optional property that can also contain another optional property.

Q: When do we use optional chaining vs if let or guard?

A: Optional chaining is used when we do not really care if the operation fails.

Q: Can you describe how to convert an optional Boolean property, say, feature toggle, which can be enabled, disabled or not set, into an enum?

A:

enum FeatureToggle: RawRepresentable {
  case enabled
  case disabled
  case notSet

  init(rawValue: Bool?) {
    switch rawValue {
      case true?: self = .enabled
      case false?: self = .disabled
      default: self = .notSet
    }
  }

  var rawValue: Bool? {
    switch self {
      case .enabled: return true
      case .disabled: return false
      case .notSet: return nil
    }
  }
}

Q: When is force unwrapping acceptable?

A: Ideally force unwrapping should not be used, it tries to convert an optional to a value regardless of it contains a value or not, so if it contains .none, nil, it triggers an error that crashes the app. But sometimes it cannot be avoided because otherwise the app can end up in a bad state, e.g. the app can’t recover from a nil value:

Q: Possible cases when implicitly unwrapped optionals cannot be avoided?

A:

  1. When you cannot initialize a property that is not nil by nature at instantiation time. A typical example is an Interface Builder outlet, which always initializes after its owner. In this specific case — assuming it’s properly configured in Interface Builder — you’ve guaranteed that the outlet is non-nil before you use it.
  2. To solve the strong reference cycle problem, which is when two instances refer to each other and require a non-nil reference to the other instance. In such a case, you mark one side of the reference as unowned, while the other uses an implicitly unwrapped optional.

Q: If no optionals in a function are allowed to have a value, what would be a good tactic to make sure that all the optionals are filled?

A: Use guard to block optionals at the start of a function.

Q: If a number of functions are executed in different paths depending on the optionals inside them, what would be a correct approach to handle all these paths?

A: Put multiple optionals inside a tuple allows you to pattern match on them and take different paths in a function.

Q: What are good alternatives to implicit unwrapped optional?

A: Lazy properties or factories that are passed via an initializer.

Q: What are the various ways to unwrap an optional? How do they rate in terms of safety?

A: There are several ways:

var x : String? = "Test"

Forced unwrapping — unsafe.

let a: String = x!

Implicitly unwrapped variable declaration — unsafe in many cases.

var a = x!

Optional binding — safe.

if let a = x {
  print("x was successfully unwrapped and is = \(a)")
}

Optional chaining — safe.

let a = x?.count

Nil coalescing operator — safe.

let a = x ?? ""

guard statement — safe.

guard let a = x else {
  return
}

Optional pattern — safe.

if case let a? = x {
  print(a)
}

Q: What’s the difference between nil and .none?

A: There is no difference, as Optional.none (.none for short) and nil are equivalent. In fact, this statement outputs true:

nil == .none

The use of nil is more common and is the recommended convention.

Q: What happens when you call map on optionals?

A: Mapping on optionals lets you perform actions on the optional as if the optional were unwrapped, so inside map’s closure you don’t need to know or deal with optional, this saves you some boilerplate code for unwrapping with temporary variables manually. On top of this, you can also chain several mapping operations.

Back to ToC

Protocols and Generics

Q: What is Protocol Extension?

A: Protocol extension is to extend a protocol and provide default implementation of methods, so that classes that conform to the protocol will have the default implementation for free, and classes can selectively override protocol methods just like class inheritance. The benefit of this is that a class can have 0 or 1 superclass but protocols don’t have such constraint.

Q: Explain how a function with a generic type parameter works behind the scene.

A: Swift creates multiple functions behind the scenes for different types of the generic parameter types, this process is called monomorphization where the compiler turns polymorphic code into concrete singular code. Swift is clever enough to prevent all combinations of concrete parameter types that lead to large binaries. Swift uses several measures involving metadata to limit the amount of code generation. In the case of function with generic parameter type, the compiler creates a low-level function; For relevant types, Swift generates metadata, called value witness tables. At runtime, Swift passes the corresponding metadata to the low-level representation of generic parameter type when needed.

Q: What is invariance for Swift’s generics? Is there any exception?

A: Swift’s generics are invariant, meaning even if a generic type wraps a subclass, it does not make it a subtype of a generic wrapping its superclass. Invariance is a safe way to handle polymorphism. Swift’s builtin generic types, such as Array or Optional, do allow for subtyping with generics. e.g. where it expects Array<BaseClass>, it’s OK to pass an instance of type Array<SubClass>.

Q: Given the below protocol:

protocol SomeProtocol {}

What is the difference between the following statements?

func doWork(_ something: SomeProtocol) {}
func doWork<T: SomeProtocol>(_ something: T) {}

A: The nongeneric function uses dynamic dispatch (runtime), the generic function is resolved at compile time.

Q: What are the differences between using a protocol as a type, and using generics that conform to the protocol?

A: Using a protocol as a type speeds up programming and makes mixing and swapping things around easier. Generics are more restrictive and wordy, but they give you performance benefits and compile-time knowledge of the types you’re implementing.

Q: Is there any problem with the following code:

protocol Input {}
protocol Output {}
protocol Processor {
  @discardableResult
  func start(input: Input) -> Output
}

func batchProcess(processor: Processor, input: [Input]) {
  input.forEach { value in
    processor.start(input: value)
  }
}

A: For every type you want to use for the input and output, they have to conform to Input and Output protocols, including String, URL, etc, which ends up with boilerplate code. Another downside is that you’re introducing a new protocol for each parameter and return type. Lastly, adding a new method on Input or Output would require the implementation on all the types that conform to the protocol.

One solution is to use associatedtype: an associated type gives a placeholder name to a type that’s used as part of the protocol. The actual type to use for that associated type isn’t specified until the protocol is adopted. One way to think of an associated type is that it’s a generic that lives inside a protocol. Conforming types can use the typealias keyword to specify the type that will replace each associated type placeholder. In many cases, however, Swift is able to infer that type from the context.

protocol Processor {
  associatedtype Input
  associatedtype Output

  @discardableResult
  func start(input: Input) -> Output
}

func batchProcess<P: Processor>(processor: P, input: [P.Input]) {
  input.forEach { value in
    processor.start(input: value)
  }
}

Like generics, associated types get resolved at compile time.

Q: What is a Self requirement in a protocol?

A: A Self requirement is a placeholder in a protocol for a type, used as part of the protocol, which is replaced by the concrete type that conforms to the protocol. A Self requirement can thus be thought of as a special case of an associated type. Like a protocol with an associated type, a protocol with a Self requirement is also an incomplete protocol since the type to be used in place of the Self placeholder gets resolved only when the protocol is adopted. The difference is that, while a conforming type can use any type it wants to replace an associated type, any Self placeholder must be replaced by the confirming type itself.

Q: In the following code, how could you limit the Input to be only of type String?

func batchProcess<P: Processor>(processor: P, input: [P.Input]) {
  input.forEach { value in
    processor.start(input: value)
  }
}

A:

func batchProcess<P>(processor: P, input: [P.Input]) where P: Processor, P.Input == String {
  input.forEach { value in
    processor.start(input: value)
  }
}

Q: Given the following code, how could you apply protocol inheritance to further constrain it, say Input has to be an URL and Output has to be Data?

protocol Processor {
  associatedtype Input
  associatedtype Output

  @discardableResult
  func start(input: Input) -> Output
}

func batchProcess<P>(processor: P, input: [P.Input]) where P: Processor, P.Input == URL, P.Output == Data {}

A:

protocol URLProcessor: Processor where Input == URL, Output == Data {}

func batchProcess<P: URLProcessor>(processor: P, input: [P.Input]) {}

Q: Given the following piece of code of a Broadcastor with a default implementation, how could you add a default implementation for it that also sanitise the message before broadcasting, using protocol inheritance? And how could you do the same with protocol composition? What are the downsides of each approach?

protocol Broadcaster {
  func send(_ message: String)
}

extension Broadcaster {
  func send(_ message: String) {
    print("Message is sent!")
  }
}

A:

Protocol inheritance:

protocol SanitisingBroadcaster: Broadcaster {
  func sanitise(_ message: String) throws
}

extension SanitisingBroadcaster {
  func send(_ message: String) {
    guard try? sanitise(message) else {
      preconditionFailure("Message invalid!")
    }
    print("Sending: \(message)")
  }
  func sanitise(_ message: String) throws {
    // check message
  }
}

struct OnlineBroadcaster: SanitisingBroadcaster {
  // …
}

let broadcaster = OnlineBroadcaster()
broadcaster.send("Hello, world!")

Protocol composition:

protocol BroadcastSanitiser {
  func sanitise(_ message: String) throws
}

extension BroadcastSanitiser {
  func sanitise(_ message: String) throws {
    // …
  }
}

struct OnlineBroadcaster: Broadcaster, BroadcastSanitiser {}

extension BroadcastSanitiser where Self: Broadcaster {
  func send(_ message) {
    guard try? sanitise(message) else {
      preconditionFailure("Invalid message!")
    }
    print("Sending: \(message)")
  }
}

let broadcaster = OnlineBroadcaster()
broadcaster.send("Hello, world!")

Q: What are conditional conformance?

A: Conditional comformance is conforming to a protocol only if certain conditions are true. For example, making Array conforming to PlayerList only if the array’s elements conform to Player.

protocol Player {
  var historyScore: Int { get }
}

extension Array where Element: Player {
  func totalScore() -> Int {
    return reduce(0) { result, element in
      result + element.historyScore
    }
  }
}

Q: What’s the problem with the following code and how could you improve it?

protocol AdsProtocol {
  func showAds()
}

extension UIViewController: AdsProtocol {
  func showAds() {}
}

A:

extension AdsProtocol where Self: UIViewController {
  func showAds() {}
}

Now a view controller can choose to conform to AdsProtocol and gets the method implementation for free on an as-needed basis.

Back to ToC

Collections

Q: Explain Sequence in Swift

A: A type that provides sequential, iterated access to its elements. A sequence is a list of values that can be stepped through one at a time, the most common way to iterate over the elements of a sequence is to use for-in loop. This capability gives you access to a large number of operations that can be performed on any sequence, such like contains(_:).

To add Sequence conformance to your own custom type, add a makeIterator() method and returns an iterator. Alternatively, if your type can act as its own iterator, implementing the requirements of IteratorProtocol and declaring conformance to both Sequence and IteratorProtocol.

struct Countdown: Sequence, IteratorProtocol {
    var count: Int

    mutating func next() -> Int? {
        guard count > 0 else {
            return nil
        }
        deter { count -= 1 }
        return count
    }
}

Q: Explain zip(_:_:)

A:

zip creates a sequence of pairs built out of two underlying sequences.

let words = ["one", "two", "three", "four"]
let numbers = 1...4

for (word, number) in zip(words, numbers) {
    print("\(word): \(number)")
}

// prints "one: 1"
// prints "two: 2"
// prints "three: 3"
// prints "four: 4"

If the two sequences are different lengths, the resulting sequence is the same length as the shorter sequence:

let naturalNumbers = 1...Int.max
let zipped = Array(zip(words, naturalNumbers))
// zipped == [("one", 1), ("two", 2), ("three", 3), ("four", 4)]

Q: What is Hashable?

A: A type that can be hashed to produce an integer hash value.

The Hashable protocol inherits from Equatable protocol, which must also be satisfied.

If use properties that don’t all conform to Hashable, or if you want to have your own comparison for equality, say a Student object may have a studentID which is enough to uniquely identify a particular student, then you need to implement your own hash(into:) method, combining the properties that need to be taken into account.

Q: What is subscripts?

A: Subscripts are shortcuts for accessing the member elements of a collection, list or sequence, or a custom type that supports subscripts.

Q: What is the difference between reduce and reduce into?

A:

The both can be used to produce a single value from the elements of an entire sequence.

reduce

reduce iterates over a collection, taking an initial value and a closure which applies a transformation to that value. The closure provides two parameters, the partial result obtained in each iteration, and the next element of the collection.

reduce makes sense when you are not creating expensive copies for each iteration, such as reducing into an integer.

reduce into

reduce into passes the partial result by reference instead of value, it is preferred over reduce(_:_:) for efficiency when the result is a copy-on-write type, for example an Array or a Dictionary.

Q: What is the Sequence protocol and how could you implement it?

A: A type that provides sequential, iterated access to its elements. To add Sequence conformance to your own custom type, add a makeIterator() method that returns an iterator. Alternatively, if your type can act as its own iterator, implementing the requirements of the IteratorProtocol protocol and declaring conformance to both Sequence and IteratorProtocol are sufficient.

Q: What is AnyIterator?

A: AnyIterator is a type erased iterator, it accepts a closure when initialized, which is called whenever next is called on the iterator.

Q: [coding] Create a Bin that represents classified garbage bin that stores waste by types with number of count. e.g.

var bin = Bin<String>()
bin.insert("thought")
bin.insert("thought")
bin.insert("thought")
bin.insert("value")
bin.remove("thought")
bin.count // 3
print(bin)
// Output:
// thought occurs 2 times
// value occurs 1 time

let anotherBin: Bin = [1, 2, 2, 5, 5, 5]
print(anotherBin)
// Output:
// 1 occurs 1 time
// 2 occurs 2 times
// 5 occurs 3 times

A:

struct Bin<Element: Hashable>: IteratorProtocol {

  private var store = [Element: Int]()

  mutating func insert(_ element: Element) {
    store[element, default: 0] += 1
  }

  mutating func remove(_ element: Element) {
    store[element]? -= 1
    if store[element] == 0 {
      store[element] = nil
    }
  }

  var count: Int {
    store.values.reduce(0, +)
  }
}

extension Bin: CustomStringConvertible {
  var description: String {
    var summary = String()
    for (key, value) in store {
      let times = value == 1 ? "time" : "times"
      summary.append("\(key) occurs \(value) \(times)\n")
    }
    return summary
  }
}

To implement Sequence you need an iterator:

struct BinIterator<Element: Hashable>: Sequence, IteratorProtocol {
  
  var store = [Element: Int]()

  mutating func next() -> Element? {
    guard let (key, value) = store.first else {
      return nil
    }
    if value > 1 {
      store[key]? -= 1
    } else {
      store[key] = nil
    }
    return key
  }
}

Alternatively, we can implement Sequence by returning a new AnyIterator:

extension Bin: Sequence {
  func make Iterator() -> AnyIterator<Element> {
    var exhaustiveStore = store // create a copy
    return AnyIterator<Element> {
      guard let (key, value) = exhaustiveStore.first else {
        return nil
      }
      if value > 1 {
        exhaustiveStore[key]? -= 1
      } else {
        exhaustiveStore[key] = nil
      }
      return key
    }
  }
}

Finally, we can make Bin implement ExpressibleByArrayLiteral:

extension Bin: ExpressibleByArrayLiteral {
  typealias ArrayLiteralElement = Element
  init(arrayLiteral elements: Element...) {
    store = elements.reduce(into: [Element: Int]()) { (updatingStore, element) in
      updatingStore[element, default: 0] += 1
    }
  }
}

Q: What is IteratorProtocol?

A:

It has an associated type element and that element is the type of the thing that you’re going to be pending or the type of the thing that you’re going to be iterating over, and it has one function called next, which returns the next element and mutates that Iterator:

protocol IteratorProtcol {
    associatedtype Element
    mutating func next() -> Element?
}

Q: What are the differences between Sequence and Collection?

A:

Sequence:

Sequence is a list of elements. It has two important caveats:

  1. it can be either finite or infinite
  2. you can only ever iterate through it one time, sometimes you will be able to iterate it more than once, but your not guaranteed to be able to iterate it more than once

Collection:

Collection inherits from Sequence. Every Collection will always be finite, and you can iterate that Collection as many times as you want.

protocol Collection {
  associatedtype Index: Comparable
  var startIndex: Index
  var endIndex: Index
  subscript(position: Index) -> Iterator.Element { get }
  func index(after index: Index) -> Index
}

Q: [coding] Create a TodoList which is a Collection, with every Day mapping to one or more Activity. Given the definition of Day and Activity below:

struct Activity: Equatable {
  let date: Date
  let description: String
}

struct Day: Hashable {
  let date: Date
  init(date: Date) {
    let unitFlags: Set<Calendar.Component> = [.day, .month, .year]
    let components = Calendar.current.dateComponents(unitFlags, from: date)
    guard let convertedDate = Calendar.current.date(from: components) else {
      self.date = date
      return
    }
    self.date = convertedDate
  }
}

A:

struct TodoList {
  typealias DataType = [Day: [Activity]]
  private var todos = DataType()
  init(activities: [Activities]) {
    self.todos = Dictionary(grouping: activities) { activity in
      Day(date: activity.date)
    }
  }
}

extension TodoList: Collection {
  typealias KeysIndex = DataType.Index
  typealias DataElement = DataType.Element
  var startIndex: KeysIndex { return todos.keys.startIndex }
  var endIndex: KeysIndex { return todos.keys.endIndex }

  func index(after i: KeysIndex) -> KeysIndex {
    return todos.index(after: i)
  }

  subscript(index: KeysIndex) -> DataElement {
    return todos[index]
  }
}

extension TodoList {
  subscript(date: Date) -> [Activity] {
    return todos[Day(date: date)] ?? []
  }
  subscript(day: Day) -> [Activity] {
    return todos[day] ?? []
  }
}

extension TodoList: ExpressibleByArrayLiteral {
  init(arrayLiteral elements: Activity...) {
    self.init(activities: elements)
  }
}

Q: What is the difference between Array and Set in term of performance?

A: Set is faster than Array in general because elements in a Set need to conform to the Hashable protocol which allows Set to be optimized for performance, it takes same amount of time to lookup an element in a small collection vs a large collection.

Back to ToC

UIKit

Q: What are the pros and cons of creating UI programatically vs using storyboard?

A:

Storyboard is straightforward with drag and drop, gives visual representation of the screen and properties of the UI elements. Constructing UI programatically gives you a sense of control that you see the code of what you change on top of default values. Multiple people working on the same storyboard can create merge conflicts.

Q: When multiple people working on different screens defined inside a single storyboard, what is the technique to avoid merge conflict?

A: Use storyboard reference.

Q: What is unwind segue?

A: An unwide segue (sometimes called exit segue) can be used to navigate back through push, modal or popover segues, you can go back multiple steps in navigation hierarchy.

Q: What is the difference between the frame and the bounds?

A: The bounds of an UIView is the rectangle, expressed as location and size relative to its own coordinate system; The frame of an UIView is the rectangle, expressed as location and size relative to the superview it is contained within.

For UIScrollView, its frame can be (0, 0) while its bounds can be non-zero, if its contentOffset isn’t 0.

Q: How could you setup live rendering in storyboard?

A: Using @IBInspectable and @IBDesignable lets you build interface with custom controls and have them rendered in real-time during designing in storyboard.

@IBInspectable properties provide access to user-defined runtime attributes. Accessible from the identity inspector, it’s basically a mechanism for configuring any key-value coded property of an instance in a NIB, XIB, or storyboard.

Built-in view types can also be extended to have inspectable properties beyond the ones already in Interface Builder’s attribute inspector. e.g. the following code exposes the cornerRadius property of a view’s layer:

extension UIView {
    @IBInspectable var cornerRadius: CGFloat {
        get {
            return layer.cornerRadius
        }
        set {
            layer.cornerRadius = newValue
            layer.masksToBounds = newValue > 0
        }
    }
}

Prefixing @IBDesignable (or IB_DESIGNABLE macro in Objective-C) allows Interface Builder to perform live updates on a particular view.

Q: How could you design UI layout so it adapts to different devices, ie. iPhone and iPad?

A: On top of auto-layout, you can use size classes to custom your user interface for given device class, based on its orientation and screen size. Size classes let you add extra layout configuration to your app so that your UI works well across different devices.

Q: What is Intrinsic Content Size?

A: Intrinsic Content Size is something that a view can provide, to indicate the size needed for the view to render properly; It gives information to the Auto Layout engine that a particular view has a predefined size that the engine can use to calculate and lay it out among other views.

Q: What is the layer object of a view?

A: It’s a data object that represents visual content. Layer objects are used by views to render their content. Custom layer objects can also be added to the interface to implement complex animations and other types of sophisticated visual effects.

Q: What is the “file owner” in interface builder?

A: The file owner is the object that loads the nib, i.e. the object which receives the message loadNibNamed: or initWithNibName:; If you want to access any objects in the nib after loading it, you can set an outlet in the file owner. By default View Controllers act as file owners, it’s external to the nib and not part of it, it’s only available when the nib is loaded.

Q: What is Safe area?

A: The safe area of a view reflects the area not covered by navigation bars, tab bars, toolbars and other system component that overlaps a view controller’s view. Additional insets can be specified via additionalSafeAreaInsets.

Q: Difference between accessibilityIdentifier, accessibilityLabel and accessibilityValue

A:

Q: How could you dictate the order in which views are read when VoiceOver is turned on?

A: You can use the method in your container view called index of accessibility element, which returns the index of the specified accessibility element, so you can arrange the order of the subviews in term of VoiceOver.

Q: What’s the difference between xib and storyboard?

A: Both for creating views using interface builder, xib is for view or single view controller, storyboard support multiple view controllers with transition flow.

Q: What is Dynamic Type?

A: Dynamic Type is a feature on iOS that enables the app’s content to scale based on the user’s preferred content size. It helps users who need larger text for better readability. And it also accommodates those who can read smaller text, allowing for more information to appear on the screen.

Q: How could you add shadow to a view?

A: In UIKit, all view layers have options for shadow opacity, radius, offset, color and path. In SwiftUI, you can use the shadow() modifier. Dynamic shadow can be expensive with transparency calculations, rasterising the layer makes the shadow part of the overall bitmap which is less expensive but the current opacity of the layer is not rasterized.

Q: What are your experience using CoreGraphics?

A: Draw shapes and convert UIView into UIImage for saving locally.

Q: What are the benefits of using child view controllers?

A: Dividing a large view controller into child view controllers, the same functionality remains but in several smaller parts which are easier to maintain; Also, there can be cases where a view is reused as subviews in several screens (or view controllers), and the view has a number of business logic in it, which deserves a dedicated view controller.

Q: What are the pros and cons of using viewWithTag()?

A: The only possible pro is that it provides a way to find a subview, but it should not be used just because you can, as using magic number isn’t a good idea.

Q: When would you choose to use a collection view rather than a table view?

A: Collection views can display tiles in columns and rows, it can also handle custom layouts, whereas table view is for linear lists with headers and footers. In iOS 14, UICollectionView got a new feature that lets you include lists in the collection view.

Q: What happens when Color or UIColor has values outside 0 to 1?

A: In RGB color space, values outside of 0 to 1 should be clamped, but with the addition of wide colors, e.g. extended color space, it’s valid to have color values that are outside 0 to 1.

Back to ToC

Concurrency

Q: Why do we need to specify self to refer to a stored property or a method in asynchronous code?

A: Since the code is dispatched to a background thread, we need to capture a reference to the correct object.

Q: What is DispatchGroup?

A: DispatchGroup allows for aggregate synchronization of work, you can use them to submit multiple different work items and track when they all complete.

Q: What are the common ways to perform asynchronous operations?

A:

Closures/Call backs

It’s a block of code in which we perform the operation and that block of code can be executed asynchronously. Usually it calls a completion block or delegate callback when the operation finishes.

Pros: simple for basic async tasks. Cons: it can become difficult to manage if the block has dependency on other block of code in the form of closure, you end up with nested closures which is hard to maintain.

Combine

In combine there are three main roles: subscriber, publisher and subscription

Subscriber subscribes to Publisher who creates and hands over Subscription; Upon receiving Subscription, Subscriber can request data so the Subscription receives the demand and starts working, emits data when the work is done, Subscriber receives data and determine if it need more data by adding demands.

Actors

Actor is also a concrete nominal type like struct, class or enum, it’s created using the actor keyword.

import UIKit

actor Hero {
  var name = "Nameless"

  func printIntro() {
    print("The hero is \(name).")
  }

  func visit(_ hero: Hero) async {
    print("Visiting another hero.")
    await hero.printIntro()
  }
}

let hero1 = Hero()
let hero2 = Hero()

Task {
    print(await hero1.name)
    await hero1.visit(hero2)
}

Output:

Nameless
Visiting another hero.
The hero is Nameless.

Q: Swift 5.5 introduced modern concurrency features, what are async, await, Task and TaskGroup?

A:

Swift splits up the code into logical units called partial tasks, or partials, the runtime schedules each of them separately for asynchronous execution.

The TaskGroup can spawn multiple tasks simutaneously, wait for the completion, and return the result only after all the child tasks have finished execution. The withThrowingTaskGroupfunction method that does this, requires a result type that specifies the type of the result of the function.

With TaskGroup:

Q: What is DispatchWorkItem?

A: DispatchWorkItem encapsulates work to be performed on a dispatch queue or a dispatch group. It is primarily used in scenarios where we require the capability of delaying or canceling a block of code from executing. It lets you cancel the task if the task hasn’t started.

A common use case is searching, you want to delay the search for 0.3 second to make sure user has finished typing all the characters.

var workItem: DispatchWorkItem?

func search(_ term: String) {
  workItem?.cancel()
  let searchWorkItem = DispatchWorkItem {
    // perform search
  }
  workItem = searchWorkItem
  DispatchQueue.global().asyncAfter(deadline: .now() + .milliseconds(300), execute: workItem)
}

notify schedules the execution of specified work item after the completion of another work item:

func fetchPlayers() {
  let fetchWorkItem = DispatchWorkItem {
    // fetch players from server
  }
  let notifyWorkItem = DispatchWorkItem {
    // notify the completion of fetching
  }
  fetchWorkItem.notify(queue: .main) {
    notifyWorkItem.perform() // start execution of the work item synchronuously on the current thread
  }
  DispatchQueue.global().async(execute: fetchWorkItem)
}

wait causes the caller to wait synchronously until the dispatch work item finishes executing.

let workItem = DispatchWorkItem {}
DispatchQueue.global().async(execute: workItem)
workItem.wait()

DispatchWorkItemFlags defines a set of behaviours for a work item, such as quality-of-service class and whether to create a barrier or spawn a new detached thread. The most commonly used flags are assignCurrentContext and barrier.

Q: What are the syntax of async/await for function, property and closure?

A:

Function: add async before return type, if the function throws, add async before throws:

func someFunction() async throws -> Int {}
let someValue = try await someFunction()

Computed property: add async to the getter and access it by prepending await:

var someProperty: Int {
  get async {
    
  }
}

let value = await someProperty

Closure: add async to the signature:

func doSomething(completion: async (Result<Data, Error>) -> Void) {
  // …
}

doSomething { result in
  await parse(result)
}

Q: What does the runtime do when execution reaches a method called with await?

A:

Q: The following code makes two asynchronous calls, marking them with await makes the second call won’t happen until the first call finishes. How could you make them happen at the same time?

playerProfiles = try await game.playerProfiles()
playerPhotos = try await game.playerPhotos()

A: Swift offers a special syntax that groups several async calls and await them all together:

do {
  async let playerProfiles = try game.playerProfiles()
  async let playerPhotos = try game.playerPhotos()
} catch {
  // …
}

let (profiles, photos) = try await (playerProfiles, playerPhotos)

Q: What is the syntax in modern Swift concurrency world (ie. async/await) that runs code on the main thread, equivalent to DispatchQueue.main.async?

A:

await MainActor.run {}

To make sure a method is executed on the main actor automatically:

@MainActor func someMethod()

Q: What is AsyncSequence?

A: AsyncSequence is a protocol describing a sequence that can produce elements asynchronously, similar to Sequence, except that the next element need to be await.

for try await item in asyncSequence {}
while let item = try await asyncSequence.makeAsyncIterator().next() {}

Q: Is NSNotification send asynchronously or synchronously? If a notification is sent from a non-main thread, in which thread it is received?

A: Synchronous. It will be received on the same non-main thread.

Q: When should GCD and NSOperation be used?

A:

For one-off computation, or simply speeding up an existing method, it will often be more convenient to use a lightweight GCD dispatch than employ NSOperation. NSOperation can be scheduled with a set of dependencies at a particular queue priority and quality of service. Unlike a block scheduled on a GCD queue, an NSOperation can be cancelled and have its operational state queried. And by subclassing, NSOperation can associate the result of its work on itself for future reference.

Q: What are the options for converting old async code to modern async/await?

A:

XCode’s Refactor function provides three options to conver async code to modern async/await. For code below:

func performSearch(completion: @escaping (Result<[SearchResult], SearchError>) -> Void) {
  guard let url = URL(string: "https://itunes.apple.com/search?term=\(searchText)") else {
    completion(.failure(.generic))
    return
  }

  URLSession.shared.dataTask(with: url) { data, response, error in
    if let error = error { completion(.failure(error)) }
    if let data = data {
      let response = try? JSONDecoder().decode(SearchResponse.self, from: data)
      completion(.success(response?.results ?? []))
      return
    }
    completion(.failure(.generic))
  }.resume()
}

performSearch { [weak self] result in
  switch result {
    case .success(let response):
      // …
    case .failure(let error):
      // …
  }
}

Refactor the async function has three options:

Add Async Wrapper

func performSearch() async throws -> [SearchResult] {
  return try await withCheckedThrowingContinuation { continuation in
    performSearch() { result in
      continuation.resume(with: .result)
    }
  }
}

The original function has been modified:

@available(*, renamed: "performSearch()")
func performSearch(completion: @escaping (Result<[SearchResult], SearchError>) -> Void) {}

So now you can use:

Task {
  do {
    results = try await performSearch()
  } catch {
    // …
  }
}

// instead of
// performSearch { [weak self] result in
//   switch result {
//     case .success(let response):
//       // …
//     case .failure(let error):
//       // …
//   }
// }

Add Async Alternative

func performSearch() async throws -> [SearchResult] {
  guard let url = URL(string: "https://itunes.apple.com/search?term=\(searchText)") else {
    completion(.failure(.generic))
    return
  }

  return try await withCheckedThrowingContinuation { continuation in
    URLSession.shared.dataTask(with: url) { data, response, error in
      if let error = error { completion(.failure(error)) }
      if let data = data {
        let response = try? JSONDecoder().decode(SearchResponse.self, from: data)
        completion(.success(response?.results ?? []))
        return
      }
      completion(.failure(.generic))
    }.resume()
  }
}

Convert Function to Async

The last option simply converts the old function to an Async one. This is the most disruptive approach that you will have to modify every place where the old function is called.

Further Refactor

func performSearch() async throws -> [SearchResult] {
  guard let url = URL(string: "https://itunes.apple.com/search?term=\(searchText)") else {
    throw .generic
  }
  let (data, _) = try await URLSession.shared.data(from: url)
  let result = try JSONDecoder().decode(SearchResponse.self, from: data)
  return result.results ?? []
}

Back to ToC

Language Specific

TODO: Explain Swift vs Objective-C

A:

Both are OO.

Swift

Objective-C

Q: How does Swift ensure type safety?

A: Swift doesn’t allow implicit type casting, so you can’t declare something as Int and store a Double value in it.

Q: Why is immutability important?

A: Immutability is like a contract, saying something will never change. So for developers, it allows you not having to worry about what the value is at different stages, especially if the value is unexpected, how and when it was change; For the compiler it can perform certain optimizations, like putting struct on stack instead of heap, it even gives you warning if something can be let but is declared as var.

Q: What is identity operator in Swift?

A: === and !== are the identity operators in Swift, they are only used for object types.

Q: What is the guard statement?

A: A guard statement is used to transfer program control out of scope. Guard statement is similar to the if statement, but it runs only when some conditions are not met.

Q: Describe how to define a custom operator.

A:

  1. Declare:
precedencegroup ExponentPrecedence {
  higherThan: MultiplicationPrecedence
  associativity: right
}
infix operator **: ExponentPrecedence
  1. Implementation:
func **(base: Int, exponent: Int) -> Int {
  let l = Double(base)
  let r = Double(exponent)
  let p = pow(l, r)
  return Int(p)
}

Q: What is Type Aliasing?

A: Assigning another name to an existing data type.

Q: How do you use existing keyword as variable identifier?

A: By surrounding the variable with single tick mark.

Q: Is a single character in a pair of double quotation mark a Character or String?

A: By default all characters are Strings, you have to declare it as Character to make it a Character.

Q: What’s the computational complexity of String’s count?

A:

O(n).

Swift uses extended grapheme clusters for Character values, each can be composed of multiple Unicode scalars, as a result, the number of characters in a string can’t be calculated without iterating through the string to determine its extended grapheme cluster boundaries.

The count of the characters returned by the count property isn’t always the same as the length property of an NSString that contains the same characters. The length of an NSString is based on the number of 16-bit code units within the string’s UTF-16 representation and not the number of Unicode extended grapheme clusters within the string.

Q: Is floating-number Float?

A: By default all floating point values are doubles, use explicit type annotation to make it a Float.

Q: What is ternary operator?

A: Ternary operator operates on three targets, if the first expression is true, it evaluates and returns the value of second expression, otherwise it evaluates and returns the value of the third expression.

Q: How do you define a type that can be initialized with an Int literal? Say

level1 = Level(1)
level2: Level = 2

A:

Swift offers several protocols that allow you to initialize a type with literal values by using the assignment operator.

public struct Level {
  private let level: Int
  public init(_ level: Int) {
    self.level = level
  }
}

extension Level: ExpressibleByIntegerLiteral {
    public init(integerLiteral value: IntegerLiteralType) {
        self.init(value)
    }
}

a: Level = 1

Q: What is the difference between class and static properties or functions?

A:

Q: How does constants in ObjC const differ from a let constant in Swift?

A: A const variable is initialized at compile time with a value or expression that have to be resolved at compile time; A let variable is a constant determined at runtime, it can be initialized with either a static or dynamic expression.

Q: What is LLVM, LLDB and Clang?

A: The LLVM Project is a collection of modular and reusable compiler and toolchain technologies. Clang is a C language front-end for LLVM. LLDB builds on libraries provided by LLVM and Clang to provide a great native debugger.

Q: Explain Semaphore in iOS?

A: DispatchSemaphore provides an efficient implementation of a traditional counting semaphore, which can be used to control access to a resource across multiple execution contexts.

Q: What is availability attributes?

A:

It indicates the code should only be called if running system meet the requirement specified.

@available(iOS 9.0, *)

@available(iOS, introduced: 9.0)
@available(OSX, introduced: 10.11)
// is replaced by
@available(iOS 9.0, OSX 10.11, *)

if #available(iOS 9.0, *) {}

Q: What is the difference between Any and AnyObject?

A:

Q: What is property wrapper?

A: A property wrapper adds a layer of separation between code that manages how a property is stored and the code that defines a property. For example, if you have properties that provide thread-safety checks or store their underlying data in a database, you have to write that code on every property. When you use a property wrapper, you write the management code once when you define the wrapper, and then reuse that management code by applying it to multiple properties.

To define a property wrapper, you make a structure, enumeration, or class that defines a wrappedValue property. In the code below, the TwelveOrLess structure ensures that the value it wraps always contains a number less than or equal to 12. If you ask it to store a larger number, it stores 12 instead.

@propertyWrapper
struct TwelveOrLess {
    private var number: Int
    init() { self.number = 0 }
    var wrappedValue: Int {
        get { return number }
        set { number = min(newValue, 12) }
    }
}

You can also use generics with property wrappers.

@propertyWrapper
struct UserDefaultsStore<Value> {
    let store = UserDefaults.standard
    let key: String
    
    var wrappedValue: Value? {
        get {
            store.value(forKey: key) as? Value
        }
        set {
            store.setValue(newValue, forKey: key)
            store.synchronize()
        }
    }
}

struct Player {
  @UserDefaultsStore<String?>(key: "name") var name
  @UserDefaultsStore<Int?>(key: "age") var age
}

Property wrapper and closure:

@propertyWrapper
struct MainThread<T> {
  var closure: ((T) -> Void)?

  var wrappedValue: ((_ data: T) -> Void)? {
    get {
      { data in
        DispatchQueue.main.async {
          closure?(data)
        }
      }
    }
    set {
      closure = newValue
    }
  }
}

class ViewModel {
  @MainThread var updateUI: ((_ response: Result<ResponseModel, ResponseError>) -> Void)?

  func fetchData() {
    DispatchQueue(label: "background").async { [weak self] in
      guard let self = self else {
        return
      }
      let result = // fetch data
      self.updateUI?(result)
    }
  }
}

With Swift 5.5 we can use property wrapper as arguments:

private func toLowercase(@Lowercased _ str: String) -> String {
  str
}

Some limitations of property wrappers:

Q: What is type erasure?

A:

Type erasure is useful when types are long and complex, which is often the case with Combine. For example, AnyPublisher<SomeType, Never> is a publisher that will provide SomeType and never throw an error, we don’t care what exactly the publisher is.

Q: What is downcasting?

A:

When we’re casting an object to another type in Objective-C, it’s pretty simple since there’s only one way to do it. In Swift, though, there are two ways to cast — one that’s safe and one that’s not . as used for upcasting and type casting to bridged type as? used for safe casting, return nil if failed as! used to force casting, crash if failed. should only be used when we know the downcast will succeed.

Q: What is inout?

A: inout parameter means if function modifies the parameter as local variable, the passed-in parameter is also modified, it’s like inout means reference type whereas value type if without inout.

Q: Differences between internal, fileprivate, private, and public private(set)?

A:

Q: What are Non-Escaping and Escaping Closures?

A: The lifecycle of a non-escaping closure is simple: Pass a closure into a function The function runs the closure (or not) The function returns Escaping closure means, inside the function, you can still run the closure (or not); the extra bit of the closure is stored some place that will outlive the function. There are several ways to have a closure escape its containing function: Asynchronous execution: If you execute the closure asynchronously on a dispatch queue, the queue will hold onto the closure for you. You have no idea when the closure will be executed and there’s no guarantee it will complete before the function returns. Storage: Storing the closure to a global variable, property, or any other bit of storage that lives on past the function call means the closure has also escaped.

Q: What are designated and convenience initializers?

A: Designated initializers are:

Convenience initializers are:

Every class must have a designated initializer, if this class inherits from another, the designated initializer is responsible for calling the designated initializer of its immediate superclass.

Classes can have any number of convenience initializers, a convenience initializer must call another initializer from the same class, whether it is a designated initializer or another conveniences initializer.

Convenience initializers must ultimately call a designated initializer.

Q: What does final do in Swift?

A: Prevents class or method from being overridden

Q: Differences between open, public?

A:

Q: When a class has more properties than its super class, how could you still use the designated initializer of superclass?

A: override the designated initializer of the superclass, initialize all properties that the subclass defines before calling super’s designated initializer.

Q: What are the purposes of required initializer?

A: Adding required keyword to an initializer assures that subclasses implement the required initializer. The two common reasons are factory methods and protocols.

Q: What is defer?

A: defer keyword provides a safe and easy way to declare a block that will be executed only when execution leaves the current scope.

Defer is usually used to cleanup the code after execution. This might involve deallocating container, releasing memory or close the file or network handle. When put into the routine, code inside defer block is last to execute before routine exits. Routine in this case could refer to a function. Sometimes when function returns there are hanging threads, incomplete operation which needs to cleanup. Instead of having them scattered across function, we can group them together and put in the defer block. When function exits, cleanup code in the defer gets executed.

Q: What is platform limitation of tvOS?

A:

  1. no browser support, this means the app can’t link out to a web browser for things like OAuth or social media sites
  2. cannot explicitly use local storage
  3. app bundle cannot exceeed 4GB

Q: What is ABI?

A:

ABI stands for Application Binary Interface, at runtime, Swift program binaries interact with other libraries through an ABI, it defines many low level details for binary. ABI stability means locking down the ABI to the point that future compiler versions can produce binaries conforming to the stable ABI. It enables binary compatibility between applications and libraries compiled with different Swift versions.

Q: What is KVO?

A: KVO stands for Key-Value Observing and allows a controller or class to observe changes to a property value. In KVO, an object can ask to be notified of any changes to a specific property; either its own or that of another object.

Q: What is KVC?

A: KVC adds stands for Key-Value Coding. It’s a mechanism by which an object’s properties can be accessed using string’s at runtime rather than having to statically know the property names at development time.

Q: What is the difference between Delegate and KVO?

A: Both are ways to have relationships between objects. Delegation is a one-to-one relationship where one object implements a delegate protocol and another uses it and sends messages to it, assuming that those methods are implemented since the receiver promised to comply to the protocol. KVO is a many-to-many relationship where one object could broadcast a message and one or multiple other objects can listen to it and react. KVO does not rely on protocols. KVO is the first step and the fundamental block of reactive programming (RxSwift, ReactiveCocoa, etc.)

Q: What are failable and throwing initializers?

A: Very often initialization depends on external data, this data can exist as it can not, for that Swift provides two ways to deal with this. Failable initializers return nil of there is no data, and let the developer “create” a different path in the application based on that. In other hand throwing initializers returns an error on initialization instead of returning nil.

Q: Can you describe what is conditional conformance and give an example?

A: conditional conformance is a Swift feature introduced in version 4.1, it allows you to automatically conform to certain protocols if all properties in your custom type conform to these protocols.

Q: How could you make your custom type conform to Hashable?

A:

  1. implement the func hash(into hasher: inout Hasher)
  1. also conform to Equatable

Q: What is Codable?

A: Introduced in Swift 4, the Codable API enables us to leverage the compiler in order to generate much of the code needed to encode and decode data to/from a serialized format, like JSON.

Codable is actually a type alias that combines two protocols — Encodable and Decodable. By conforming to either of those protocols when declaring a type, the compiler will attempt to automatically synthesize the code required to encode or decode an instance of that type, which will work as long as we’re only using stored properties that themselves are encodable/decodable.

struct Player: Codable {
    var name: String
    var age: Int
}

Encode a Player into Data:

do {
    let player = Player(name: "Doe", age: 19)
    let encoder = JSONEncoder()
    let data = try encoder.encode(player)
} catch {
    print("Failed to encode player: \(error)")
}

Decode a Player from `Data:

let decoder = JSONDecoder()
let player = try decoder.decode(Player.self, from: data)

Q: How does Codable work if the field names in data source e.g. JSON do not match the name in data model defined in code?

{
    "player_data": {
        "full_name": "John Doe",
        "user_age": 19
    }
}

A: Two ways that have downsides:

  1. Change the code of data model to match with data source. This may introduce unconventional coding conventions like full_name instead of name or fullName; Also, if the project has code quality automation set up, e.g. SwiftLint, it may stop the code from compiling.
  2. Manually perform encoding and decoding. This would require extra code.

Another way is to define a new type that’s specifically used for encoding and decoding:

extension Player {
    struct CodingData: Codable {
        struct Container: Codable {
            var fullName: String
            var playerAge: Int
        }

        var playerData: Container
    }
}

extension Player.CodingData {
    var player: Player {
        return Player(
            name: playerData.fullName,
            age: playerData.userAge
        )
    }
}

To decode:

let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let codingData = try decoder.decode(Player.CodingData.self, from: data)
let player = codingData.player

Q: What is the difference between the Float, Double, and CGFloat data types?

A: Float is always 32-bit, Double is always 64-bit, and CGFloat is either 32-bit or 64-bit depending on the device it runs on, but realistically it’s just 64-bit all the time. In Swift 5.5, the compiler can automatically perform conversion between Double and CGFloat values.

Q: What are one-sided ranges and when would you use them?

A: In Swift, you can create a range using closed range operator or the half-open range operator:

let closedRange = 0...9
closedRange.count   // 10

let halfOpenRange = 0..<10
halfOpenRange.count // 10

One-sided ranges allow us to skip either the start or end of a range to have Swift infer the starting point for us:

let items = [
  "keyboard",
  "cable",
  "monitor",
  "harddrive",
  "charger"
]

items[...2] // ["keyboard", "cable", "monitor"]
items[..<2] // ["keyboard", "cable"]
items[2...] // ["monitor", "harddrive", "charger"]

One-sided ranges can be useful when you want to read from a certain portion of a collection, such as if you want to skip the first N elements in an array.

Q: What does it mean when we say “strings are collections in Swift”?

A: This statement means that Swift’s String type conform to the Collection protocol, which means String values have all the methods defined in Collection protocol, for example, you can loop over characters, count how long the string is, map the characters and so on.

Q: What is UUID and how do you generate it?

A: UUID stands for universally unique identifier, which means if you generate a UUID right now using UUID it’s guaranteed to be unique across all devices in the world. It’s a great way to generate a unique identifier for something you need to reference.

let uuid = UUID().uuidString

Q: What is @autoclosure and when would you use it?

A: @autoclosure lets you define an argument that automatically gets wrapped in a closure. It’s usually used when you want to defer the execution of a potentially expensive or unnecessary expression when it’s passed as argument. One example is the assert function which are triggered only in debug builds, there’s no need to evaluate it in release builds.

Q: What is the difference between self and Self?

A: self refers to the current object the code is runing inside, Self refers to the current type the code is running inside.

Q: What does targetEnvironment do?

A: It’s a condition that lets you differentiate between builds that are for physical devices and those that are for a simulated environment.

Q: What are key paths?

A: A key path refers to a property in a type rather than the exact value of that property in one particular instance.

struct Player {
  let name: String
  let age: Int
}

let players: [Players] = // …
let allNames = players.map(\.name)

Q: When would you use defer?

A: When you need to delay a piece of code until a function ends. For example, removing a temporary file when the function finishes.

Q: What is a variadic function?

A: Variadic function accepts any number of parameters, written as ... (three dots):

func arithmeticMean(_ numbers: Double...) -> Double {
    var total: Double = 0
    for number in numbers {
        total += number
    }
    return total / Double(numbers.count)
}

Q: What does the #available syntax do?

A: This syntax was introduced in Swift 2.0 to allow run-time version checking of features by OS version number. It allows you to target an older version of iOS while selectively limiting features available only in newer iOS versions.

Q: What does CaseIteratable do?

A: A type that provides a collection of all of its values.

enum Direction: CaseIterable {
    case north, south, east, west
}

let directions = Direction.allCases

Q: What is String Interpolation?

A: It’s the process of embedding values inside the String object.

Q: What is Opaque Type and what problem does it solve?

A:

Opaque type is type with a prefix keyword called some. It allows you to return a value by providing the protocol it conforms to, without exposing the concrete type. This is useful when developing a module where you don’t want to leak the implementation detail. It sounds similar to generic type, but we can often run into a problem where the compiler complains saying the protocol can only be used as a generic constraint because it has Self or associated type requirements. This is because the compiler can’t resolve the type of the value being returned, by adding the keyword some, we make it an opaque type that allows the compiler to extract additional information about the concrete type.

For example, we have light provider factory that produces object which conforms to LightProviding:

public protocol LightProviding {
    func letThereBeLight() -> String
}

struct DefaultLightProvider: LightProviding {
    func letThereBeLight() -> String {
        return "Let there be light."
    }
}

public struct LightProviderFactory {
    public func makeLightProvider() -> DefaultLightProvider {
        return DefaultLightProvider()
    }
}

We get an error saying:

Method cannot be declared public because its result uses an internal type

because the public factory returns a DefaultLightProvider which is internal.

To make it work, we change the factory to return the public protocol:

public struct LightProviderFactory {
    public func makeLightProvider() -> LightProviding {
        return DefaultLightProvider()
    }
}

It works until we have associated type in the LightProviding protocol:

public protocol LightProviding {
    associatedtype Light
    func letThereBeLight() -> Light
}

struct DefaultLightProvider: LightProviding {
    func letThereBeLight() -> String {
        return "Let there be light."
    }
}

public struct LightProviderFactory {
    public func makeLightProvider() -> LightProviding {
        return DefaultLightProvider()
    }
}

Protocol ‘LightProviding’ can only be used as a generic constraint because it has Self or associated type requirements

We fix the error by making the return type opaque:

public struct LightProviderFactory {
    public func makeLightProvider() -> some LightProviding {
        return DefaultLightProvider()
    }
}

Q: What is phantom types?

A: Phantom types are a type that doesn’t use at least one its generic parameters – they are declared with a generic parameter that isn’t used in their properties or methods. So types are used as markers, rather than being instantiated to represent values or objects.

enum PaymentType {
  enum BPay {}
  enum PayID {}
  enum BSBAccount {}
}

struct Transaction<Type> {
  let amount: Decimal
  let payer: Payer
  let payee: Payee
}

func confirmBPay(_ transaction: Transaction<PaymentType.BPay>) {}
func confirmBSBAccount(_ transaction: Transaction<PaymentType.BSBAccount>) {}
func confirmPayID(_ transaction: Transaction<PaymentType.PayID>) {}

Even though we don’t use the generic type parameter, Swift does, which means it will treat two instances of our type as being incompatible if their generic type parameters are different. They aren’t used often, but when they are used they help the compiler enforce extra rules on our behalf – they make bad states impossible because the compiler will refuse to build our code.

Q: What are the differences between Some and Any keywords?

A:

The some keyword was introduced in Swift 5.1. It is used together with a protocol to create an opaque type that represents something that is conformed to a specific protocol. When used in the function’s parameter position, it means that the function is accepting some concrete type that conforms to a specific protocol.

The following 3 function signatures are the same:

func sayHi<T: Thing>(to something: T) {}
func sayHi<T>(to something: T) where T: Thing {}
func sayHi(to something: some Thing) {}

When we use the some keyword on a variable, we are telling the compiler that we are working on a specific concrete type, thus the opaque type’s underlying type must be fixed for the scope of the variable.

Assigning a new instance of the same concrete type to a variable is prohibited by compiler:

var human: some Thing = Human()
human = Human() // Compiler error

let things: [some Thing] = [
  Human(),
  Human(),
  Dog() // Compiler error
]

// Compiler error
func giveMeSomething() -> some Thing {
  if likeHuman {
    return Human()
  } else {
    return Animal()
  }
}

The any keyword was introduced in Swift 5.6 for the purpose of creating an existential type. It’s mandatory in Swift 5.7.

func sayHi(to something: any Thing) {}

// No compiler error in Swift 5.7
func giveMeAnything() -> any Thing {
  if likeHuman {
    return Human()
  } else {
    return Animal()
  }
}

// No compiler error in Swift 5.7
let things: [any Thing] = [
  Human(),
  Human(),
  Dog()
]

The limitation of any is you can’t use == to compare two instances of the existential type.

let t1 = giveMeAnything()
let t2 = giveMeAnything()
let areTheyTheSame = t1 == t2 // Compiler error

Back to ToC

Objective-C

Q: What is the difference between _ vs self. in Objective-C?

A: You typically use either when accessing a property in Objective-C. When you use _, you’re referencing the actual instance variable directly. You should avoid this. Instead, you should use self. to ensure that any getter/setter actions are honored.

In the case that you would write your own setter method, using _ would not call that setter method. Using self. on the property, however, would call the setter method you implemented.

Q: What are blocks in Objective-C?

A: Blocks are a language-level feature of Objective (C and C++ too). They are objects that allow you to create distinct segments of code that can be passed around to methods or functions as if they were values. This means that a block is capable of being added to collections such as NSArray or NSDictionary. Blocks are also able to take arguments and return values similar to methods and functions.

The syntax to define a block literal uses the caret symbol(^).

Q: What is Method Swizzling?

A: Method swizzling allows the implementation of an existing selector to be switched at runtime for a different implementation in a classes dispatch table. Swizzling allows you to write code that can be executed before and/or after the original method. For example perhaps to track the time method execution took, or to insert log statements.

#import "UIViewController+Log.h"
@implementation UIViewController (Log)
    + (void)load {
        static dispatch_once_t once_token;
        dispatch_once(&once_token,  ^{
            SEL viewWillAppearSelector = @selector(viewDidAppear:);
            SEL viewWillAppearLoggerSelector = @selector(log_viewDidAppear:);
            Method originalMethod = class_getInstanceMethod(self, viewWillAppearSelector);
            Method extendedMethod = class_getInstanceMethod(self, viewWillAppearLoggerSelector);
            method_exchangeImplementations(originalMethod, extendedMethod);
        });
    }
    - (void) log_viewDidAppear:(BOOL)animated {
        [self log_viewDidAppear:animated];
        NSLog(@"viewDidAppear executed for %@", [self class]);
    }
@end

Q: What is the difference between Array and NSArray?

A: Array is a struct, thus it’s value type in Swift, NSArray is an immutable ObjectiveC class, it is reference type and bridged to Array<AnyObject> in Swift.

Q: What is the difference between iVar and @property

A: iVar is like the backing variable for declared @property, @synthesized property will have backing iVar created, @property is used with KVO, and can be overwritten with custom getter and setter.

Q: What is selector in Objc?

A: In Objective-C, selector has two meanings. It can be used to refer simply to the name of a method when it’s used in a source-code message to an object. It also, though, refers to the unique identifier that replaces the name when the source code is compiled. Compiled selectors are of type SEL. All methods with the same name have the same selector. You can use a selector to invoke a method on an object—this provides the basis for the implementation of the target-action design pattern in Cocoa.

Q: Explain the difference between atomic and nonatomic synthesized properties?

A:

Q: What is the difference between strong, weak, readonly and copy?

A:

Q: What is @dynamic in ObjC?

A: @dynamic tells the compiler that getter and setter are implemented somewhere else, examples like subclasses of NSManagedObject.

Q: What is @synthesize in ObjC?

A:

Synthesize generates getter and setter methods for property, there are a few special cases:

Q: By calling performSelector:withObject:afterDelay: is the object retained?

A: Yes, the object is retained. It creates a timer that calls a selector on the current threads run loop. It may not be 100% precise time-wise as it attempts to dequeue the message from the run loop and perform the selector.

Q: What happens when you call autorelease on an object?

A: When you send an object a autorelease message, its retain count is decremented by 1 at some stage in the future. The object is added to an autorelease pool on the current thread. The main thread loop creates an autorelease pool at the beginning of the function, and release it at the end. This establishes a pool for the lifetime of the task. However, this also means that any autoreleased objects created during the lifetime of the task are not disposed of until the task completes. This may lead to the taskʼs memory footprint increasing unnecessarily. You can also consider creating pools with a narrower scope or use NSOperationQueue with itʼs own autorelease pool. (Also important – You only release or autorelease objects you own.)

Q: What’s the output of the following code:

@property (nonatomic, strong) NSString *strongString;
@property (nonatomic, weak)   NSString *weakString;

strongString =  [NSString stringWithFormat:@"%@",@"string1"];
weakString =  strongString;
strongString = nil;
	
NSLog(@"%@", weakString);

A:

Q: Are atomic properties thread-safe?

A: It’s only safe for its getter and setter, not safe in other cases like if you have an atomic mutable array and you add objects to it.

Q: What is KVO?

A: Key-value observing is a Cocoa programming pattern you use to notify objects about changes to properties of other objects. It’s useful for communicating changes between logically separated parts of your app—such as between models and views. You can only use key-value observing with classes that inherit from NSObject.

Q: What are the steps involved to expose Objective-C class to Swift?

A:

First we need a bridging header file which allows us to define which ObjC files we want to expose to Swift. XCode automatically asks if you’d like to create bridging header file when you add the first Swift file in Objective-C project, or first Objective-C file in Swift project. You can also create the bridging header manually.

Suppose you have an Objective-C code, you have have a different name for Swift:

NS_SWIFT_NAME(ObjcClass)
@interface MyObjcClass: NSObject

@property (strong, nonatomic) NSString *_Nonnull name;

- (void)doSomething;
- (NSString *_Nullable)getSomethingIfAvailable;

@end

Then import this header in the bridging header file:

#import "MyObjCClass.h"

Q: What are the steps involved to expose Swift file to Objective-C?

A:

class MySwiftClass: NSObject {

  @objc
  let prefix = "Mr"

  @objc
  func doSomething() {}
}

enum

Exposing Swift enums to Objective-C requires the enum not having associated values and have to be interger enumeration.

objc(XYZPaymentType)
enum PaymentType: Int {
  case cash
  case debitCard
  case creditCard
}

When enum has associated value:

enum CardType: String {
  case debit
  case credit
}

enum PaymentType {
  case cash
  case card(CardType)

  var rawValue: String {
    switch self {
      case .cash: return "Cash"
      case .card(let cardType): return "Card(\(cardType.rawValue))"
    }
  }
}

@objcmembers class XYZPaymentType: NSObject {
  static let cash = PaymentType.cash.rawValue

  static func card(cardType: String) -> String {
    PaymentType.card(CardType(rawValue: cardType)!).rawValue
  }
}

Back to ToC

Error Handling

Q: What are the ways to handle runtime error in Swift?

A:

In Swift you can define custom Error types, they can use an enumeration that conforms to the Swift error protocol. Methods and initializers can have the throws keyword in the signature to indicate the particular method or initializer can throw error; Errors thrown by the method or initializer propogate to the caller, caller can wrap the code in a try…catch block to catch and handle the error.

Q: What do assert and precondition do and what are the differences?

A: They both evaluate, and if the result is false, they both crash the app. The difference is assert is only active in debug mode, whereas precondition is also active in release mode. As a general rule, assert is used for checking your own code for internal errors and precondition is for checking if the business rule are satisfied, for example, if the arguments are valid to proceed.

Q: Explain rethrows vs throws

A:

Q: Explain how could you make your custom Error bridging to NSError (with proper errorDomain, errorCode and errorUserInfo)?

A: Make your custom Error implement the CustomNSError protocol.

Q: What’s the difference between try? and try! for calling a throwing function?

A: try? prevents the propogation of errors, calling a throwing function with try? doesn’t care about the reason for failure if it throws; try! asserts that an error won’t occur, like force unwrap, either it works or you get a crash.

Q: Can you name one or more downsides of how Swift handles errors?

A: Functions that are marked as throwing doesn’t reveal which errors can be thrown.

Q: What is Result type in Swift?

A: Result type is an enum with two cases: a success case and a failure case:

public enum Result<Success, Failure: Error> {
  case success(Success)
  case failure(Failure)
}

It’s often used in asynchronous code where you get a result asynchronously and the result can be either a value or an error. You can send the result around and handle it at a later stage.

Q: What does map and mapError do on Result?

A: map transform the result’s success value if present, for example, if you have a Result<Data, NetworkError> type, and a variable of this type, if its value is success, calling map you can turn it into Result<JSON, NetworkError> type, transforming the successful Data value into a JSON string; If the variable has an error value, calling mapError you can map the failure value using the given transformation and return a new result, for example, you can transform network error into business domain error.

Q: What does Result(catching:) do?

A: It creates a new result by evaluating a throwing closure, capturing the returned value as a success, or any thrown error as a failure.

Back to ToC

Networking

Q: What are the main components of a HTTP URL? What purpose do they have?

A:

Every HTTP URL consists of the following components:

scheme://host:port/path?query
var components = URLComponents() 
components.scheme = "https" 
components.host = "www.test.com" 
components.path = "/request/path" 
components.queryItems = [URLQueryItem(name: "q", value: "swift"),
                         URLQueryItem(name: "quantity", value: "100")] 
let url = components.url
// https://www.test.com/request/path?q=swift&quantity=100

Q: What is URLSession?

A: URLSession is responsible for sending and receiving HTTP requests, created via URLSessionConfiguration

Q: Difference between WKWebView and UIWebView?

A:

UIWebview:

WKWebView:

Q: What is OAuth?

A: OAuth is an open standard authorization protocol or framework that describes how unrelated servers and services can safely allow authenticated access to their assets without sharing the initial, related, single logon credential. One simple example is when you go to log onto a website, it offers a few ways to log on using another website’s/service’s logon.

Q: What are the different ways of showing web content to users?

A:

  1. UIWebView and WKWebView (UIWebView is deprecated)
  1. SFSafariViewController provides a visible standard interface for browsing the web
  2. UIApplication has a method that opens an URL.

Q: What are the differences between NSCache and URLCache?

A:

NSCache is an in-memory mutable collection to temporarily store key-value pairs. It’s like an NSMutableDictionary that automatically frees up space in memory as needed, as well as more thread-safety and conforms to NSCopying protocol. NSCache uses system’s memory and will allocate a proportional size to the size of data. Until other applications need memory and system forces this app to minimize its memory footprint by removing some of its cached objects. Though, NSCache doesn’t guarantee that the purge process will be in orderly manner. Moreover, the cached objects won’t be there in next run. The main advantages of NSCache are performance and auto-purging feature for objects with transient data that are expensive to create.

URLCache is both in-memory and on-disk cache, and it doesn’t allocate a chunk of memory for it’s data. You can define it’s in-memory and on-disk size, which is more flexible. URLCache will persist the cached data until the system runs low on disk space. For any network data management we should use URLCache rather than NSCache for caching any data.

URLCache.shared = {
    let cacheDirectory = (NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true)[0] as String).appendingFormat("/\(Bundle.main.bundleIdentifier ?? "cache")/" )

    return URLCache(memoryCapacity: /*your_desired_memory_cache_size*/,
                    diskCapacity: /*your_desired_disk_cache_size*/,
                    diskPath: cacheDirectory)
}()

Q: In iOS, a networking feature called App Transport Security (ATS) requires that all HTTP connections use HTTPS. Why?

A:

HTTPS (Hypertext Transfer Protocol Secure) is an extension of HTTP. It is used for secure computer network communication. In HTTPS, the communication protocol is encrypted using TLS (Transport Layer Security).

The goal of HTTPS is to protect the privacy and integrity of the exchanged data against eaves-droppers and man-in-the-middle attacks. It is achieved through bidirectional encryption of communications between a client and a server.

So through ATS, Apple is trying to ensure privacy and data integrity for all apps. There is a way to bypass these protections by setting NSAllowsArbitraryLoads to true in the app’s Information Property List file. But Apple will reject apps who use this flag without a specific reason.

Q:

Build a simply networking layer.

A:


protocol Endpoint {
  var scheme: String { get }
  var baseURL: String { get }
  var path: String { get }
  var parameters: [URLQueryItem] { get }
  var method: String { get }
}

final class Service {
    class func request<ResponseType: Codable>(endpoint: Endpoint, completion: @escaping (Result<ResponseType, Error>) -> Void) {
        var components = URLComponents()
        components.scheme = endpoint.scheme
        components.host = endpoint.baseURL
        components.path = endpoint.path
        components.queryItems = endpoint.parameters
        
        guard let url = components.url else {
            return
        }
        
        var request = URLRequest(url: url)
        request.httpMethod = endpoint.method
        
        let session = URLSession(configuration: .default)
        let dataTask = session.dataTask(with: request) { data, response, error in
            if let error = error {
                completion(.failure(error))
                return
            }
            
            guard response != nil, let data = data else { return }
            
            DispatchQueue(label: "background").async {
                if let responseObject = try? JSONDecoder().decode(ResponseType.self, from: data) {
                    completion(.success(responseObject))
                } else {
                    let error = NSError(domain: "ServiceDomain", code: 200)
                    completion(.failure(error))
                }
            }
        }
        dataTask.resume()
    }
}

enum PaymentEndpoint: Endpoint {

    case payToMobile
    case payToBSBAccount
    case payBill(billerCode: String, referenceCode: String)

    var scheme: String {
        "https"
    }
    
    var baseURL: String {
        "www.xyzpayment.com"
    }

    var path: String {
        switch self {
        case .payToMobile:
            return "payToMobile"
        case .payToBSBAccount:
            return "payToBSBAccount"
        case .payBill:
            return "payBill"
        }
    }
    
    var parameters: [URLQueryItem] {
        switch self {
        case .payToMobile, .payToBSBAccount:
            return []
        case let .payBill(billerCode, referenceCode):
            return [URLQueryItem(name: "biller", value: billerCode),
                    URLQueryItem(name: "reference", value: referenceCode)]
        }
    }
    
    var method: String {
        "GET"
    }
}

Back to ToC

Architecture

Q: What are the common problems that a poor architecture may have?

A:

The problems above is a result of not sticking to SOLID principles, which often manifests as highly interdependent code and long types.

Some considerations when solving the above problems:

refactor large classes by adding dependencies

Break up large classes into a number of smaller classes, the original large class becomes smaller and dependent on the smaller classes that used to be part of the original class.

remove duplicate code

make duplicate code a reuseable component

Q: Explain polymorphism?

A:

Polymorphism is about provisioning a single interface to entities of different types.

Polymorphism allows the expression of some sort of contract, with potentially many types implementing the contract in different ways, each according to their own purpose.

Q: What is dependency injection?

A: It’s a set of software design principles and patterns that enables loosely coupling, basically an object receives other instances it depends on, commonly done by requiring implementations that each conforms to certain protocol. It can be done via initializer, or set as properties of the object. Often, if the dependency doesn’t need to live longer than the object that requires it, the object can instantiate as a default value for its initializer argument.

Dependency injection provides the following benefits:

There are things to be careful with DI:

  1. Do you have to change everywhere in your code to enable DI? If so, this usually means your classes have to wrap and pass around the dependencies through a long route before they’re actually used.
  2. Do you have to inject all dependencies via initializers? If so, you may have messy code which create a number of dependencies and sub-dependencies, and long initializers everywhere.

Q: What is Dependency Inversion Principle?

A: DI decouples classes from one another by explicitly providing dependencies for them, rather than having them create the dependencies themselves.

Q: Why is design pattern important?

A: Design patterns are reusable solutions to common problems in software design. They are templates designed to help you write code that’s easy to understand and reuse.

Q: What is Singleton pattern?

A:

The singleton pattern ensures that only one instance exists for a given class and that there’s a global access point to that instance. It usually uses lazy loading to create the single instance when it’s needed for the first time.

Q: What is Facade pattern?

A:

The facade pattern provides a single interface to a complex subsystem. Instead of exposing the user to a set of of classes and their APIs, only a simple unified API is exposed.

Q: What is Decorator pattern?

A:

The decorator pattern dynamically adds behaviours and responsibilities to an object without modifying its code. It’s an alternative to subclassing where you modify a class’s behavior by wrapping it with another object.

In ObjC two common implementations are Category and Delegation, in Swift there are Extension and Delegation.

Q: What is Adapter pattern?

A:

An adapter allows classes with incompatible interfaces to work together, it wraps itself around an object and expose a standard interface to interact with that object.

Protocol A makes request, B is a class that makes special request, adapter implements A and wraps a B instance, its request implementation would call B’s special request.

Q: What is Observer pattern?

A:

In observer pattern, one object notifies other objects of any state changes.

Q: What is Memento pattern?

A:

In memento pattern, your stuff is saved somewhere external, later when needed, externalised state can be restored without violating encapsulation.

Q: What is VIPER?

A:

View, Interactor, Presenter, Entity and Router:

Pros:

Cons:

Implementation

Q: What is the RIBS pattern used by Uber?

A:

RIBs is Uber’s cross-platform architecture framework. This framework is designed for large mobile applications that contain many nested states.

The main idea of this architecture is that the app should be driven by business logic, and not by the view.

During the app lifecycle, RIBs can be attached and detached, create the child nodes, and interact with them.

RIBs stands for “Router Interactor Builder”.

Example: on the confirm payment screen in an app that has payment.

  1. user taps on the “Confirm” button on payment confirmation screen (View)
  2. the view informs the interactor about the confirmation
  3. interactor then tells the presenter to show an activity indicator, also sends the request to confirm payment
  4. upon payment success, the interactor informs the router, which then navigates user to the receipt screen

Q: What is delegation pattern?

A: The delegation pattern is a powerful pattern used in building iOS applications. The basic idea is that one object will act on another object’s behalf or in coordination with another object. The delegating object typically keeps a reference to the other object (delegate) and sends a message to it at the appropriate time. It is important to note that they have a one to one relationship.

Q: What is factory pattern?

A: Factory Method is used to replace class constructors, to abstract and hide objects initialization so that the type can be determined at runtime, and to hide and contain switch/if statements that determine the type of object to be instantiated.

Q: What is MVC?

A: MVC stands for Model-View-Controller. It is a software architecture pattern for implementing user interfaces.

MVC consists of three layers: the model, the view, and the controller.

Q: What is MVVM pattern?

A:

The MVVM defines the following:

The key aspect of the MVVM pattern is the binding between the View and the View Model. In other words, the View is automatically notified of changes to the View Model.

In iOS, this can be accomplished using Key-Value-Observer (KVO) Pattern. In the KVO Pattern (https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/KeyValueObserving/KeyValueObserving.html) , one object is automatically notified of changes to the state of another object. In Objective C, this facility is built into the run-time. However, it’s not as straightforward in Swift. One option would be to add the “dynamic” modifiers to properties that need to be dynamically dispatched. However, this is limiting as objects now need to be of type NSObject. The alternate is to simulate this behavior by defining a generic type that acts as a wrapper around properties that are observable.

Q: What is MVP pattern?

A:

The MVP defines the following:

In iOS, the interaction between the View and Presenter can be implemented using the Delegation Pattern (https://developer.apple.com/library/content/documentation/General/Conceptual/DevPedia-CocoaCore/Delegation.html). The Delegation Pattern allows one object to delegate tasks to another object. In iOS, the pattern is implemented using a Protocol. The Protocol defines the interface that is to be implemented by the delegate.

In the MVP pattern, the View and Presenter are aware of each other. The Presenter holds a reference to the view it is associated with.

The PresenterProtocol defines the base set of methods that any Presenter must implement. Applications must extend this Protocol to include application specific methods.

protocol PresenterProtocol: class{
    func attachPresentingView(_ view: PresentingViewProtocol)
    func detachPresentingView(_ view: PresentingViewProtocol)
}

The PresentingViewProtocol defines the base set of methods that View must implement. By providing default implementation of the methods in this interface, the conformant view does not have to provide its own implementation. This interface can be extended to define application specific methods.

protocol PresentingViewProtocol: class{
    func dataStartedLoading()
    func dataFinishedLoading()
    func showErrorAlertWithTitle(_ title: String?, message: String)
    func showSuccessAlertWithTitle(_ title: String?, message: String)
}

Q: What is coordinator pattern and what problem does it solve?

A:

The coorindator pattern decouples the application’s navigation responsibility away from the view controller in a self contained module making them more reusable and easier to test. Coordinators only keep navigation logic between screens, and/or references to a storage that has data used by factories for creating modules.

The main goal is to have unchained module-to-module responsibility and to build modules completely independently from each other.

A coorindator may consists of the following items:

struct PaymentCoorindator {
  private let router: Router
  private let factory: PaymentFactory
}

extension PaymentCoordinator {
  func showRecipientList() {
    let recipientList = factory.makeRecipientList()
    recipientList.onSelectRecipient = { [weak self] recipient in
      self?.showRecipientDetail(recipient)
    }
    recipientList.onCreateRecipient = { [weak self] in
      self?.showCreateRecipientScreen()
    }
    router.setRoot(recipientList)
  }
}

Q: What is Redux pattern and what problem does it solve?

A:

Redux is the implementation of an architectural software pattern that prioritizes unidirectional data flow. Created from the Flux architecture (developed by Facebook), it has grown considerably in the development of applications and promises great advantages in its use. It is an alternative to other architectural patterns such as: MVC, MVVM and Viper.

It has the following components:

View dispatches a new action to the store, the store then passes this action to reducer along with the current state and then receives a new state back from reducer; The view is then informed when a new state is created, this can be implemented by using the Observer pattern where view is a subscriber of the store to be notified.

Q: Can you give some examples of where singletons might be a good idea?

A: When there’s something that can have a single instance exist at any time, and it doesn’t makes sense to have multiple instances of it, for example, UIApplication. Although, direct use of UIApplication can be avoided by having specific protocol for the functionalities in UIApplication and refer to the protocol instead of UIApplication’s shared instance, this way you can have dependency injection and it’s also useful for unit testing.

Q: What are the SOLID principles?

A:

S - Single-responsibility principle

A class should have one and only one reason to change, meaning that a class should have only one job.

O - Open-Closed Principle

Objects or entities should be open for extension but closed for modification.

A class should be extendable without modifying the class itself.

L - Liskov Substitution Principle

Let q(x) be a property provable about objects of x of type T. Then q(y) should be provable for objects y of type S where S is a subtype of T.

The principle defines that objects of a superclass shall be replaceable with objects of its subclasses without breaking the application. That requires the objects of your subclasses to behave in the same way as the objects of your superclass.

I - Interface Segregation Principle

A client should never be forced to implement an interface that it doesn’t use, or clients shouldn’t be forced to depend on methods they do not use.

The Interface Segregation Principle (ISP) states that a client should not be exposed to methods it doesn’t need. Declaring methods in an interface that the client doesn’t need pollutes the interface and leads to a “bulky” or “fat” interface.

D - Dependency Inversion Principle

Entities must depend on abstractions, not on concretions. It states that the high-level module must not depend on the low-level module, but they should depend on abstractions.

The Dependency Inversion Principle (DIP) states that high level modules should not depend on low level modules; both should depend on abstractions. Abstractions should not depend on details. Details should depend upon abstractions.

Q: In achitecture design, why is a unidirectional data flow so important?

A: It allows you to clearly reason about your code. In the massive view controller problem, the VC handles all the logic for the application. By defining clear interfaces between layers of the system, and requiring all data flow in the same direction, it’s easy to understand where state is handled and easy to troubleshoot problems.

Q: In architecture design, why is immutability so important?

A: Immutable data structures help prevent a whole lot of state related problems. Immutability simplifies state management across thread so you don’t have to lock mutable states; Prevents accidental state manipulation, and helps enforcing unidirectional data flow by not allowing layers to “cheat” and manipulate existing data structures.

Back to ToC

XCode Project

Q: What are the pros and cons of cocoapods and Swift package manager?

A:

Cocoapods

SPM

Q: Library vs Framework?

A:

Libraries are files that define pieces of code and data that are not a part of your Xcode target.

The process of merging external libraries with app’s source code files is known as linking. The product of linking is a single executable file that can be run on a device.

Based on how libraries are linked to the app, there are two categories: static and dynamic (another category exists also: text Based .dylib stubs — .tbd).

Object files have Mach-O format which is a special file format for iOS and macOS operating systems. It is basically a binary stream with the following chunks:

Header: Specifies the target architecture of the file. Since one Mach-O contains code and data for one architecture, code intended for x86-64 will not run on arm64.

Load commands: Specify the logical structure of the file, like the location of the symbol table. Raw segment data: Contains raw code and data.

Mach-O files support single architecture, lipo tool can be used to package multiple libraries into a universal one, called fat binary, or vice-versa.

Framework is a package that can contain resources such as dynamic libraries, strings, headers, images, storyboards etc. With small changes to its structure, it can even contain other frameworks. Such aggregate is known as umbrella framework.

Frameworks are also bundles ending with .framework extension. They can be accessed by NSBundle / Bundle class from code and, unlike most bundle files, can be browsed in the file system that makes it easier for developers to inspect its contents. Frameworks have versioned bundle format which allows to store multiple copies of code and headers to support older program version.

Q: Static library vs dynamic library?

A:

Static Library

Libraries that are copied as part of the executable file when it is generated, when app is launched, the app including linked static libraries is loaded into the app’s address space.

Pros:

Cons:

Dynamic Library

Dynamic libraries are not statically linked to the client apps, not part of the executable file. They can be loaded and linked into an app either when the app is launched or as it runs.

Pros:

Cons:

Q: What can Cocoapod do with linking libraries?

A:

By default, CocoaPods builds and links all the dependencies as static libraries; You can add use_frameworks! to Podfile to let Cocoapod build and link the dependencies as dynamic frameworks; You can also use specify use_frameworks! :linkage => :static to make Cocoapod build and link dependencies as static frameworks.

Q: What are the problems with Git submodule?

A:

  1. It requires a good understanding of how Git workk. e.g. make sure submodule is pushed separately before pushing the parent repo, checkinng in a bad reference to a submodule blocks other people from using it.
  2. Updating repos to point to a new version of submodule require updating all repos that uses the submodule to point to the latest version.
  3. Git clone doesn’t clone submodule by default, you need git submodule update or git clone --recursive
  4. Git submodule may be out-of-sync if someone updates its and you forgot to specifically pull it.
  5. Switching branches in a repo with submodules is a pain.

App Submission

Q: What is “app ID” and “bundle ID”?

A:

An App ID is a two-part string used to identify one or more apps from a single development team. The string consists of a Team ID and a bundle ID search string, with a period (.) separating the two parts. The Team ID is supplied by Apple and is unique to a specific development team, while the bundle ID search string is supplied by the developer to match either the bundle ID of a single app or a set of bundle IDs for a group of apps.

The bundle ID defines each App and is specified in Xcode. A single Xcode project can have multiple targets and therefore output multiple apps. A common use case is an app that has both lite/free and pro/full versions or is branded multiple ways.

Before code signing app during distribution, XCode automatically prefixes the bundle ID with team ID - the unique character sequence issued by Apple to each development team - and stores the combined string as the app ID. Bundle ID unique identifies an application in Apple’s ecosystem.

Q: What is bitcode?

A: Bitcode refers to the type of code: “LLVM Bitcode” that is sent to iTunes Connect, this allows Apple to use certain calculations to re-optimize apps further and this can be done without uploading a new build.

Slicing is the process of Apple optimizing app for user’s specific device App Thinning is the combination of slicing, bitcode and on-demand resources

Q: What is the purpose of code signing in Xcode?

A: It’s useful for verifying developer identity to make sure the app shipped is safe, also it validates the functionalities enabled by the provisioning profile.

Back to ToC