Slide 1

Slide 1 text

@amlcurran Using Swift to write better code

Slide 2

Slide 2 text

@amlcurran

Slide 3

Slide 3 text

@amlcurran No fancy APIs

Slide 4

Slide 4 text

@amlcurran No cool features

Slide 5

Slide 5 text

@amlcurran No mind-blowing frameworks

Slide 6

Slide 6 text

@amlcurran No life-changing architectures

Slide 7

Slide 7 text

@amlcurran But learning to use all these better

Slide 8

Slide 8 text

@amlcurran Software craft

Slide 9

Slide 9 text

@amlcurran Some common craft keywords Unit test SOLID Architecture Mentorship Pragmatism Dependency inversion Functional Imperative Single responsibility Self-improvement Continuous integration Continuous deployment TDD Outside-in Clean code

Slide 10

Slide 10 text

@amlcurran Some common craft keywords Unit test SOLID Architecture Mentorship Clean code Pragmatism Dependency inversion Functional Imperative Single responsibility Self-improvement Continuous integration Continuous deployment TDD Outside-in

Slide 11

Slide 11 text

@amlcurran Why write good code • Makes working with your team (and yourself) easier • New features are quicker to code • Easier to onboard new developers

Slide 12

Slide 12 text

@amlcurran Swift as a platform • Swift is a new(ish) software language • Lots of flexibility • Huge potential for great code • Rapidly evolving guidelines

Slide 13

Slide 13 text

@amlcurran –Me “A good software developer uses something because they can, A great software developer uses something because they must”

Slide 14

Slide 14 text

@amlcurran Write it like you read it • You write code once • You and other people read it multiple times • Write code to be easy to read, not easy to write

Slide 15

Slide 15 text

@amlcurran Extensions

Slide 16

Slide 16 text

@amlcurran Wrap APIs which are generic let string = "Hello world! Wie geht's?" let attributedString = NSMutableAttributedString(string: string) let range = (attributedString.string as NSString).range(of: "Hello") attributedString.addAttributes([NSForegroundColorAttributeName: UIColor.red], range: range) attributedString.setForegroundColor(.red, for: "Hello")

Slide 17

Slide 17 text

@amlcurran Wrap APIs which are generic let string = "Hello world! Wie geht's?" let attributedString = NSMutableAttributedString(string: string) let range = (attributedString.string as NSString).range(of: "Hello") attributedString.addAttributes([NSForegroundColorAttributeName: UIColor.red], range: range) attributedString.setForegroundColor(.red, forText: "Hello")

Slide 18

Slide 18 text

@amlcurran Private extensions for local code • Private extensions provide even more specific wrappers • Write code in your domain, not Apple’s fileprivate extension EKEventEditViewController { convenience init(calendarItem: SCCalendarItem, delegate: EKEventEditViewDelegate, eventStore: EKEventStore = EKEventStore.instance) { self.init() self.eventStore = eventStore self.event = EKEvent(representing: calendarItem) self.editViewDelegate = delegate } }

Slide 19

Slide 19 text

@amlcurran Using small extensions let translation = gestureRecognizer.translationInView(gestureRecognizer.view!.superview!).y let rawProgress = (translation - initialPosition) / fullGestureLength let progress = CGFloat(fminf(fmaxf(Float(rawProgress), 0.0), 1.0))

Slide 20

Slide 20 text

@amlcurran Using small extensions let progress = gestureRecognizer.yTranslation(from: initialPosition) .asProportion(of: fullGestureLength) .clamp(between: 0, 1) let translation = gestureRecognizer.translationInView(gestureRecognizer.view!.superview!).y let rawProgress = (translation - initialPosition) / fullGestureLength let progress = CGFloat(fminf(fmaxf(Float(rawProgress), 0.0), 1.0))

Slide 21

Slide 21 text

@amlcurran My favourite extension let maybeInt = Int("4") let addedUsingOperator = (maybeInt ?? 0) + 3 let addedUsingMethod = maybeInt.or(0) + 3

Slide 22

Slide 22 text

@amlcurran Opinionated slides alert

Slide 23

Slide 23 text

