Slide 1

Slide 1 text

4XJGUѪ޷ձWPM,PUMJOѪ޷ձWPM!,* 3TXJGU͔Β"TTFU$BUBMPH΁ ʙ4XJGU4ZOUBYΛԊ͑ͯʙ

Slide 2

Slide 2 text

.FTXJGU let name = "(redacted)" let twitter = "417_72ki" let github = "417-72KI" let company = "(redacted)" var products = [ "MockUserDefaults", "MultipartFormDataParser", "BuildConfig.swift", "SSGH", ] var contributing = [ "Danger-Swift", "octokit.swift", "etc..." ]

Slide 3

Slide 3 text

3TXJGU࢖ͬͯ·͔͢ʁ

Slide 4

Slide 4 text

3TXJGUͱ͸ w YDPEFQSPKͷதΛղੳͯ͠ը૾΍YJCɺTUPSZCPBSEͳͲΛίʔυิ׬Ͱ͖ΔΑ ͏ʹͯ͘͠ΕΔπʔϧ w 4XJGUొ৔ॳظ͔Β͋ΔރΕͨπʔϧ͔ͱࢥ͍͖΍#VJME5PPM1MVHJOʹରԠ ͍ͯͨ͠ΓࠓͰ΋੝Μʹߋ৽͞Ε͍ͯΔ w IUUQTHJUIVCDPNNBDDBJO3TXJGU

Slide 5

Slide 5 text

՝୊ 9DPEFͰϏϧυʹࣦഊ͢Δ৔߹͕͋Δ

Slide 6

Slide 6 text

ݪҼ A*NBHF3FTPVSDFA͕಺෦Ͱੜ੒͞Ε͍ͯΔ

Slide 7

Slide 7 text

ݪҼ A*NBHF3FTPVSDFA͕಺෦Ͱੜ੒͞Ε͍ͯΔ

Slide 8

Slide 8 text

