转载

iOS 10 Day By Day: Xcode Source Editor Extensions

iOS 10 Day By Day: Xcode Source Editor Extensions

  • 原文: iOS 10 Day By Day: Xcode Source Editor Extensions

  • 译者:CocoaChina--CoderWQ

在 Xcode 8.0的版本里面,苹果引入了一个新的面向 macOS 的 App Extension,叫做 Xcode Source Editor(源代码编辑器)。通过这个源代码编辑器,我们也拥有了自己编写一个Xcode插件的能力 。此外,我们再也不用因为Xcode升级,来寻找各种各样让Alcatraz正常运行的办法了。

简介

Xcode是苹果开发平台中一个让开发者又爱又恨的IDE,在使用过程中,我们发现Xcode的插件的数量一直供不应求。开发者可以添加一些功能做为内置功能的扩展。比如检查拼写或者转换所有的 UIColor 使用新的常量。

在Xcode8以前,Xcode是没有这个能力的。所以Alcatraz填补了这个空白,看上去仿佛Xcode的插件管理器就是 Alcatraz 。后来由于安全性跟稳定性的担忧,利用Xcode8做开发,我们再也不能使用Alcatraz了。幸运的是,苹果添加了新的源代码编辑器来扩展Xcode的插件功能。

项目

我们创建一个extension来实现替换ASCII字符为表情的功能。举个例子:比如"That’s a nice thing to say :)"我们想变成That’s a nice thing to say“。往常,如果我们想实现这个功能,我们可能会怀着希望去Github找。我们往下面看:

iOS 10 Day By Day: Xcode Source Editor Extensions

像我在 Day1 中创建iMessage App那样,苹果这次推出的各类Extensions 跟Xcode Extensions是差不多的,  但这个官方的Xcdoe插件最大的有点就是可以上架Mac App Store。我们可以利用Extension来扩展整个程序,听上去有点夸张,其实它更像是一种配置选项。Xcode提供了一个没有UI的菜单选项来运行一个命令。

接下来我们创建一个新的MacOS的项目,名字是Emojificate。这里要说明一下,我们可以先忽略对Mac app的相关文件,专注于extension方面的事情。

我们需要创建一个新的target,选择的Xcode Extension,我们还需要取一个独一无二的名字,我这里使用的名字是Emojificator_Extension。

iOS 10 Day By Day: Xcode Source Editor Extensions

Xcode创建了我们需要的一切。

Info.plist

我们改变extension的名字,只需要设置Bundle name的key值为Emojificator。

iOS 10 Day By Day: Xcode Source Editor Extensions

像其他的扩展那样,我们可以在info.plist中定一些属性。我们展开NSExtension,在下面,我们可以定义extension的type(Xcode.extension.source-editor),以及插件的类名,key名为XCSourceEditorExtensionPrincipalClass ,大家会发现,在我们的这个项目中,类名是SourceEditorExtension。我们还可以定义扩展中,用户可以执行的命令。

你的扩展还可以包含大量的命令行,在info.plist中我们可以指定他们的顺序。在我们的项目中我们只有一个命令行,它被定义在EmojificatorCommand这个类里,并且包含了XCSourceEditorCommand的协议。如果想定义的这个类的名称,那么我们值需要修改XCSourceEditorCommandClassName的值就好了。

这个类并不限制运行命令行的个数,如果你有多个命令行需要执行,都可以放到同一个类中。如果有相同的方法,你还可以抽取出来公用,通常,我们区分不同的命令是通过标识(即XCSourceEditorCommandIdentifier这个key对应的值)来区分的。

最后,我们在简单说说,修改命令行的名称。在我们的项目中,XCSourceEditorCommandClassName所对应的值是Emojificate!,修改对应的key,我们就可以修改命令行的名称咯。

iOS 10 Day By Day: Xcode Source Editor Extensions

SourceEditorExtension.swift

这个文件我们必须遵守XCSourceEditorExtension这个协议。当我们设置好我们的plist文件后,我们不需要做任何事,系统就已经帮我们初始化好了。如果你想自定义初始化,你需要在extensionDidFinishLaunching这个方法来自定义初始化。

Xcode在启动时,扩展就会启动。在命令执行时,扩展只是执行。这么做的好处就是,快!

