Fundamentals Flashcards
What is let and var in Swift?
The let keyword is used to declare a constant variable and the var keyword is used to declare a variable. Variables created with these are either references/pointers or values.
The difference between them is that when you create a constant with let, you have to give it a value upon declaration (or within the calling scope) and you can’t reassign it. In contrast, when you declare a variable with var, it can either be assigned right away or at a later time or not at all (i.e. be nil).
This is a fundamental Swift thing that you should be familiar with. In Objective- C everything is dynamic and can be nil. Also, nil can receive messages without breaking everything (i.e. throwing an exception). In Swift, though, you have to be very explicit about what you are declaring.
At the end of the day, let, var, nil, and Optionals help define how you handle state in your apps. Swift forces you to be more explicit about it.
What is Optional in Swift and nil in Swift and Objective-C?
In Objective-C nil used to be a very handy “value” for variables. It typically meant an absence of value or simply “nothing”. You could send a message to a nil and instead of your app blowing up with an exception it would simply ignore it and do nothing (or return nil).
With the introduction of let and var in Swift, however, it became apparent that not all constants and variables can be defined and set at the time of declaration. We needed to somehow declare that a variable has not been determined yet and that it potentially could have a value or no value.
That’s where Optional comes into play. Optional is defined with ? appended at the end of the variable type you declare. You can set a value to that variable right away or at a later time or not set one at all. When you use Optional variables you have to either explicitly unwrap them, using ! at the end of the variable to get the value stored in it or youcoulddoaso-called Optional Binding to find out whether an Optional contains a value.
To do that you’d use a construct. if
let unwrappedOptional = someOptional { // your code here }
Optionals can be used with constants (lets) only if they were give a value right away (whether it’s nil or an actual value). In general a constant has to be defined at the time of its declaration and therefore it has a value and is not an Optional.
In Swift, unlike in Objective-C, sending messages to nil causes a runtime exception. There is, though, a way of sending a message to an Optional in Swift and if the value is a nil, it will just ignore the message and return nil instead of raising an exception. This is much like the old Objective-C behavior and allows you to do method chaining calls. If one of the Optional values in the call sequence is nil, the whole chain will return nil. Optionals make Swift lean more towards the functional side of programming languages partially mimicking the Maybe concept of Haskel and similar languages.
Ultimately, just like let, var, and nil, Optional is a helpful construct that forces you to be more mindful of how you handle the state of your applications. In general, you should use nil and consequently Optionals to represent an absence of value as little as possible. Every time you declare an Optional, ask yourself if you really, really need it.
NOTE: Objective-C now has no null and nullable directives to give it explicit variable type declaration, which is more Swift-like.
What is the difference between struct and class in Swift? When would you use one or the other?
Both structs and classes in Swift can have properties, methods, subscripts or initializers, be extended, and conform to protocols. Classes are reference types. They increase their reference count when passed to a function or assigned to a variable or constant. They also have some extra stuff like inheritance from a superclass (structs can’t do that), type casting, and deinitializers (former dealloc). Structs are so-called value types. That means that when a struct is assigned to a variable or a constant, or is passed to a function, its value is copied instead of increasing its reference count.
The key thing about choosing between using a class or a struct is reference or value passing. If you need to store some primitives (i.e. Ints, Floats, Strings, etc.), use struct. However, if you need custom behavior where passing by reference is preferable (so that you refer to the same instance everywhere), use class.
How is memory management handled in iOS?
Swift uses Automatic Reference Counting (ARC). This is conceptually the same thing in Swift as it is in Objective-C.
ARC keeps track of strong references to instances of classes and increases or decreases their reference count accordingly when you assign or unassign instances of classes (reference types) to constants, properties, and variables. It deallocates memory used by objects which reference count got down to zero. ARC does not increase or decrease the reference count of value types because, when assigned, these are copied. By default, if you don’t specify otherwise, all the references will be strong references.
One of the gotchas of ARC that you need to be aware of is Strong Reference Cycles. For a class instance to be fully deallocated under ARC, it needs to be free of all strong references to it. But there is a chance that you could structure your code in such a way that two instances strongly reference each other and therefore never let each other’s reference count drop down to zero.
There are two ways of resolving this in Swift: weak references and unowned references. Both of these approaches will assign an instance without keeping a strong reference to it. Use the weak keyword for one and the unowned keyword for the other before a property or variable declaration. Weak reference is used when you know that a reference is allowed to become nil whereas unowned refer- ence is used when you are certain that the reference has a longer lifecycle and will never become nil. Since weak references can have a value or no value at all, they must be defined as optional variables. An unowned reference has to be defined as non-optional since it is assumed to always have a value.
Another important gotcha is Strong Reference Cycle in Closures. When you use closures within a class instance they could potentially capture self. If self, in turn, retains that closure, you’d have a mutual strong reference cycle between closure and class instance. This often occurs when you use lazy loaded properties for example. To avoid it, you’d use the same keywords weak and unowned.
When you define your closure, you should attach to its definition a so called capture list. A capture list defines how the closure would handle references captured in it. By default, if you don’t use a capture list, everything will be strongly referenced. Capture lists are defined either on the same line where the closure open bracket is or on the next line after that. They are defined with a pair of square brackets and every element in them has a weak or unowned keyword prefix and is separated from other elements by a comma. The same thinking applies to a closure capture list as to variable references: define a capture variable as a weak Optional if it could become nil’ and the closure won’t be deallocated before then, and define a captured reference as unowned if it will never become nil before the closure is deallocated.
What are properties and instance variables in Objective-C and Swift?
Properties in Objective-C are used to store data in instances of classes. They define the memory management, type, and access attributes of the values they store such as strong, weak, assign, readonly and readwrite.
Properties store values assigned to them in an instance variable that, by convention, has the same name as the property but starts with an underscore prefix.
When you declare a property in Objective-C that declaration will also synthesize it, meaning create a getter and setter to access and set the underlying instance variable. The strong, weak and assign property attributes define how memory for a property will be managed. It is going to be either strongly referenced, weakly referenced (set to nil if deallocated), or assigned (not set to nil if deallocated).
One great feature of Objective-C properties that is often overlooked is Key Value Observation (KVO). Every Objective-C property can be observed for changes enabling low-level Functional Reactive Programming capabilities. In Swift, however, properties defined with a simple let or var are strong by default. They can be declared as weak or unowned references with the weak and unowned keywords before let/var. Swift properties in types are called stored properties.
Unlike Objective-C properties, they do not have a backing instance variable to store their values. They do declare setters and getters that can be overridden, however. Swift enforces basic dependency injection with properties. If you define a let or var property, it has to be either initialized in the property declaration and will be instantiated with that type’s instance or it has to be injected in a designated initializer instead. Optional properties don’t have to be initialized right away or injected because, by their nature, they can be nil.
Also, Swift properties can’t be KVOed and instead have a greatly simplified mechanic built in - Property Observers (willSet/didSet). The only way to have property KVO in Swift is to subclass from NSObject or its sub- classes. Class or type properties are the properties defined for the entire type/class rather than individual instances of that type. In Swift, they can be defined with the static keyword for value types (struct, enum) and with the class keyword for class types.
In Objective-C, since Swift 3 and Xcode 8, you can also define class properties using the class keyword in property declaration. Properties in both Swift and Objective-C can be lazy loaded. In Swift, you’d use @lazy directive in front of a property declaration. In Objective-C, you’d have to override property getter and set and initialize its value only if the un- derlying instance variable is nil.
What is a protocol (both Objective-C and Swift)? When and how is it used?
Protocols (or, in other languages, Interfaces) are declarations of what a type that adopts them should implement. A protocol only has a description or signature of the methods, properties, operators, etc. that a type implements without the actual implementation.
In both Swift and Objective-C protocols can inherit from one or more other protocols. In Objective-C, protocols can declare properties, instance methods, and class methods. They can be adopted only by classes. You could define methods and properties as optional or required. They are required by default. In Swift, protocols can declare properties, instance methods, type methods, operators and subscripts. They can be adopted by classes, structs, and enums. By default, everything in Swift protocols is required. If you’d like to have optional methods and properties, you have to declare your Swift protocol as Objective-C compatible by prefixing it with @objc. If you prefix your protocol with @objc, it can only be adopted by classes.
Swift also lets you provide a default implementation for your protocols with a protocol extension. Protocols are a great addition to any OO language because they allow you to clearly and explicitly declare interfaces of things in your code and be able to rely on them. It is a way to abstract internal implementation details out and care about types rather than about inheritance structures. Declaring clear protocols allows you to dynamically change objects that conform to the same protocol at runtime. It also lets you abstract things out and code against interfaces rather than specific classes or other types. It helps, for example, with the implementation of core Cocoa Touch design patterns such as Delegation.
Also, developing against protocols could help with test-driven development (TDD) because stubs and mocks in tests could adopt the necessary protocols and substitute or “fake” the real implementation.
What is a category/extension? When is it used?
Categories in Objective-C and Extensions in Swift are ways to extend existing functionality of a class or type. They allow you to add additional methods in Objective-C and Swift without subclassing. And in Swift to add computed properties, static properties, instance/type methods, initializers, subscripts, new nested types, and make existing type conform to a protocol without subclassing.
In Objective-C, categories are typically used to extend the functionality of 3rd-party or Apple framework classes. You can also use them in your own classes to distribute implementation into separate source files or to declare private or “protected” methods.
In Swift, extensions are used to extend the functionality of existing types or to make a type conform to a protocol. The drawback of extensions and categories is that they are globally applied, unlike protocols. This means that after you define an extension/category for a class or type, it will be applied to all the instances of that type, even if they were created before the extension/category was defined. Neither categories nor extensions can add new stored properties.
Another important gotcha with categories and extensions is name clashes. If you define the same name from another category/extension or existing class/type in an extension/category, you can’t predict what implementation will take prece- dence at runtime.
To avoid that collision, you should namespace your methods with a prefix and an underscore; i.e., something like func ab_myExtensionMethodName() whereabisyourcodebase’sclass/type name prefix (same convention as with the NS prefix for Cocoa’s legacy NextStep).
What are closures/blocks and how are they used?
Blocks in Objective-C and closures in Swift declare and capture a piece of executable code that will be launched at a later time. You can either define them inline or give them dedicated type names to be referenced and used later.
Blocks and closures are the first steps to multi-threading and asynchronicity in Swift and Objective-C since they are the building blocks that capture work that needs to be executed at later time (a.k.a. asynchronously). Blocks/closures are reference types and will retain/strongly reference everything put in them unless otherwise specified.
You can avoid strong reference cycle issues by using the __block and __weak keywords in Objective-C (or, betterstill,use@strongify/@weakify)and[weak self]/[unowned self] in Swift.
What is MVC?
MVC stands for Model View Controller. It is a software design pattern Apple chose to be the main approach to iOS application development.
- Application data are captured and represented by Models.
- Views are responsible for drawing things on the screen.
- Controllers control the data flow between Model and View.
- Model and View never communicate with each other directly and instead rely on Controller to coordinate the communication.
A typical representation of each MVC layer in an iOS application would be the following:
- UIView subclasses (Cocoa Touch or custom) are the Views
- UIViewControllers and their subclasses are the Controllers
- and any data objects, NSManagedObject subclasses and similar are the Models MVC is a great general purpose design pattern but using it solely limits your architecture and often leads to notorious “Massive View Controller”.
- “Massive View Controller” is the state of a codebase where a lot of logic and responsi- bility has been shoved into View Controllers that doesn’t belong in them. That practice makes your code rigid, bloated, and hard to change.
There are other design patterns that can help you remedy this, such as MVVM and the general SRP principle. Even though Apple keeps telling us that MVC is everything, don’t be fooled by it and stick to SOLID principles.
What are Singletons? What are they used for?
Singleton is a class that returns only one-and-the-same instance no matter how many times you request it. Singletons are sometimes considered to be an anti-pattern. There are multiple disadvantages to using singletons. The two main ones are global state/statefulness and object lifecycle and dependency injection. Singletons are often misused and can breed in global state, which makes debugging and working with your code difficult. It starts off very innocently when you think you’ll have only one instance of a class and you make it globally available across your codebase. But at some point you need to either reset it, do something else with the data stored on it, or realize that sharing it across the whole app doesn’t make sense anymore. This is when you get into trouble because your singleton is everywhere now and data stored in it is unreliable because you don’t know who might’ve changed it and when. Using singletons makes it hard for you to inject dependencies because with a singleton there’s only one instance of your singleton class. That prevents you from injecting it as a dependency for the purposes of testing and just general inversion of control architecture.
What is Delegate pattern in iOS?
Delegate pattern is a variation of Observer pattern where only one object can observe events coming from another object. That effec- tively makes Delegate pattern a one-to-one relationship. Delegates are commonly used across iOS frameworks.
Two of the arguably most commonly used examples would be UITableViewDelegate and UITableViewDataSource. These are both represented by a protocol that an object conforms to and UITableView uses the single object it is provided with to send messages/events.
Unlike with Observer pattern, there can be only one delegate object. Delegation is sometimes abused by iOS developers. Be careful not to reassign your delegate object throughout the flow of your app because that might lead to unexpected consequences. The delegate/delegatee relationship is tightly coupled.
What is KVO (Key-Value Observation)?
KVO is one of the core parts of Cocoa Touch and is used widely across the platform. KVO stands for Key-Value Observation and provides mechanics through which you can observe changes on properties in iOS.
In contrast to Delegate KVO entails a one-to-many relationship. Multiple objects could subscribe to changes in a property of another object. As soon as that property changes, all objects subscribing to it will be notified. Under-the-hood implementation uses instance variables defined with properties to store the actual value of the property and setters/getters supplied by synthesization of those properties. Internally, when you assign a property it will call willChangeValueForKey: and didChangeValueForKey: to trigger the change broadcast to observers.
Another way that KVO is used in iOS apps is public broadcasting of messages through NSNotificationCenter. The underlying mechanics are the same as with property KVO but the broadcasting can be triggered via a post: method on NSNotificationCenter default center rather than a property change. Originally an Objective-C feature, this is also available in Swift to classes sub-classed from NSObject.
What does iOS application lifecycle consist of?
The main point of entry into iOS apps is UIApplicationDelegate.
UIApplicationDelegate is a protocol that your app has to implement to get notified about user events such as app launch, app goes into background or foreground, app is terminated, a push notification was opened, etc.
Lifecycle methods: When an iOS app is launched the first thing called is application: willFinishLaunchingWithOptions:-> Bool. This method is intended for initial application setup. Storyboards have already been loaded at this point but state restoration hasn’t occurred yet.
1. Launch
- application: didFinishLaunchingWithOptions: -> Bool is called next. This callback method is called when the application has finished launching and restored state and can do final initialization such as creating UI.
- applicationWillEnterForeground: is called after application: didFinishLaunchingWithOptions: or if your app becomes active again after receiving a phone call or other system interruption.
- applicationDidBecomeActive: is called after applicationWillEnterForeground: to finish up the transition to the foreground.
2. Termination
- applicationWillResignActive: is called when the app is about to become inactive (for example, when the phone receives a call or the user hits the Home button).
- applicationDidEnterBackground: is called when your app enters a background state after becoming inactive. You have approximately five seconds to run any tasks you need to back things up in case the app gets terminated later or right after that.
- applicationWillTerminate: is called when your app is about to be purged from memory. Call any final cleanups here.
Both application: willFinishLaunchingWithOptions: and application: didFinishLaunchingWithOptions: can potentially be launched with options identifying that the app was called to handle a push notification or url or something else. You need to return true if your app can handle the given activity or url. Knowing your app’s lifecycle is important to properly initialize and set up your app and objects. You don’t have to run everything in application: didFinishLaunchingWithOptions, which often becomes a kitchen sink of setups and initializations of some sort.
What is View Controller? What is its lifecycle?
View Controllers are one of the core fundamental building units of Cocoa Touch applications. View Controllers (VC) are the Controller part of the MVC triangle. They are responsible for controlling the view. Every time you want to display more or less significantly complex pieces of UI, you would want to use a View Controller to handle the lifecycle and user interaction callbacks of that UI.
In a nutshell, a View Controller is a simple subclass of UIViewController that has to have a view to draw the UI on. It is either attached to a UIWindow as the root View Controller of that window or managed by a UINavigationController or by another VC or system to be presented to the user. At the end of the day, when you develop iOS apps there are two main reasons you’d want to use View Controllers:
- get lifecycle callback for the view that the VC is managing (when the view was loaded, displayed, hidden, etc.)
- get handy built-in system integrations to present and dismiss VCs using UINavigationController, modal presentation, or parent/child containment API View Controller lifecycle callbacks:
- loadView(): you can override this method if you’d like to create the view for your VC manually.
- viewDidLoad(): this method is called once when your VC’s view was loaded in memory for the first time. Do any additional setup and initializations here. Typically, this is the method where most of your custom view initialization and auto-layout setup will go. Also, start your services and other async data-related stuff here.
- viewWillAppear(): this method is called when the view is about to appear on the screen. It will be called after viewDidLoad and every subsequent time after view disappears from screen and then appears again. For example, when you present a view in a navbar it will call viewDidLoad and then viewWillAppear/viewDidAppear for that VC. Later, if you push a new VC on top of it, viewWillDisappear and viewDidDisappear will be called because it’s no longer the fore- ground/top VC. Later still, if the user taps the Back button, viewWill- Appear and viewDidAppear for that first VC will be called because it becomes the top VC again. Use this method to do final UI customizations, hook up UI observers, etc.
- viewWillLayoutSubviews() is called right before layoutSubviews() in underlying UIView for that VC. It is rarely used to adjust your view positioning.
- viewDidLayoutSubviews() is called right after layoutSubviews() in underlying UIView for that VC. It is rarely used to adjust your view positioning.
- viewDidAppear(): this method is called right after the view was shown on the screen and follows a viewWillAppear call.
- viewWillDisappear() is called when the view is about to become “hidden” i.e. not the top view controller presented to the user.
- viewDidDisappear() is called after viewWillDisappear and indicates that the view is “hidden”.
- didReceiveMemoryWarning() is called when the system is low on memory and needs to release additional resources. Deallocate as much as you can here. Don’t go crazy about it, though, because nowadays phones are so powerful that memory warnings rarely happen. Additionally, View Controller, just like Views, can be initialized either programmatically in code using init… constructor/initializer methods or loaded from a storyboard/xib file. In the former case, one of the initializer methods will be called and in the latter it will be via initWithCoder.