Upgrade to PRO for Only $50/Year—Limited-Time Offer! 🔥

Using Swift to write better code

Alex Curran
October 10, 2016

Using Swift to write better code

Writing better code is what makes great developers, a great codebase, and great products. So how can you use Swift's power to make your life easier and your code even better?

Alex Curran

October 10, 2016
Tweet

More Decks by Alex Curran

Other Decks in Technology

Transcript

  1. @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
  2. @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
  3. @amlcurran Why write good code • Makes working with your

    team (and yourself) easier • New features are quicker to code • Easier to onboard new developers
  4. @amlcurran Swift as a platform • Swift is a new(ish)

    software language • Lots of flexibility • Huge potential for great code • Rapidly evolving guidelines
  5. @amlcurran –Me “A good software developer uses something because they

    can, A great software developer uses something because they must”
  6. @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
  7. @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")
  8. @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")
  9. @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 } }
  10. @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))
  11. @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))
  12. @amlcurran My favourite extension let maybeInt = Int("4") let addedUsingOperator

    = (maybeInt ?? 0) + 3 let addedUsingMethod = maybeInt.or(0) + 3
  13. @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 }
  14. @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
  15. @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 }
  16. @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
  17. @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
  18. @amlcurran Avoid long methods func foo(string: String) -> String {

    // do something if (someCondition()) { // something else } else { // backup } // do more stuff return value }
  19. @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
  20. @amlcurran Without function pointers let shoppingList = items .filter({ $0.priceCents

    < 99}) .map({ "\($0.name): €\(Double($0.priceCents) / 100.0)" }) .joined(separator: ", ")
  21. @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: ", ")
  22. @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)") } }) })
  23. @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)") } }) })
  24. @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