Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Swift Style Guides And Best Practices by Minal,...

Swift Style Guides And Best Practices by Minal, Swift Hyderabad

Abstract: In this session, I will provide an overview of the best coding practices, discuss style guides and code smells, which helps in writing beautiful code.

Bio: Having 4+ years experience in iOS, I am working with Swift since the first stable release. And currently working at hedgehog lab since last year. I have developed applications for education, real estate restaurants, and business domain.

Swift India

May 13, 2017
Tweet

More Decks by Swift India

Other Decks in Programming

Transcript

  1. Does Your Code Smell? Code is more often read than

    written. You who are writing code, the future you who reads that code, your extended team, and anyone who visits and maintains your repositories are all first class consumers of the code you write today. Code that’s readable and consistent costs lowers in terms of life span, maintainability, and error prevention. Any style that produces readable, maintainable, and well-documented code is, by definition, successful.
  2. What’s in This Session Swift Style guides you through the

    ins and outs of some Swift programming best practices. Critical dos and don’ts for writing readable Swift code. Explores common coding challenges and the best practices that address them. 1. Structure Your Code for Readability 2. Adopt Conventional Styling 3. Establish Preferred Practices 4. Design the Right APIs
  3. Naming Use descriptive names with camel case for classes, methods,

    variables, etc. Class names should be capitalized, while method names and variables should start with a lower case letter. Preferred: let maximumWidgetCount = 100 class WidgetContainer { var widgetButton: UIButton let widgetHeightPercentage = 0.85 } Name variables, parameters, and associated types according to their roles, rather than their type constraints. Preferred: var greeting = "Hello" protocol ViewController { associatedtype ContentView : View } class ProductionLine { func restock(from supplier: WidgetFactory) } Not Preferred: let MAX_WIDGET_COUNT = 100 class app_widgetContainer { var wBut: UIButton let wHeightPct = 0.85 } Not Preferred: var string = "Hello" protocol ViewController { associatedtype ViewType : View } class ProductionLine { func restock(from widgetFactory: WidgetFactory) }
  4. For functions and init methods, prefer named parameters for all

    arguments unless the context is very clear. Include external parameter names if it makes function calls more readable. func dateFromString(_ dateString: String) -> NSDate func convertPointAt(column: Int, row: Int) -> CGPoint func timedAction(delay: NSTimeInterval, perform action: SKAction) -> SKAction! // would be called like this: dateFromString("2014-03-14") convertPointAt(column: 42, row: 13) timedAction(delay: 1.0, perform: someOtherAction) For methods, follow the standard Apple convention of referring to the first parameter in the method name: class Guideline { func moveBy(x deltaX: Double, y deltaY: Double) { ... } func increment(by amount: Int) { ... } }
  5. Spacing Indent using 2 spaces rather than tabs to conserve

    space and help prevent line wrapping. Be sure to set this preference in Xcode as shown below: Tip: You can re-indent by selecting some code (or ⌘A to select all) and then Control-I (or Editor\Structure \Re-Indent in the menu). Some of the Xcode template code will have 4-space tabs hard coded, so this is a good way to fix that.
  6. Types Always use Swift's native types when available. Swift offers

    bridging to Objective-C so you can still use the full set of methods as needed. Preferred: let width = 120.0 // Double let widthString = (width as NSNumber).stringValue // String Comments When they are needed, use comments to explain why a particular piece of code does something. Comments must be kept up-to-date or deleted. Not Preferred: let width: NSNumber = 120.0 // NSNumber let widthString: NSString = width.stringValue // NSString Control Flow Prefer the for-in style of for loop over the for-condition-increment style. Preferred: for _ in 0..<3 { print("Hello three times") } for (index, person) in enumerate(attendeeList) { print("\(person) is at position #\(index)") } Not Preferred: for var i = 0; i < 3; i++ { print("Hello three times") } for var i = 0; i < attendeeList.count; i++ { let person = attendeeList[i] print("\(person) is at position #\(i)") }
  7. Enumerations Use UpperCamelCase for enumeration name and LowerCamelCase for enumeration

    values: enum CompassPoint { case north case south case east case west } Use of Self For conciseness, avoid using self since Swift does not require it to access an object's properties or invoke its methods. Use self when required to differentiate between property names and arguments in initializers, and when referencing properties in closure expressions (as required by the compiler): class BoardLocation { let row: Int, column: Int init(row: Int, column: Int) { self.row = row self.column = column let closure = { print(self.row) } } }
  8. Classes and Structures Which one to use? - Structs have

    value types. struct SomeStruct { var number: Int = 0 } - Classes have reference types. class SomeClass { var number: Int = 0 } var reference = SomeClass() reference.number = 42 var reference2 = reference reference.number = 43 print("The number in reference2 is \(reference2.number)”) var value = SomeStruct() value.number = 42 var value2 = value value.number = 43 print("The number in value2 is \(value2.number)") This prints: The number in reference2 is 43 The number in value2 is 42
  9. Examples when to use structure over class: Use a value

    type [e.g. struct, enum] when: - You want copies to have independent state - The data will be used in code across multiple threads Use a reference type [e.g. class] when: - You want to create shared, mutable state In Swift, Array, String, and Dictionary are all value types. They behave much like a simple int value in C, acting as a unique instance of that data.
  10. Protocol When adding protocol to a class, prefer adding a

    separate class extension for the protocol methods. This keeps the related methods grouped together with the protocol and can simplify instructions to add a protocol to a class with its associated methods. Also, don't forget the // MARK: comment to keep things well-organized! Preferred: class MyViewcontroller: UIViewController { // class stuff here } // MARK: UITableViewDataSource extension MyViewcontroller: UITableViewDataSource { // table view data source methods } // MARK: UIScrollViewDelegate extension MyViewcontroller: UIScrollViewDelegate { // scroll view delegate methods } Not Preferred: class MyViewcontroller: UIViewController, UITableViewDataSource, UIScrollViewDelegate { // all methods }
  11. Syntactic Prefer the shortcut versions of type declarations over the

    full generics syntax. Preferred: var deviceModels: [String] var employees: [Int: String] var faxNumber: Int? Not Preferred: var deviceModels: Array<String> var employees: Dictionary<Int, String> var faxNumber: Optional<Int> Type Inference Prefer compact code and let the compiler infer the type for a constant or variable, unless you need a specific type other than the default such as CGFloat or Int16. Preferred: let message = "Click the button" let currentBounds = computeViewBounds() var names = [String]() let maximumWidth: CGFloat = 106.5 Not Preferred: let message: String = "Click the button" let currentBounds: CGRect = computeViewBounds() var names: [String] = [] NOTE: Following this guideline means picking descriptive names is even more important than before.
  12. Closure Expressions There are several special forms that allow closures

    to be written more concisely: - A closure can omit the types of its parameters, its return type, or both. If you omit the parameter names and both types, omit the in keyword before the statements. - A closure may omit names for its parameters. Its parameters are then implicitly named $ followed by their position: $0, $1, $2, and so on. - A closure that consists of only a single expression is understood to return the value of that expression. The contents of this expression are also considered when performing type inference on the surrounding expression. - The following closure expressions are equivalent: myFunction { (x: Int, y: Int) -> Int in return x + y } myFunction { x, y in return x + y } myFunction { return $0 + $1 } myFunction { $0 + $1 }
  13. Five Signs of Code Smell in Swift • Forced Unwrapping

    & Downcasting • Monster Classes • Ignoring Errors • Singletons • String Literals
  14. 1. Forced Unwrapping & Downcasting Once you get used to

    optionals, you come to appreciate their value. Not only optionals are making your code safer, they also make your code more readable. An optional carries a message that says “I may not have a value. Be careful.” override func prepare(for segue: UIStoryboardSegue, sender: Any?) { if segue.identifier == viewController { let navigationController = segue.destinationViewController as! UINavigationController let addViewController = navigationController.viewControllers.first as! AddViewController addViewController.delegate = self } } It does not matter how certain you are, an optional should be treated with caution. Forced unwrapping or forced downcasting is asking for trouble. There are situations in which you can use the ! operator (outlets, for example), but do not force unwrap an optional to avoid an if statement or a few extra lines of code. The updated example shows you how elegant optional binding can be. The if statement tells the developer what we expect, but, at the same time, it hints that we may not get what we expected. override func prepare(for segue: UIStoryboardSegue, sender: Any?) { if segue.identifier == addItemViewController { if let navigationController = segue.destinationViewController as? UINavigationController, let addItemViewController = navigationController.viewControllers[0] as? AddItemViewController { addItemViewController.delegate = self } } }
  15. guard guard is a new conditional statement that requires execution

    to exit the current block if the condition isn’t met. Any new optional bindings created in a guard statement’s condition are available for the rest of the function or block, and the mandatory else must exit the current scope, by using return to leave a function, continue or break within a loop, or a @noreturn function like fatalError(): for imageName in imageNamesList { guard let image = UIImage(named: imageName) else { continue } // do something with image }
  16. 2. Monster Classes Some of us apply the MVC (Model-View-Controller)

    pattern a bit too strictly. It is fine to create classes that are not models, views, or controllers. By isolating functionality, you put the view controllers of your project lightweight and make code easier to reuse. Why not extract a piece of functionality in a Swift extension. If the implementation of a class exceeds 1000 lines of code, you should start refactoring of your code. Can you load 1000 lines of code in your working memory? That is what it takes to work with a class. If you make changes to a class, you need to know how other parts are affected and that is only possible if you know what the various parts of the class do.
  17. 3. Ignoring Errors It is great to see that error

    handling is tightly integrated into Swift. In Objective-C, it is easy to ignore errors, too easy. I don’t know anyone who enjoys error handling. But if you want to write code that works and that can recover when things start to go wrong, then you need to accept that error handling is part of it. Ignoring errors also makes debugging more difficult. Putting your head in the sand is not the solution when things go wrong.
  18. 4. Singletons Singletons are great. They are so useful that

    less experienced developers tend to overuse them. Even though I don’t consider the singleton pattern an anti-pattern, sometimes it seems as if developers have forgotten how to pass objects by reference. After removing the singletons from a project, it loses some of its complexity. By passing objects by reference, even if only one instance is alive at any one time, it becomes much clearer where the object is used and what role it plays.
  19. 5. String Literals String literals are smelly by default. They

    are great in playgrounds, but how often do you (or should you) use string literals in a project? The only valid use cases I can think of are localization, constants, and logging. I agree that creating constants or enumerations is tedious, but it pays off in the long run. You avoid typos and it significantly improves maintainability. When you are browsing through lines and lines of code, the string literals stand out immediately. If you see too much red then it is time to refactor.
  20. References - The Swift API Design Guidelines: https://swift.org/documentation/api-design-guidelines/ - The

    Swift Programming Language: https://developer.apple.com/library/content/documentation/Swift/ Conceptual/Swift_Programming_Language/index.html