@amlcurran A common scenario… class ViewController: UIViewController { // implementation of the view controller } extension ViewController: UITableViewDataSource { // implementation of data source } extension ViewController: UITableViewDelegate { // implementation of delegate }

Slide 24

Slide 24 text

@amlcurran Avoid implementation extensions • Common Swift pattern - but avoid! • Makes your code look organised… when really it isn’t • One class implementing protocol interfaces = doing multiple things

Slide 25

Slide 25 text

@amlcurran A common scenario… class ViewController: UIViewController { let strings = ["foo", "bar", "baz"] } extension ViewController: UITableViewDataSource { func numberOfSections(in tableView: UITableView) -> Int { return strings.count } } extension ViewController: UITableViewDelegate { // implementation of delegate }

Slide 26

Slide 26 text

@amlcurran A common solution class ViewController: UIViewController { let strings = ["foo", "bar", "baz"] override func viewDidLoad() { tableView.dataSource = StringDataSource(strings) tableView.delegate = OpenDetailsTableViewDelegate() } } Replacing inheritance with delegation

Slide 27

Slide 27 text

@amlcurran Functions

Slide 28

Slide 28 text

@amlcurran Avoid long methods • Long methods are difficult to read, require a lot of time • Multiple if or switch statements make it difficult to see what happens • Break methods down into smaller ones

Slide 29

Slide 29 text

@amlcurran Avoid long methods func foo(string: String) -> String { // do something if (someCondition()) { // something else } else { // backup } // do more stuff return value }

Slide 30

Slide 30 text

@amlcurran Function pointers tell a story • Where you can use a closure, you can use a function pointer • These are named - use the name to show what a closure does • Levels of abstraction

Slide 31

Slide 31 text

@amlcurran Without function pointers let shoppingList = items .filter({ $0.priceCents < 99}) .map({ "\($0.name): €\(Double($0.priceCents) / 100.0)" }) .joined(separator: ", ")

Slide 32

Slide 32 text

@amlcurran With function pointers let shoppingList = items .filter({ $0.priceCents < 99}) .map({ "\($0.name): €\(Double($0.priceCents) / 100.0)" }) .joined(separator: ", ") let shoppingList = items .filter(itemsUnder99Cents) .map(nameAndDescription) .joined(separator: ", ")

Slide 33

Slide 33 text

@amlcurran Combining it all let inputItems = extensionContext?.inputItems ?? [] _ = inputItems .flatMap({ return $0 as? NSExtensionItem }) .flatMap({ (extensionItem: NSExtensionItem) -> [NSItemProvider] in let attachments = extensionItem.attachments ?? [] return attachments.flatMap({ (attachment) -> NSItemProvider? in return attachment as? NSItemProvider }) }) .filter({ $0.hasItemConformingToTypeIdentifier("public.url") }) .forEach({ (urlItem) -> Void in urlItem.loadItem(forTypeIdentifier: "public.url", options: nil, completionHandler: { (result, error) in if let url = result as? NSURL { // Save the URL } self.extensionContext!.completeRequest(returningItems: []) { (expired) in print("did expire: \(expired)") } }) })

Slide 34

Slide 34 text

@amlcurran Extensions + functions = inputItems.orEmptyArray() .attemptTo(castAsExtensionItems) .then(extractAllAttachments) .filter(onlyUrlItems) .forEach(loadUrl(using: extensionContext)) let inputItems = extensionContext?.inputItems ?? [] _ = inputItems .flatMap({ return $0 as? NSExtensionItem }) .flatMap({ (extensionItem: NSExtensionItem) -> [NSItemProvider] in let attachments = extensionItem.attachments ?? [] return attachments.flatMap({ (attachment) -> NSItemProvider? in return attachment as? NSItemProvider }) }) .filter({ $0.hasItemConformingToTypeIdentifier("public.url") }) .forEach({ (urlItem) -> Void in urlItem.loadItem(forTypeIdentifier: "public.url", options: nil, completionHandler: { (result, error) in if let url = result as? NSURL { // Save the URL } self.extensionContext!.completeRequest(returningItems: []) { (expired) in print("did expire: \(expired)") } }) })

Slide 35

Slide 35 text

@amlcurran Some tips • Think about “what does this code mean to me”? • “How can I make this clearer to others?” • Don’t be afraid to make private extensions which just wrap a function with a better name

Slide 36

Slide 36 text

@amlcurran Resources • https://sourcemaking.com/ • http://martinfowler.com/ • “Clean Code” by Bob Martin • www.amlcurran.co.uk/tutorials/better-swift.zip

Slide 37

Slide 37 text

@amlcurran Go write great code! or come write great code with me - [email protected]