菜单项是动态加载的,可以实现 SourceEditorExtension 的 commandDefinitions: 实现,但是这会覆盖掉 Info.plist 中的定义。

举个例子,下面的代码会返回跟工程现在一样的配置。

var commandDefinitions: [[XCSourceEditorCommandDefinitionKey: AnyObject]] {
    // If your extension needs to return a collection of command definitions that differs from those in its Info.plist, implement this optional property getter.
    return [[
        .classNameKey : "Emojificator_Extension.EmojificateCommand",
        .identifierKey : "com.shinobicontrols.Emojificator_Extension.EmojificateCommand",
        .nameKey : "Emojificate!"
    ]]
}

EmojificatorCommand.swift

通常,Xcode会创建一个SourceEditorCommand的类,不过在这里,我重新命名成EmojificateCommand了。为了保证跟系统的一致,我们要保证它遵守XCSourceEditorComman

的协议。

import Foundation
import XcodeKit

class EmojificateCommand: NSObject, XCSourceEditorCommand {
}

举个例子,我们定义了一组表情跟ASCII之间的映射,并且只是用定义好的这些表情。

let asciiToEmojiMap = [
        ":)" : ":)",
        ";)" : ";)",
        ":(" : ":("]
  *Note: Use actual emojis in second column

为了实现把ASCII字符替换成Emoji表情,我们要用2个方法

extension EmojificateCommand {
    /// Returns whether the string contains an item that can be converted into emoji
    func replaceableItemsExist(in string: String) -> Bool {
        for asciiItem in asciiToEmojiMap.keys {
            if string.contains(asciiItem) {
                return true
            }
        }
        return false
    }

    /// Replaces any ASCII items with their emoji counterparts and returns the newly 'emojified' string
    func replaceASCIIWithEmoji(in string: String) -> String {
        var line = string
        for asciiItem in asciiToEmojiMap.keys {
            line = line.replacingOccurrences(of: asciiItem, with: asciiToEmojiMap[asciiItem]!)
        }
        return line
    }
}

最后,我们在.m文件中,实现`perform(with: completionHandler:)`方法,来在xcode命令行执行的时候来调用它。

func perform(with invocation: XCSourceEditorCommandInvocation, completionHandler: (Error?) -> Void) {
    let lines = invocation.buffer.lines
    for (index, line) in lines.enumerated() {
       if let line = line as? String,
           replaceableItemsExist(in: line) {
           lines[index] = replaceASCIIWithEmoji(in: line)
       }
    }
    // Command must perform completion to signify it has completed
    completionHandler(nil)
}

在`perform(with: completionHandler:)`方法中,我们有一个invocation变量,类型是XCSourceEditorCommandInvocation,它是命令调用的文件内容,比如.swift文件,oc头文件,或者空格等字符。我们要做的就是在buffer中根据当前行,获取我们要的数据。我们还可以使用`completeBuffer`,它可以是字符串或者数组。如果只是简单的修改一些字符,苹果建议使用数组来处理,因为Xcode对数组的处理性能更为好。

最后我们调用completionHandler(nil),告诉Xcode命令执行完毕。

Xcode Extension是在一个单独的进程中运行,这真是极好的,这意味着缓慢执行的命令行并不会影响到Xcode本身的使用。另一件值得注意的,Xcode对命令运行的时间要求极为严格,如果没有在很短时间内完成,你的扩展将会被"点名批评",同时用户也可以选择取消命令。

运行"Emojificate"

这有一点跟我们平常开发不一样的是,Xcode实在测试版本中来测试我们的命令。这个测试版本的Xcode的图标颜色是灰色的,这有助于我们区分开发的内容。

iOS 10 Day By Day: Xcode Source Editor Extensions

下面是执行的效果图:

iOS 10 Day By Day: Xcode Source Editor Extensions

更多

苹果的这项举措必定会大大扩展开发者的生态圈,也会提高了开发者的效率。这些命令的执行,默认仅允许修改一些文件的buffer和沙盒。如果你需要,你可以请求更高的权限。据我所知,未来Xcode工程师会让这个工具更加强大,所以如果你有一些不能实现的特定目标,还可以将你的用例提交给苹果公司。

详情请见 WWDC视频 的介绍。

原文  http://www.cocoachina.com/ios/20161213/18344.html
正文到此结束
Loading...