本文始發(fā)于我的博文手把手教你用Source Editor Extension開(kāi)發(fā)Xcode插件况木,現(xiàn)轉(zhuǎn)發(fā)至此蜂挪。
目錄
- 前言
- Xcode 插件發(fā)展史
- 實(shí)現(xiàn)插件
- 創(chuàng)建 macOS 應(yīng)用
- 編寫插件代碼
- 修改插件命名
- 調(diào)試插件
- 分發(fā)插件
- 設(shè)置快捷鍵
- 進(jìn)階講解
- Demo 邏輯
- Plist 文件處理
- XCSourceEditorCommand 協(xié)議
- XCSourceEditorExtension 協(xié)議
- 總結(jié)
前言
一個(gè)項(xiàng)目工程近速,隨著架構(gòu)的階段性穩(wěn)定肮疗、公共組件的抽離和代碼規(guī)范的制定等教届,勢(shì)必會(huì)進(jìn)入一個(gè)"重復(fù)勞動(dòng)"的階段。所謂"重復(fù)勞動(dòng)"烟具,即需求都有固定的模式和分解步驟去完成袄膏,大部分是重復(fù)勒魔、一致的代碼編寫甫煞,只有少部分工作需要思考、抽象冠绢、實(shí)現(xiàn)抚吠。但往往這些"重復(fù)勞動(dòng)"占據(jù)了大部分時(shí)間成本,而且由于其機(jī)械性所以最容易出現(xiàn)問(wèn)題弟胀。
于是將重復(fù)勞動(dòng)自動(dòng)化楷力,即用代碼寫代碼,是一個(gè)團(tuán)隊(duì)的重點(diǎn)工作之一孵户,讓成員將時(shí)間和精力放在更值得關(guān)注的事情上萧朝。最近投入寫各種腳本,而開(kāi)發(fā) IDE 插件夏哭,也可以實(shí)現(xiàn)這種代碼層面的自動(dòng)化检柬。
本來(lái)想做個(gè)插件,實(shí)現(xiàn)生成 cell 的 .xib 和 .swift 文件并自動(dòng)關(guān)聯(lián)等功能竖配,練練手厕吉,結(jié)果發(fā)現(xiàn)目前 Xcode 開(kāi)放的插件并不能支持。
Xcode插件史
在Xcode 8之前械念,Xcode 插件有著比較輝煌的發(fā)展,各種便利的插件运悲、專門的插件管理工具 Alcatraz 等龄减。
但從 Xcode 8 開(kāi)始,出于安全性考慮(比如說(shuō) Xcode ghost 事件)班眯,Apple 不再支持第三方的插件希停,但提供了解決方案—— Xcode Source Editor Extension烁巫,目前只能完成有限的文本編輯輔助。
實(shí)現(xiàn)插件
本文開(kāi)發(fā)環(huán)境:Xcode Version 10.2.1 (10E1001)
創(chuàng)建macOS應(yīng)用
打開(kāi)Xcode宠能,F(xiàn)ile->New->Project…亚隙,選擇 macOS->Application>Cocoa App,填寫 Product Name
新建 Target违崇,F(xiàn)ile->New->Target…阿弃,選擇 macOS->Application Extension->Xcode Source Editor Extension,填寫 Product Name羞延,如 ZExtension渣淳。在彈窗中選擇 Activate。
注意:該 Target 的命名會(huì)成為后面使用插件時(shí)一級(jí)菜單名稱伴箩。
編寫插件代碼
修改 SourceEditorCommand.swift 文件入愧。
以下代碼實(shí)現(xiàn)將import排序的功能
func perform(with invocation: XCSourceEditorCommandInvocation, completionHandler: @escaping (Error?) -> Void ) -> Void {
// Implement your command here, invoking the completion handler when done. Pass it nil on success, and an NSError on failure.
let linesToSort = invocation.buffer.lines.filter { line in
return (line as? String)?.hasPrefix("import") ?? false
}
guard linesToSort.count > 0 else {
completionHandler(nil)
return
}
let firstLineIndex = invocation.buffer.lines.index(of: linesToSort[0]) // For insert
guard firstLineIndex >= 0 else {
completionHandler(nil)
return
}
invocation.buffer.lines.removeObjects(in: linesToSort)
let linesSorted = (linesToSort as? [String] ?? []).sorted() {$0 <= $1}
linesSorted.reversed().forEach { (line) in
invocation.buffer.lines.insert(line, at: firstLineIndex)
}
let selectionsUpdated: [XCSourceTextRange] = (0..<linesSorted.count).map { (index) in
let lineIndex = firstLineIndex + index
let endColumn = linesSorted[index].count - 1
return XCSourceTextRange(start: XCSourceTextPosition(line: lineIndex, column: 0), end: XCSourceTextPosition(line: lineIndex, column: endColumn))
}
invocation.buffer.selections.setArray(selectionsUpdated)
completionHandler(nil)
}
修改插件命名
在 ZExtension/Info.plist 中可以修改插件名稱,對(duì)應(yīng)的 Key 是 XCSourceEditorCommandName嗤谚,支持中文棺蛛。
不修改則默認(rèn)是 Source Editor Command。
調(diào)試插件
選擇該新建的 Scheme巩步,如 ZExtension旁赊,運(yùn)行(Command+R)。在彈窗中選擇 Xcode渗钉,點(diǎn) 擊Run彤恶。
接下來(lái)會(huì)彈出灰色的Xcode界面,新建項(xiàng)目或者打開(kāi)測(cè)試項(xiàng)目鳄橘。本文用了測(cè)試項(xiàng)目 Test声离。
測(cè)試項(xiàng)目:https://github.com/sapphirezzz/ZXcodeExtension/tree/master/Test)
使用插件排序,點(diǎn)擊 Editor->ZExtension->Source Editor Command瘫怜。
以下為插件運(yùn)行后的結(jié)果:
分發(fā)插件
- 上架 Mac App Store
編寫的插件可以發(fā)布术徊,上架到 Mac App Store。在 Xcode->Xcode Extensions… 可以看到上架的插件鲸湃。筆者還沒(méi)有發(fā)布赠涮,先略過(guò)。
- 內(nèi)部使用
在插件項(xiàng)目中暗挑,將 Products->ZXcodeExtension.app 文件拷貝到應(yīng)用程序笋除,并雙擊打開(kāi)。此時(shí)在系統(tǒng)偏好設(shè)置->擴(kuò)展->Xcode Source Editor炸裆,可以看到該插件垃它,并且已勾選。重啟 Xcode 就可以使用了。
設(shè)置快捷鍵
可以給插件設(shè)置快捷鍵国拇,方便使用洛史。
在 Xcode->Preferences…->Key Bindings->Editor Menu for Source Code,找到并設(shè)置酱吝。建議用 alt 如 alt+s也殖,避免和其他快捷鍵沖突。
進(jìn)階講解
實(shí)現(xiàn)之后务热,簡(jiǎn)單講解下一些細(xì)節(jié)忆嗜。
Demo邏輯
Demo中主要操作了兩個(gè)內(nèi)容:
- invocation.buffer.lines
- invocation.buffer.selections
lines 是當(dāng)前編輯文件的每一行的內(nèi)容,selections 是當(dāng)前編輯文件選中的內(nèi)容陕习。
Demo 邏輯是:
- 篩選出符合條件的行 linesToSort(以 import 開(kāi)頭)
- 記錄第一個(gè)符合條件的行的行數(shù)firstLineIndex霎褐,作為排序后的插入位置
- 從 invocation.buffer.lines 中刪除符合條件的行
- 將符合條件的行進(jìn)行排序得出 linesSorted
- 將排序后的行插入 invocation.buffer.lines
- 獲取所有改動(dòng)行信息 selectionsUpdated,設(shè)置 invocation.buffer.selections
主要是對(duì) XCSourceEditorCommand 協(xié)議的實(shí)現(xiàn)该镣。
Plist 文件處理
Info.plist 文件中重要的 key 是 NSExtension 的 NSExtensionAttributes冻璃,包含兩個(gè) key:
- XCSourceEditorCommandDefinitions
- XCSourceEditorExtensionPrincipalClass
XCSourceEditorCommandDefinitions
XCSourceEditorCommandDefinitions 是設(shè)置了每個(gè)命令(二級(jí)菜單)的信息:
- XCSourceEditorCommandClassName
- XCSourceEditorCommandIdentifier
- XCSourceEditorCommandName
第一個(gè)是處理這個(gè)命令的類名,該類需實(shí)現(xiàn) XCSourceEditorCommand 協(xié)議损合;第二個(gè)是每個(gè)命令的標(biāo)示省艳,用于 XCSourceEditorCommand 協(xié)議的方法區(qū)分處理命令;第三個(gè)是命令的展示名字嫁审。
XCSourceEditorExtensionPrincipalClass
該擴(kuò)展的類名跋炕,該類需實(shí)現(xiàn) XCSourceEditorExtension 協(xié)議。
XCSourceEditorCommand協(xié)議
/** A command provided by a source editor extension. There does not need to be a one-to-one mapping between command classes and commands: Multiple commands can be handled by a single class, by checking their invocation's commandIdentifier at runtime. */
@protocol XCSourceEditorCommand <NSObject>
根據(jù)官方注釋律适,一個(gè)實(shí)現(xiàn)了 XCSourceEditorCommand 的類可以處理多種命令辐烂,即多個(gè)二級(jí)菜單,通過(guò) invocation.commandIdentifier 來(lái)區(qū)分捂贿。而 commandIdentifier 是 Info.plist 中纠修,XCSourceEditorCommandDefinitions 里面每一項(xiàng)的 XCSourceEditorCommandIdentifier 所定義的。
/** Perform the action associated with the command using the information in \a invocation. Xcode will pass the code a completion handler that it must invoke to finish performing the command, passing nil on success or an error on failure.
A canceled command must still call the completion handler, passing nil.
\note Make no assumptions about the thread or queue on which this method will be invoked.
*/
- (void)performCommandWithInvocation:(XCSourceEditorCommandInvocation *)invocation completionHandler:(void (^)(NSError * _Nullable nilOrError))completionHandler;
這是 XCSourceEditorCommand 協(xié)議定義的方法厂僧。
- XCSourceEditorCommandInvocation
commandIdentifier 屬性扣草,用于區(qū)分不同命令;buffer颜屠,XCSourceTextBuffer 類型辰妙,主要用它的 lines 和 selections 屬性。
- completionHandler
實(shí)現(xiàn)邏輯之后甫窟,必須調(diào)用 completionHandler 以結(jié)束插件命令密浑,成功時(shí)傳參 nil,失敗時(shí)傳參 error 對(duì)象粗井。即使取消處理也需要調(diào)用并傳參 nil肴掷。
結(jié)合 Plist 文件和 XCSourceEditorCommand 協(xié)議敬锐,我們可以編寫處理多個(gè)命令的插件。
XCSourceEditorExtension協(xié)議
/** Invoked when the extension has been launched, which may be some time before the extension actually receives a command (if ever).
\note Make no assumptions about the thread or queue on which this method will be invoked.
*/
- (void)extensionDidFinishLaunching;
插件被加載后的處理呆瞻。
總結(jié)
可以看出,目前 Xcode Source Editor Extension 解決方案能實(shí)現(xiàn)的插件功能很有限径玖,不支持UI交互痴脾,只能局限于文本處理上。希望以后蘋果能擴(kuò)展更多 API 供開(kāi)發(fā)者使用梳星。