Slide 1

Slide 1 text

ࣗݾ঺հ • @giginet • iOSςοΫϦʔυ@ Cookpad • ίϛολʔ fastlane/XcodeGen/CarthageͳͲ • ٕज़ސ໰ @ taskey, MoneyForward

Slide 2

Slide 2 text

No content

Slide 3

Slide 3 text

େن໛ͳΞϓϦͷϚϧνϞ δϡʔϧߏ੒ͷ࣮ફ iOSDC 2021 9/19 Track A @giginet iOSDC 2021

Slide 4

Slide 4 text

͜ͷτʔΫʹ͍ͭͯ • ୈ1ষɿ2019೥ࠒ͔ΒΫοΫύουϨγϐΞϓϦͷϚϧνϞδϡʔϧ ԽΛਐΊ͖ͯͯ͠͹Β͘ܦͬͨͷͰݱঢ়Λ঺հ • ୈ2ষɿଞͷϓϩδΣΫτ͕େن໛ͳϦΞʔΩςΫνϟΛ࣮ફ͢Δʹ ͸Ͳ͏ͨ͠ΒΑ͍͔

Slide 5

Slide 5 text

5 View Model ViewController 🔥ϩδοΫΛ࣋ͭ7JFX XXXManager 🔥γϯάϧτϯͷσʔλιʔε 🔥.BTTJWF7JFX$POUSPMMFS 🔥ڞ௨Խ͞ΕͯංେԽͨ͠Ϟσϧ ☠ 🔥σουίʔυ 🔥ີ݁߹ 🔥7JFXͷܧঝɺ࢖͍ճ͠ 🈲 🔥৮ͬͯ͸ ͍͚ͳ͍΍ͭ 🌀 🔥ࠞಱ ϚϧνϞδϡʔϧԽҎલ

Slide 6

Slide 6 text

ΫοΫύουΞϓϦ • 50 Releases / year • 15 developers / release • 40 ~ 50 PR / release • 328,000 lines

Slide 7

Slide 7 text

2019೥ࠒͷঢ়گͱ໨త • ։ൃ࣌ͷϏϧυ଴ͪΛݮΒ͍ͨ͠ • ࠩ෼Ϗϧυʹ΋1ʙ2෼ͷ଴ͪ࣌ؒ • ػೳؒͷґଘ΍෭࡞༻ΛݮΒ͍ͨ͠ • ςετهड़Λ؆ུԽ͍ͨ͠ • ObjCͳͲͷݹ͍࣮૷ΛӅṭ͍ͨ͠

Slide 8

Slide 8 text

No content

Slide 9

Slide 9 text

• https://fortee.jp/iosdc-japan-2021/proposal/6b6fb68b-a45a-4878-a8b9-0dd5ed90fc39 ΫοΫύουͷΤϯδχΞ͕ޠΔɺڊେͰྺ࢙͋ΔΞϓϦʹ͓͚Δഁ յͱ૑଄ - ϩάϛʔTech https://logmi.jp/tech/articles/321186

Slide 10

Slide 10 text

ΞʔΩςΫνϟ • ΫϦʔϯΞʔΩςΫνϟ͸͋Δఔ౓ಋೖࡁΈʢಋೖ͞Ε͍ͯͳ͍ͱ͜ Ζ΋͋ͬͨʣ • VIPERΛ࠾༻ɺΞϓϦέʔγϣϯ૚ͱυϝΠϯ૚ʹ෼͔ΕΔ • ֤ը໘ΛVIPERγʔϯͱݺͿ

Slide 11

Slide 11 text

No content

Slide 12

Slide 12 text

Feature A Feature B Feature C 7*1&3γʔϯ

Slide 13

Slide 13 text

ݱࡏͷΫοΫύουΞϓϦ

Slide 14

Slide 14 text

CookpadCore Cookpad ɾɾɾ Cookpad Tsukuru Feature A Feature B Feature C 🔥

