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
Swift collection types
Array
Dictionary
Set
? in Swift
- used during the declaration of a property and tells the compiler that the property is optional
- may or may not hold a value.
?? in Swift
nil coalescing operator: either unwraps an optional value or provides a default value
a != nil ? a! : b
viewDidLoad vs. viewDidAppear
viewDidLoad
-called when the view is loaded. Use when data is fairly static and not likely to change.
viewDidAppear
- called every time the view is presented on the device
- use when data changes regularly.
In both cases, the data should be loaded synchronously on a background thread to avoid blocking the UI.
how to fix a memory leak
-use XCode debug memory graph to detect simple retain cycles. It displays all objects currently alive. Purple exclamation mark indicate related leaked objects.
- in the box at top projectName > device, click Edit scheme
- select Malloc Scribble which will fill freed memory with a predefined value to make it more obvious when memory is leaked
- select Logging>Malloc Stack>Live Allocations Only which allows Xcode to build an allocation backtrace
- make the debug console visible
- run the app
- click the Memory Debugger button that looks like three joined circles in a >.
- this will display the heap contents with purple exclamation marks beside the items in the debug navigator
- ———————————————————————————
- use Allocations instrument which tracks all objects the app allocates over the course of its run
- ———————————————————————————
- use Leaks instrument to check all memory and figure out the leaked objects
ways to persist data
- in-memory arrays, dictionaries and sets (doesn’t need to be persisted)
- UserDefaults: good for small amounts of data
- Keychain (simple key/value): sensitive, secure data
- file/disk storage (write to/from disk): file system folders: Documents, Library, tmp
- Core Data, Realm (frameworks to simplify work with databases): Apple’s solution, not a database, is an object graph, complex
- SQLite (relational database): large amounts of data with relationships
HTTP networking
Alamofire and AFNetworking are the most popular solutions
Cocoa
- application framework for macOS development.
- combination of Appkit Framework and Foundation Framework.
app bundle
- a file directory that combines related resources in one place and contains the related resources the application requires for successful execution
- Xcode packages the app as a bundle
Cocoa Touch
- application framework for iPhone and iPad
- includes Foundation Framework and UIKit Framework
plist
- represents Property Lists, a key-value store for persisting data values in iPhone apps
- basically an XML file
IPA
- iOS App Store Package
- has .ipa extension which represents an iPhone application archive file.
how to improve battery life during app execution
- app is notified whenever it’s transferred between foreground and background
- knowing the background functions helps to extend battery life.
framework used to construct app’s UI
UIKit framework:
provides Views, Drawing Model, Controls, Event Handling and Windows specifically designed for a touch screen interface
singleton pattern
- design pattern that ensures that only one instance exists for a given class and that there’s a global access point to that instance
- usually uses lazy loading to create the single instance when it’s needed the first time.
facade pattern
- design pattern that provides a single interface to a complex subsystem
- instead of exposing the user to a set of classes and their APIs, it only exposes one simple unified API.
decorator pattern
- design pattern that dynamically adds behaviors and responsibilities to an object without modifying its code.
- alternative to subclassing where a class’s behavior is modified by wrapping it with another object.
adapter pattern
- design pattern that allows classes with incompatible interfaces to work together
- wraps itself around an object and exposes a standard interface to interact with that object.
observer pattern
design pattern where one object notifies other objects of any state changes
memento pattern
- design pattern that saves your stuff somewhere
- later on, this externalized state can be restored without violating encapsulation; that is, private data remains private
- implementations include Archiving, Serialization and State Restoration.
OAuth
- an authorization protocol concerned with authorization of third-party applications that can be used to access user data without identifying the user or exposing its credentials
- has two libraries namely, OAuth2 and OAuthSwift
IBAction and IBOutlet
IBOutlet:
- variable that can be set when a XIB (interface builder object) is loaded
- tells Xcode that you can connect to the property from the Interface Builder
- resolves to void
IBAction:
- function that the objects loaded by the XIB can call
- indicates that the method is an action that you can connect to from your storyboard in Interface Builder
- resolves to nothing
Main Thread Checker
- new tool launched with Xcode 9 which detects the invalid use of Apple’s frameworks like AppKit, UIKit, etc., that are supposed to be used from main thread but are accidentally used in the background thread
- the effect of the invalid usage can result in missed visual defects, UI updates, crashes, and data corruption.
how to avoid retain cycles when using closures in Swift
closures in Swift can cause retain cycles because they have a strong reference to the object that uses them.
strong reference to aClosure which also captures self strongly:
class SomeObject { var aClosure = { self.doSomething() } ... }
aClosure captures variables as weak or unowned:
class SomeObject { var aClosure = { [unowned self, weak delegate = self.delegate] in self.doSomething() delegate?.doSomethingElse() } ... }
reuse identifier in UITableViewCell constructor
- tells UITableView which cells may be reused within the table, effectively grouping together rows in a UITableView that differ only in content but have similar layouts
- improves scroll performance by alleviating the need to create new views while scrolling
- instead the cell is reused whenever dequeueReusableCellWithIdentifier: is called.
var a1 = [1, 2, 3 ,4 5] var a2 = a1 a2.append(6) var x = a1.count
What is the value of x?
- in Swift, arrays are implemented as structs, making them value types rather than reference types (i.e., classes).
- when a value type is assigned to a variable as an argument to a function or method, a copy is created and assigned or passed
- as a result, the value of “x” or the count of array “a1” remains equal to 5 while the count of array “a2” is equal to 6, appending the integer “6” onto a copy of the array “a1.” The size of a1 is 5, and the size of a2 is 6. Therefore, x = 5.
what to do if your app is prone to crashing
- This classic interview question is designed to see how well your prospective programmer can solve problems
- what you’re looking for is a general methodology for isolating a bug, and their ability to troubleshoot issues like sudden crashes or freezing
- in general, when something goes wrong within an app, a standard approach might look something like this:
- Determine the iOS version and make or model of the device.
- Gather enough information to reproduce the issue.
- Acquire device logs, if possible.
- Once you have an idea as to the nature of the issue, acquire tooling or create a unit test and begin debugging.
-a great answer would include all of the above, with specific examples of debugging tools like Buglife or ViewMonitor, and a firm grasp of software debugging theory
—knowledge on what to do with compile time errors, run-time errors, and logical errors
-the one answer you don’t want to hear is the haphazard approach
—visually scanning through hundreds of lines of code until the error is found
-when it comes to debugging software, a methodical approach is must.
4 most important Objective-C data types
NSString: Represents a string.
CGfloat: Represents a floating point value.
NSInteger: Represents an integer.
BOOL: Represents a boolean.
framework used to construct UI
- The UIKit framework is used to develop application’s user interface
- The UIKit framework provides event handling, drawing model, windows, views, and controls, specifically designed for a touch screen interface.
- The UIKit framework (UIKit.framework) provides the crucial infrastructure needed to construct and manage iOS apps.
This framework provides:
- window and view architecture to manage an app’s user interface
- event handling infrastructure to respond to user input
- an app model to drive the main run loop and interact with the system
difference between retain and assign
Assign creates a reference from one object to another without increasing the source’s retain count.
if (_variable != object) {
[_variable release];
_variable = nil;
_variable = object;
}
———————————————————————————–
Retain creates a reference from one object to another and increases the retain count of the source object.
if (_variable != object) { [_variable release]; _variable = nil; _variable = [object retain]; }
files in an app bundle
MyApp.app -MyApp (executable, name=app name) -MyAppIcon.png -MySearchIcon.png -Info.plist (configuration information) -Default.png -MainWindow.nib (default interface objects to load at launch time) -Settings.bundle (special plug-in containing app specific preferences to add to Settings) -MySettingsIcon.png -iTunesArtwork -en.lproj MyImage.png -fr.lproj MyImage.png.
force unwrap
- adding a ! after an Optional value to automatically unwrap it without having to check whether it is nil or not.
- This is dangerous. Never use it for IBOutlets.
NSManagedObject
instance which implements the basic behavior required of a Core Data model object.
ways to debug an app
NSLog and print functions can be used for output into console.
Breakpoints can also be used together with the Debug bar and Variables view as an alternative.
Other tools such as Instruments and Crash Logs instead of the two above.
considerations when showing images downloaded from a server
- only download the image when the cell is scrolled into view (when cellForRowAtIndexPath is called)
- download the image asynchronously on a background thread so as not to block the UI
- when the image has downloaded for a cell, check if the cell is still in view, or if it has been reused by another piece of data
- if the cell has been reused, discard the image
- else, switch back to the main thread to change the image on the cell
weak vs. strong
strong:
- the reference count will be increased, and the reference to it will be maintained through the life of the object.
- ———————————————————————————–
weak:
- the object is pointed to, but the reference count is not increased
- often used when creating a parent-child relationship
- the parent has a strong reference to the child, but the child only has a weak reference to the parent.
copy vs. retain
copy:
-when an object is copied, it does not share the same version of the object passed
-it is a duplicate of the object created with duplicated values.
————————————————————————————
retain:
-increases retain count by one
-when the retain count of an object reaches zero, it will be deallocated and released from memory.
Spot the bug:
class ViewController: UIViewController { @IBOutlet var alert: UILabel! override func viewDidLoad() { super.viewDidLoad() let frame: CGRect = CGRect(x: 100, y: 100, width: 100, height: 50) self.alert = UILabel(frame: frame) self.alert.text = "Please wait..." self.view.addSubview(self.alert) } DispatchQueue.global(qos: .default).async { sleep(10) self.alert.text = "Waiting over" } }
All UI updates must be performed on the main thread.
DispatchQueue.global(qos: .default).async { sleep(10) DispatchQueue.main.async { self.alert.text = "Waiting over" } }
Swift variables
var mutableDouble:Double = 1.0
mutableDouble = 2.0
let constantDouble:Double = 1.0 // constantDouble = 2.0 // error
var mutableInferredDouble = 1.0
var optionalDouble:Double? = nil
optionalDouble = 1.0
if let definiteDouble = optionalDouble { definiteDouble } ------------------------------------------------------------------------------------ Variable types:
Int 1, 2, 500, 10000
Float
Double 1.5, 3.14, 578.234
Bool true, false
String “Kermit”, “Gonzo”, “Ms. Piggy”
ClassName UIView, UIButton, etc
Swift control flow
Conditional Statements
var condition = true if condition { } else { } ------------------------------------------------------------------------------------ Switch statements in Swift do not fall through the bottom of each case and into the next one by default.
var val = 5 switch val { case 1: "foo" case 2: "bar" default: "baz" } ------------------------------------------------------------------------------------ Switch Interval Matching
let approximateCount = 62 let countedThings = "moons orbiting Saturn" let naturalCount: String switch approximateCount { case 0: naturalCount = "no" case 1..<5: naturalCount = "a few" case 5..<12: naturalCount = "several" case 12..<100: naturalCount = "dozens of" case 100..<1000: naturalCount = "hundreds of" default: naturalCount = "many" } print("There are \(naturalCount) \(countedThings).") // Prints "There are dozens of moons orbiting Saturn." ------------------------------------------------------------------------------------ Switch Tuples
let somePoint = (1, 1) switch somePoint { case (0, 0): print("\(somePoint) is at the origin") case (_, 0): print("\(somePoint) is on the x-axis") case (0, _): print("\(somePoint) is on the y-axis") case (-2...2, -2...2): print("\(somePoint) is inside the box") default: print("\(somePoint) is outside of the box") } // Prints "(1, 1) is inside the box" ------------------------------------------------------------------------------------ Switch Value Bindings
let anotherPoint = (2, 0) switch anotherPoint { case (let x, 0): print("on the x-axis with an x value of \(x)") case (0, let y): print("on the y-axis with a y value of \(y)") case let (x, y): print("somewhere else at (\(x), \(y))") } // Prints "on the x-axis with an x value of 2" ------------------------------------------------------------------------------------ Switch Where
let yetAnotherPoint = (1, -1) switch yetAnotherPoint { case let (x, y) where x == y: print("(\(x), \(y)) is on the line x == y") case let (x, y) where x == -y: print("(\(x), \(y)) is on the line x == -y") case let (x, y): print("(\(x), \(y)) is just some arbitrary point") } // Prints "(1, -1) is on the line x == -y" ------------------------------------------------------------------------------------ Switch Compound Cases
let someCharacter: Character = "e" switch someCharacter { case "a", "e", "i", "o", "u": print("\(someCharacter) is a vowel") case "b", "c", "d", "f", "g", "h", "j", "k", "l", "m", "n", "p", "q", "r", "s", "t", "v", "w", "x", "y", "z": print("\(someCharacter) is a consonant") default: print("\(someCharacter) is not a vowel or a consonant") } // Prints "e is a vowel" ------------------------------------------------------------------------------------ For Loops
for var i=1; i<=10; ++i { // do something using i as it goes from 1 to 10 }
for index in 1…10 { // do something using index as it goes from 1 to 10 println("index is \(index)") }
for index in 1 ..< 10 { // do something using index as it goes from 1 to 9 println("index is \(index)") }
let names = ["Anna", "Alex", "Brian", "Jack"] for name in names { print("Hello, \(name)!") } // Hello, Anna! // Hello, Alex! // Hello, Brian! // Hello, Jack!
If you don’t need each value from a sequence, you can ignore the values by using an underscore in place of a variable name.
var abcAges = [“Alice” : 24, “Bob” : 27, “Carol” : 29]
for (name, age) in abcAges {
println(“(name) is (age) years old.”)
}
var i = 1 while i <= 10 { println ("\(i)") i++ }
let minuteInterval = 5 for tickMark in stride(from: 0, to: minutes, by: minuteInterval) { // render the tick mark every 5 minutes (0, 5, 10, 15 ... 45, 50, 55) }
let hours = 12
let hourInterval = 3
for tickMark in stride(from: 3, through: hours, by: hourInterval) {
// render the tick mark every 3 hours (3, 6, 9, 12)
}
————————————————————————————
While Loops
let finalSquare = 25 var board = [Int](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 var square = 0 var diceRoll = 0 while square < finalSquare { // roll the dice diceRoll += 1 if diceRoll == 7 { diceRoll = 1 } // move by the rolled amount square += diceRoll if square < board.count { // if we're still on the board, move up or down for a snake or a ladder square += board[square] } } print("Game over!")
repeat { // move up or down for a snake or ladder square += board[square] // roll the dice diceRoll += 1 if diceRoll == 7 { diceRoll = 1 } // move by the rolled amount square += diceRoll } while square < finalSquare print("Game over!") ------------------------------------------------------------------------------------ Control Transfer Statements
-continue (stop looping and start again at the beginning of the next loop iteration)
-break (ends execution of a control statement immediately)
-fallthrough (causes code execution to move directly t the statements inside the next case)
-return
-throw
————————————————————————————
Labelled Statements
Early Exit
A guard statement requires that a condition must be true for the code after the guard statement to be executed.
func greet(person: [String: String]) { guard let name = person["name"] else { return }
print("Hello \(name)!")
guard let location = person["location"] else { print("I hope the weather is nice near you.") return }
print("I hope the weather is nice in \(location).") }
greet(person: [“name”: “John”])
// Prints “Hello John!”
// Prints “I hope the weather is nice near you.”
greet(person: [“name”: “Jane”, “location”: “Cupertino”])
// Prints “Hello Jane!”
// Prints “I hope the weather is nice in Cupertino.”
Swift array
Syntax:
[type]
these declarations are equivalent:
let someArray: Array = [“Alex”, “Brian”, “Dave”]
let someArray: [String] = [“Alex”, “Brian”, “Dave”]
-multidimensional arrays can be created by nesting pairs of square brackets
three-dimensional array:
var array3D: [[[Int]]] = [ [ [1, 2], [3, 4] ], [ [5, 6], [7, 8] ] ]
array3D[0] refers to [[1, 2], [3, 4]], array3D[0][1] refers to [3, 4], and array3D[0][1][1] refers to the value 4.
- declaring, appending, iterating, accessing:
var person1 = "Ray" var person2 = "Brian" var array:[String] = [person1, person2] array.append("Waldo") for person in array { print("person: \(person)") } var waldo = array[2] ------------------------------------------------------------------------------------ -bridged to Foundation's NSArray class ------------------------------------------------------------------------------------ -can create an Array with a default value:
var threeDoubles = Array(repeating: 0.0, count: 3)
// threeDoubles is of type [Double], and equals [0.0, 0.0, 0.0]
————————————————————————————
-can create an Array by adding two Arrays together:
var anotherThreeDoubles = Array(repeating: 2.5, count: 3) // anotherThreeDoubles is of type [Double], and equals [2.5, 2.5, 2.5]
var sixDoubles = threeDoubles + anotherThreeDoubles
// sixDoubles is inferred as [Double], and equals [0.0, 0.0, 0.0, 2.5, 2.5, 2.5]
————————————————————————————
-methods:
.count .isEmpty .append(_:) .insert(_:at:) .remove(at:) .removeLast() ------------------------------------------------------------------------------------ -can change a range of values:
shoppingList[4…6] = [“Bananas”, “Apples”]
// shoppingList now contains 6 items
————————————————————————————
-iterate over entire set of values:
for item in shoppingList { print(item) } // Six eggs // Milk // Flour // Baking Powder // Bananas ------------------------------------------------------------------------------------ -iterate over values and indices:
for (index, value) in shoppingList.enumerated() { print("Item \(index + 1): \(value)") } // Item 1: Six eggs // Item 2: Milk // Item 3: Flour // Item 4: Baking Powder // Item 5: Bananas ------------------------------------------------------------------------------------ -map method: transforms source array into an array of another type whose values are obtained by executing the closure passed as a parameter to each element of the array.
struct Planet {
let name: String
let distanceFromSun: Double
}
let planets = [ Planet(name: "Mercury", distanceFromSun: 0.387), Planet(name: "Venus", distanceFromSun: 0.722), Planet(name: "Earth", distanceFromSun: 1.0), Planet(name: "Mars", distanceFromSun: 1.52), Planet(name: "Jupiter", distanceFromSun: 5.20), Planet(name: "Saturn", distanceFromSun: 9.58), Planet(name: "Uranus", distanceFromSun: 19.2), Planet(name: "Neptune", distanceFromSun: 30.1) ]
let result1 = planets.map { $0.name }
result1 is an array of strings, containing the list of the planet names
- reduce method: returns a single value obtained by recursively applying the closure to each element of the array.
let result2 = planets.reduce(0) { $0 + $1.distanceFromSun }
result2 is a double, calculated as the sum of the distance of all planets
Swift dictionary
Syntax:
[key type : value type]
these declarations are equivalent:
let someDictionary: [String: Int] = [“Alex”: 31, “Paul”: 39]
let someDictionary: Dictionary = [“Alex”: 31, “Paul”: 39]
- key type must conform to Swift Hashable protocol
- stores associations between keys of the same type and values of the same type in a collection with no defined ordering
- bridged to Foundations’s NSDictionary class
- dictionary key must conform to the Hashable protocol
- type is Dictionary
- type shorthand (preferred) is [Key: Value]
- create empty dictionary:
var namesOfIntegers = Int: String
// namesOfIntegers is an empty [Int: String] dictionary
————————————————————————————
-create a Dictionary with a Dictionary literal:
var airports: [String: String] = [“YYZ”: “Toronto Pearson”, “DUB”: “Dublin”]
- declaring, accessing, adding:
var dict:[String: String] = ["Frog": "Kermit", "Pig": "Ms. Piggy", "Weirdo": "Gonzo" ] dict["Weirdo"] = "Felipe" dict["Frog"] = nil // delete frog for (type, muppet) in dict { print("type: \(type), muppet:\(muppet)") } ------------------------------------------------------------------------------------ -methods:
.count
.isEmpty
.updateValue(-:forKey:)
.removeValue(forKey:)
————————————————————————————
-retrieve a iterable collection of keys or values:
for airportCode in airports.keys { print("Airport code: \(airportCode)") } // Airport code: LHR // Airport code: YYZ
for airportName in airports.values { print("Airport name: \(airportName)") } // Airport name: London Heathrow // Airport name: Toronto Pearson
Swift closures
- self-contained blocks of functionality that can be passed around and used in your code
- are reference types
- can be written without a name by surrounding the code with {} braces
- if closure type is known, can omit the type of its parameters or return type or both
- single statement closures implicitly return the value of their only statement
- can refer to parameters by number instead of by name
- global functions are closures that have a name and do not capture any values
- nested functions are closures that have a name and can capture values from their enclosing function
- closure expressions are unnamed closures written in a lightweight syntax that can capture values from their surrounding context
- can be defined inline right where you want to use them
- can be stored as properties and local variables and can be passed as arguments to functions
- similar to blocks in C and Objective-C and lambdas in other programming languages.
- called closures because they can capture and store references to any constants and variables from the context in which they are defined (closing over those constants and variables).
Syntax: { (parameters) -> return type in statements } ------------------------------------------------------------------------------------------- Fuller version of syntax:
let closureName:(parameter types) -> return type =
{
(parameter name:parameter type) -> return type in
…
}
- name of the closure: let closureName
- closure type syntax: (parameter types) -> return type
-closure expression syntax:
{ (parameter name:parameter type) -> return type in}
It repeats the closure type and includes names for the parameters
——————————————————————————————-
Real world use: completion handlers
let imageView = UIImageView()
HTTP.request(“http://imgur.com/kittens”, completionHandler: { data in
imageView.image = data
})
Define an image view, start the networking request, and provide a completion handler. The completion handler is executed when the lengthy task is completed.
Initialize an object with a closure
let bobView = { () -> UIView in let view = UIView() view.backgroundColor = .black return view }() ------------------------------------------------------------------------------------------- Example: let simpleClosure:(String) -> (String) = { name in let greeting = "Hello, World! " + "Program" return greeting } let result = simpleClosure("Hello, World") print(result)
Output: Hello, World! Program
name: parameter
in: keyword that separates the parameter from the statements
- ———————————————————————————–
- trailing closure is written after the function call’s parentheses even though it is the function’s final argument
reversedNames = names.sorted() { $0 > $1 }
- a closure can capture constants and variables from the surrounding context in which it is defined. The incrementer() function captures runningTotal and amount.
func makeIncrementer(forIncrement amount: Int) -> () -> Int { var runningTotal = 0 func incrementer() -> Int { runningTotal += amount return runningTotal } return incrementer } ------------------------------------------------------------------------------------ -if a closure is assigned a property of a class instance, and the closure captures that instance by referring to the instance or its members, a strong reference cycle between the close and the instance is created. ------------------------------------------------------------------------------------ -a closure escapes a function when the closure is passed as an argument to the function but is called after the function returns. The closure needs to refer to self explicitly.
var completionHandlers: [() -> Void] = [] func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) { completionHandlers.append(completionHandler) }
class SomeClass { var x = 10 func doSomething() { someFunctionWithEscapingClosure { self.x = 100 } } } ------------------------------------------------------------------------------------ -an autoclosure is a closure that is automatically created to wrap an expression passed as an argument to a function. It takes no arguments and returns the value of the expression wrapped inside of it. Autoclosures enable delayed execution.
var customersInLine = ["Chris", "Alex", "Ewa", "Barry", "Daniella"] print(customersInLine.count) // Prints "5"
let customerProvider = { customersInLine.remove(at: 0) } print(customersInLine.count) // Prints "5"
print("Now serving \(customerProvider())!") // Prints "Now serving Chris!" print(customersInLine.count) // Prints "4" ------------------------------------------------------------------------------------ Another autoclosure
// customersInLine is ["Ewa", "Barry", "Daniella"] func serve(customer customerProvider: @autoclosure () -> String) { print("Now serving \(customerProvider())!") } serve(customer: customersInLine.remove(at: 0)) // Prints "Now serving Ewa!" ------------------------------------------------------------------------------------ -an autoclosure that is allowed to escape
// customersInLine is [“Barry”, “Daniella”]
var customerProviders: [() -> String] = []
func collectCustomerProviders(_ customerProvider: @autoclosure @escaping () -> String) {
customerProviders.append(customerProvider)
}
collectCustomerProviders(customersInLine.remove(at: 0))
collectCustomerProviders(customersInLine.remove(at: 0))
print("Collected \(customerProviders.count) closures.") // Prints "Collected 2 closures." for customerProvider in customerProviders { print("Now serving \(customerProvider())!") } // Prints "Now serving Barry!" // Prints "Now serving Daniella!"
Objective-C class header (.h)
import “AnyHeaderFile.h”
@interface ClassName : SuperClass
// define public properties // define public methods
@end
Objective-C class implementation (.m)
import “YourClassName.h”
@interface ClassName () // define private properties // define private methods @end
@implementation ClassName { // define private instance variables }
// implement methods
@end
Objective-C defining methods
(type) doIt;
- (type)doItWithA:(type)a;
- (type)doItWithA:(type)a
b: (type)b;
Objective-C implementing methods
- (type)doItWithA:(type)a
b:(type)b {
// Do something with a and b…
return retVal;
}
Objective-C creating an object
ClassName * myObject =
[[ClassName alloc] init];
Objective-C calling a method
[myObject doIt];
[myObject doItWithA:a];
[myObject doItWithA:a b:b];
Objective-C declaring variables
type myVariable;
Variable types:
int 1, 2, 500, 10000
float
double 1.5, 3.14, 578.234
BOOL YES, NO
ClassName * NSString *, NSArray *, etc.
id Can hold reference to any object
Objective-C defining properties
@property (attribute1, attribute2)
type propertyName;
strong Adds reference to keep object alive
weak Object can disappear, become nil
assign Normal assign, no reference
copy Make copy on assign
nonatomic Make not threadsafe, increase perf
readwrite Create getter & setter (default)
readonly Create just getter
Objective-C using properties
[myObject setPropertyName:a];
myObject.propertyName = a; // alt
a = [myObject propertyName]; a = myObject.propertyName; // alt
Objective-C what is a property?
1) Automatically defines a private instance variable:
type _propertyName;
2) Automatically creates a getter and setter:
- (type)propertyName;
- (void)setPropertyName:(type)name;
-using _propertyName uses the private instance
variable directly
-using self.propertyName uses
the getter/setter.
Objective-C custom initializer example
- (id)initWithParam:(type)param { if ((self = [super init])) { _propertyName = param; } return self; }