iOS Development Q & A
Below is a summary of notes taken during my Swift/iOS development study.
TOC
- Data Structure
- Memory Management
- Optionals
- Protocols and Generics
- Collections
- UIKit
- Concurrency
- Language Specific
- Objective-C
- Error Handling
- Networking
- Architecture
- App Submission
Data Structure
Q: What are the commonalities and differences between struct
and class
?
A: struct
and class
have the following features in common:
- both can define properties to store values, and they can define functions
- both can define subscripts to provide access to values with subscript syntax
- both can define initializers to set up their initial state, with
init()
- both can be extended with extension
- both can conform to protocols
- both can work with generics to provide flexible and reusable types
They also have the following differences:
struct:
- value type
- doesn’t support inheritance
- allocated on stack, faster compared to heap
- immutable when declared as
let
class:
- reference type
- supports inheritance unless defined as
final
- allocated on heap, slower compared to stack
- can be deinitialized using
deinit()
- a
let
class instance can still be mutable - can be identity checked by
===
- same instance can be referenced more than once
Stack and Heap:
- Stack is used for static memory allocation and Heap for dynamic memory allocation, both stored in RAM. Allocation of stack is dealt with in compile-time, thus optimisation is possible.
- An important note to keep in mind is that in cases where a value stored on a stack is captured in a closure, that value will be copied to the heap so that it’s still available by the time the closure is executed.
Q: What are the common use cases of struct
and class
?
A:
struct:
- simple data types, e.g.
CGRect
, data models that are well defined without complex relationship between objects - thread safety is concerned in a multi-thread context
- no need for inheritance
- immutability ie.
let
struct instance
class:
- inheritance is needed, e.g. a base class that’s designed to be inherited
- deinitialization is needed, e.g. cleanup a flow
- the same object that need to be referenced by multiple parties
- copying doesn’t make sense, e.g.
UIViewController
- Objective-C compatibility via
@obj
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: 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.
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.
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, 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:
- postponing error handling: when you want to produce working code quickly for success scenario
- when you know better than the compiler: e.g. creating an
URL
from a string
Q: Possible cases when implicitly unwrapped optionals cannot be avoided?
A:
- 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.
- 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.
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!")
- it doesn’t separate functionality and semantics. e.g. anything that sanitises message has to be a regular
Broadcastor
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!")
- composition approach may make the code too decoupled that implementers may not know precisely which method implementation is used under the hood
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:
- assuming all view controllers need to conform to this protocol is probably not safe
- if the code is part of a framework, it will pollute the user of the framework that every view controller now gets the extension whether they like it or not
- it could clash with existing extension if they share the same name
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.
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.
- for
struct
: all its stored properties must conform toHashable
- for
enum
: all its associated values must conform toHashable
, anenum
without associated values hasHashable
conformance even without declaration
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:
- it can be either finite or infinite
- 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.
UIKit
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:
accessibilityIdentifier
: An identifier can be used to uniquely identify an element in the scripts you write using the UI Automation interfaces. Using an identifier allows you to avoid inappropriately setting or accessing an element’s accessibility label.accessibilityLabel
: a succinct label that identifies the accessibility element, in a localized string.accessibilityValue
: the value of the accessibility element, in a localized string. Used when an accessibility element has static label and dynamic value, for example, a text field for message may have “message” as its label and the actual text as its value.
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.
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: Swift 5.5 introduced modern concurrency features, what are async
, await
and Task
?
A:
- async: Indicates that a method or function is asynchronous. Calling it lets you suspend execution until a result is returned from an asynchronous method.
- await: Indicates that the code might pause its execution while it waits for a method or function to return, annotated with
async
. - Task: a unit of asynchronous work, it can be waited to complete or cancelled before it finishes.
Task
is a type that represents a top-level async task, meaning it can create an asynchornous context, that can start from a synchronous context. A newTask
is needed if you want to run asynchronous code from a synchronous context.
Swift splits up the code into logical units called partial tasks, or partials, the runtime schedules each of them separately for asynchronous execution.
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: (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:
- execution of the current code will be suspended.
- the method after
await
will either execute immediately or later, depending on system load and priorities. - if the method or one of its child tasks throws an error, the error will bubble up in the call hierarchy to the nearest
catch
statement.
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.
Language Specific
Q: Describe how to define a custom operator.
A:
- Declare:
- type: unary or binary
- operator itself
- associativity
- precedence
precedencegroup ExponentPrecedence {
higherThan: MultiplicationPrecedence
associativity: right
}
infix operator **: ExponentPrecedence
- 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 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:
static
properties or functions cannot be overridden, equivalent toclass final
class
properties or functions can be overriden
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:
- Any can represent an instance of any type at all, including function types and optional types
- AnyObject can represent an instance of any class type
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) }
}
}
Q: What is downcasting?
A:
as
: used for upcasting and type casting to bridged typeas?
: used for safe casting, returnsnil
if failedas!
: used to force casting, crash if failed
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:
internal
entities can be used within any source file from defining module but not in any file outsidefile-private
entities can only be used by its own defining source fileprivate
entities can be used only by the enclosing declaration and extensions of the declaration in the same file- default access level is
internal
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:
- The CENTRAL point of initialization of a class.
- Classes MUST have at least one.
- Is responsible for initializing stored properties.
- Is responsible for calling super init.
Convenience initializers are:
- SECONDARY supporting initializers for a class.
- It can only call a designated initializer that is defined in the same class
- It can also call another convenience initializers defined in the same class
- They are not required, that sort of implied. they are just initializers that we can write for a CONVENIENCE use case
- In a class, They use the keyword “convenience” before the init keyword.
- Finally here are 3 basic rules of class initialization:
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:
- open allows other modules to use and inherit the class,
open
class members can be used as well as overridden. - public only allows other modules to use the public classes and members, public classes cannot be subclassed, nor public member can be overridden.
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:
- no browser support, this means the app can’t link out to a web browser for things like OAuth or social media sites
- cannot explicitly use local storage
- 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:
- implement the
func hash(into hasher: inout Hasher)
- e.g. call combine on the supplied hasher for each value you want to hash
- 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:
- Change the code of data model to match with data source. This may introduce unconventional coding conventions like
full_name
instead ofname
orfullName
; Also, if the project has code quality automation set up, e.g. SwiftLint, it may stop the code from compiling. - 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
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
, @synthesize
d 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:
atomic
: ensures integrity of setter and getter, safer thannonatomic
, any thread access atomic object can get the correct objectnonatomic
: doesn’t ensure integrity of setter and getter, might be in problem state when different threads trying to access the same object, faster thanatomic
Q: What is the difference between strong
, weak
, readonly
and copy
?
A:
strong
: the reference count will be increased and the reference to it will be maintained through the life of the objectweak
: referencing an object without increasing its reference count, often used when creating a parent child relationshipreadonly
: a property can be read but not modifiedcopy
: the value of an object is copied when it’s assigned, prevents its value from being modified
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:
- readwrite property with custom getter and setter when providing both a getter and setter custom implementation, the property won’t be automatically synthesized
- readonly property with custom getter when providing a custom getter for a readonly property, this won’t be automatically synthesized
@dynamic
when using@dynamic propertyName
, the property won’t be automatically synthesized- properties declared in a
@protocol
when comforming to a protocol, any property the protocol defines won’t be automatically synthesized - properties declared in a category
- overriden property when you override a property of a superclass, you must explicitly synthesize it
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:
NSString
created bystringWithFormat
is autoreleased, there is a delay in releasing it, so the output may not benull
.- In 64-bit system, when a string’s length is less than or equal to 9, the type of string will be
NSTaggedPointerString
, if it has non-ASCII characters, the type would be__NSCFString
. - if the string is initialized via
initWithString:
, it will be of type__NSCFConstantString
.
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.
Error Handling
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:
throws
: used to indicate a function, method or initializer can throw an errorrethrows
: indicate a function or method could throw an error only if one of its function parameters throws an error, it’s for functions that do not throw errors on their own, but only forward errors from their function parameters
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)
}
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.
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
- Scheme — The scheme identifies the protocol used to access a resource, e.g. http or https.
- Host — The host name identifies the host that holds the resource.
- Port — Host names can optionally be followed by a port number to specify what service is being requested.
- Path — The path identifies a specific resource the client wants to access.
- Query — The path can optionally by followed by a query to specify more details about the resource the client wants to access.
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
:
- part of
UIKit
, no need to import additional module - can scale page to fit
WKWebView
:
- need to import
WebKit
framework - higher and more efficient performance
- runs outside of app’s main process
- supports server-side authentication challenge
WKWebViewConfiguration
provides more custom options
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:
UIWebView
andWKWebView
(UIWebView
is deprecated)
UIWebView
is part ofUIKit
,WKWebView
is part ofWebKit
WKWebView
runs in a separate process to the app, it loads web pages faster and more efficient with less memory overhead- Requests made by
UIWebView
go throughNSURLProtocol
whereWKWebView
doesn’t
SFSafariViewController
provides a visible standard interface for browsing the webUIApplication
has a method that opens an URL.
Architecture
Q: Explain polymorphism?
A: 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 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:
View
: UI related operations, similar to View in MVVM, user interaction is passed to Presenter.Presenter
: similar to ViewModel in MVVM, responds to user interaction, if needed, it asks Interactor to alter data modelRouter
: Screen transition and UI components switchingInteractor
: Data processing, including network request, data storage and modellingEntity
: Data model
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.
- The model layer is typically where the data resides (persistence, model objects, etc)
- The view layer is typically where all the UI interface lies. Things like displaying buttons and numbers belong in the view layer. The view layer does not know anything about the model layer and vice versa.
- The controller (view controller) is the layer that integrates the view layer and the model layer together.
Q: What is MVVM pattern?
A:
The MVVM defines the following:
- The View, which is generally passive, corresponds to the Presentation Layer
- The Model, corresponds to the Business Logic Layer and is identical to the MVC pattern
- The View Model sits between the View
- The Model, corresponds to the Application Logic Layer
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:
- The View, which is generally passive, corresponds to the Presentation Layer
- The Model, corresponds to the Business Logic Layer and is identical to the MVC pattern
- The Presenter sits between the View and the Model, corresponds to the Application Logic Layer. A View is typically associated with one Presenter.
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: 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.
App Submission
Q: What is “app ID” and “bundle ID”?
A: 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.