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

99 Problems, but MVC ain't one

99 Problems, but MVC ain't one

- Many iOS apps suffer from the Massive View Controller (MVC) anti-pattern.
- The poor, overworked UIViewController becomes bloated with business logic, model data transformation, user input processing, view layout, data source and delegate code.
- This leads to apps that are difficult to extend, maintain and test.
- This talk will explore a number of different architectural approaches that can help address this problem.
- It will also highlight some options for data binding mechanisms you can use in Objective-C and Swift apps.

Avatar for Tim Antrobus

Tim Antrobus

July 09, 2015

More Decks by Tim Antrobus

Other Decks in Technology

Transcript

  1. MODEL-VIEW-CONTROLLER DEFINES NOT ONLY THE ROLES OBJECTS PLAY IN THE

    APPLICATION, IT DEFINES THE WAY OBJECTS COMMUNICATE WITH EACH OTHER. EACH OF THE THREE TYPES OF OBJECTS IS SEPARATED FROM THE OTHERS BY ABSTRACT BOUNDARIES AND COMMUNICATES WITH OBJECTS OF THE OTHER TYPES ACROSS THOSE BOUNDARIES. DEVELOPER.APPLE.COM
  2. Layout & view hierarchy management View lifecyle presentation logic Model

    data transformation Delegate protocol conformance User interaction handling & validation
  3. MVC AS IOS ANTIPATTERN #3. MASSIVE VIEW CONTROLLERS (MVC) ARE

    HARD TO TEST ADEQUATELY AND THUS BECOME EXPENSIVE TO MAINTAIN & EXTEND
  4. BETTER SEPARATION OF CONCERNS LOOSER COUPLING OF COMPONENTS PLATFORM AGNOSTIC

    VIEWMODELS ARE PONSOS OR SWIFT VALUE TYPES == EASY TO UNIT TEST
  5. FUNCTIONAL REACTIVE PROGRAMMING SIGNALS : A STREAM OF EVENTS OVER

    TIME HIGHER ORDER FUNCTIONS : COMBINE, MAP, FILTER CONVENIENCE BINDINGS FOR UIKIT ELEMENTS
  6. MODEL @interface EmojiMonster: NSObject @property (strong,nonatomic) NSString *name; @property (strong,nonatomic)

    NSString *fightingName; @property (strong,nonatomic) NSNumber *intelligenceLevel; @property (strong,nonatomic) NSNumber *fightingPowerLevel; @property (strong,nonatomic) NSNumber *battlesFought; @end
  7. VIEWMODEL @interface EmojiMonsterViewModel: NSObject @property (strong,nonatomic) NSString *name; @property (strong,nonatomic)

    NSString *fightingName; @property (strong,nonatomic) NSString *intelligence; @property (strong,nonatomic) NSString *fightingPower; @property (strong,nonatomic) NSString *battles; @property (strong,nonatomic) id<ModelServices> modelServices; @end
  8. VIEW @interface EmojiMonsterViewController: UIViewController @property (weak,nonatomic) IBOutlet UILabel *monsterNameLabel; @property

    (weak,nonatomic) IBOutlet UILabel *intelligenceLabel; @property (weak,nonatomic) IBOutlet UILabel *fightingPowerLabel; @property (weak,nonatomic) IBOutlet UILabel *monsterNameLabel; @property (weak,nonatomic) IBOutlet UILabel *battlesLabel; @property (strong,nonatomic) EmojiMonsterViewModel *viewModel; @end
  9. VIEW - SIGNAL FROM VIEWMODEL PROPERTY RACSignal *nameSignal = [[RACObserve(self,

    viewModel.name) ignore:nil] distinctUntilChanged] deliverOn:[RACScheduler mainThreadScheduler]];
  10. VIEW - SIGNAL FROM VIEWMODEL PROPERTY RACSignal *fightingNameSignal = [[RACObserve(self,

    viewModel.fightingName) ignore:nil] distinctUntilChanged] deliverOn:[RACScheduler mainThreadScheduler]];
  11. VIEW - AVOID RETAIN CYCLES #import "RACEXTScope.h" @weakify(self); [[[RACObserve(self, viewModel.battles)

    ignore:nil] distinctUntilChanged] subscribeNext:^(NSString *newValue){ @strongify(self); self.battlesLabel.text = newValue; }];
  12. VIEWMODEL - BIND UI ACTIONS TO COMMANDS [[RACCommand alloc] initWithSignalBlock:^RACSignal

    *(id input) { return [[[self refreshSignal:input] ignore:nil] deliverOn:[RACScheduler mainThreadScheduler]]; }];
  13. VIEW - BIND UI ANIMATIONS TO VIEWMODEL REFRESHES RACSignal *viewModelRefreshSignal

    = self.viewModel.executeRefresh.executing; [viewModelRefreshSignal subscribeNext:^(NSNumber *x) { @strongify(self); if (x.boolValue) { [self animateActivity]; } else { [self stopAnimateActivity]; } }];
  14. PROPERTY OBSERVERS struct EmojiMonster { var name: String { didSet

    { println("Bow before the mighty \(name) underlings !") } } init(_ name: String){ self.name = name } }
  15. STANDARD LIBRARY TYPE WRAPPING struct BindableString { var value: String

    { didSet { // invoke some useful logic here } } init(_ v: String){ value = v } }
  16. DEFINE A LISTENER typealias Listener = String -> Void var

    listener: Listener? func bind(listener: Listener?){ self.listener = listener listener?(value) } var value: String { didSet { listener?(value) } }
  17. GENERIC BINDABLE struct Bindable<T> { typealias Listener = T ->

    Void var listener: Listener? var value: T { didSet { listener?(value) } } init(_ v: T){ value = v } }
  18. BINDING A CLOSURE let name = Bindable("Tiddles") let fightingName =

    Bindable("The Dark Destroyer") let nameLabel = UILabel() let fightingNameLabel = UILabel() name.bind { nameLabel.text = $0 } fightingName.bind { fightingNameLabel.text = $0 }
  19. MVVM VIEWMODEL IMPLEMENTATION struct EmojiMonsterViewModel: EmojiViewModel { let name :

    Bindable<String> let monster : EmojiMonster init(_ emoji: EmojiMonster){ self.monster = emoji self.name = monster.name } }
  20. MVVM VIEW BIND VIEWMODEL class EmojiMonsterViewController { var nameLabel: UILabel

    var viewModel: EmojiViewModel { didSet { viewModel.name.bind { [unowned self] in self.nameLabel.text = $0 } } } }