Slide 1

Slide 1 text

Factoryͷࣗಈੜ੒ʹΑ ΓςετΛॻ͖΍͘͢ ͢Δ 1 Factoryͷࣗಈੜ੒ʹΑΓςετΛॻ͖΍͘͢͢Δ, iOSDC 2018 Reject Conference days1

Slide 2

Slide 2 text

Takeshi Ihara • AbemaTV • Twitter: @nonchalant0303 • GitHub: Nonchalant • Climbing ! • Game " 2 Factoryͷࣗಈੜ੒ʹΑΓςετΛॻ͖΍͘͢͢Δ, iOSDC 2018 Reject Conference days1

Slide 3

Slide 3 text

Test struct User { let age: Int var isAdult: Bool { return age >= 20 } } class UserTests: XCTestCase { func test20ࡀҎ্ͳΒ੒ਓͰ͋Δ() { let user = User(age: 20) XCTAssertTrue(user.isAdult) } func test20ࡀະຬͳΒ੒ਓͰͳ͍() { let user = User(age: 19) XCTAssertFalse(user.isAdult) } } 3 Factoryͷࣗಈੜ੒ʹΑΓςετΛॻ͖΍͘͢͢Δ, iOSDC 2018 Reject Conference days1

Slide 4

Slide 4 text

New Property struct User { let name: String let age: Int var isAdult: Bool { return age >= 20 } } class UserTests: XCTestCase { func test20ࡀҎ্ͳΒ੒ਓͰ͋Δ() { let user = User(age: 20) XCTAssertTrue(user.isAdult) } func test20ࡀະຬͳΒ੒ਓͰͳ͍() { let user = User(age: 19) XCTAssertFalse(user.isAdult) } } 4 Factoryͷࣗಈੜ੒ʹΑΓςετΛॻ͖΍͘͢͢Δ, iOSDC 2018 Reject Conference days1

Slide 5

Slide 5 text

