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

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

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

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

Avatar for yohei sugigami

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ͱͯ͠੾Γग़͠ ڞ௨Խ͍ͨ͠ίʔυ͸ҎԼͷ׆༻Λݕ౼͢Δ