iOS Flashcards
Difference between frame and bounds
bounds of a UIView:
the rectangle expressed as a location (x,y) and size (width, height) relative to its own coordinate system.
frame of a UIView:
the rectangle expressed as a location (x,y) and size (width, height) relative to the superview it is contained within.
Benefits of Guard
- avoids pyramid of Doom (lots of annoying if let statements nested inside each other moving further and further to the right
- provides an early exit out of the function using break or return
Any vs. AnyObject
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.
Swift Automatic Reference Counting (ARC)
- tracks and manages app’s memory usage
- most of the time, memory management “just works”
- reference counting only applies to class and not to structures and enumerations because they are value types, not reference types
- keeps track of how many strong references properties, constants and variables make to each class instance
- increases/decreases reference count accordingly
- deallocates memory when reference count drops to zero
- does not increase/decrease reference count of value types
- by default, all references are strong references
class Person { let name: String init(name: String) { self.name = name print("\(name) is being initialized") } deinit { print("\(name) is being deinitialized") } }
var reference1: Person?
var reference2: Person?
var reference3: Person?
reference1 = Person(name: "John Appleseed") // Prints "John Appleseed is being initialized"
reference2 = reference1 reference3 = reference1
reference1 = nil reference2 = nil
reference3 = nil // Prints "John Appleseed is being deinitialized" ------------------------------------------------------------------------------------ -a strong reference cycle occurs when two class instances hold a strong reference to each other such that each instance keeps the other alive. The class instances never get to a point where they have zero references. Strong references are resolved by defining some of the relationships between classes as weak or unowned references instead of strong references.
-an example of how a strong reference cycle can be created by accident:
class Person { let name: String init(name: String) { self.name = name } var apartment: Apartment? deinit { print("\(name) is being deinitialized") } }
class Apartment { let unit: String init(unit: String) { self.unit = unit } var tenant: Person? deinit { print("Apartment \(unit) is being deinitialized") } }
var john: Person?
var unit4A: Apartment?
john = Person(name: "John Appleseed") unit4A = Apartment(unit: "4A")
-the strong reference cycle is created below:
john!.apartment = unit4A unit4A!.tenant = john
-neither deinitializer is called below:
john = nil
unit4A = nil
————————————————————————————
-two ways to resolve strong reference cycles: weak references and unowned references
- a weak reference is a reference that does not keep a strong hold on the instance it refers to and so does not stop ARC from disposing of the referred instance. Use a weak reference when the other instance has a shorter lifetime. A weak reference is indicated by placing the weak keyword before a property or variable declaration. A weak reference is automatically set to nil when the instance it refers to is deallocated. Therefore they are always declared as variables of an optional type.
- the Apartment type’s tenant property is declared as a weak reference:
class Person { let name: String init(name: String) { self.name = name } var apartment: Apartment? deinit { print("\(name) is being deinitialized") } }
class Apartment { let unit: String init(unit: String) { self.unit = unit } weak var tenant: Person? deinit { print("Apartment \(unit) is being deinitialized") } }
var john: Person?
var unit4A: Apartment?
john = Person(name: "John Appleseed") unit4A = Apartment(unit: "4A")
john!.apartment = unit4A unit4A!.tenant = john
john = nil // Prints "John Appleseed is being deinitialized"
unit4A = nil // Prints "Apartment 4A is being deinitialized" ------------------------------------------------------------------------------------ -an unowned reference does not keep a strong hold on the instance it refers to, but it is used when the other instance has the same or longer lifetime. An unowned reference is indicated by placing the unknown keyword before a property or variable declaration. An unowned reference is always expected to have a value, and ARC never sets its value to nil. Therefore unowned references are defined using non-optional types.
- use an unowned instance only when sure that the reference always refers to an instance that has not been deallocated.
- in the following example, the Customer has an optional card property. The CreditCard has an unowned customer property because it will always be associated with a customer, it will not outlive the Customer it refers to, and it always has a customer instance associated with it when the it is created.
class Customer { let name: String var card: CreditCard? init(name: String) { self.name = name } deinit { print("\(name) is being deinitialized") } }
class CreditCard { let number: UInt64 unowned let customer: Customer init(number: UInt64, customer: Customer) { self.number = number self.customer = customer } deinit { print("Card #\(number) is being deinitialized") } }
var john: Customer?
john = Customer(name: "John Appleseed") john!.card = CreditCard(number: 1234_5678_9012_3456, customer: john!)
john = nil // Prints "John Appleseed is being deinitialized" // Prints "Card #1234567890123456 is being deinitialized"
- unsafe unowned references disable runtime safety checks and is indicated by writing unowned (unsafe). Trying to access an unsafe unowned reference after the instance it refers to is deallocated is an unsafe operation.
- there is a third scenario in which both properties should always have a value, and neither property should ever be nil once initialization is complete. One class can have an unowned property, and the other class can have an implicitly unwrapped optional property.
-every country must always have a capital city, and every city must belong to a country:
class Country { let name: String var capitalCity: City! init(name: String, capitalName: String) { self.name = name self.capitalCity = City(name: capitalName, country: self) } }
class City { let name: String unowned let country: Country init(name: String, country: Country) { self.name = name self.country = country } }
var country = Country(name: "Canada", capitalName: "Ottawa") print("\(country.name)'s capital city is called \(country.capitalCity.name)") // Prints "Canada's capital city is called Ottawa" ------------------------------------------------------------------------------------ -a strong reference cycle can occur if a closure is assigned to a property of a class instance, and the body of that closure captures the instance. The capture might occur with something like self.someProperty or self.someMethod(). This happens because closures are reference types.
-asHTML holds a strong reference to its closure, and the closure holds a strong reference to the HTMLElement instance:
class HTMLElement {
let name: String let text: String?
lazy var asHTML: () -> String = { if let text = self.text { return "\(text)\(self.name)>" } else { return "" } }
init(name: String, text: String? = nil) { self.name = name self.text = text } deinit { print("\(name) is being deinitialized") } }
var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world") print(paragraph!.asHTML()) // Prints "<p>hello, world</p>"
-the HTMLElement instance is not deallocated:
paragraph = nil
- a closure capture list defines the rules to use when capturing one or more reference cycles within a closure’s body. Each captured reference is declared to be a weak or unowned reference rather than a strong reference.
-each item in a capture list is a pairing of the weak or unowned keyword with a reference to a class instance or a variable defined with some value
lazy var someClosure = { [unowned self, weak delegate = self.delegate] (index: Int, stringToProcess: String) -> String in // closure body goes here }
-if a closure does not specify a parameter list or return type because they can be inferred from context, place the capture list at the very start of the closure followed by the in keyword:
lazy var someClosure = { [unowned self, weak delegate = self.delegate] in // closure body goes here }
-define a capture as an unowned reference when the closure and the instance it captures will always refer to each other and will always be deallocated at the same time
class HTMLElement {
let name: String let text: String?
lazy var asHTML: () -> String = { [unowned self] in if let text = self.text { return "\(text)\(self.name)>" } else { return "" } }
init(name: String, text: String? = nil) { self.name = name self.text = text } deinit { print("\(name) is being deinitialized") } }
-define a capture as a weak reference when the captured reference may become nil at some point in the future. Weak references are always of an optional type and automatically become nil when the instance they reference is deallocated.
xib
- file extension associated with Interface Builder files
- graphics software used to test, develop and design the UI interfaces of different software products.
bundle id
- uniquely defines every iOS application
- specified in Xcode.
Notification vs. Delegate
delegate:
- use when one object needs to send information to only one object
- an object can have only one delegate.
notification:
use when one object needs to send information to more than one object.
Core Data
- framework that is used to manage model layer objects
- has the ability to persist object graphs to a persistent store
- data is organized into relational entity-attribute model
- automatic support for storing objects
- automatic validation of property values
Swift Protocols
- a type that defines a blueprint of methods, properties and other requirements that suit a particular task or functionality
- can be adopted by a class, structure or enumeration to provide an actual implementation of those requirements
- a type that satisfies the requirements of a protocol is said to conform to that protocol
- a protocol doesn’t implement any functionality itself, but it defines the functionality
- a protocol can be extended to implement some of the requirements or to implement additional functionality that conforming types can take advantage of
- definition:
protocol SomeProtocol { // protocol definition goes here }
-adopting protocols:
struct SomeStructure: FirstProtocol, AnotherProtocol { // structure definition goes here }
-list superclass before any protocols:
class SomeClass: SomeSuperclass, FirstProtocol, AnotherProtocol {
// class definition goes here
}
————————————————————————————
Property Requirements
- a protocol can require any conforming type to provide an instance property or type property with a particular name and type. It also specifies whether each property must be gettable or gettable and settable.
- property requirements are always declared as variable properties prefixed with the var keyword:
protocol SomeProtocol {
var mustBeSettable: Int { get set }
var doesNotNeedToBeSettable: Int { get }
}
-always prefix type property requirements with the static keyword when they are defined in a protocol:
protocol AnotherProtocol {
static var someTypeProperty: Int { get set }
}
-protocol with a single instance property requirement:
protocol FullyNamed {
var fullName: String { get }
}
-structure that adopts and conforms to the FullyNamed protocol:
struct Person: FullyNamed {
var fullName: String
}
let john = Person(fullName: “John Appleseed”)
// john.fullName is “John Appleseed”
————————————————————————————
Method Requirements
-protocols can require specific instance methods and type methods to be implemented by conforming types. They are written without curly braces or a method body. Variadic parameters are allowed. Default values cannot be specified within a protocol’s definition. Type method requirements are prefixed with the static keyword when they are defined in a protocol.
protocol SomeProtocol { static func someTypeMethod() } ------------------------------------------------------------------------------------ Mutating Method Requirements
-for instance methods on value types (structures and enumerations), the mutating keyword is placed before the method’s func keyword to indicate that the method is allowed to modify the instance it belongs to and any properties of that instance
protocol Togglable {
mutating func toggle()
}
enum OnOffSwitch: Togglable { case off, on mutating func toggle() { switch self { case .off: self = .on case .on: self = .off } } } var lightSwitch = OnOffSwitch.off lightSwitch.toggle() // lightSwitch is now equal to .on ------------------------------------------------------------------------------------ Initializer Requirements
-protocols can require specific initializers to be implemented by conforming types. They are written without curly braces or an initializer body.
protocol SomeProtocol {
init(someParameter: Int)
}
————————————————————————————
Class Implementations of Protocol Initializer Requirements
-a conforming class can implement a protocol initializer as either a designated initializer or a convenience initializer. The initializer implementation must be marked with the required modifier. Classes marked as final do not need to use the required modifier since they cannot be subclassed:
class SomeClass: SomeProtocol { required init(someParameter: Int) { // initializer implementation goes here } }
-if a subclass overrides a designated initializer from a superclass and also implements a matching initializer requirement from a protocol, mark the initializer implementation with the required and override modifiers:
protocol SomeProtocol {
init()
}
class SomeSuperClass { init() { // initializer implementation goes here } }
class SomeSubClass: SomeSuperClass, SomeProtocol { // "required" from SomeProtocol conformance; "override" from SomeSuperClass required override init() { // initializer implementation goes here } } ------------------------------------------------------------------------------------ Failable Initializer Requirements
Protocols as Types
- protocols can be used as fully-fledged types. Using a protocol as a type is sometimes called an existential type.
- a protocol can be used:
- -as a parameter type or return type in a function, method, or initializer
- -as the type of a constant, variable, or property
- -as the type of items in an array, dictionary, or other container
-example:
class Dice { let sides: Int let generator: RandomNumberGenerator init(sides: Int, generator: RandomNumberGenerator) { self.sides = sides self.generator = generator } func roll() -> Int { return Int(generator.random() * Double(sides)) + 1 } }
-create a six-sided dice with a LinearCongruentialGenerator instance:
var d6 = Dice(sides: 6, generator: LinearCongruentialGenerator()) for _ in 1...5 { print("Random dice roll is \(d6.roll())") } // Random dice roll is 3 // Random dice roll is 5 // Random dice roll is 4 // Random dice roll is 5 // Random dice roll is 4 ------------------------------------------------------------------------------------ Delegation
-a design pattern that enables a class or structure to hand off (delegate) some of its responsibilities to an instance of another type. The conforming type is known as a delegate. Delegates are declared as weak references to prevent strong reference cycles. Marking a protocol as class-only (inherits from AnyObject) lets a class declare that its delegate must use a weak reference.
protocol DiceGame { var dice: Dice { get } func play() } protocol DiceGameDelegate: AnyObject { func gameDidStart(_ game: DiceGame) func game(_ game: DiceGame, didStartNewTurnWithDiceRoll diceRoll: Int) func gameDidEnd(_ game: DiceGame) } ------------------------------------------------------------------------------------ -a version of Snakes and Ladders that uses a Dice instance, adopts the DiceGame protocol and notifies a DiceGameDelegate about its progress:
class SnakesAndLadders: DiceGame { let finalSquare = 25 let dice = Dice(sides: 6, generator: LinearCongruentialGenerator()) var square = 0 var board: [Int] init() { board = Array(repeating: 0, count: finalSquare + 1) board[03] = +08; board[06] = +11; board[09] = +09; board[10] = +02 board[14] = -10; board[19] = -11; board[22] = -02; board[24] = -08 } weak var delegate: DiceGameDelegate? func play() { square = 0 delegate?.gameDidStart(self) gameLoop: while square != finalSquare { let diceRoll = dice.roll() delegate?.game(self, didStartNewTurnWithDiceRoll: diceRoll) switch square + diceRoll { case finalSquare: break gameLoop case let newSquare where newSquare > finalSquare: continue gameLoop default: square += diceRoll square += board[square] } } delegate?.gameDidEnd(self) } }
-DiceGameTracker adopts the DiceGameDelegate protocol:
class DiceGameTracker: DiceGameDelegate { var numberOfTurns = 0 func gameDidStart(_ game: DiceGame) { numberOfTurns = 0 if game is SnakesAndLadders { print("Started a new game of Snakes and Ladders") } print("The game is using a \(game.dice.sides)-sided dice") } func game(_ game: DiceGame, didStartNewTurnWithDiceRoll diceRoll: Int) { numberOfTurns += 1 print("Rolled a \(diceRoll)") } func gameDidEnd(_ game: DiceGame) { print("The game lasted for \(numberOfTurns) turns") } }
-using DiceGameTracker:
let tracker = DiceGameTracker() let game = SnakesAndLadders() game.delegate = tracker game.play() // Started a new game of Snakes and Ladders // The game is using a 6-sided dice // Rolled a 3 // Rolled a 5 // Rolled a 4 // Rolled a 5 // The game lasted for 4 turns ------------------------------------------------------------------------------------ Adding Protocol Conformance with an Extension
- an existing type can be extended without access to the source code for the existing type
- TextRepresentable can be implemented by any type that has a way to be represented as text:
protocol TextRepresentable {
var textualDescription: String { get }
}
-the Dice class can be extended to adopt and conform to TextRepresentable:
extension Dice: TextRepresentable { var textualDescription: String { return "A \(sides)-sided dice" } }
- a generic type can conditionally conform to a protocol
- Array instances conform to the TextRepresentable protocol whenever they store elements of a type that conforms to TextRepresentable:
extension Array: TextRepresentable where Element: TextRepresentable { var textualDescription: String { let itemsAsText = self.map { $0.textualDescription } return "[" + itemsAsText.joined(separator: ", ") + "]" } } let myDice = [d6, d12] print(myDice.textualDescription) // Prints "[A 6-sided dice, A 12-sided dice]"
-a type conforms to a protocol but has not stated that it adopts that protocol, it can adopt the protocol with an empty extension. Type must always explicitly declare their adoption of the protocol:
struct Hamster { var name: String var textualDescription: String { return "A hamster named \(name)" } } extension Hamster: TextRepresentable {} ------------------------------------------------------------------------------------ -a protocol can be used as the type stored in a collection
let things: [TextRepresentable] = [game, d12, simonTheHamster]
for thing in things { print(thing.textualDescription) } // A game of Snakes and Ladders with 25 squares // A 12-sided dice // A hamster named Simon ------------------------------------------------------------------------------------ -a protocol can inherit one or more protocols and can add further requirements on top of the requirements it inherits
protocol PrettyTextRepresentable: TextRepresentable {
var prettyTextualDescription: String { get }
}
extension SnakesAndLadders: PrettyTextRepresentable { var prettyTextualDescription: String { var output = textualDescription + ":\n" for index in 1...finalSquare { switch board[index] { case let ladder where ladder > 0: output += "▲ " case let snake where snake < 0: output += "▼ " default: output += "○ " } } return output } } ------------------------------------------------------------------------------------ -protocol adoption can be limited to class types (and not structures and enumerations) by adding the AnyObject protocol to a protocol's inheritance list. Use a class-only protocol when the behavior defined by that protocol's requirements assumes or requires that a conforming type has reference semantics rather than value semantics.
protocol SomeClassOnlyProtocol: AnyObject, SomeInheritedProtocol { // class-only protocol definition goes here } ------------------------------------------------------------------------------------ -multiple protocols can be combined into a single requirement with protocol composition. Protocol compositions behave as if a temporary local protocol that has the combined requirements of all the protocols in the composition was defined. A protocol composition can also include one class type that can be used to specify a required superclass. Protocol compositions have the form SomeProtocol & Another Protocol.
-birthdayPerson conforms to both protocols:
protocol Named { var name: String { get } } protocol Aged { var age: Int { get } } struct Person: Named, Aged { var name: String var age: Int } func wishHappyBirthday(to celebrator: Named & Aged) { print("Happy birthday, \(celebrator.name), you're \(celebrator.age)!") } let birthdayPerson = Person(name: "Malcolm", age: 21) wishHappyBirthday(to: birthdayPerson) // Prints "Happy birthday, Malcolm, you're 21!" ------------------------------------------------------------------------------------ -use the is and as operators to check for protocol conformance and to cast to a specific protocol
- the is operator returns true if an instance conforms to a protocol and returns false if it doesn’t.
- the as? version of the downcast operator returns an optional value of the protocol’s type, and this value is nil if the instance doesn’t conform to that protocol.
- the as! version of the downcast operator forces the downcast to the protocol type and triggers a runtime error if the downcast doesn’t succeed.
protocol HasArea {
var area: Double { get }
}
class Circle: HasArea { let pi = 3.1415927 var radius: Double var area: Double { return pi * radius * radius } init(radius: Double) { self.radius = radius } } class Country: HasArea { var area: Double init(area: Double) { self.area = area } }
-HasArea protocol:
protocol HasArea {
var area: Double { get }
}
-classes that conform to the HasArea protocol:
class Circle: HasArea { let pi = 3.1415927 var radius: Double var area: Double { return pi * radius * radius } init(radius: Double) { self.radius = radius } } class Country: HasArea { var area: Double init(area: Double) { self.area = area }
-class that does not conform to the HasArea protocol:
class Animal { var legs: Int init(legs: Int) { self.legs = legs } }
-array that stores values of type AnyObject:
let objects: [AnyObject] = [ Circle(radius: 2.0), Country(area: 243_610), Animal(legs: 4) ]
-iterate array and check for HasArea protocol conformance:
for object in objects { if let objectWithArea = object as? HasArea { print("Area is \(objectWithArea.area)") } else { print("Something that doesn't have an area") } } // Area is 12.5663708 // Area is 243610.0 // Something that doesn't have an area ------------------------------------------------------------------------------------ -optional protocol requirements can be defined. They do not have to be implemented by types conforming to the protocol. Optional requirements are prefixed by the optional modifier as part of the protocol's definition. Optional requirements allow writing code that interoperates with Objective-C. A method or property used in an optional requirement type automatically becomes an optional.
@objc protocol CounterDataSource {
@objc optional func increment(forCount count: Int) -> Int
@objc optional var fixedIncrement: Int { get }
}
-Counter class has an optional dataSource property of type CounterDataSource?
class Counter { var count = 0 var dataSource: CounterDataSource? func increment() { if let amount = dataSource?.increment?(forCount: count) { count += amount } else if let amount = dataSource?.fixedIncrement { count += amount } } } ------------------------------------------------------------------------------------ -protocols can be extended to provide method, initializer, subscript and computed property implementations to conforming types.
-RandomNumberGenerator is extended to provide a randomBool() method.
extension RandomNumberGenerator { func randomBool() -> Bool { return random() > 0.5 } }
-all conforming types automatically gain this method implementation.
let generator = LinearCongruentialGenerator() print("Here's a random number: \(generator.random())") // Prints "Here's a random number: 0.3746499199817101" print("And here's a random Boolean: \(generator.randomBool())") // Prints "And here's a random Boolean: true" ------------------------------------------------------------------------------------ -protocol extensions can add implementations to conforming types but cannot make a protocol extend or inherit from another protocol ------------------------------------------------------------------------------------ -protocol extensions can provide a default implementation to any method or computed property requirement of that protocol
extension PrettyTextRepresentable {
var prettyTextualDescription: String {
return textualDescription
}
}
————————————————————————————
-protocol extensions can specify constraints that conforming types must satisfy before the methods and properties of the extension are available. These constraints are written after the name of the protocol being extended by writing a generic where clause.
extension Collection where Element: Equatable { func allEqual() -> Bool { for element in self { if element != self.first { return false } } return true } }
let equalNumbers = [100, 100, 100, 100, 100]
let differentNumbers = [100, 100, 200, 100, 200]
print(equalNumbers.allEqual()) // Prints "true" print(differentNumbers.allEqual()) // Prints "false"
public vs. open
open access: can subclassed by modules they are defined in, modules that import the module in which the class is defined and class members as well.
public: in Swift 3, classes declared public can only be subclassed in the module they are defined in.
app execution states
Active:
app is running in the foreground and is receiving events (normal mode)
Inactive:
app is running in the foreground but is currently not receiving events (it might be executing other code)
Background:
- app is in the background but it can execute code
- app might be on the way to being suspended, might stay here for a while, or might be launched directly to background.
Suspended:
- app is in the background but is not executing code (system does this automatically)
- the system might purge it if a low-memory condition occurs.
bounding box
smallest measure (area or volume) within a given set of points
MVC
Models:
- responsible for the domain data or a data access layer which manipulates the data
- think of ‘Person’ or ‘PersonDataProvider’ classes.
Views :
- responsible for the presentation layer (GUI)
- for iOS environment think of everything starting with ‘UI’ prefix.
Controller:
- the glue or the mediator between the Model and the View
- in general responsible for altering the Model by reacting to the user’s actions performed on the View and updating the View with changes from the Model.
- in iOS, the UIViewController contains the View and the Controller
- it updates the Model, and the Model notifies the UIViewController of changes
- can make the UIViewController too complex and hard to test
- MVC = Massive View Controller.
MVVM
Models:
- hold application data
- usually structs or simple classes
- responsible for the domain data or a data access layer which manipulates the data
- think of ‘Person’ or ‘PersonDataProvider’ classes.
Views:
- display visual elements and controls on the screen
- typically subclasses of UIView
- responsible for the presentation layer (GUI)
- for iOS environment think of everything starting with ‘UI’ prefix.
ViewModel:
- transform model information into values that can be displayed on a view
- usually classes
- provides a set of interfaces which represent UI components in the View
- the interfaces and UI components are connected by binding.
- should not contain UIKit code
View Controller < ^ | | | V | Model <=> View Model <=> View
- use when you need to transform models into another representation for a view
- in iOS, the UIViewController initiates, lays out and presents UI components, and it binds UI components with the ViewModel
- the ViewModel does controller logic and provides interfaces to the View
- the ViewModel can get complex.
Example: model: public struct BreachModel { var title: String }
view: public class BreachView: UIView {
public let titleLabel: UILabel public override init(frame: CGRect) { let titleFrame = CGRect(x: 0, y: 0, width: frame.width, height: frame.height) titleLabel = UILabel(frame: titleFrame) titleLabel.textAlignment = .center super.init(frame: frame) addSubview(titleLabel) titleLabel.translatesAutoresizingMaskIntoConstraints = false titleLabel.centerXAnchor.constraint(equalTo: self.centerXAnchor).isActive = true titleLabel.centerYAnchor.constraint(equalTo: self.centerYAnchor).isActive = true } @available(*, unavailable) required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } }
viewModel: class BreachViewModel { // MARK: - Initialization init(model: [BreachModel]? = nil) { if let inputModel = model { breaches = inputModel } }
var breaches = [BreachModel]() public func configure(_ view: BreachView) { view.titleLabel.text = breaches.first?.title } }
extension BreachViewModel { func fetchBreaches(completion: @escaping (Result) -> Void) { completion(.success(breaches)) } }
viewController: class BreachesViewController: UIViewController {
// the view model is setup with simple var breachesViewModel = BreachViewModel(model: [BreachModel(title: "000webhost")])
override func viewDidLoad() { super.viewDidLoad() let breachView = BreachView(frame: self.view.frame) breachesViewModel.configure(breachView) self.view.addSubview(breachView) breachView.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ breachView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor), breachView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor), breachView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), breachView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor), ]) }
Realm
open source database framework that is implemented from scratch, has zero object copy store and is fast
Swift Advantages
- compiled
- statically typed
optional types:
–make apps crash-resistant
- built-in error handling
- tuples and multiple return values
- generics
- fast iteration over range or collection
- structs that support methods, extensions and protocols
- functional programming patterns (map, filter)
- native error handling (try/catch/throw)
- closures unified with function pointers
- much faster than other languages-LLVM compiler
-type safety:
variables, constants and functions have their types declared in advance
-strongly typed:
types are checked at compile time
(Java is also strongly typed)
-type inference:
type is inferred from the initial value given
-type constraint: specifies that a generic type must inherit from a specific class or conform to a particular protocol
-supports pattern matching
Swift Generics
- enables the writing of flexible, reusable functions and types that can work with any type, subject to requirements that can be defined
- can write code that avoids duplication and expresses its intent in a clear, abstracted manner.
- T is a placeholder type that will be replaced at compile time
- the generic function’s name is followed by the placeholder type name (T) inside angle brackets (). The brackets tell Swift that T is a placeholder type name within the function definition, so Swift doesn’t look for an actual type T.
- Array and Dictionary are generic collections
func swapTwoValues(_ a: inout T, _ b: inout T) { let temporaryA = a a = b b = temporaryA } ------------------------------------------------------------------------------------ -type parameters specify and name a placeholder type and are written immediately after the function's name between a pair of matching angle brackets. More than one type parameter can be provided by writing multiple parameter names within the angle brackets, separated by commas.
-type parameters can have descriptive names such as Key and Value, but it’s traditional to use single letters such as T, U and V when there is not a meaningful relationship between them.
- always use upper camel case names for type parameters to indicate they are placeholders for a type, not a value
- generic types can be defined. They are custom classes, structures and enumerations that can work with any type.
-generic stack example using an Element type parameter:
struct Stack { var items = [Element]() mutating func push(_ item: Element) { items.append(item) } mutating func pop() -> Element { return items.removeLast() } }
-create a stack of strings:
var stackOfStrings = Stack() stackOfStrings.push("uno") stackOfStrings.push("dos") stackOfStrings.push("tres") stackOfStrings.push("cuatro") // the stack now contains 4 strings
-when a generic type is extended, a type parameter is not provided as part of the definition. The type parameter list from the original type definition is available within the body of the extension, and the original type parameter names are used. Extensions of a generic type can also include requirements that instances of the extended type must satisfy in order to gain the new functionality.
extension Stack { var topItem: Element? { return items.isEmpty ? nil : items[items.count - 1] } } ------------------------------------------------------------------------------------ -type constraints can be enforced on types that can be used with generic functions and generic types. Type constraints can be defined. Type constraints are specified by placing a single class or protocol constraint after a type parameter's name, separated by a colon, as part of the type parameter list.
-adding the type constraint Equatable so types must be able to be compared with the equal to (==) operator
func findIndex(of valueToFind: T, in array:[T]) -> Int? { for (index, value) in array.enumerated() { if value == valueToFind { return index } } return nil } ------------------------------------------------------------------------------------ -an associated type gives a placeholder name to a type that is used as part of the protocol.
-an associated name enables the Container protocol to refer to the type of values it stores.
protocol Container { associatedtype Item mutating func append(_ item: Item) var count: Int { get } subscript(i: Int) -> Item { get } }
struct IntStack: Container { // original IntStack implementation var items = [Int]() mutating func push(_ item: Int) { items.append(item) } mutating func pop() -> Int { return items.removeLast() } // conformance to the Container protocol typealias Item = Int mutating func append(_ item: Int) { self.push(item) } var count: Int { return items.count } subscript(i: Int) -> Int { return items[i] } } ------------------------------------------------------------------------------------ -an existing type can be extended to add conformance to a protocol.
-type constraints can be added to an associated type in a protocol to require that conforming types satisfy those constraints.
protocol Container { associatedtype Item: Equatable mutating func append(_ item: Item) var count: Int { get } subscript(i: Int) -> Item { get } } ------------------------------------------------------------------------------------ -a protocol can appear as part of its own requirements
protocol SuffixableContainer: Container { associatedtype Suffix: SuffixableContainer where Suffix.Item == Item func suffix(_ size: Int) -> Suffix }
extension Stack: SuffixableContainer { func suffix(_ size: Int) -> Stack { var result = Stack() for index in (count-size)..() stackOfInts.append(10) stackOfInts.append(20) stackOfInts.append(30) let suffix = stackOfInts.suffix(2) // suffix contains 20 and 30 ------------------------------------------------------------------------------------ -a generic where clause enables defining requirements for associated types
-a function which checks to see if two Container instances contain the same items in the same order. The two containers do not have to be the same type, but they musts contain the same type of items.
func allItemsMatch (_ someContainer: C1, _ anotherContainer: C2) -> Bool where C1.Item == C2.Item, C1.Item: Equatable {
// Check that both containers contain the same number of items. if someContainer.count != anotherContainer.count { return false }
// Check each pair of items to see if they're equivalent. for i in 0..() stackOfStrings.push("uno") stackOfStrings.push("dos") stackOfStrings.push("tres")
var arrayOfStrings = [“uno”, “dos”, “tres”]
if allItemsMatch(stackOfStrings, arrayOfStrings) { print("All items match.") } else { print("Not all items match.") } // Prints "All items match." ------------------------------------------------------------------------------------ -a generic where clause can be used as part of an extension. In the example, the Stack definition does not require its items to be equatable, so using the == operator could result in a compile-time error. Using the generic where clause to add a new requirement prevents this problem.
extension Stack where Element: Equatable { func isTop(_ item: Element) -> Bool { guard let topItem = items.last else { return false } return topItem == item } } ------------------------------------------------------------------------------------ -a generic where clause can be used with extensions to a protocol
-adding a startsWith(_:) method:
extension Container where Item: Equatable { func startsWith(_ item: Item) -> Bool { return count >= 1 && self[0] == item } }
-require Item to be a specific type:
extension Container where Item == Double { func average() -> Double { var sum = 0.0 for index in 0.. Item { get }
associatedtype Iterator: IteratorProtocol where Iterator.Element == Item func makeIterator() -> Iterator } ------------------------------------------------------------------------------------ -a generic where clause can add a constraint to an inherited associated type:
protocol ComparableContainer: Container where Item: Comparable { }
-a generic where class can be added to generic subscripts. The placeholder type name is written inside the angle brackets, and the generic where clause is written right before the open curly brace of the subscript body. In the example, the value passed for the indices parameter is a sequence of integers.
extension Container { subscript(indices: Indices) -> [Item] where Indices.Iterator.Element == Int { var result = [Item]() for index in indices { result.append(self[index]) } return result } }
lazy in Swift
- variables that are created using a specified function called only when that variable is first requested
- if never requested, the function is never run, so it saves processing time.
lazy var players = String
rules:
- can’t use lazy with let
- can’t use with computed properties because a computed property returns the value every time it’s accessed after executing the code inside the computation block
- can use lazy only with members of struct and class
- initialized atomically and so is not thread safe.
3 ways to pass data between view controllers
-segue (prepareForSegue method (forward))
- Delegate (backward):
- -delegate protocol
- -delegator that delegates the tasks
- -delegate object that implements the delegate protocol and does the actual work
-setting variable directly (forward)
Readers-Writers problem
multiple threads are reading at the same time when there should only be one thread writing
Swift pattern matching techniques
tuple patterns:
used to match values of corresponding tuple types
type-casting patterns:
allow you to cast or match types
wildcard patterns:
match and ignore any kind and type of value
optional patterns:
used to match optional values
enumeration case patterns:
match cases of existing enumeration types
expression patterns:
allow you to compare a given value against a given expression
var vs let
var refers to a variable that can be changed
let refers to a constant that cannot be changed once set
GCD
Grand Central Dispatch
- low-level API for managing concurrent operations
- uses thread pool pattern
- improves app’s responsiveness by deferring computationally expensive tasks and running them in the background
-provides easier concurrency model than locks and threads and helps to avoid concurrency bugs
3 queues:
main:
highest priority, runs on the main thread and is a serial queue. All UI updates should be done on the main thread.
DispatchQueue.main.async { // do any UI work here }
global:
- concurrent queues that are shared by the whole system
- four such queues with different priorities or qualities of service:
- -high
- -default
- -low
- -background (lowest priority and is throttled in any i/o activity to minimize negative system impact)
DispatchQueue.global(qos: .background).async { // do any heavy operation here }
custom:
- queues that you create which can be serial or concurrent
- requests in these queues actually end up in one of the global queues
let concurrentQueue = DispatchQueue(label: “concurrentQueue”, qos: .background, attributes: .concurrent)
let serialQueue = DispatchQueue(label: “serialQueue”, qos: .background)
———————————————————————————–
Quality of Service classes:
-User-interactive: represents tasks that must complete immediately in order to provide a good user experience. Should run on main thread.
-User-initiated: represents tasks a user initiates from the UI. Use them when the user is waiting for immediate results and for tasks required to continue user interaction.
-Utility: represents long-running tasks, typically with a user-visible progress indicator.
-Background: represents tasks the user is not directly aware of. Use for tasks that don’t require user interaction and aren’t time-sensitive.
————————————————————————————
Two ways to execute tasks:
DispatchQueue.sync:
returns control to the caller after the task completes
DispatchQueue.async:
returns immediately, ordering the task to start but not waiting for it to complete
————————————————————————————
Example: using queues together to download an image and display it in an imageView:
DispatchQueue.global(qos: .background).async { let image = downloadImageFromServer() DispatchQueue.main.async { self.imageView.image = image } }
autolayout
dynamically calculates the size and position of views based on constraints