(FOFSBUFE"TTFU4ZNCPMTTXJGU 🤔

Slide 9

Slide 9 text

9DPEFͷ"TTFU4ZNCPMT ग़యIUUQTEFWFMPQFSBQQMFDPNEPDVNFOUBUJPOYDPEFSFMFBTFOPUFTYDPEFSFMFBTFOPUFT

Slide 10

Slide 10 text

9DPEFͷ"TTFU4ZNCPMT ஫ҙ఺ w Ϧιʔε໊͸શͯDBNFM$BTFʹม׵͞ΕΔ w ม׵͞Εͨ݁Ռඃͬͨ৔߹ແࢹ͞ΕΔϦιʔε͕ൃੜ͢Δ

Slide 11

Slide 11 text

஫ҙ఺ w Ϧιʔε໊͸શͯDBNFM$BTFʹม׵͞ΕΔ w ม׵͞Εͨ݁Ռඃͬͨ৔߹ແࢹ͞ΕΔϦιʔε͕ൃੜ͢Δ

Slide 12

Slide 12 text

ରԠࡦ "TTFU4ZNCPMTͷࣗಈੜ੒Λແޮʹ͢Δ A3TXJGU3FTPVSDFTAΛ໌ࣔ͢Δ 3ʓʓͷݺͼग़͠Λ"TTFU4ZNCPMTʹࠩ͠ସ͑Δ

Slide 13

Slide 13 text

ରԠࡦ "TTFU4ZNCPMTͷࣗಈੜ੒Λແޮʹ͢Δ

Slide 14

Slide 14 text

ରԠࡦ "TTFU4ZNCPMTͷࣗಈੜ੒Λແޮʹ͢Δ A3TXJGU3FTPVSDFTAΛ໌ࣔ͢Δ 3ʓʓͷݺͼग़͠Λ"TTFU4ZNCPMTʹࠩ͠ସ͑Δ

Slide 15

Slide 15 text

ରԠࡦ A3TXJGU3FTPVSDFTAΛ໌ࣔ͢Δ import SwiftUI import RswiftResources struct Item { var image: ImageResource } struct ItemView: View { var item: Item var body: some View { VStack { Image(item.image) } } } #if DEBUG struct ItemView_Previews: PreviewProvider { static var previews: some View { Group { ItemView(item: .init(image: R.image.foo)) // Cannot convert value of type } } } #endif

Slide 16

Slide 16 text

ରԠࡦ A3TXJGU3FTPVSDFTAΛ໌ࣔ͢Δ import SwiftUI import RswiftResources struct Item { var image: RswiftResources.ImageResource } struct ItemView: View { var item: Item var body: some View { VStack { Image(item.image) } } } #if DEBUG struct ItemView_Previews: PreviewProvider { static var previews: some View { Group { ItemView(item: .init(image: R.image.foo)) // no error } } } #endif

Slide 17

Slide 17 text

ରԠࡦ "TTFU4ZNCPMTͷࣗಈੜ੒Λແޮʹ͢Δ A3TXJGU3FTPVSDFTAΛ໌ࣔ͢Δ 3ʓʓͷݺͼग़͠Λ"TTFU4ZNCPMTʹࠩ͠ସ͑Δ

Slide 18

Slide 18 text

ରԠࡦ 3ʓʓͷݺͼग़͠Λ"TTFU4ZNCPMTʹࠩ͠ସ͑Δ import SwiftUI import RswiftResources struct Item { var image: ImageResource } struct ItemView: View { var item: Item var body: some View { VStack { Image(item.image) } } } #if DEBUG struct ItemView_Previews: PreviewProvider { static var previews: some View { Group { ItemView(item: .init(image: R.image.foo)) // Cannot convert value of type } } } #endif

Slide 19

Slide 19 text

ରԠࡦ 3ʓʓͷݺͼग़͠Λ"TTFU4ZNCPMTʹࠩ͠ସ͑Δ import SwiftUI import RswiftResources struct Item { var image: ImageResource } struct ItemView: View { var item: Item var body: some View { VStack { Image(item.image) } } } #if DEBUG struct ItemView_Previews: PreviewProvider { static var previews: some View { Group { ItemView(item: .init(image: .foo)) // no error } } } #endif

Slide 20

Slide 20 text

ରԠࡦ 3ʓʓͷݺͼग़͠Λ"TTFU4ZNCPMTʹࠩ͠ସ͑Δ import SwiftUI struct Item { var image: ImageResource } struct ItemView: View { var item: Item var body: some View { VStack { Image(item.image) } } } #if DEBUG struct ItemView_Previews: PreviewProvider { static var previews: some View { Group { ItemView(item: .init(image: .foo)) // no error } } } #endif

Slide 21

Slide 21 text

؆୯΍ΜɺΤϥʔҎ֎΋ม͑ͨΖ ʌ AЧ ů

Slide 22

Slide 22 text

ΜͳΘ͚͋Δ͔͍ bшb㱬ኯˑ Ч ůŰƅŖƃ

Slide 23

Slide 23 text

3TXJGUͰݺͼग़ͤΔόϦΤʔγϣϯ // Image(UIKit) R.image.foo() UIImage(resource: R.image.foo) // Image(SwiftUI) Image(R.image.foo) Image(uiImage: R.image.foo()!) Image(uiImage: UIImage(resource: R.image.foo)!) // Color(UIKit) R.color.primary() UIColor(resource: R.color.primary) //Color(SwiftUI) Color(R.color.primary) Color(uiColor: R.color.primary()!) Color(uiColor: UIColor(resource: R.color.primary)!)

Slide 24

Slide 24 text

͜ΕΛݸݸݟͯॻ͖׵͑Δͱ͔ ʌůɾ㱼ɾA ŷžŷž ਖ਼نදݱͰؤுͬͯ΋͍͍͚Ͳ

Slide 25

Slide 25 text

ͦ͜Ͱ

Slide 26

Slide 26 text

4XJGU4ZOUBY

Slide 27

Slide 27 text

4XJGU4ZOUBY w 4XJGUίʔυͷߏจղੳɺݕࠪɺੜ੒ɺม׵͕Ͱ͖Δ"QQMF੡ͷϥΠϒϥϦ w IUUQTHJUIVCDPNBQQMFTXJGUTZOUBY w ؛઒͞Μͷ͋Γ͕͍͓ͨ࿩ w IUUQTTQFBLFSEFDLDPNLJTIJLBXBLBUTVNJNBTUFSJOHTXJGUTZOUBY

Slide 28

Slide 28 text

4XJGU4ZOUBYͰ΍Γ͍ͨ͜ͱ w 3JNBHFGPP ΍6**NBHF SFTPVSDF3JNBHFGPP Λ 6**NBHF SFTPVSDFGPP ʹม׵͢Δ w *NBHF 3JNBHFGPP ౳Λ*NBHF GPP ʹม׵͢Δ w 3DPMPSGPP ΍6*$PMPS SFTPVSDF3DPMPSGPP Λ 6*$PMPS SFTPVSDFGPP ʹม׵͢Δ w $PMPS 3DPMPSGPP ౳Λ$PMPS GPP ʹม׵͢Δ

Slide 29

Slide 29 text

΍Δ͜ͱ w ύλʔϯຖʹରԠͨ͠ม׵ॲཧΛ࣮૷͢Δ w 3JNBHFGPP 6**NBHF SFTPVSDFGPP w 3JNBHFGPP 6**NBHF SFTPVSDFGPP w 6**NBHF SFTPVSDF3JNBHFGPP 6**NBHF SFTPVSDFGPP w 6**NBHF SFTPVSDF3JNBHFGPP 6**NBHF SFTPVSDFGPP w *NBHF 3JNBHFGPP *NBHF GPP w *NBHF VJ*NBHF3JNBHFGPP *NBHF GPP w *NBHF VJ*NBHF6**NBHF SFTPVSDF3JNBHFGPP *NBHF GPP

Slide 30

Slide 30 text

΍Δ͜ͱ w ύλʔϯຖʹରԠͨ͠ม׵ॲཧ3FXSJUFSΛ࣮૷ͯ͠࠶ؼతʹݺͼग़͢ w 3JNBHFGPP 3JNBHFGPP 6**NBHF SFTPVSDFGPP w 6**NBHF SFTPVSDF3JNBHFGPP 6**NBHF SFTPVSDF3JNBHFGPP 6**NBHF SFTPVSDFGPP w *NBHF 3JNBHFGPP *NBHF GPP w *NBHF VJ*NBHF3JNBHFGPP *NBHF VJ*NBHF6**NBHF SFTPVSDF 3JNBHFGPP *NBHF GPP

Slide 31

Slide 31 text

3JNBHFGPP 6**NBHF SFTPVSDFGPP final class RswiftResourceRewriter: SyntaxRewriter { private let rewritableAssets = [ "image": "UIImage", "color": "UIColor" ] override func visit(_ node: FunctionCallExprSyntax) -> ExprSyntax { if let base = node.calledExpression.as(MemberAccessExprSyntax.self), let expr = visit(base).as(MemberAccessExprSyntax.self), base != expr, let assetType = base.base?.as(MemberAccessExprSyntax.self)?.declName.baseName.text, let convertedType = rewritableAssets[assetType] { // return `UIImage(resource: .expr)` or `UIColor(resource: .expr)` syntax } }

Slide 32

Slide 32 text

3JNBHFGPP 6**NBHF SFTPVSDFGPP 3JNBHFGPPGPP override func visit(_ node: MemberAccessExprSyntax) -> ExprSyntax { if let base = node.base?.as(MemberAccessExprSyntax.self), base.base?.as(DeclReferenceExprSyntax.self)?.baseName.text == "R" { let resourceName = node.declName.baseName.text if rewritableAssets.keys.contains(base.declName.baseName.text) { return ExprSyntax( MemberAccessExprSyntax( leadingTrivia: node.leadingTrivia, period: .periodToken(), name: .identifier(resourceName.camelized), trailingTrivia: node.trailingTrivia ) ) } } return super.visit(node) }

Slide 33

Slide 33 text

3JNBHFGPP 6**NBHF SFTPVSDFGPP 3JNBHFGPP 3JNBHFGPP override func visit(_ token: ForceUnwrapExprSyntax) -> ExprSyntax { if let expr = token.expression.as(FunctionCallExprSyntax.self), let converted = visit(expr).as(FunctionCallExprSyntax.self), expr != converted { return ExprSyntax(converted) } return super.visit(token) }

Slide 34

Slide 34 text

6**NBHFΛ௚઀ม׵ final class UIImageRewriter: SyntaxRewriter { private let typeName = "UIImage" override func visit(_ node: FunctionCallExprSyntax) -> ExprSyntax { if case typeName = node.calledExpression.as(DeclReferenceExprSyntax.self)?.baseName.text, let argument = node.arguments.first, argument.label?.text == "resource" { let newArgument = rswiftResourceRewriter.visit(argument) return ExprSyntax(node.with(\.arguments, [newArgument])) } if let member = node.calledExpression.as(MemberAccessExprSyntax.self), case "init" = member.declName.baseName.text, let argument = node.arguments.first, argument.label?.text == "resource" { let newArgument = rswiftResourceRewriter.visit(argument) return ExprSyntax(node.with(\.arguments, [newArgument])) } return super.visit(node) }

Slide 35

Slide 35 text

6**NBHFΛ௚઀ม׵ 6**NBHF SFTPVSDF3JNBHFGPP 6**NBHF SFTPVSDFGPP final class UIImageRewriter: SyntaxRewriter { private let typeName = "UIImage" override func visit(_ node: FunctionCallExprSyntax) -> ExprSyntax { if case typeName = node.calledExpression.as(DeclReferenceExprSyntax.self)?.baseName.text, let argument = node.arguments.first, argument.label?.text == "resource" { let newArgument = rswiftResourceRewriter.visit(argument) return ExprSyntax(node.with(\.arguments, [newArgument])) } if let member = node.calledExpression.as(MemberAccessExprSyntax.self), case "init" = member.declName.baseName.text, let argument = node.arguments.first, argument.label?.text == "resource" { let newArgument = rswiftResourceRewriter.visit(argument) return ExprSyntax(node.with(\.arguments, [newArgument])) } return super.visit(node) }

Slide 36

Slide 36 text

6**NBHFΛ௚઀ม׵ JOJU SFTPVSDF3JNBHFGPP 6**NBHF SFTPVSDFGPP final class UIImageRewriter: SyntaxRewriter { private let typeName = "UIImage" override func visit(_ node: FunctionCallExprSyntax) -> ExprSyntax { if case typeName = node.calledExpression.as(DeclReferenceExprSyntax.self)?.baseName.text, let argument = node.arguments.first, argument.label?.text == "resource" { let newArgument = rswiftResourceRewriter.visit(argument) return ExprSyntax(node.with(\.arguments, [newArgument])) } if let member = node.calledExpression.as(MemberAccessExprSyntax.self), case "init" = member.declName.baseName.text, let argument = node.arguments.first, argument.label?.text == "resource" { let newArgument = rswiftResourceRewriter.visit(argument) return ExprSyntax(node.with(\.arguments, [newArgument])) } return super.visit(node) }

Slide 37

Slide 37 text

3"$$POWFSUFS w 3TXJGUUP"TTFU$BUBMPH w IUUQTHJUIVCDPN,*3"$$POWFSUFS

Slide 38

Slide 38 text

%FNP

Slide 39

Slide 39 text

·ͱΊ w 3TXJGUͷ"1*ͱ9DPEFͷ"TTFU4ZNCPMT͕$PO fl JDU͢Δ w 4XJGU4ZOUBYΛ࢖ͬͯ3JNBHF̋̋Λ"TTFU4ZNCPMTʹม׵Ͱ͖Δ w ׬શ୤3TXJGUʹ͸·ͩ՝୊͕͋Δ w "TTFU4ZNCPMT͕9*#4UPSZCPBSE΍%BUB"TTFUT౳ʹରԠ͍ͯ͠ͳ͍

Slide 40

Slide 40 text

ελʔ͍ͩ͘͞ ҙ༁ɿUIY