or use coding guidelines consistently per (at least) File / Project • Discuss with team - PR rejection is not enough • Use automation tools: SwiftLint, SwiftFormat Style and semantics Solution
not Object extension Array where Element: Equatable { // Two terms describe single entity mutating func removeObject(_ object: Element) { if let index = self.index(of: object) { self.remove(at: index) } } mutating func removeObjectsInArray(_ array: [Element]) { for object in array { self.removeObject(object) } } } Style and semantics
{ mutating func remove(_ element: Element) { if let index = self.index(of: element) { self.remove(at: index) } } mutating func remove<C: Collection>(contentsOf elements: C) where C.Iterator.Element == Element { for element in elements { self.remove(element) } } } Style and semantics
describe actual intent - don't give false expectations • Reuse existing API terms - don't pollute with new ones without a need • Follow vendor/recommended/any API design guidelines Links: • Swift API Design Guidelines: https://goo.gl/YT3LA5 • SE-0006: https://goo.gl/nX78X0 • How to name things: the hardest problem in programming: https://goo.gl/ZGEy81 • Hunting for great names in programming: https://goo.gl/H6zxcT • Erica Sadun: Swift protocol names: https://goo.gl/iM2re7 • Swift Protocol Naming Convention: https://goo.gl/2xnIxT Summary
describe actual intent - don't give false expectations • Reuse existing API terms - don't pollute with new ones without a need • Follow vendor/recommended/any API design guidelines Links: • Swift API Design Guidelines: https://goo.gl/YT3LA5 • SE-0006: https://goo.gl/nX78X0 • How to name things: the hardest problem in programming: https://goo.gl/ZGEy81 • Hunting for great names in programming: https://goo.gl/H6zxcT • Erica Sadun: Swift protocol names: https://goo.gl/iM2re7 • Swift Protocol Naming Convention: https://goo.gl/2xnIxT Summary
describe actual intent - don't give false expectations • Reuse existing API terms - don't pollute with new ones without a need • Follow vendor/recommended/any API design guidelines Links: • Swift API Design Guidelines: https://goo.gl/YT3LA5 • SE-0006: https://goo.gl/nX78X0 • How to name things: the hardest problem in programming: https://goo.gl/ZGEy81 • Hunting for great names in programming: https://goo.gl/H6zxcT • Erica Sadun: Swift protocol names: https://goo.gl/iM2re7 • Swift Protocol Naming Convention: https://goo.gl/2xnIxT Summary
describe actual intent - don't give false expectations • Reuse existing API terms - don't pollute with new ones without a need • Follow vendor/recommended/any API design guidelines Links: • Swift API Design Guidelines: https://goo.gl/YT3LA5 • SE-0006: https://goo.gl/nX78X0 • How to name things: the hardest problem in programming: https://goo.gl/ZGEy81 • Hunting for great names in programming: https://goo.gl/H6zxcT • Erica Sadun: Swift protocol names: https://goo.gl/iM2re7 • Swift Protocol Naming Convention: https://goo.gl/2xnIxT Summary
describe actual intent - don't give false expectations • Reuse existing API terms - don't pollute with new ones without a need • Follow vendor/recommended/any API design guidelines Links: • Swift API Design Guidelines: https://goo.gl/YT3LA5 • SE-0006: https://goo.gl/nX78X0 • How to name things: the hardest problem in programming: https://goo.gl/ZGEy81 • Hunting for great names in programming: https://goo.gl/H6zxcT • Erica Sadun: Swift protocol names: https://goo.gl/iM2re7 • Swift Protocol Naming Convention: https://goo.gl/2xnIxT Summary
no difference between public and private anymore • Breaks "black box" by exposing implementation • Encourages to use fast and dirty workarounds Language Impact
no difference between public and private anymore • Breaks "black box" by exposing implementation • Encourages to use fast and dirty workarounds Language Impact
no difference between public and private anymore • Breaks "black box" by exposing implementation • Encourages to use fast and dirty workarounds Language Impact
still available to Interface Builder • use private(set) to hide mutability • Declare private first! Expose to public/ internal only when needed Language Solution
still available to Interface Builder • use private(set) to hide mutability • Declare private first! Expose to public/ internal only when needed Language Solution
still available to Interface Builder • use private(set) to hide mutability • Declare private first! Expose to public/ internal only when needed Language Solution
are OK when: • it is clear that a cycle will be broken • guarantees correct app execution • Not all API leads to the permanent retain cycles: • dispatch_(a)sync on the main/global/any unowned queue • UIView.animate • MOC.performBlock(AndWait) Code/SDK behavior understanding
are OK when: • it is clear that a cycle will be broken • guarantees correct app execution • Not all API leads to the permanent retain cycles: • dispatch_(a)sync on the main/global/any unowned queue • UIView.animate • MOC.performBlock(AndWait) Code/SDK behavior understanding
API func reportAppState() { // collect usage data and send it to the server self.networkAPI.makeRequest { response in self.blockFurtherExecutionIfNeeded() } } } // usage AbuseDetector().reportAppState() data consistency Code/SDK behavior understanding
API func reportAppState() { // collect usage data and send it to the server self.networkAPI.makeRequest { [unowned self] response in self.blockFurtherExecutionIfNeeded() } } } // usage AbuseDetector().reportAppState() data consistency Code/SDK behavior understanding
API func reportAppState() { // collect usage data and send it to the server self.networkAPI.makeRequest { [unowned self] response in // crash! self.blockFurtherExecutionIfNeeded() } } } // usage AbuseDetector().reportAppState() data consistency Code/SDK behavior understanding
API func reportAppState() { // collect usage data and send it to the server self.networkAPI.makeRequest { [weak self] response in // no crash self.blockFurtherExecutionIfNeeded() } } } // usage AbuseDetector().reportAppState() data consistency Code/SDK behavior understanding
API func reportAppState() { // collect usage data and send it to the server self.networkAPI.makeRequest { [weak self] response in // no crash. But `self` was deallocated self.blockFurtherExecutionIfNeeded() } } } // usage AbuseDetector().reportAppState() data consistency Code/SDK behavior understanding
API func reportAppState() { // collect usage data and send it to the server self.networkAPI.makeRequest { response in // Better to not rely on external env. and to guarantee response handling self.blockFurtherExecutionIfNeeded() } } } // usage AbuseDetector().reportAppState() data consistency Code/SDK behavior understanding
code does its job • Learn tools you're using everyday • Layout cycle isn't that straightforward • Fundamentals understanding is a must • Apple Guides is worth reading Links • objc.io: Advanced autolayout toolbox: https://goo.gl/ebc9jd • Apple: Changing constrains: https://goo.gl/UbWlhI • Krakendev: weak and unowned refs in swift: https://goo.gl/6eqYco Summary
code does its job • Learn tools you're using everyday • Layout cycle isn't that straightforward • Fundamentals understanding is a must • Apple Guides is worth reading Links • objc.io: Advanced autolayout toolbox: https://goo.gl/ebc9jd • Apple: Changing constrains: https://goo.gl/UbWlhI • Krakendev: weak and unowned refs in swift: https://goo.gl/6eqYco Summary
code does its job • Learn tools you're using everyday • Layout cycle isn't that straightforward • Fundamentals understanding is a must • Apple Guides is worth reading Links • objc.io: Advanced autolayout toolbox: https://goo.gl/ebc9jd • Apple: Changing constrains: https://goo.gl/UbWlhI • Krakendev: weak and unowned refs in swift: https://goo.gl/6eqYco Summary
code does its job • Learn tools you're using everyday • Layout cycle isn't that straightforward • Fundamentals understanding is a must • Apple Guides is worth reading Links • objc.io: Advanced autolayout toolbox: https://goo.gl/ebc9jd • Apple: Changing constrains: https://goo.gl/UbWlhI • Krakendev: weak and unowned refs in swift: https://goo.gl/6eqYco Summary
func authorize(request: URLRequest) { let authorizationRequired = // perform some non-trivial check let credentialKey = // key depends on the domain/protocol/api/etc if authorizationRequired, let credential = Keychain.getValue(for: credentialKey) { let headers = // form custom HTTP headers // append to the request } } } No-tests-ever design
func authorize(request: URLRequest) { let authorizationRequired = // perform some non-trivial check let credentialKey = // key depends on the domain/protocol/api/etc if authorizationRequired, let credential = Keychain.getValue(for: credentialKey) { let headers = // form custom HTTP headers // append to the request } } } // How to evaluate whether request requires authorization or not? No-tests-ever design
func authorize(request: URLRequest) { let authorizationRequired = // perform some non-trivial check let credentialKey = // key depends on the domain/protocol/api/etc if authorizationRequired, let credential = Keychain.getValue(for: credentialKey) { let headers = // form custom HTTP headers // append to the request } } } // How to evaluate whether request requires authorization or not? // How to evaluate credential key? No-tests-ever design
func authorize(request: URLRequest) { let authorizationRequired = // perform some non-trivial check let credentialKey = // key depends on the domain/protocol/api/etc if authorizationRequired, let credential = Keychain.getValue(for: credentialKey) { let headers = // form custom HTTP headers // append to the request } } } // How to evaluate whether request requires authorization or not? // How to evaluate credential key? // Hot to ensure that headers will be in the correct form? No-tests-ever design
func authorize(request: URLRequest) { let authorizationRequired = // perform some non-trivial check let credentialKey = // key depends on the domain/protocol/api/etc if authorizationRequired, let credential = Keychain.getValue(for: credentialKey) { let headers = // form custom HTTP headers // append to the request } } } // How to evaluate whether request requires authorization or not? // How to evaluate credential key? // Hot to ensure that headers will be in the correct form? // Side effects! No-tests-ever design
• Really improves your API and code • Makes your code "tests ready" for free • Doesn't require to write tests immediately • Saves you from some potential bugs Summary
- zero cost • Simpler to maintain in short-term • Bundled with MR_defaultContext() / Realm() • Leads to no-tests-ever design • Tricky to manage data from different users Database usage
- zero cost • Simpler to maintain in short-term • Bundled with MR_defaultContext() / Realm() • Leads to no-tests-ever design • Tricky to manage data from different users • Tricky to perform db cleanup Database usage
reset database for the UserB let path = "Documents/Database.sqlite" FileManager.default.removeItem(atPath: path) // Are we good to go? No! // How about: Database.sqlite-shm Database.sqlite-wal Database misuse
reset database for the UserB let path = "Documents/Database.sqlite" FileManager.default.removeItem(atPath: path) // Are we good to go? No! // How about: Database.sqlite-shm Database.sqlite-wal // Or maybe Database_SUPPORT/_EXTERNAL_DATA/ Database misuse
reset database for the UserB let path = "Documents/Database.sqlite" FileManager.default.removeItem(atPath: path) // Are we good to go? No! // How about: Database.sqlite-shm Database.sqlite-wal // Or maybe Database_SUPPORT/_EXTERNAL_DATA/ // Nobody cares! Database misuse
reset database for the UserB let coordinator: NSPersistentStoreCoordinator // Choose a store you'd like to reset let store = coordinator.persistentStores.first Database misuse Use framework API!
reset database for the UserB let coordinator: NSPersistentStoreCoordinator // Choose a store you'd like to reset let store = coordinator.persistentStores.first if let url = store.url { try coordinator.destroyPersistentStore(at: url, ofType: store.type) } // Add a new store using the same options Database misuse Use framework API!
// UserA logged out // UserB logged in // Statistics: received response // Statistics: writing UserA's results to the Database // Results has been written to the UserB database or crashed due to the missing DB Database misuse
database(s) per user • Keep service(s) per user • Keep folders on the disk per user • Prefer DI over MR_defaultContext / Realm() to gain control over your resources Database misuse A better approach
UserA logged out // UserB logged in // UserA.Statistics: received response // UserA.Statistics: writing results to the UserA.Database Database and disk usage Ideally
UserA logged out // UserB logged in // UserA.Statistics: received response // UserA.Statistics: writing results to the UserA.Database // UserA.Services are shutting down // UserA.Database removed or closed Database and disk usage Ideally
UserA logged out // UserB logged in // UserA.Statistics: received response // UserA.Statistics: writing results to the UserA.Database // UserA.Services are shutting down // UserA.Database removed or closed // Easy to achieve with DI Database and disk usage Ideally
!"" 0e76292794888d4f1fa75fb3aff4ca27c58f56a6 !"" 7a85f4764bbd6daf1c3545efbbf0f279a6dc0beb #"" e5f26f9227e3ff4ece1431c2fc497c054d0e8269 Common case Files management
Better tests with @testable due to `internal` • Prevents you from leaking layers - no way to mix UI with Core.framework easily • Speeds up compilation (Swift only) One target - one love Benefits
Better tests with @testable due to `internal` • Prevents you from leaking layers - no way to mix UI with Core.framework easily • Speeds up compilation (Swift only) One target - one love Benefits
Better tests with @testable due to `internal` • Prevents you from leaking layers - no way to mix UI with Core.framework easily • Speeds up compilation (Swift only) One target - one love Benefits
Better tests with @testable due to `internal` • Prevents you from leaking layers - no way to mix UI with Core.framework easily • Speeds up compilation (Swift only) One target - one love Benefits
let storedModelVersion = ..., storedModelVersion < desiredModelVersion { // perform migration } return true } // What if storedModelVersion == nil, but we actually do have a data with old version? Model versioning
let storedModelVersion = ..., storedModelVersion < desiredModelVersion { // perform migration } return true } // What if storedModelVersion == nil, but we actually do have a data with old version? func application(..., didFinishLaunchingWithOptions: ...) -> Bool { let storedModelVersion = UserDefaults.standard.integer(forKey: "version") if storedModelVersion == Keychain.hasCredentialInOldFormat { // we're running untracked old model version } ... return true } Model versioning
let storedModelVersion = ..., storedModelVersion < desiredModelVersion { // perform migration } return true } // What if storedModelVersion == nil, but we actually do have a data with old version? func application(..., didFinishLaunchingWithOptions: ...) -> Bool { let storedModelVersion = UserDefaults.standard.integer(forKey: "version") if storedModelVersion == Keychain.hasCredentialInOldFormat { // we're running untracked old model version } ... return true } // Migration also can no be fully automated because of this special case Model versioning