Slide 15

Slide 15 text

CookpadCore Cookpad ɾɾɾ Cookpad Tsukuru Feature A Feature B Feature C 🔥 $PSFϞδϡʔϧ ந৅ԽͷͨΊͷΠϯλʔϑΣΠε ɾΞϓϦ಺Ͱڞ௨ͯ͠࢖͏6*ίϯϙʔ ωϯτͷఏڙ

Slide 16

Slide 16 text

CookpadCore Cookpad ɾɾɾ Cookpad Tsukuru Feature A Feature B Feature C 🔥 'FBUVSF.PEVMF ڞ௨ͷυϝΠϯ૚Λ͍͔࣋ͭͭ͘ͷ 7*1&3γʔϯΛଋͶͨϞδϡʔϧ

Slide 17

Slide 17 text

CookpadCore Cookpad ɾɾɾ Cookpad Tsukuru Feature A Feature B Feature C 🔥 7*1&3γʔϯ 'FBUVSF.PEVMF

Slide 18

Slide 18 text

CookpadCore Cookpad ɾɾɾ Cookpad Tsukuru Feature A Feature B Feature C 🔥 ΞϓϦέʔγϣϯ "QQMJDBUJPO5BSHFUʹ૬౰ શͯͷґଘΛ஌ΕΔ

Slide 19

Slide 19 text

CookpadCore Cookpad ɾɾɾ Cookpad Tsukuru Feature A Feature B Feature C 🔥 $PPLQBE 7*1&3Խ͞Ε͍ͯΔ͕ϞδϡʔϧԽ͞ Ε͍ͯͳ͍γʔϯͷू߹

Slide 20

Slide 20 text

CookpadCore Cookpad ɾɾɾ Cookpad Tsukuru Feature A Feature B Feature C 🔥 5TVLVSV ࠞಱ

Slide 21

Slide 21 text

CookpadCore Cookpad ɾɾɾ Cookpad Tsukuru Feature A Feature B Feature C 🔥

Slide 22

Slide 22 text

Feature A Feature B ❌ Feature Moduleؒ͸ґଘΛ࣋ͯͳ͍ ॥؀ࢀরΛආ͚ΔͨΊ Feature Aͷ࣋ͭγʔϯ͔ΒFeature Bͷ࣋ͭ γʔϯʹભҠ͍ͨ͠৔߹ͳͲʹࠔΔ

Slide 23

Slide 23 text

CookpadCore Feature A Feature B Environment Cookpad ᶃ&OWJSPONFOU΁ͷ໰͍߹Θͤ ᶄґଘͷ஫ೖ ᶅґଘͷऔಘ

Slide 24

Slide 24 text

Environment • શͯͷґଘΛ࣋ͭProtocol • CoreϞδϡʔϧʹ͍Δʢ=͢΂͔ͯΒࢀরՄೳʣ • ΞϓϦέʔγϣϯλʔήοτͰ࣮૷ͯ͠ґଘΛ஫ೖ • ֤γʔϯ͸ੜ੒࣌ʹEnvironmentΛ΋Β͏ • ෭࡞༻΍ґଘʹ͸Environmentܦ༝Ͱ৮Δ͜ͱͰɺ؀ڥʹΑͬͯڍಈ Λࠩ͠ସ͑Δ͜ͱ͕Ͱ͖Δ

Slide 25

Slide 25 text

