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

MVVMをベースに複雑な振る舞いを しっかり把握できるアプリ開発

MVVMをベースに複雑な振る舞いを しっかり把握できるアプリ開発

Realm meetup #6 で発表した
Sync iOS開発の舞台裏についてです
プロジェクトの話や、MVVM、ViewBindingなど多義にわたり解説しています

yohei sugigami

August 26, 2015
Tweet

More Decks by yohei sugigami

Other Decks in Technology

Transcript

  1. iOS Schedule ݄̑ ݄̒ ݄̓ ϓϩτλΠϓ։ൃ 'JSFCBTFબఆ +0*/ ਃ੥ νʔϜ಺4MBDLஔ͖׵͑

    ࣾ಺4MBDLஔ͖׵͑ +0*/ ຊ։ൃ ࣾ֎ϢʔβϑΟʔυόοΫ ϲ݄ɺ̒ਓ݄͙Β͍ +0*/
  2. Very Slow Swift Compile .BD#PPL1SP JODI -BUF ͰϑϧίϯύΠϧ࣌ؒ ˠɹ̐෼ඵ
 ίʔυΛݟ௚ͯ͠ίϯύΠϧߴ଎Խ


    ˠɹ෼ඵʢඵʣ ˠɹࠩ෼ίϯύΠϧͷՄೳੑ΋Ξοϓ  ܕਪ࿦͠ͳ͍ͰܕΛ໌ه͢Δʢ"SSBZ΍%JDUJPOBSZ΋ʣ  ෳ਺ͷTXJGUϑΝΠϧΛҰͭʹ·ͱΊΔ  ܧঝ͠ͳ͍DMBTT͸pOBMΛ໌ه͢Δ  $BSUBHFରԠͷϥΠϒϥϦ͸1PETͰΠϯετʔϧ͠ͳ͍
  3. MVVM (෩) $POUSPMMFS 7JFX.PEFM .PEFM 3FBDU,JU 4XJGU5BTL 4XJGUZ+40/ 4XJGU#POE 3FBMN

    3BX%BUB 1FSTJTUFOU 4UBUF.BOBHFNFOU -PHJD 7JFX%BUB 6*,JU%FMFHBUF #JOEJOH -JCSBSZ 3FTQPOTJCJMJUZ $SFBUF7JFX 4%8FC*NBHF 7JFX7JFX -BZFS
  4. ViewDataBinding 4UBUF 7JFX $POUSPMMFS 7JFX.PEFM %BUB ੹຿ͱؔ৺ࣄͷ෼཭ -PHJD 7JFX 7JFX.PEFMͷ-PHJD͸

    4UBUF %BUBͷมߋ͕੹຿ 7JFXʹ͍ͭͯ͸ؔ஌͠ͳ͍ PS #JOE 7JFX͸4UBUF %BUB͕มߋ
 ͞ΕͨΒಈతʹը໘Λߋ৽ %BUB 4UBUFͷϓϩηε͸ؔ஌͠ͳ͍ 7JFX #JOEJOH .FUIPE$BMM
  5. Instane Relation 7JFX7JFX $POUSPMMFS 7JFX.PEFM .PEFM ʜ ʜ 3FGFSFODF #VTJOFTT-PHJD

    4VC-PHJD 4IBSF-PHJD 4VC-PHJD %BUB %BUB %BUB %BUB %BUB %BUB .BQQJOH #JOEJOH 7JFX #JOEJOH 1SFTFOUBUJPO-PHJD 7JFX.PEFM͸ଞͷϨΠϠʔͷࢀরΛ࣋ͨͳ͍
  6. Request ViewBinding final class RequestListViewModel<T: Identifier> { let items: DynamicArray<T>

    = DynamicArray([]) var requestListState = Dynamic<RequestListState>(.None) var noDataFirstViewHidden: Dynamic<Bool> { let a = indicatorViewHidden.map { $0 == false } let b = requestListFirstState.map { $0 == .Error } return reduce(a, b, c) { $0 || $1 == true } } var indicatorViewHidden: Dynamic<Bool> { let a = requestListFirstState.map { $0 != RequestListState.Requesting } let b = items.map { count($0) > 0 } return reduce(a, b) { $0 || $1 == true } } var retryViewHidden: Dynamic<Bool> { let a = requestListFirstState.map { $0 != RequestListState.Error } let b = items.map { count($0) > 0 } return reduce(a, b) { $0 || $1 == true } } 3FRVFTU4UBUFͱ*UFNTͷঢ়ଶͷΑΔڍಈΛએݴ

  7. Request ViewBinding final class ContactsViewController: UIViewController { var tableViewDataSourceBond: UITableViewDataSourceBond<ContactCell>!

    let viewModel = ContactsViewModel() let indicatorView = InstantiateFromNib(IndicatorView) let retryView = InstantiateFromNib(RetryView) let noDataView = InstantiateFromNib(NoDataView) override func viewDidLoad() { super.viewDidLoad() viewModel.requestList.indicatorViewHidden ->> indicatorView.dynHidden viewModel.requestList.retryViewHidden ->> retryView.dynHidden viewModel.requestList.noDataFirstViewHidden ->> noDataView.dynHidden } 7JFX$POUSPMMFSͰ7JFX.PEFMͷ4XJGU#POEͱ7JFXΛ#JOEJOH
  8. Request ViewBinding final class RequestListViewModel<T: Identifier> { typealias RequestTask =

    Task<Progress, ResponseCollection<T>, NSError> func requestFirst(task: RequestTask) -> RequestTask { self.requestListState.value = .Requesting task.success { [weak self] (collection: ResponseCollection<T>) -> Void in self?.items.setArray(collection.items) self?.requestListState.value = .None }.failure { [weak self] (errorInfo: RequestTask.ErrorInfo) -> Void in self?.requestListState.value = .Error } return task } 3FRVFTUͷ։࢝ɺਖ਼ৗ׬ྃɺҎ্׬ྃͰ4BUFΛߋ৽ ਖ਼ৗ׬ྃ࣌ʹJUFNTΛߋ৽
  9. Request ViewBinding final class RequestListViewModel<T: Identifier> { lazy var stateChangedObserver

    = Bond<RequestListState> { [weak self] state in switch state { case .Requesting: UIApplication.sharedApplication().networkActivityIndicatorVisible = true default: UIApplication.sharedApplication().networkActivityIndicatorVisible = false } } init() { requestListState ->| stateChangedObserver } OFUXPSL"DUJWJUZ*OEJDBUPS7JTJCMFΛ4UBUFͰ#JOEJOH
  10. Validation ViewBinding final class SignUpViewModel { let email = Dynamic<String>("")

    let password = Dynamic<String>("") var emailError: Dynamic<NSError?> { return email.map { Validator.Email($0).validate() } } var passwordError: Dynamic<NSError?> { return password.map { Validator.Password($0).validate() } } var isSignInInput: Dynamic<Bool> { let isEmailValid = emailError.map { nil == $0 } let isPasswordValid = passwordError.map { nil == $0 } return reduce(isEmailValid, isPasswordValid) { $0 && $1 == true } } &NBJMͱ1BTTXPSEͷ7BMJEBUJPOʹΑͬͯ 4JHO6Q͕&OBCMFʹͳΔ͔Λએݴ

  11. Validation ViewBinding final class SignUpViewController { override func viewDidLoad() {

    super.viewDidLoad() emailTextField ->> viewModel.email passwordTextField ->> viewModel.password viewModel.signUpState ->| signUpStateChangedObserver viewModel.isSignInInput ->> signUpButton.dynEnabled 7JFX$POUSPMMFSͰ7JFX.PEFMͷ4XJGU#POEͱ7JFXΛ#JOEJOH enum Validator { case Email(String?) case Password(String?) func validate() -> NSError? { switch self { case .Email(var value): return NGRValidator.validateValue(value, named: LocalizedString("Email")) { (validator: NGRPropertyValidator!) in validator.required().msgNil(LocalizedString("is required.")) validator.syntax(NGRSyntax.Email).msgWrongSyntax(NGRSyntax.Email, LocalizedString("is not valid syntax.")) } case .Password(var value): return NGRValidator.validateValue(value, named: LocalizedString("Password")) { (validator: NGRPropertyValidator!) in validator.required().msgNil(LocalizedString("is required.")) validator.minLength(6).msgTooShort(LocalizedString("is too short.")) }
  12. No inherits 7JFX$POUSPMMFS 7JFX 7JFX.PEFM .PEFMʹ͓͍ͯ "CTUSBDUϨΠϠʔΛઃ͚ͳ͍ʢܧঝ͠ͳ͍ʣ ܧঝ͢Δͱɺɺɺ  ϩδοΫ΍ڍಈΛཧղʹ͠ʹ͍͘

     ڞ௨Խͨ͠ίʔυΛมߋ͠ʹ͍͘ SFGT J04ΞϓϦͷઃܭͰ#BTF7JFX$POUSPMMFSͷΑ͏ͳͷ͸࡞Γͨ͘ͳ͍CZࠓ৓ઌੜ
 IUUQRJJUBDPNZJNBKPJUFNTFGFCECGE
  13. No inherits  %FMFHBUF  (FOFSJDT  .PEFMΛ೚ҙͷ1SPUPDPMΛ४ڌͤͯ͞
 (FOFSJDTͰϩδοΫΛڞ௨Խ 

    "TQFDU  ϩΪϯά΍"OBMZUJDTૹ৴  $MBTT  ڞ௨ॲཧΛ$MBTTͱͯ͠੾Γग़͠ ڞ௨Խ͍ͨ͠ίʔυ͸ҎԼͷ׆༻Λݕ౼͢Δ