Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

3

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

✟ R.I.P ✟ ɹ ɹ 5

Slide 6

Slide 6 text

6

Slide 7

Slide 7 text

7

Slide 8

Slide 8 text

8

Slide 9

Slide 9 text

9

Slide 10

Slide 10 text

… 10

Slide 11

Slide 11 text

! 11

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

takasek iOS Developer @takasek OSS ActionClosurable Notifwift౳ 14

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

20

Slide 21

Slide 21 text

େৎ෉ʂ 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

Slide 22

Slide 22 text

େৎ෉ʂ 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

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

25

Slide 26

Slide 26 text

26

Slide 27

Slide 27 text

27

Slide 28

Slide 28 text

28

Slide 29

Slide 29 text

29

Slide 30

Slide 30 text

30

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

32

Slide 33

Slide 33 text

33

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

XcodeKit.framework ͷ API 35

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

40

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

43

Slide 44

Slide 44 text

44

Slide 45

Slide 45 text

45

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

࠷ऴষ Ͳ͏ొΔͷ͔ 49

Slide 50

Slide 50 text

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

Slide 51

Slide 51 text

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

Slide 52

Slide 52 text

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

Slide 53

Slide 53 text

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

Slide 54

Slide 54 text

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

Slide 55

Slide 55 text

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

Slide 56

Slide 56 text

56

Slide 57

Slide 57 text

น(2) ݴޠػೳͷน 57

Slide 58

Slide 58 text

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

Slide 59

Slide 59 text

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

Slide 60

Slide 60 text

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

Slide 61

Slide 61 text

61

Slide 62

Slide 62 text

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

Slide 63

Slide 63 text

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

Slide 64

Slide 64 text

64

Slide 65

Slide 65 text

ղܾࡦ 65

Slide 66

Slide 66 text

66

Slide 67

Slide 67 text

น(4) Sandbox ͷน 67

Slide 68

Slide 68 text

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

Slide 69

Slide 69 text

69

Slide 70

Slide 70 text

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

Slide 71

Slide 71 text

71

Slide 72

Slide 72 text

खڧ͍ 72

Slide 73

Slide 73 text

Ͱ΋ 73

Slide 74

Slide 74 text

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

Slide 75

Slide 75 text

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

Slide 76

Slide 76 text

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

Slide 77

Slide 77 text

Target Λ௥Ճ 77

Slide 78

Slide 78 text

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

Slide 79

Slide 79 text

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

Slide 80

Slide 80 text

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

Slide 81

Slide 81 text

81

Slide 82

Slide 82 text

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

Slide 83

Slide 83 text

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

Slide 84

Slide 84 text

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

Slide 85

Slide 85 text

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

Slide 86

Slide 86 text

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

Slide 87

Slide 87 text

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

Slide 88

Slide 88 text

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

Slide 89

Slide 89 text

⛏ 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

Slide 90

Slide 90 text

⛏ 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

Slide 91

Slide 91 text

⛏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

Slide 92

Slide 92 text

⛏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

Slide 93

Slide 93 text

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

Slide 94

Slide 94 text

94

Slide 95

Slide 95 text

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

Slide 96

Slide 96 text

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

Slide 97

Slide 97 text

97

Slide 98

Slide 98 text

98

Slide 99

Slide 99 text

99

Slide 100

Slide 100 text

100

Slide 101

Slide 101 text

101

Slide 102

Slide 102 text

102

Slide 103

Slide 103 text

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

Slide 104

Slide 104 text

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

Slide 105

Slide 105 text

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

Slide 106

Slide 106 text

Q&A 106