import Foundation import UIKit public protocol Environment { var client: ServiceClient { get } var pvLogger: PVLogger { get } var userFeatures: UserFeatures { get } var activityLogger: ActivityLogger { get } // … } CookpadCore ґଘΛQSPUPDPMԽ

Slide 26

Slide 26 text

final class CookpadEnvironment: Environment { var client: ActivityLogger { CookpadActivityLogger( ) } // … } Cookpad ΞϓϦέʔγϣϯλʔήοτͰ ۩ମతͳ࣮૷Λฦ͢

Slide 27

Slide 27 text

environment.activityLogger.post(eventName: “tap_button”) Feature A 'FBUVSF.PEVMF͔Β FOWJSPONFOUܦ༝Ͱར༻Ͱ͖Δ

Slide 28

Slide 28 text

28 Environment CookpadEnvironment StubbableEnvironment Cookpad Tests Network Logger ը໘ભҠ

Slide 29

Slide 29 text

let viewController = RecipeDetails.RecipeDetailsViewController(recipeID: 42 ) present(viewController, animated: true, completion: nil ) Feature A 3FDJQF%FUBJMTϞδϡʔϧ಺ͷ γʔϯ͸'FBUVSF"Ϟδϡʔϧ͔ Β͸ࢀরͰ͖ͳ͍ ❌

Slide 30

Slide 30 text

DescriptorͱResolver • Environment͕࣋ͭґଘղܾͷ࢓૊Έ • Descriptor͸ͦͷ࣮૷Λࣔ͢ϚʔΧʔ • CoreϞδϡʔϧʹ͍Δɻʢ=͢΂͔ͯΒࢀরՄೳʣ • EnvironmentʹDescriptorΛ౉ͯ͠ɺܕফڈ͞Ε࣮ͨଶΛ΋Βͬͯ͘ Δ࢓૊Έ = ResolverͱݺΜͰ͍Δ

Slide 31

Slide 31 text

import Foundation import UIKit public protocol Environment { var client: ServiceClient { get } var pvLogger: PVLogger { get } var userFeatures: UserFeatures { get } var activityLogger: ActivityLogger { get } // … func resolve(_ descriptor: Descriptor) -> Descriptor.Outpu t } CookpadCore 3FTPMWFS %FTDSJQUPSΛड͚औͬͯ ࣮૷Λฦ͢ϝιου %FTDSJQUPS ࣮૷Λࢦࣔ͢͠ϚʔΧʔ

Slide 32

Slide 32 text

public struct RecipeDetailsDescriptor: TypedDescriptor { public typealias Output = UIViewControlle r public var recipeID: Int6 4 public init(recipeID: Int64) { self.recipeID = recipeI D } } CookpadCore %FTDSJQUPS ࣮૷Λࢦࣔ͢͠ϚʔΧʔ

Slide 33

Slide 33 text

final class CookpadEnvironment: Environment { public func resolve(_ descriptor: Descriptor) -> Descriptor.Output { switch descriptor { case let d as ViewDescriptor.RecipeDetailsDescriptor : return RecipeDetailsViewBuilder.build(with: d, environment: self) as! Descriptor.Outpu t } } } Cookpad %FTDSJQUPS͝ͱʹ࣮૷Λฦͯ͠ ΞοϓΩϟετ͢Δ

Slide 34

Slide 34 text

let viewController: UIViewController = environment.resolve(ViewDescriptor.RecipeDetailsDescriptor(recipeID: 42) ) viewController.present(viewController, animated: true, completion: nil ) Feature A 3FTPMWFSܦ༝ͰऔΓग़͢͜ͱͰɺผͷϞδϡʔϧ ಺ͷγʔϯ΋6*7JFX$POUSPMMFSͱͯ͠औಘͰ͖Δ

Slide 35

Slide 35 text

No content

Slide 36

Slide 36 text

CookpadCore FeatureASandbox Feature A SandboxΞϓϦ

Slide 37

Slide 37 text

SandboxΞϓϦ ΞϓϦશମ

Slide 38

Slide 38 text

ػೳ͝ͱʹಈ࡞͢ΔϛχΞϓϦͰϓϨϏϡʔαΠΫϧΛര଎ʹͨ͠࿩ https://fortee.jp/iosdc-japan-2021/proposal/6b6fb68b-a45a-4878-a8b9-0dd5ed90fc39

Slide 39

Slide 39 text

ϚϧνϞδϡʔϧԽͷޮՌ • ϛχΞϓϦ(Sandbox)ͷ੔උʹΑΔ։ൃମݧ(DX)ͷ޲্ • Feature ModuleؒͷӨڹΛݮΒͤͨ • ಛఆϞδϡʔϧ಺ͰͷΈͷSwiftUIಋೖͳͲɺϞδϡʔϧ಺Ͱͷ௅ઓ͕ ͠΍͘͢ͳͬͨ

Slide 40

Slide 40 text

·ͱΊ • Ϟδϡʔϧ෼཭։࢝લͷ໨తͷఆٛ͸ॏཁ • ໨త͕ఆ·͍ͬͯΕ͹ࣗͣͱ෼཭ํ๏͕ܾ·Δ • վળͷঢ়گΛఆྔԽͯ͠؂ࢹ • ґଘղܾͷ࢓૊Έ͸೉͍͠ • ࣮ࡍʹ෼཭͕ਐΜͩΒ͔֬ʹޮՌ͕͋ͬͨʢΑ͔ͬͨʣ

Slide 41

Slide 41 text

࣮ફతϚϧνϞδϡʔϧ

Slide 42

Slide 42 text

Ϟδϡʔϧ෼཭Λਁಁͤ͞Δʹ͸ • શͯΛҰ౓ʹҠߦ͠ͳ͍ • ݀Λ࠹͙ • ։ൃऀ͕໎͏఺ΛݮΒ͢

Slide 43

Slide 43 text

શͯΛҰ౓ʹҠߦ͠ͳ͍ ෦෼తͳҠߦΛҙࣝͨ͠ઃܭ

Slide 44

Slide 44 text

෦෼తͳҠߦΛҙࣝͨ͠ઃܭ • ݹ͍࣮૷ΛҠߦ͠ͳͯ͘΋৽ن࣮૷ͷΈΛϞδϡʔϧ෼཭Ͱ͖ΔΑ͏ ʹҙࣝͯ͠ઃܭ • protocolʹΑΔந৅Խ • protocolΛऔΓग़ͤΔResolver

Slide 45

Slide 45 text

ྫ1ɿݹ͍γϯάϧτϯͷ࣮૷ͷར༻ • ݹ͍࣮૷͸protocolΛCoreϞδϡʔϧʹఆٛ͠ɺFeature Module͔ Β͸protocolͱͯ͠ར༻͢Δ • ྫ͑͹LegacyActivityLogger.sharedΛந৅Խͯ͠environment͔Βఏ ڙ͢Δ • ͜͏͢Δ͜ͱͰɺLegacyActivityLoggerΛมߋͤͣʹ֤γʔϯ͔Β͸ ࣮૷ΛӅṭͰ͖Δ

Slide 46

Slide 46 text

import Foundation public protocol ActivityLogger { func post(eventName: String, payload: [String: Any]? ) } public protocol Environment { var activityLogger: ActivityLogger { get } // … } CookpadCore

Slide 47

Slide 47 text

import CookpadCor e private struct CookpadActivityLogger: ActivityLogger { func post(eventName: String, payload: [String: Any]?) { LegacyActivityLogger.shared.post(eventName: eventName, payload: payload ) } } final class CookpadEnvironment: Environment { var activityLogger: ActivityLogger { return CookpadActivityLogger( ) } } Cookpad ΞϓϦέʔγϣϯλʔήοτ͔ Βಈ͔ͤͳ͍ݹ͍࣮૷Λϥοϓ ͢Δ

Slide 48

Slide 48 text

environment.activityLogger.post(eventName: “search” , payload: ["keyword": "όλʔείονγφϞϯύΠ"] ) Feature A 'FBUVSF.PEVMF͔Β FOWJSPONFOUܦ༝Ͱར༻Ͱ͖Δ

Slide 49

Slide 49 text

ྫ2ɿݹ͍ViewController΁ͷભҠ • Resolverͷ࢓૊Έ͸ݹ͍࣮૷ͷӅṭʹ΋໾ཱͭ • ΫϦʔϯΞʔΩςΫνϟԽ͞Ε͍ͯͳ͍ݹ͍ը໘͕͋ͬͨͱͯ͠΋ɺ ResolverΛ࣮૷͢Ε͹৽͍࣮͠૷͔Βಉ͡Α͏ʹར༻Ͱ͖Δ • ݹ͍ը໘͸Ϟδϡʔϧ෼཭͠ͳͯ͘΋ɺΞϓϦέʔγϣϯλʔήοτ ͔Β஫ೖ͢Ε͹ྑ͍

Slide 50

Slide 50 text

final class LegacyViewBuilder: ViewBuilder { func build(environment: Environment) -> UIViewController { // VIPERԽ͞Ε͍ͯͳ͍Objective-CͰ࣮૷͞Εͨݹ͍ViewController let legacyViewController = CKDLegacyViewController( ) return legacyViewControlle r } } Cookpad ΫϦʔϯΞʔΩςΫνϟԽ͞Ε͍ͯͳ͍ݹ͍ը໘΋ 7JFX#VJMEFSͰϥοϓ͢Δͱ3FTPMWFSܦ༝ͰऔಘͰ͖Δ

Slide 51

Slide 51 text

৽ن࣮૷͕࣮֬ʹҠߦ͞ΕΔΑ͏ʹ͢Δ ݀Λ࠹͙

Slide 52

Slide 52 text

৽ن࣮૷͕࣮֬ʹҠߦ͞ΕΔΑ͏ʹ͢ΔͨΊʹ • ݹ͍࣮૷͸Ұ୴ఘΊΔ • ৽͍࣮͠૷ʹ͍ͭͯίʔυੜ੒ػͳͲΛ४උ͠ɺ؆୯ʹϞδϡʔϧ͕ ੜ੒Ͱ͖ΔΑ͏ʹ͢Δ

Slide 53

Slide 53 text

No content

Slide 54

Slide 54 text

No content

Slide 55

Slide 55 text

Ϟδϡʔϧͷੜ੒ • yonaskolb/XcodeGenΛར༻ • XcodeGenͷTarget TemplateػೳͰॊೈʹTargetΛ૿΍ͤΔ • XcodeGenͷProject SpecΛੜ੒͢Δ࢓૊Έ(XcodeGenGen)

Slide 56

Slide 56 text

targetTemplates : FeatureModule : platform: iOS type: framework sources : - ${frameworkName} settings : base : PRODUCT_BUNDLE_IDENTIFIER: com.cookpad.${frameworkName} MACH_O_TYPE: staticlib info : path: ${frameworkName}/Info.plist dependencies : - sdk: Foundation.framework - sdk: UIKit.framework - target: CookpadCor e prebuildScripts : - name: SwiftLint path: "configs/scripts/swift_lint_module.sh" ม਺

Slide 57

Slide 57 text

-- - targets : MyAwesomeModule : templates : - FeatureModule templateAttributes : frameworkName: MyAwesomeModule ม਺ ܧঝݩͷ ςϯϓϨʔτ

Slide 58

Slide 58 text

γʔϯͷੜ੒ • yonaskolb/GenesisΛར༻ • ςϯϓϨʔτ͔Βίʔυੜ੒Λߦ͏ϢʔςΟϦςΟ • ؆୯ͳઃఆϑΝΠϧͰର࿩తͳΠϯλʔϑΣΠεΛ࡞ΕΔ

Slide 59

Slide 59 text

import CookpadCore import UIKit public final class {{ name }}ViewController: UIViewController, {{ name }}ViewContract { private var presenter: {{ name }}PresenterContract ! private let environment: Environmen t init(environment: Environment) { self.environment = environmen t super.init(nibName: nil, bundle: nil ) } @available(*, unavailable ) required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented" ) } func inject(presenter: {{ name }}PresenterContract) { self.presenter = presente r } override public func viewDidLoad() { super.viewDidLoad( ) // Insert code here to connect presenter } ม਺

Slide 60

Slide 60 text

# name must be given as options to Genesis options : - name: name question: VIPER scene name? description: new VIPER scene name to generate(e.g. RecipeDetails). type: string required: true - name: module question: Destination target? description: Target destination name to generate new VIPER scene. (e.g. RecipeDetails) type: string required: true files : - template: VIPER/FeatureModule/Regular/Contract.stencil path: "{{ module }}/Application/{{ name }}/{{ name }}Contract.swift" - template: VIPER/FeatureModule/Regular/ViewController.stencil path: "{{ module }}/Application/{{ name }}/{{ name }}ViewController.swift" - template: VIPER/FeatureModule/Regular/Wireframe.stencil path: "{{ module }}/Application/{{ name }}/{{ name }}Wireframe.swift ” … ࣭໰ͷ౴͑Λม਺ʹ֨ೲͰ͖Δ ͲͷςϯϓϨʔτΛͲ͜ʹੜ੒͢Δ͔ࢦఆ

Slide 61

Slide 61 text

։ൃऀ͕໎͏఺ΛݮΒ͢ ৘ใͷڞ༗ͱؒҧ͑ͳ͍࢓૊ΈԽ

Slide 62

Slide 62 text

։ൃऀ͕໎͏఺ΛݮΒ͢ • υΩϡϝϯτ΍τϥϒϧγϡʔςΟϯάͳͲͷϦιʔεΛ༻ҙ͢Δ • มߋ͕͋ͬͨ৔߹௥ै͢Δ • มߋʹ͍ͭͯɺࣾ಺ϒϩάͳͲͰ௨஌͢Δ • ษڧձ΍࠲ஊձͳͲΛ௨ͯ͠՝୊Λ἞Έ্͛Δ • ೔ʑͷϨϏϡʔ

Slide 63

Slide 63 text

No content

Slide 64

Slide 64 text

No content

Slide 65

Slide 65 text

ϕετϓϥΫςΟεͷఏڙ • ࣮૷࣌ʹఆ͕ٛ͋΍;΍ͱͳͬͯ͠·͏఺ʹ͍ͭͯɺݸผʹϕετϓ ϥΫςΟεΛఏڙ͢Δ • Ͳ͜ͷϞδϡʔϧʹ΋ஔ͚Δ࣮૷ΛͲ͜ʹ͓͘΂͖͔ • ༷ʑͳґଘղܾͷखஈ͕͋Δͱ͖ʹͲͷΑ͏ͳखஈΛ࠾༻͢΂͖͔

Slide 66

Slide 66 text

࢓૊ΈʹΑΔϦΞʔΩςΫνϟͷਪਐ • ΠϯηϯςΟϒͷઃܭ • Ϟδϡʔϧ෼཭͕ਐΉͱ୯ମϏϧυ͕ૣ͘ͳͬͯ։ൃ͠΍͘͢ͳΔ ͱݴͬͨϝϦοτΛଧͪग़͢ • δΣωϨʔλ΍ϝλϓϩάϥϛϯά • ੩తղੳ

Slide 67

Slide 67 text

ϝλϓϩάϥϛϯάͷར༻ • ίʔυੜ੒ʢϝλϓϩάϥϛϯάʣΛར༻Ͱ͖ΔϲॴͰ͸ར༻͢Δ • ࣮૷ͷ܁Γฦ͕͠ආ͚ΒΕɺҠߦ͕͠΍͘͢ͳΔ • ఆܕ͔Β֎Ε࣮ͨ૷͕Ͱ͖ͳ͘ͳͬͨΓ • ࣮૷๨ΕΛίϯύΠϧ࣌ʹݕग़Ͱ͖ΔΑ͏ʹͰ͖Δ

Slide 68

Slide 68 text

ྫ1: Resolverͷੜ੒ࣗಈԽ • ઌ΄ͲݟͨDescriptor͔ΒViewControllerΛresolve͢Δ࣮૷Λϝλϓ ϩάϥϛϯάʹΑͬͯࣗಈԽ • ࣅͨΑ͏ͳίʔυͷ܁Γฦ͠Λຖճ࣮૷͠ͳͯ͘ྑ͘ͳͬͨ

Slide 69

Slide 69 text

final class CookpadEnvironment: Environment { public func resolve(_ descriptor: Descriptor) -> Descriptor.Output { switch descriptor { case let d as ViewDescriptor.RecipeDetailsDescriptor : return RecipeDetailsViewBuilder.build(with: d, environment: self) as! Descriptor.Outpu t } } } Cookpad ࣮૷๨Ε͕͋ΔͱΫϥογϡ͢Δ ୯७ʹຖճ࣮૷͢Δͷ͕໘౗ ͜͜ʹෳࡶͳॲཧ͕ॻ͚ͯ͠·͏ ❌ ❌ ❌

Slide 70

Slide 70 text

// ࣗಈੜ੒͢ΔΑ͏ʹͨ͠ final class CookpadEnvironment: Environment { public func resolve(_ descriptor: Descriptor) -> Descriptor.Output { switch descriptor { case let d as ViewDescriptor.RecipeDetailsDescriptor : return RecipeDetailsViewBuilder.build(with: d, environment: self) as! Descriptor.Outpu t } } } Cookpad શέʔε͕໢ཏ͞ΕΔΑ͏ʹ ຖճ࣮૷͠ͳͯ͘ྑ͍ ఆܗ֎ͷ࣮૷͕ෆՄೳʹ

Slide 71

Slide 71 text

͜ͷ࢓૊Έͷར఺ • ໢ཏੑͷ୲อ • શDescriptorͷResolver͕͋Δ͜ͱΛίϯύΠϧ࣌ʹอূͰ͖Δ • ن໿΁ͷద߹ͷอূ • ResolverͷதͰҙਤ͠ͳ͍ڍಈΛ࣮૷͢ΔͳͲ͕Ͱ͖ͳ͘ͳΓɺΠϯ λʔϑΣΠε͕ἧ͑ΒΕΔ • লྗԽ • ։ൃऀ͸DescriptorͱViewBuilderΛ༻ҙ͢Δ͚ͩͰྑ͍

Slide 72

Slide 72 text

ίʔυੜ੒Λ༻͍ͨiOSΞϓϦϚϧνϞδϡʔϧԽͷͨΊͷґଘղܾ https://techlife.cookpad.com/entry/2021/06/16/110000

Slide 73

Slide 73 text

ྫ2: ϞοΫͷࣗಈੜ੒ • CoreϞδϡʔϧʹ͍ΔProtocolͷϞοΫΛࣗಈੜ੒͢Δ • Ϣχοτςετ΍SandboxΞϓϦͰμϛʔͷ஋Λྲྀ͠ࠐΈ͍ͨͱ͖ʹ ༗༻ • krzysztofzablocki/SourceryͰ࣮ݱ͍ͯ͠Δ • ࠷ۙ͸uber/mockoloͱ͍͏΍͕ͭ͋Γஔ͖׵͑ΒΕͦ͏

Slide 74

Slide 74 text

protocol RecipeSearchDataSource { func searchRecipe(keyword: String) -> Single<[Recipe]> } CookpadCore

Slide 75

Slide 75 text

class MockRecipeSearchDataSource: RecipeSearchDataSource { lazy var searchRecipeReturnValue: Single<[Recipe]> = { fatalError("\ (#line)") }( ) // MARK: - searchRecipe var searchRecipeCallsCount = 0 var searchRecipeCalled: Bool { return buildCallsCount > 0 } func searchRecipe(keyword: String) -> Single<[Recipe]> { searchRecipeCallsCount += 1 return searchRecipeReturnValu e } } let dataStore = MockRecipeSearchDataSource( ) dataStore.searchRecipeReturnValue = .just([recipe0, recipe1] ) dataStore.searchRecipe(keyword: “φΠεΫϦʔϜ” )

Slide 76

Slide 76 text

ͦͷଞͷ޻෉ • Interface Builderͷ੩తղੳΛߦ͏ • IBDecodable/IBLinter • ϞδϡʔϧҠߦͷͨΊͷεΫϦϓτ੔උ

Slide 77

Slide 77 text

Ϟδϡʔϧ෼཭ͷਁಁͷͨΊʹͰ͖Δ͜ͱ • ͍ͬ΃ΜʹҠߦ͢Δͷ͸೉͍͠ • ৽͍࣮͠૷ͷΈʹ͍ͭͯҠߦ͠΍͍͢Α͏ʹ͢Δ • ݹ͍࣮૷Λޙճ͠ʹͰ͖Δઃܭ • ϝϯόʔͷڠྗΛऔΓ෇͚Δ޻෉ • ਓखΛ࢖Θͳͯ͘΋Ҡߦ͕ਐΉ࢓૊Έ࡞Γ • ίʔυੜ੒ɺlinterɺ ੩తղੳ

Slide 78

Slide 78 text

·ͱΊ • ΫοΫύουͰͷϚϧνϞδϡʔϧԽͷ͜Ε·Ͱͱݱঢ় • ଞͷϓϩδΣΫτ΁ͷదԠͷҰॿͱͳΕ͹޾͍Ͱ͢ • աڈͷࢿྉΛࢀর௖͍ͨΓɺask the speakerͰ࿩͠·͠ΐ͏ • Twitter : @giginet ·Ͱ͓ؾܰʹ

Slide 79

Slide 79 text

ࢀߟࢿྉ • ʙբ͕ؔʙ ΫοΫύουiOSΞϓϦͷഁյͱ૑଄ɺͦͯ͠ະདྷ • https://techconf.cookpad.com/2019/kohki_miki.html • ΫοΫύουͷΤϯδχΞ͕ޠΔɺڊେͰྺ࢙͋ΔΞϓϦʹ͓͚Δഁ յͱ૑଄ - ϩάϛʔTech • https://logmi.jp/tech/articles/321186

Slide 80

Slide 80 text

• XcodeGenʹΑΔ৽࣌୅ͷiOSϓϩδΣΫτ؅ཧ • https://techlife.cookpad.com/entry/2019/04/26/110000 • େن໛ͳiOSΞϓϦͷը໘։ൃΛޮ཰Խ͢ΔͨΊʹಈ࡞֬ೝ༻ϛχΞ ϓϦΛߏங͢Δ • https://techlife.cookpad.com/entry/2020/08/05/090000 • ίʔυੜ੒Λ༻͍ͨiOSΞϓϦϚϧνϞδϡʔϧԽͷͨΊͷґଘղܾ • https://techlife.cookpad.com/entry/2021/06/16/110000

Slide 81

Slide 81 text

• 5/26 Cookpad Lounge #3 ʮΫοΫύου iOS ΞϓϦΛര଎Ͱ։ൃͰ ͖ΔΑ͏ʹ͢Δ࿩ʯ • https://www.youtube.com/watch?v=hqWiOeRyZ94 • 6/16 iOS Tech Talk ʙ Multi module ઓུ࠲ஊձ ʙ • https://www.youtube.com/watch?v=5p6h5yiQ2PQ • 7/2 iOS Tech Talk ʙ Multi module ઓུ࠲ஊձ vol.2 • https://www.youtube.com/watch?v=glpfnnDDaz8

Slide 82

Slide 82 text

એ఻ • After Party iOSDC Japan 2021 • https://cookpad.connpass.com/event/222056/

Slide 83

Slide 83 text

͝ਗ਼ௌ ͋Γ͕ͱ͏͍͟͝·ͨ͠ @giginet