$30 off During Our Annual Pro Sale. View Details »

Xcode Source Editor Extensionの世界(完全版) / 20170916 #iosdc

takasek
September 16, 2017

Xcode Source Editor Extensionの世界(完全版) / 20170916 #iosdc

iOSDC Japan 2017 ( https://iosdc.jp/2017/ ) での発表資料です。

## 発表詳細

https://iosdc.jp/2017/node/1518

## 動画

https://www.youtube.com/watch?v=8OUYTcgVk8Q

## デモExtension

この発表に出てきたテクニックはすべて、以下のリポジトリで動作するデモアプリ(Extension)として確認できます。
takasek/XcodeExtensionSample: Various sample commands to implement Xcode Source Editor Extension
https://github.com/takasek/XcodeExtensionSample

## 他、参考リンク

### To find awesome extensions

tib/awesome-xcode-extensions: Awesome native Xcode extensions.
https://github.com/tib/awesome-xcode-extensions

Search · topic:xcode-extension
https://github.com/search?q=topic%3Axcode-extension&type=Repositories

### Apple WWDC Videos

Using and Extending the Xcode Source Editor - WWDC 2016 - Videos - Apple Developer
https://developer.apple.com/videos/play/wwdc2016/414/

Cocoa Interprocess Communication with XPC - WWDC 2012 - Videos - Apple Developer
https://developer.apple.com/videos/play/wwdc2012/241/

takasek

September 16, 2017
Tweet

More Decks by takasek

Other Decks in Programming

Transcript

  1. Xcode Source Editor Extensionͷੈք
    by.
    2017/9/16 iOSDC 2017
    1

    View Slide

  2. ͔ͭͯӫ՚Λތͬͨ
    ϓϥάΠϯɾύοέʔδϚωʔδϟʔ͕͋ͬͨ
    2

    View Slide

  3. 3

    View Slide

  4. ɹ
    ɹ
    4 ίʔυͷ੔ܗ
    4 ίϯιʔϧϩάͷϦονԽ
    4 ૢ࡞ମܥͷVimԽ
    4 etc...
    4

    View Slide

  5. ✟ R.I.P ✟
    ɹ
    ɹ
    5

    View Slide

  6. 6

    View Slide

  7. 7

    View Slide

  8. 8

    View Slide

  9. 9

    View Slide


  10. 10

    View Slide

  11. !
    11

    View Slide

  12. WWDC17 ͰԿ΋࿩୊ʹ
    ͳΒͳ͔ͬͨͷͳΒɺ
    iOSDC2017Ͱ
    ࿩୊ʹ͢Δ͔͠
    12

    View Slide

  13. Xcode Source Editor Extensionͷੈք
    by.
    2017/9/16 iOSDC 2017
    13

    View Slide

  14. takasek
    iOS Developer
    @takasek
    OSS
    ActionClosurable
    Notifwift౳
    14

    View Slide

  15. ୈ1ষ
    Xcode Source Editor
    Extension Λ
    ࢖ͬͯΈΑ͏
    15

    View Slide

  16. Xcode Source Editor Extension Λ࢖ͬͯΈΑ͏
    4 App Store ͳͲ͔Βೖख
    4 extension ͸ඞͣ .app ͱηοτʹͳ͍ͬͯΔ
    4 appΛҰ౓࣮ߦ͢ΔͱɺXcodeͰextension͕ར༻Մೳʹ
    16

    View Slide

  17. XcodeͰ͸…
    4 Editor ϝχϡʔʹίϚϯυ͕ग़ݱ
    4 ଞͷϝχϡʔͱಉ͘͡ɺ
    Xcode ͷ ⌘ઃఆ ͔Β
    ΩʔόΠϯυΛઃఆͰ͖Δ !
    17

    View Slide

  18. ୈ2ষ
    Xcode Source Editor
    Extension Λ
    ୳ͯ͠ΈΑ͏
    18

    View Slide

  19. ! AppStore "
    "xcode extension" Ͱ
    ݕࡧͯ͠ΈΔͱ…ʂ
    19

    View Slide

  20. 20

    View Slide

  21. େৎ෉ʂ
    GitHub ʹ͸໺ੜͷ
    extension ͕͍ͬͺ͍ʂ
    4 awesomeϦϙδτϦ͔Β୳͢
    https://github.com/tib/awesome-xcode-extensions
    4 GitHub ͷ topic ͰϦϙδτϦ୳

    https://github.com/search?
    o=desc&q=topic%3Axcode-
    extension&s=stars&type=Repositories
    21

    View Slide

  22. େৎ෉ʂ
    GitHub ʹ͸໺ੜͷ
    extension ͕͍ͬͺ͍ʂ
    4 awesomeϦϙδτϦ͔Β୳͢
    https://github.com/tib/awesome-xcode-extensions
    4 GitHub ͷ topic ͰϦϙδτϦ୳

    https://github.com/search?
    o=desc&q=topic%3Axcode-
    extension&s=stars&type=Repositories
    22

    View Slide

  23. ໺ྑ Extension ͷ
    ಋೖํ๏
    4 Code Signing Λࣗ෼ͷ΋ͷʹม͑
    ͯ Archive → Export ͢Δ͚ͩ
    ɹ˞ Release ϏϧυՄೳͳ
    ɹɹ ։ൃऀΞΧ΢ϯτ͕ඞཁ
    4 Archive ͯ͠ग़དྷ্͕ͬͨ .app Λ
    ࣮ߦ͢Ε͹Πϯετʔϧ׬ྃ
    23

    View Slide

  24. ୈ3ষ
    Xcode Source Editor
    Extension Λ
    ࡞ͬͯΈΑ͏
    24

    View Slide

  25. 25

    View Slide

  26. 26

    View Slide

  27. 27

    View Slide

  28. 28

    View Slide

  29. 29

    View Slide

  30. 30

    View Slide

  31. ɹ
    ɹ
    ɹɹɹɹɹɹɹɹɹɹɹɹɹɹɹɹɹ !͜ͷҰߦΛՃ͑Δ͚ͩͰ
    ɹɹɹɹɹɹɹɹɹɹɹɹ Ͱ͖͕͋Γ
    31

    View Slide

  32. 32

    View Slide

  33. 33

    View Slide

  34. invocation.buffer.lines.add("hello extension!")
    ͷ࣮ߦʹ੒ޭʂɹ؆୯ʂ
    34

    View Slide

  35. XcodeKit.framework
    ͷ API
    35

    View Slide

  36. XcodeKit.framework ͷ API
    4 XCSourceEditorExtension
    4 XCSourceEditorCommand
    4 XCSourceEditorCommandInvocation
    4 XCSourceTextBuffer
    4 XCSourceTextRange
    4 XCSourceTextPosition
    36

    View Slide

  37. XcodeKit.framework ͷ API
    4 XCSourceEditorExtension
    4 XCSourceEditorCommand
    4 XCSourceEditorCommandInvocation
    4 XCSourceTextBuffer
    4 XCSourceTextRange
    4 XCSourceTextPosition
    37

    View Slide

  38. XCSourceTextBuffer ͷ໾ׂ
    4 ΤσΟλͷςΩετόοϑΝͷಡΈॻ͖
    4 completeBuffer: String
    ɹ˞ શମॻ͖ସ͑
    4 lines: NSMutableArray
    4 selections: NSMutableArray
    ɹ˞ ཻ౓ͷࡉ͔͍ॻ͖ସ͑
    4 ϝλσʔλͷऔಘ
    4 UTI (ϑΝΠϧछผ)ͱ͔ɺλϒɾΠϯσϯτͷ৘ใΛऔಘՄ
    38

    View Slide

  39. ͬ͘͟Γݴ͑͹
    XcodeKit ͕Ͱ͖Δ͜ͱ͸…
    1. XCSourceTextBuffer ͷ lines, selections ΛಡΉ
    2. ͳΜΒ͔ͷՃ޻
    3. XCSourceTextBuffer ͷ lines, selections ʹ
    ॲཧ݁ՌΛೖΕ௚͢
    39

    View Slide

  40. 40

    View Slide

  41. ͜Ε͚ͩʁ
    ΋ͬͱԿ͔ͳ͍ʂʁ
    ϓϩδΣΫτߏ଄Λ͍͡Δͱ͔ɺ
    ผͷϑΝΠϧʹΞΫηε͢Δͱ͔…
    41

    View Slide

  42. WWDC16 1
    ᐌ͘ɺ
    1 Using and Extending the Xcode Source Editor - WWDC 2016
    https://developer.apple.com/videos/play/wwdc2016/414/
    42

    View Slide

  43. 43

    View Slide

  44. 44

    View Slide

  45. 45

    View Slide

  46. Xcode Extensionͷੈք͸ڱ͘ɺ
    ߴ͍น
    ʹ્·Ε͍ͯͨ
    46

    View Slide

  47. น͕͋ͬͨΒ…
    Ͳ͏͢Δʁ
    47

    View Slide

  48. iOSΤϯδχΞ͸ɺน͕͋ͬͨΒ
    ɹ
    ొΔɻ
    48

    View Slide

  49. ࠷ऴষ
    Ͳ͏ొΔͷ͔
    49

    View Slide

  50. น(1)
    ೖग़ྗͷน
    ςΩετόοϑΝͷಡΈॻ͖͔͠Ͱ͖ͳ͍
    50

    View Slide

  51. ղܾࡦ
    AppKit ͷॿ͚ΛआΓΔ
    51

    View Slide

  52. NSPasteboard Ͱ ΫϦοϓϘʔυͷೖग़ྗ
    NSWorkspace Ͱ URL scheme ͱͷ࿈ܞ
    NSWorkspace Ͱ ΠϯετʔϧࡁApp Λ։͘
    ౳ʑ
    52

    View Slide

  53. NSPasteboard Ͱ ΫϦοϓϘʔυͷೖग़ྗ
    let pasteboard = NSPasteboard.general
    // ΫϦοϓϘʔυ͔ΒtextΛऔಘ
    let text = pasteboard.pasteboardItems?.first?
    .string(forType: NSPasteboard.PasteboardType(
    rawValue: "public.utf8-plain-text"
    ))
    // ΫϦοϓϘʔυʹtextΛೖΕΔ
    pasteboard.declareTypes([.string], owner: nil)
    pasteboard.setString(text!, forType: .string)
    53

    View Slide

  54. NSWorkspace Ͱ URL scheme ͱͷ࿈ܞ
    ྫ: TwitterΞϓϦͰ౤ߘը໘Λ։͘
    var c = URLComponents(string: "twitter://post")!
    c.queryItems = [
    URLQueryItem(name: "message", value: text!)
    ]
    NSWorkspace.shared.open(c.url!)
    54

    View Slide

  55. NSWorkspace Ͱ
    ΠϯετʔϧࡁApp Λ։͘
    ྫ: ΧϨϯμʔΛ։͘
    NSWorkspace.shared.launchApplication("Calendar")
    55

    View Slide

  56. 56

    View Slide

  57. น(2)
    ݴޠػೳͷน
    57

    View Slide

  58. ૢ࡞ͷͨΊʹͲ͏ͯ͠΋
    LinuxίϚϯυ
    Λ࢖͍͍ͨΜͩʂ
    ͱ͍͏৔߹ʹͲ͏ͨ͠Β͍͍͔
    58

    View Slide

  59. ղܾࡦ
    Process Λ࢖ͬͯ
    LinuxίϚϯυ࣮ߦ
    ※ͨͩ͠Ұ෦ͷίϚϯυʹݶΔ
    59

    View Slide

  60. func runTask(command: String, arguments: [String], standardInput: Pipe? = nil) throws -> Pipe {
    let task = Process(), standardOutput = Pipe(), standardError = Pipe()
    task.launchPath = "/usr/bin/env"
    task.arguments = [command] + arguments
    task.currentDirectoryPath = NSTemporaryDirectory()
    task.standardInput = standardInput
    task.standardOutput = standardOutput
    task.standardError = standardError
    task.launch()
    task.waitUntilExit()
    guard task.terminationStatus == 0 else {
    // ҟৗऴྃ
    let errorOutput = String(data: standardError.fileHandleForReading.readDataToEndOfFile(), encoding: .utf8) ?? ""
    throw Error.commandFailed(errorOutput)
    }
    return standardOutput
    }
    let tmpFilePath = NSTemporaryDirectory().appending("inputFile")
    let catOutput = try runTask(
    command: "cat",
    arguments: [tmpFilePath]
    )
    let result = String(data: catOutput.fileHandleForReading.readDataToEndOfFile(), encoding: .utf8) ?? ""
    60

    View Slide

  61. 61

    View Slide

  62. น(3)
    ωοτϫʔΫͷน
    62

    View Slide

  63. URLSession
    iOS։ൃͰ΋͓ͳ͡ΈͰ͢Ͷ
    63

    View Slide

  64. 64

    View Slide

  65. ղܾࡦ
    65

    View Slide

  66. 66

    View Slide

  67. น(4)
    Sandbox ͷน
    67

    View Slide

  68. !ωοτ͔ΒGETͨ͠΋ͷ͸
    ɹΤσΟλʹग़͢ͷ΋ͳΜͩ͠ɺ
    σεΫτοϓͱ͔ʹ
    ॻ͖ग़͍ͨ͠ͳʔ
    68

    View Slide

  69. 69

    View Slide

  70. ͔͠͠
    Xcode Source Editor
    Extension ͷ Sandbox ͸
    OFFෆՄ
    ※ OFFʹͯ͠͠·͏ͱ
    ϝχϡʔʹίϚϯυ͕
    දࣔ͞Εͳ͍ʂ
    70

    View Slide

  71. 71

    View Slide

  72. खڧ͍
    72

    View Slide

  73. Ͱ΋
    73

    View Slide

  74. extesnionҎ֎ͷXPCαʔϏεͰ͋Ε͹
    SandboxΛOFFʹͰ͖Δ
    74

    View Slide

  75. extesnionҎ֎ͷXPCαʔϏε
    ɹ
    #ͱ͸
    75

    View Slide

  76. XPCͱ͸2
    ΞϓϦΛෳ਺ͷαʔϏεʹ෼ׂ
    ͠ɺϓϩηεؒ௨৴Ͱ࿈ܞ͢Δ
    ϝΧχζϜɻ֤XPCαʔϏε͝ͱ
    ʹݖݶΛ෼཭͢Ε͹ɺηΩϡΞ
    ͳΞϓϦ͕࡞ΕΔɻ
    ※ Source Editor Extension ΋ XPC ͷҰछ
    2 Cocoa Interprocess Communication with XPC - WWDC 2012
    https://developer.apple.com/videos/play/wwdc2012/241/
    76

    View Slide

  77. Target Λ௥Ճ
    77

    View Slide

  78. entitlementͰSandbox͕OFFʹͳͬͯͯ΋ಈ࡞͢Δʂ
    78

    View Slide

  79. ͨͩ͠ɺAppStoreʹ͸ग़ͤͳ͘ͳΔ
    ͜ΕΛ΍Δͱ͖͸ɺGitHub͕φϫόϦͷ ໺ྑExtensionͱͯ͠
    ੜ͖Δ֮ޛΛܾΊ͍ͯͩ͘͞ɻ
    79

    View Slide

  80. extension ͱ XPC ͷ࿈ܞ
    ࢀߟʹͳΔ࣮ྫ
    https://github.com/norio-
    nomura/SwiftLintForXcode
    ɹ
    /usr/local/bin/swiftlint ࣮ߦ࣌ʹ
    ى͜Δಉ༷ͷ permission ໰୊Λɺ
    XPCΛܦ༝͢Δ͜ͱͰճආ͍ͯ͠Δɻ
    80

    View Slide

  81. 81

    View Slide

  82. น(5)
    GUIͷน
    extension͸no UI
    82

    View Slide

  83. XPC (extensionؚ) ͸ GUI Λ࣋ͨͳ͍
    ϑΝΠϧબ୒Ϗϡʔ͢Βग़ͤͳ͍
    83

    View Slide

  84. WWDC16ͷηογϣϯ
    ʹΑΔͱɺ
    4 ʮGUI͸Appʹஔ͚ʯ
    4 AppStoreʹ΋ɺ
    ىಈͨ͠AppΛઃఆը໘ͱͯ͠
    ࢖͏extension͕͋Δ
    (Protocol.app)
    84

    View Slide

  85. App ⁶ extensionؒͷσʔλڞ༗ํ๏
    app ͱ extension Λ ಉ͡ App Group ʹઃఆ͢Δͱɺ
    UserDefaultsΛڞ༗Ͱ͖Δ
    85

    View Slide

  86. ͚Ͳɺ
    extensionͷίϚϯυ࣮ߦதʹ
    UIཉ͍͜͠ͱ͋ΔΑͶ
    ϑΝΠϧબ୒ͤͨ͞Γͱ͔ɺ
    Alertදࣔͨ͠Γͱ͔…
    86

    View Slide

  87. ղܾࡦ
    ίϚϯυ͔ΒAppΛ্ཱͪ͛ͯσʔλΛ౉͢
    AppͰUIΛදࣔ͠ɺ݁ՌΛίϚϯυʹฦ͢
    ͱ͍͏͜ͱ͕Ͱ͖Ε͹͍͍
    87

    View Slide

  88. ⛏extension -> App
    4 URL Scheme
    4 iOSͱ͸URLͷϋϯυϦϯάํ๏͕ҧ͏ͷͰ஫ҙ
    ⛏App -> extension
    4 UserDefaults ͷߋ৽Λ observe ͢Δ
    4 DistributedNotificationCenter
    88

    View Slide

  89. ⛏ extension -> App via URL Scheme ᶃ
    @ Appͷ info.plist
    4 URL Types Λઃఆ
    @ Extension
    var c = URLComponents(string: "xcextsample://")!
    c.queryItems = [
    URLQueryItem(name: "title", value: "΄͛΄͛")
    ]
    NSWorkspace.shared.open(c.url!)
    89

    View Slide

  90. ⛏ extension -> App via URL Scheme ᶄ
    @ App
    class AppDelegate: NSObject, NSApplicationDelegate {
    func applicationWillFinishLaunching(_ aNotification: Notification) {
    NSAppleEventManager.shared().setEventHandler(
    self,
    andSelector: #selector(AppDelegate.handleGetURLEvent(event:replyEvent:)),
    forEventClass: AEEventClass(kInternetEventClass),
    andEventID: AEEventID(kAEGetURL)
    )
    }
    @objc func handleGetURLEvent(event: NSAppleEventDescriptor?, replyEvent: NSAppleEventDescriptor?) {
    guard
    let urlString = event?.paramDescriptor(forKeyword: keyDirectObject)?.stringValue,
    let components = URLComponents(string: urlString)
    else { return }
    guard
    let title = components.queryItems?.first(where: { $0.name == "title" })?.value
    else { return }
    ...
    }
    90

    View Slide

  91. ⛏App -> extension via UserDefaults
    @ Extension
    extension UserDefaults {
    @objc dynamic var valueFromApp: String? {
    return string(forKey: "valueFromApp")
    }
    }
    let semaphore = DispatchSemaphore(value: 0)
    let userDefaults = UserDefaults(suiteName: "YourAppGroup")!
    userDefaults.synchronize()
    let observation = userDefaults.observe(\UserDefaults.valueFromApp, options:[.old, .new]) { ud, change in
    selectedResult = change.newValue?.flatMap { $0 }
    semaphore.signal()
    }
    _ = semaphore.wait()
    @ App
    UserDefaults(suiteName: "YourAppGroup")?.set(selectedFileURL?.absoluteString, forKey: "valueFromApp")
    91

    View Slide

  92. ⛏App -> extension via DistributedNotificationCenter
    @ Extension
    DistributedNotificationCenter.default().addObserver(
    self,
    selector: #selector(HogeCommand.doSomething(notification:)),
    name: Notification.Name("XcodeExtensionSample.applicationDidSomething"),
    object: nil,
    suspensionBehavior: .deliverImmediately
    )
    @ App
    DistributedNotificationCenter.default().postNotificationName(
    Notification.Name("XcodeExtensionSample.applicationDidSomething"),
    object: nil,
    userInfo: selectedFileURL.flatMap { ["url": $0] },
    deliverImmediately: true
    )
    92

    View Slide

  93. ͪͳΈʹɺσόοάʹ͍ͭͯͷ౾৘ใ
    4 XPC΍App͸extensionͱ͸ผϓϩηε͕ͩɺ
    SchemeΛઃఆ͓͚ͯ͠͹ϒϨʔΫϙΠϯτΛுͬͨΓίϯιʔϧϩάΛݟΔ͜ͱ΋Մೳ
    93

    View Slide

  94. 94

    View Slide

  95. นʹ્·Εͨɺ
    ڱ͍Xcode Source Editor Extensionͷੈք
    95

    View Slide

  96. ͔͠͠ɺͦͷนΛొͬͨઌʹ͸——
    96

    View Slide

  97. 97

    View Slide

  98. 98

    View Slide

  99. 99

    View Slide

  100. 100

    View Slide

  101. 101

    View Slide

  102. 102

    View Slide

  103. ͦͷนΛొͬͨઌʹ͸——
    ࣗ༝ͳແݶͷੈք͕޿͕͍ͬͯͨ
    103

    View Slide

  104. ͋ͳͨ΋Ұॹʹɺ
    Xcode Source Editor ExtensionͰ
    ༡ΜͰΈ·ͤΜ͔ʁ
    104

    View Slide

  105. Xcode Source Editor Extensionͷੈք
    ׬
    refer to
    https://github.com/takasek/XcodeExtensionSample
    105

    View Slide

  106. Q&A
    106

    View Slide