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

"Refactoring iOS projects" from Adrian Kosmaczewski

"Refactoring iOS projects" from Adrian Kosmaczewski

For many developers, refactoring is a menu in their IDE. The truth is that there are tons of refactoring techniques, most of them described in detail by Martin Fowler in his book of that name. In Adrian's session will teach us simple yet effective techniques to refactor large iOS codebases in order to make them more testable, to adapt them to be eventually rewritten in Swift, and to make them as "future proof" as possible.

uaMobiTech

July 28, 2016
Tweet

More Decks by uaMobiTech

Other Decks in Programming

Transcript

  1. Refactoring (noun): a change made to the internal structure of

    software to make it easier to understand and cheaper to modify without changing its observable behavior.
  2. > To improve the design of software > To make

    it easier to understand > To make it easier to find and fix bugs > To program faster
  3. BAD SMELLS ! > Duplicated code > Long methods >

    Large class > Long parameter list > Divergent change > Shotgun surgery > Feature envy > Data clumps > Primitive obsession > Switch statements > Parallel inheritance hierarchies > Lazy class > Speculative generality > Temporary field > Message chains > Middle Man > Inappropriate intimacy > Alternative classes with different interfaces > Incomplete library class > Data class > Refused bequest > Comments
  4. WINNER IN THE LONGEST METHOD NAME CATEGORY2 // QCPlugInContext class

    - (id) outputImageProviderFromBufferWithPixelFormat:(NSString*)format pixelsWide:(NSUInteger)width pixelsHigh:(NSUInteger)height baseAddress:(const void*)baseAddress bytesPerRow:(NSUInteger)rowBytes releaseCallback:(QCPlugInBufferReleaseCallback)callback releaseContext:(void*)context colorSpace:(CGColorSpaceRef)colorSpace shouldColorMatch:(BOOL)colorMatch; 2 Source: Quora.com and Github
  5. VALUE TYPES VS REFERENCE TYPES (1/3) protocol Modifiable { var

    value : Int { get set } mutating func increase() } extension Modifiable { mutating func increase() { value += 100 } } // create two types, TypeOne and TypeTwo, conforming to Modifiable
  6. VALUE TYPES VS REFERENCE TYPES (2/3) var a, b, c,

    d: Modifiable a = TypeOne() b = a b.increase() assert(a.value != b.value) c = TypeTwo() d = c d.increase() assert(c.value == d.value)
  7. VALUE TYPES VS REFERENCE TYPES (3/3) struct TypeOne : Modifiable

    { var value = 0 } class TypeTwo : Modifiable { var value = 0 } a = TypeOne() // Value type, copied b = a b.increase() assert(a.value != b.value) c = TypeTwo() // Reference type, not copied d = c d.increase() assert(c.value == d.value)
  8. VALUE TYPES > More testable architectures > No dependencies on

    outside state > Composable because interchangeable
  9. NSURLCACHE let config = URLSessionConfiguration.default() config.requestCachePolicy = .returnCacheDataElseLoad let memoryCapacity

    = 10 * 1024 * 1024; let diskCapacity = 20 * 1024 * 1024; let cache = URLCache(memoryCapacity: memoryCapacity, diskCapacity: diskCapacity, diskPath: nil) URLCache.setShared(cache)
  10. APPSTATE (1/3) enum AppState { case None case Active(action: Block)

    // typealias Block = (Void) -> Void case Inactive(action: Block) func execute() { switch self { case .Active(let block): block() case .Inactive(let block): block() default: return } } }
  11. APPSTATE (2/3) class SomeController { var state = AppState.None {

    didSet { state.execute() } } func viewDidLoad() { // ... }
  12. APPSTATE (3/3) func viewDidLoad() { super.viewDidLoad() let inactive = AppState.Inactive

    { [unowned self] () in self.view.backgroundColor = UIColor.red() } let active = AppState.Active { [unowned self] () in self.view.backgroundColor = UIColor.green() } // ... self.state = inactive }
  13. GKSTATE (1/2) import GameplayKit class ActiveState: GKState { override func

    isValidNextState(_ stateClass: AnyClass) -> Bool { if stateClass == InactiveState.self { return true } return false } override func didEnter(withPreviousState previousState: GKState?) { // change UI elements, etc, etc... } override func willExit(withNextState nextState: GKState) { // ... } }
  14. GKSTATE (2/2) let active = ActiveState() let inactive = InactiveState()

    let stateMachine = GKStateMachine(states: [active, inactive]) stateMachine.enterState(InactiveState)
  15. MODEL – VIEW – VIEWMODEL (MVVM 1/2) class DetailViewModel: NSObject

    { let model : Model init(withModel model: Model) { self.model = model } var displayText : String { return self.model.description } }
  16. MODEL – VIEW – VIEWMODEL (MVVM 1/2) class DetailViewController: UIViewController

    { @IBOutlet weak var label: UILabel! var viewModel : DetailViewModel? override func viewDidLoad() { super.viewDidLoad() self.label.text = self.viewModel?.displayText } }
  17. LAZY LOADING CLASS class LazyLoader { var _backingField: String? var

    property: String? { get { if _backingField == nil { _backingField = "text" } return _backingField } set { _backingField = newValue } } }
  18. MEMORY WARNINGS func applicationDidReceiveMemoryWarning(_ application: UIApplication) { // ... on

    the app delegate... } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // ... on every controller... } // ... and anywhere else let center = NotificationCenter.default() let notification = NSNotification.Name.UIApplicationDidReceiveMemoryWarning let selector = #selector(SomeClass.handler(_:)) center.addObserver(self, selector: selector, name: notification, object: nil)
  19. LAZY LOADING STRUCT struct LazyLoader { var _backingField: String? var

    property: String? { mutating get { if _backingField == nil { _backingField = "text" } return _backingField } set { _backingField = newValue } } }
  20. USER INTERFACE IDIOM // Only if you need compatibility with

    iPhone OS 3.2... really? if UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiom.pad { // ... iPad-only code } let idiom = UIDevice.current().userInterfaceIdiom if idiom == UIUserInterfaceIdiom.phone { // ... iPhone-only code }
  21. OS VERSION CHECKING3 // Since iOS 8: let version =

    OperatingSystemVersion(majorVersion: 10, minorVersion: 0, patchVersion: 0) if ProcessInfo().isOperatingSystemAtLeast(version) { print("iOS >= 9.0.0") } let os = ProcessInfo().operatingSystemVersion switch (os.majorVersion, os.minorVersion, os.patchVersion) { case (8, 0, _): print("iOS >= 8.0.0, < 8.1.0") case (8, _, _): print("iOS >= 8.1.0, < 9.0") case (9, _, _): print("iOS >= 9.0.0") default: print("iOS >= 10.0.0") } 3 http://nshipster.com/swift-system-version-checking/
  22. CUSTOM UIVIEWCONTROLLER TRANSITIONS import UIKit class TransitionController : NSObject, UIViewControllerAnimatedTransitioning

    { let duration : TimeInterval = 0.6 func transitionDuration(_ transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { return duration } func animateTransition(_ transitionContext: UIViewControllerContextTransitioning) { // ... perform UI animations and transitions here } }
  23. ASSOCIATED OBJECTS 4 extension NSSomeClassOutThere { private struct AssociatedKeys {

    static var AssociatedValue = "AssociatedValue" } var associatedString: String? { get { return objc_getAssociatedObject(self, &AssociatedKeys.AssociatedValue) as? String } set { objc_setAssociatedObject(self, &AssociatedKeys.AssociatedValue, newValue as NSString?, .OBJC_ASSOCIATION_COPY_NONATOMIC) } } } var obj = NSSomeClassOutThere() obj.associatedString = "Some other string" 4 http://nshipster.com/swift-objc-runtime/
  24. ASSOCIATED OBJECTS 5 Do not if you can use instead:

    > Subclassing > Target-Action > Gesture recognizers > Delegation > NSNotification 5 http://nshipster.com/associated-objects/