Compile Error struct User { let name: String let age: Int let birthday: Date var isAdult: Bool { return age >= 20 } } class UserTests: XCTestCase { func test20ࡀҎ্ͳΒ੒ਓͰ͋Δ() { let user = User(age: 20) // Missing argument for parameter 'name' in call XCTAssertTrue(user.isAdult) } func test20ࡀະຬͳΒ੒ਓͰͳ͍() { let user = User(age: 19) // Missing argument for parameter 'name' in call XCTAssertFalse(user.isAdult) } } 5 Factoryͷࣗಈੜ੒ʹΑΓςετΛॻ͖΍͘͢͢Δ, iOSDC 2018 Reject Conference days1

Slide 6

Slide 6 text

Fragile Test struct User { let name: String let age: Int var isAdult: Bool { return age >= 20 } } class UserTests: XCTestCase { func test20ࡀҎ্ͳΒ੒ਓͰ͋Δ() { let user = User(name: "Takeshi Ihara", age: 20) XCTAssertTrue(user.isAdult) } func test20ࡀະຬͳΒ੒ਓͰͳ͍() { let user = User(name: "Takeshi Ihara", age: 19) XCTAssertFalse(user.isAdult) } } 6 Factoryͷࣗಈੜ੒ʹΑΓςετΛॻ͖΍͘͢͢Δ, iOSDC 2018 Reject Conference days1

Slide 7

Slide 7 text

Factory Pattern ΦϒδΣΫτͷੜ੒ॲཧΛڞ௨Խ͢Δ struct UserFactory { static func provide(name: String = "", age: Int = 0) -> User { return User(name: name, age: age) } } class UserTests: XCTestCase { func test20ࡀҎ্ͳΒ੒ਓͰ͋Δ() { let user = UserFactory.provide(age: 20) XCTAssertTrue(user.isAdult) } func test20ࡀະຬͳΒ੒ਓͰͳ͍() { let user = UserFactory.provide(age: 19) XCTAssertFalse(user.isAdult) } } 7 Factoryͷࣗಈੜ੒ʹΑΓςετΛॻ͖΍͘͢͢Δ, iOSDC 2018 Reject Conference days1

Slide 8

Slide 8 text

Cost of Factory • ςετର৅ͷΦϒδΣΫτͷ਺͚ͩFactory͕ඞཁʹͳΔ • ωετ͕ਂ͍ܕͩͱґଘΦϒδΣΫτͷ਺͚ͩඞཁʹͳΔ • FactoryͳͲΛ༻ҙ͢Δίετ͕ߴ͍ͱςετΛॻ͔ͳ͘ͳΔ 8 Factoryͷࣗಈੜ੒ʹΑΓςετΛॻ͖΍͘͢͢Δ, iOSDC 2018 Reject Conference days1

Slide 9

Slide 9 text

FactoryProvider https:/ /github.com/Nonchalant/FactoryProvider 9 Factoryͷࣗಈੜ੒ʹΑΓςετΛॻ͖΍͘͢͢Δ, iOSDC 2018 Reject Conference days1

Slide 10

Slide 10 text

FactoryProvider https:/ /github.com/Nonchalant/FactoryProvider • FactoryΛࣗಈੜ੒͢ΔϥΠϒϥϦ • Enum, Struct͕ੜ੒ର৅ • LensΛαϙʔτ • ymlͰઃఆ߲໨Λఆٛ • GeneratorΛؚΊΔͨΊʹCocoapodsͰͷΈΠϯετʔϧՄೳ 10 Factoryͷࣗಈੜ੒ʹΑΓςετΛॻ͖΍͘͢͢Δ, iOSDC 2018 Reject Conference days1

Slide 11

Slide 11 text

Factory (Struct) ܕύϥϝʔλʹStructΛࢦఆ͢Δ import FactoryProvider let user = Factory.provide() // User(name: "", age: 0) 11 Factoryͷࣗಈੜ੒ʹΑΓςετΛॻ͖΍͘͢͢Δ, iOSDC 2018 Reject Conference days1

Slide 12

Slide 12 text

Factory (Enum) ܕύϥϝʔλʹEnumΛࢦఆ͢Δ import FactoryProvider let season = Factory.provide() // Season.spring enum Season { case spring case summer case automn case winter } 12 Factoryͷࣗಈੜ੒ʹΑΓςετΛॻ͖΍͘͢͢Δ, iOSDC 2018 Reject Conference days1

Slide 13

Slide 13 text

Generated Object ݻఆ஋Ͱੜ੒͞ΕΔ ! import FactoryProvider var user = Factory.provide() user.name = "Takeshi Ihara" // Cannot assign to property: 'name' is a 'let' constant struct User { let name: String let age: Int var isAdult: Bool { return age >= 20 } } 13 Factoryͷࣗಈੜ੒ʹΑΓςετΛॻ͖΍͘͢͢Δ, iOSDC 2018 Reject Conference days1

Slide 14

Slide 14 text

Lens • ෆมੑΛอͪͭͭωετͨ͠σʔλߏ଄ʹର͢ΔΞΫηεΛ Lensͷ߹੒ͰදݱͰ͖ΔΑ͏ʹͨ͠΋ͷ • ݩʑ͸Haskellͷ֓೦ • SwiftzͷLens࣮૷Λಠཱͨ͠ϑϨʔϜϫʔΫͱͯ͠੾Γग़ͨ͠ Focusͱ͍͏ϑϨʔϜϫʔΫ΋ଘࡏ • https:/ /github.com/typelift/Focus 14 Factoryͷࣗಈੜ੒ʹΑΓςετΛॻ͖΍͘͢͢Δ, iOSDC 2018 Reject Conference days1

Slide 15

Slide 15 text

Lens import FactoryProvider let user = Factory.provide() // User(name: "", age: 0) let newUser = user |> User._age *~ 20 // User._ageΛLensͱݺͿ // User(name: "", age: 20) 15 Factoryͷࣗಈੜ੒ʹΑΓςετΛॻ͖΍͘͢͢Δ, iOSDC 2018 Reject Conference days1

Slide 16

Slide 16 text

Lens (Nested) import FactoryProvider struct User { let id: UserId let name: String let age: Int } struct UserId { let value: String } let user = Factory.provide() |> User._id * UserId._value *~ "nonchalant0303" // User(id: UserId(value: "nonchalant0303"), name: "", age: 0) 16 Factoryͷࣗಈੜ੒ʹΑΓςετΛॻ͖΍͘͢͢Δ, iOSDC 2018 Reject Conference days1

Slide 17

Slide 17 text

Config ymlϑΝΠϧͰઃఆ͢Δ includes: # ੜ੒ର৅ͷStruct, EnumΛؚΜͩϑΝΠϧ΁ͷύε (ϑΝΠϧ୯ҐɺσΟϨΫτϦ୯Ґ) - Input/SubInput1 - Input/SubInput2/Source.swift excludes: # ੜ੒ର৅ͷStruct, Enumͷྫ֎ΛؚΜͩϑΝΠϧ΁ͷύε - Input/SubInput1/SubSubInput - Input/SubInput2/Source.swift testables: # ςετର৅ͷλʔήοτ - target1 - target2 output: output/Factories.generated.swift # ࣗಈੜ੒͞Εͨίʔυͷύε 17 Factoryͷࣗಈੜ੒ʹΑΓςετΛॻ͖΍͘͢͢Δ, iOSDC 2018 Reject Conference days1

Slide 18

Slide 18 text

Build Phases ςετͷ࣮ߦ࣌ʹFactoryͷࣗಈੜ੒εΫϦϓτΛݺͼग़͢ "${PODS_ROOT}/FactoryProvider/generate" --config .factory.yml # Factories.generated.swift is generated 18 Factoryͷࣗಈੜ੒ʹΑΓςετΛॻ͖΍͘͢͢Δ, iOSDC 2018 Reject Conference days1

Slide 19

Slide 19 text

How FactoryProvider Works 2ͭͷίʔυϕʔε͔Β੒Γཱͭ • FactoryProviderͷίʔυ (Fixed) • ࣗಈੜ੒͞ΕΔίʔυ (Generated in Build Phases) 19 Factoryͷࣗಈੜ੒ʹΑΓςετΛॻ͖΍͘͢͢Δ, iOSDC 2018 Reject Conference days1

Slide 20

Slide 20 text

Providable (FactoryProvider) Providableʹ४ڌͨ͠ܕ͕FactoryͰΦϒδΣΫτΛऔಘͰ͖Δ public protocol Providable { static func provide() -> Self } 20 Factoryͷࣗಈੜ੒ʹΑΓςετΛॻ͖΍͘͢͢Δ, iOSDC 2018 Reject Conference days1

Slide 21

Slide 21 text

Primitive Factory (FactoryProvider) PrimitiveͳܕͷFactory͕ఆٛ͞Ε͍ͯΔ (Int, Optional, String, ...) extension Int: Providable { public static func provide() -> Int { return 0 } } extension Optional: Providable where Wrapped: Providable { public static func provide() -> Optional { return .some(Wrapped.provide()) } } ... 21 Factoryͷࣗಈੜ੒ʹΑΓςετΛॻ͖΍͘͢͢Δ, iOSDC 2018 Reject Conference days1

Slide 22

Slide 22 text

Factory (FactoryProvider) struct Factory { static func provide() -> T { return T.provide() } } 22 Factoryͷࣗಈੜ੒ʹΑΓςετΛॻ͖΍͘͢͢Δ, iOSDC 2018 Reject Conference days1

Slide 23

Slide 23 text

Specified Factory (Generated Code) import FactoryProvider extension User: Providable { static func provide() -> User { return User( id: Factory.provide(), name: Factory.provide(), age: Factory.provide() ) } } extension UserId: Providable { static func provide() -> UserId { return UserId( value: Factory.provide() ) } } 23 Factoryͷࣗಈੜ੒ʹΑΓςετΛॻ͖΍͘͢͢Δ, iOSDC 2018 Reject Conference days1

Slide 24

Slide 24 text

Specified Factory (Generated Code) import FactoryProvider extension User: Providable { static func provide() -> User { return User( id: Factory.provide(), name: Factory.provide(), age: Factory.provide() ) } } extension UserId: Providable { static func provide() -> UserId { return UserId( value: Factory.provide() ) } } 24 Factoryͷࣗಈੜ੒ʹΑΓςετΛॻ͖΍͘͢͢Δ, iOSDC 2018 Reject Conference days1

Slide 25

Slide 25 text

Specified Factory (Generated Code) ༿͕͢΂ͯPrimitiveͳܕʹͳΔ·Ͱ໦Λ৳͹͢ 25 Factoryͷࣗಈੜ੒ʹΑΓςετΛॻ͖΍͘͢͢Δ, iOSDC 2018 Reject Conference days1

Slide 26

Slide 26 text

Lens (FactoryProvider) public struct Lens { private let getter: (Whole) -> Part private let setter: (Part, Whole) -> Whole public init(getter: @escaping (Whole) -> Part, setter: @escaping (Part, Whole) -> Whole) { self.getter = getter self.setter = setter } public func get(_ from: Whole) -> Part { return getter(from) } public func set(_ from: Part, _ to: Whole) -> Whole { return setter(from, to) } } 26 Factoryͷࣗಈੜ੒ʹΑΓςετΛॻ͖΍͘͢͢Δ, iOSDC 2018 Reject Conference days1

Slide 31

Slide 31 text

Lens (Generated Code) extension User { static var _name: Lens { return Lens( getter: { $0.name }, setter: { name, base in User(name: name, age: base.age) } ) } static var _age: Lens { return Lens( getter: { $0.age }, setter: { age, base in User(name: base.name, age: age) } ) } } 31 Factoryͷࣗಈੜ੒ʹΑΓςετΛॻ͖΍͘͢͢Δ, iOSDC 2018 Reject Conference days1

Slide 32

Slide 32 text

Decompose let user = Factory.provide() |> User._name *~ "Takeshi Ihara" = (User._name *~ "Takeshi Ihara")(Factory.provide()) = { user in Lens( getter: { $0.name }, setter: { name, base in User(name: name, age: base.age) } ).set("Takeshi Ihara", user) }(Factory.provide()) = { name, base in User(name: name, age: base.age) }("Takeshi Ihara", Factory.provide()) = User(name: "Takeshi Ihara", age: 0) 32 Factoryͷࣗಈੜ੒ʹΑΓςετΛॻ͖΍͘͢͢Δ, iOSDC 2018 Reject Conference days1

Slide 33

Slide 33 text

Decompose let user = Factory.provide() |> User._name *~ "Takeshi Ihara" = (User._name *~ "Takeshi Ihara")(Factory.provide()) = { user in Lens( getter: { $0.name }, setter: { name, base in User(name: name, age: base.age) } ).set("Takeshi Ihara", user) }(Factory.provide()) = { name, base in User(name: name, age: base.age) }("Takeshi Ihara", Factory.provide()) = User(name: "Takeshi Ihara", age: 0) 33 Factoryͷࣗಈੜ੒ʹΑΓςετΛॻ͖΍͘͢͢Δ, iOSDC 2018 Reject Conference days1

Slide 34

Slide 34 text

Decompose (|>) let user = Factory.provide() |> User._name *~ "Takeshi Ihara" = (User._name *~ "Takeshi Ihara")(Factory.provide()) = { user in Lens( getter: { $0.name }, setter: { name, base in User(name: name, age: base.age) } ).set("Takeshi Ihara", user) }(Factory.provide()) = { name, base in User(name: name, age: base.age) }("Takeshi Ihara", Factory.provide()) = User(name: "Takeshi Ihara", age: 0) 34 Factoryͷࣗಈੜ੒ʹΑΓςετΛॻ͖΍͘͢͢Δ, iOSDC 2018 Reject Conference days1

Slide 35

Slide 35 text

Decompose (Lens, *~) let user = Factory.provide() |> User._name *~ "Takeshi Ihara" = (User._name *~ "Takeshi Ihara")(Factory.provide()) = { user in Lens( getter: { $0.name }, setter: { name, base in User(name: name, age: base.age) } ).set("Takeshi Ihara", user) }(Factory.provide()) = { name, base in User(name: name, age: base.age) }("Takeshi Ihara", Factory.provide()) = User(name: "Takeshi Ihara", age: 0) 35 Factoryͷࣗಈੜ੒ʹΑΓςετΛॻ͖΍͘͢͢Δ, iOSDC 2018 Reject Conference days1

Slide 36

Slide 36 text

Decompose (Closure) let user = Factory.provide() |> User._name *~ "Takeshi Ihara" = (User._name *~ "Takeshi Ihara")(Factory.provide()) = { user in Lens( getter: { $0.name }, setter: { name, base in User(name: name, age: base.age) } ).set("Takeshi Ihara", user) }(Factory.provide()) = { name, base in User(name: name, age: base.age) }("Takeshi Ihara", Factory.provide()) = User(name: "Takeshi Ihara", age: 0) 36 Factoryͷࣗಈੜ੒ʹΑΓςετΛॻ͖΍͘͢͢Δ, iOSDC 2018 Reject Conference days1

Slide 37

Slide 37 text

Decompose let user = Factory.provide() |> User._name *~ "Takeshi Ihara" = (User._name *~ "Takeshi Ihara")(Factory.provide()) = { user in Lens( getter: { $0.name }, setter: { name, base in User(name: name, age: base.age) } ).set("Takeshi Ihara", user) }(Factory.provide()) = { name, base in User(name: name, age: base.age) }("Takeshi Ihara", Factory.provide()) = User(name: "Takeshi Ihara", age: 0) 37 Factoryͷࣗಈੜ੒ʹΑΓςετΛॻ͖΍͘͢͢Δ, iOSDC 2018 Reject Conference days1

Slide 38

Slide 38 text

Result struct User { let name: String let age: Int var isAdult: Bool { return age >= 20 } } import FactoryProvider class UserTests: XCTestCase { func test20ࡀҎ্ͳΒ੒ਓͰ͋Δ() { let user = Factory.provide() |> User._age *~ 20 XCTAssertTrue(user.isAdult) } func test20ࡀະຬͳΒ੒ਓͰͳ͍() { let user = Factory.provide() |> User._age *~ 19 XCTAssertFalse(user.isAdult) } } 38 Factoryͷࣗಈੜ੒ʹΑΓςετΛॻ͖΍͘͢͢Δ, iOSDC 2018 Reject Conference days1

Slide 39

Slide 39 text

Demo 39 Factoryͷࣗಈੜ੒ʹΑΓςετΛॻ͖΍͘͢͢Δ, iOSDC 2018 Reject Conference days1

Slide 40

Slide 40 text

Future Work (Protocol) Not support protocol ! protocol A {} // Type 'A' does not conform to protocol 'Providable' let a = Factory.provide() 40 Factoryͷࣗಈੜ੒ʹΑΓςετΛॻ͖΍͘͢͢Δ, iOSDC 2018 Reject Conference days1

Slide 41

Slide 41 text

Future Work (Protocol) Extension of protocol cannot have an inheritance clause // Extension of protocol 'A' cannot have an inheritance clause extension A: Providable { public static func provide() -> Self { fatalError() } } let a = Factory.provide() 41 Factoryͷࣗಈੜ੒ʹΑΓςετΛॻ͖΍͘͢͢Δ, iOSDC 2018 Reject Conference days1

Slide 42

Slide 42 text

Future Work (Protocol) Need concrete type ! protocol A: Providable {} extension A { static func provide() -> Self { fatalError() } } // Using 'A' as a concrete type conforming to protocol 'Providable' is not supported let a = Factory.provide() 42 Factoryͷࣗಈੜ੒ʹΑΓςετΛॻ͖΍͘͢͢Δ, iOSDC 2018 Reject Conference days1

Slide 43

Slide 43 text

Future Work (Protocol) No problem using concrete type ! protocol A: Providable {} extension A { static func provide() -> Self { fatalError() } } struct B: A {} let a = Factory.provide() 43 Factoryͷࣗಈੜ੒ʹΑΓςετΛॻ͖΍͘͢͢Δ, iOSDC 2018 Reject Conference days1

Slide 44

Slide 44 text

Θ͍Θ͍swiftc ڵຯΛ࣋ͬͨํ͸ͥͻ 44 Factoryͷࣗಈੜ੒ʹΑΓςετΛॻ͖΍͘͢͢Δ, iOSDC 2018 Reject Conference days1

Slide 45

Slide 45 text

Future Work (interface) User.provide()ͷΑ͏ʹ௚઀ੜ੒ϝιου͕ݺ΂ͯ͠·͏ extension User: Providable { static func provide() -> Self { return User( ... ) } } let user = User.provide() or Factory.provide() 45 Factoryͷࣗಈੜ੒ʹΑΓςετΛॻ͖΍͘͢͢Δ, iOSDC 2018 Reject Conference days1

Slide 46

Slide 46 text

Future Work (interface) fileprivateͳGenericsΛ࣋ͭͳΒͦͷStructࣗ਎΋fileprivateʹ ͠ͳͯ͘͸ͳΒͳ͍ ! fileprivate Protocol { static func provide() -> Self } fileprivate extension User: Providable { ... } // Generic struct must be declared private or fileprivate because its generic parameter uses a fileprivate type struct Factory { static func provide() -> T { return T.provide() } } 46 Factoryͷࣗಈੜ੒ʹΑΓςετΛॻ͖΍͘͢͢Δ, iOSDC 2018 Reject Conference days1

Slide 47

Slide 47 text

Future Work (interface) Factoryʹ͢΂ͯهड़͢Δ ! struct Factory { static func provide() -> T { switch T.self { case is User.Type: return User( name: Factory.provide(), age: Factory.provide() ) case is String.Type: return "" ... default: fatalError() } } } 47 Factoryͷࣗಈੜ੒ʹΑΓςετΛॻ͖΍͘͢͢Δ, iOSDC 2018 Reject Conference days1

Slide 48

Slide 48 text

Future Work (interface) Inner TypeΛαϙʔτ͕ࠔ೉ ! (ύʔε͕େม) struct User { let id: Id struct Id {} } struct Factory { static func provide() -> T { switch T.self { case is User.Type: return User( id: Factory.provide() // Use of undeclared type 'Id' ) ... } } } 48 Factoryͷࣗಈੜ੒ʹΑΓςετΛॻ͖΍͘͢͢Δ, iOSDC 2018 Reject Conference days1

Slide 49

Slide 49 text

FactoryProvider https:/ /github.com/Nonchalant/FactoryProvider 49 Factoryͷࣗಈੜ੒ʹΑΓςετΛॻ͖΍͘͢͢Δ, iOSDC 2018 Reject Conference days1

Slide 50

Slide 50 text

Conclusion • ʮߏ଄มԽʹڧ͍ςετʯ͕࣮ݱͰ͖ͨ • FactoryΛ༻ҙ͢Δख͕ؒͳ͘ͳͬͨͷͰςετʹूதͰ͖Δ • ςετର৅ͷϓϩύςΟ͕෼͔Γ΍͘͢ͳͬͨ • Factory.provide() |> User._age *~ 19 • SourceKitten + StencilΛ࢖ͬͯύʔεͯࣗ͠ಈੜ੒͢Δͷ ָ͍͠ $ 50 Factoryͷࣗಈੜ੒ʹΑΓςετΛॻ͖΍͘͢͢Δ, iOSDC 2018 Reject Conference days1