xcode扩展

by Khoa Pham

通过Khoa Pham

如何将Xcode插件转换为Xcode扩展名 (How to convert your Xcode plugins to Xcode extensions)

Xcode is an indispensable IDE for iOS and macOS developers. From the early days, the ability to build and install custom plugins had given us a huge boost in productivity. It was not long before Apple introduced Xcode extension due to privacy concerns.

Xcode是iOS和macOS开发人员必不可少的IDE。 从早期开始,构建和安装自定义插件的能力就极大地提高了我们的生产率。 由于隐私方面的考虑,不久之后苹果推出了Xcode扩展。

I have built a few Xcode plugins and extensions like XcodeWay, XcodeColorSense, XcodeColorSense2, and Xmas. It was a rewarding experience. I learned a lot, and the productivity I gained was considerable. In this post I walkthrough how I converted my Xcode plugins to extensions, and the experience I had in doing so.

我构建了一些Xcode插件和扩展,例如XcodeWay , XcodeColorSense , XcodeColorSense2和Xmas 。 这是一次有益的经历。 我学到了很多东西,获得的生产力相当可观。 在这篇文章中,我将逐步介绍如何将Xcode插件转换为扩展程序,以及这样做的经验。

我的第一个Xcode插件:XcodeWay (My first Xcode plugin: XcodeWay)

I choose a lazy person to do a hard job. Because a lazy person will find an easy way to do it

我选择一个懒惰的人去努力。 因为一个懒惰的人会找到一种简单的方法

I really like the above quote from Bill Gates. I try to avoid repetitive and boring tasks. Whenever I find myself doing the same tasks again, I write scripts and tools to automate that. Doing this takes some time, but I will be a bit lazier in the near future.

我真的很喜欢比尔·盖茨的上述话 。 我尽量避免重复和无聊的任务。 每当我发现自己再次执行相同的任务时,我就会编写脚本和工具来自动执行该任务。 这样做需要一些时间,但在不久的将来我会变得有些懒惰。

Besides the interest in building open source frameworks and tools, I like to extend the IDE I’m using — mostly Xcode.

除了对构建开源框架和工具感兴趣之外,我还喜欢扩展我正在使用的IDE(主要是Xcode)。

I first started iOS development in 2014. I wanted a quick way to navigate to many places right from Xcode with the context of the current project. There are many times we want to:

我于2014年首次开始iOS开发。我想要一种快速的方法,可以从Xcode到当前项目的上下文直接导航到许多地方。 我们想多次:

  • open the current project folder in “Finder” to change some files
    在“ Finder”中打开当前项目文件夹以更改某些文件
  • open Terminal to run some commands
    打开终端以运行一些命令
  • open the current file in GitHub to quickly give the link to a workmate
    在GitHub中打开当前文件,以快速将链接提供给同事
  • or to open other folders like themes, plugins, code snippets, device logs.
    或打开其他文件夹,例如主题,插件,代码段,设备日志。

Every little bit of time we save each day counts.

我们每天节省的每一分时间都很重要。

I thought it would be cool idea to write an Xcode plugin that we can do all above things right inside Xcode. Instead of waiting for other people to do it, I pulled up my sleeve and wrote my first Xcode plugin — XcodeWay— and shared it as open source.

我认为编写一个Xcode插件是一个很酷的主意,我们可以在Xcode内完成上述所有操作。 我没有等着别人去做,而是袖手旁观 ,写了我的第一个Xcode插件XcodeWay ,并将其作为开源共享。

什么是Xcode插件? (What are Xcode plugins?)

Xcode plugins are not officially supported by Xcode or recommended by Apple. There are no documents about them. The best places we can learn about them are via existing plugins’ source code and a few tutorials.

Xcode官方不支持Xcode插件或Apple不推荐使用。 没有关于它们的文件。 我们可以通过现有插件的源代码和一些教程来了解它们的最佳位置。

An Xcode plugin is just a bundle of type xcplugin and is placed at ~/Library/Application Support/Developer/Shared/Xcode/Plug-ins . Xcode, when starting, will load any Xcode plugins present in this folder. Plugins are run in the same process as Xcode, so could do anything as Xcode. A bug in any plugin can cause Xcode to crash.

Xcode插件只是xcplugin类型的捆绑包,位于~/Library/Application Support/Developer/Shared/Xcode/Plug-ins 。 Xcode启动时将加载此文件夹中存在的所有Xcode插件。 插件与Xcode的运行过程相同,因此可以像Xcode一样执行任何操作。 任何插件中的错误都可能导致Xcode崩溃。

To make an Xcode plugin, create a macOS Bundle with one class that extends from NSObject , and have an initialiser that accepts NSBundle , for example in Xmas:

要制作Xcode插件,请创建一个macOS Bundle ,其中包含一个从NSObject扩展的类,并具有一个接受NSBundle的初始化程序,例如在Xmas中 :

class Xmas: NSObject {
var bundle: NSBundle
init(bundle: NSBundle) {    self.bundle = bundle    super.init()  }}

Inside Info.plist, we need to:

Info.plist内部,我们需要:

  • declare this class as the main entry class for the plugin, and
    将该类声明为插件的主要入口类,然后
  • that this bundle has no UI, because we create UI controls and add to the Xcode interface during runtime
    该捆绑包没有UI,因为我们在运行时创建了UI控件并将其添加到Xcode接口
<key>NSPrincipalClass</key><string>Xmas</string><key>XCPluginHasUI</key><false/>

Another problem with Xcode plugins is that we have to continuously update DVTPluginCompatibilityUUIDs . This changes every time a new version of Xcode comes out. Without updating, Xcode will refuse to load the plugin.

Xcode插件的另一个问题是,我们必须不断更新DVTPluginCompatibilityUUIDs 。 每当新版本的Xcode出现时,这种情况都会改变。 如果不进行更新,Xcode将拒绝加载插件。

Xcode插件可以做什么 (What Xcode plugins can do)

Many developers build Xcode plugins because they miss specific features found in other IDEs like Sublime Text, AppCode, or Atom.

许多开发人员之所以构建Xcode插件,是因为它们缺少其他IDE(例如Sublime Text,AppCode或Atom)中的特定功能。

Since Xcode plugins are loaded in the same process as Xcode, they can do everything that Xcode can. The only limit is our imagination. We can leverage Objective C Runtime to discover private frameworks and functions. Then LLDB and Symbolic breakpoint can be used further to inspect running code and alter their behaviors. We can also use swizzling to change implementation of any running code. Writing Xcode plugins is hard — lots of guessing, and sometimes a good knowledge of assembly is required.

由于Xcode插件的加载过程与Xcode相同,因此它们可以完成Xcode可以执行的所有操作。 唯一的限制是我们的想象力。 我们可以利用Objective C运行时来发现私有框架和功能。 然后,可以进一步使用LLDB和符号断点来检查正在运行的代码并更改其行为。 我们还可以使用swizzling更改任何正在运行的代码的实现。 编写Xcode插件很辛苦–猜测很多,有时还需要一定的汇编知识。

In the golden age of plugins, there was a popular plugin manager, which itself was a plugin, called Alcatraz. It could install other plugins, which basically just downloads the xcplugin file and moves this to the Plug Ins folder.

在插件的黄金时代,有一个流行的插件管理器,它本身就是一个名为Alcatraz的插件。 它可以安装其他插件,这些插件基本上只下载xcplugin文件并将其移至Plug Ins文件夹。

To get a sense of what plugins can do, let’s take a look at some popular plugins.

为了了解插件可以做什么,让我们看一些流行的插件。

Xvim (Xvim)

First in the list is Xvim, which adds Vim keybindings right inside Xcode. It supports mostly all of the keybindings that we used to have in Terminal.

列表中的第一个是Xvim ,它在Xcode内部添加了Vim绑定。 它几乎支持我们过去在Terminal中拥有的所有键绑定。

SCXcodeMiniMap (SCXcodeMiniMap)

If you miss MiniMap mode in Sublime Text, you can use SCXcodeMiniMap to add a right map panel inside Xcode editor.

如果您错过了Sublime Text中的MiniMap模式,则可以使用SCXcodeMiniMap在Xcode编辑器中添加一个右侧地图面板。

FuzzyAutocomplete插件 (FuzzyAutocompletePlugin)

Before version 9, Xcode didn’t have proper auto completion — it was just based on prefix. That was where FuzzyAutocompletePlugin shone. It performs fuzzy auto completion based on the hidden IDEOpenQuicklyPattern feature in Xcode.

在版本9之前,Xcode没有适当的自动完成功能-它仅基于前缀。 那就是FuzzyAutocompletePlugin发光的地方。 它基于Xcode中隐藏的IDEOpenQuicklyPattern功能执行模糊自动完成。

KSImageNamed-Xcode (KSImageNamed-Xcode)

To display a bundle image inside UIImageView, we often use the imageNamed method. But remembering exactly the name of the image file is hard. KSImageNamed-Xcode is here to help. You will get a list of auto-suggested image names when you begin to type.

为了在UIImageView显示捆绑图像,我们经常使用imageNamed方法。 但是准确记住图像文件的名称很困难。 KSImageNamed-Xcode在这里可以提供帮助。 开始键入时,您将获得一个自动建议的图像名称列表。

Xcode的ColorSense (ColorSense-for-Xcode)

Another itch during development is to work with UIColor , which uses RGBA color space. We don’t get a visual indicator of the color that we specify, and manually performing checking can be time consuming. Luckily there is ColorSense-for-Xcode which shows the color being used and the color picker panel to easily select the right color.

开发过程中的另一个难题是使用UIColor ,后者使用RGBA颜色空间。 我们没有看到指定颜色的视觉指示器,并且手动执行检查可能很耗时。 幸运的是,这里有ColorSense-for-Xcode ,它可以显示正在使用的颜色,而颜色选择器面板则可以轻松选择正确的颜色。

链接控制台 (LinkedConsole)

In AppCode, we can jump to a specific line in the file that is logged inside the console. If you miss this feature in Xcode, you can use LinkedConsole. This enables clickable links inside Xcode console so we can jump to that file instantly.

在AppCode中,我们可以跳到控制台内部记录的文件中的特定行。 如果您错过了Xcode中的此功能,则可以使用LinkedConsole 。 这将在Xcode控制台中启用可单击的链接,因此我们可以立即跳转到该文件。

Xcode插件背后的艰苦工作 (The hard work behind Xcode plugins)

Making an Xcode plugin is not easy. Not only do we need to know macOS programming, but we also need to dive deep into Xcode view hierarchy. We need to explore private frameworks and APIs in order to inject the feature we want.

制作Xcode插件并不容易。 我们不仅需要了解macOS编程,而且还需要深入了解Xcode视图层次结构。 我们需要探索私有框架和API,以便注入我们想要的功能。

There are very few tutorials on how to make plugins but, luckily, most plugins are open source so we can understand how they work. Since I have made a few plugins, I can give some technical details about them.

关于如何制作插件的教程很少,但幸运的是,大多数插件都是开源的,因此我们可以了解它们的工作原理。 由于我做了一些插件,因此我可以提供一些有关它们的技术细节。

Xcode plugins are done usually with two private frameworks: DVTKit and IDEKit . System frameworks are at /System/Library/PrivateFrameworks but the frameworks that Xcode uses exclusively are under /Applications/Xcode.app/Contents/ , there you can find Frameworks , OtherFrameworks and SharedFrameworks.

Xcode插件通常使用两个私有框架完成: DVTKitIDEKit 。 系统框架位于/System/Library/PrivateFrameworks但Xcode专用的框架位于/Applications/Xcode.app/Contents/下,您可以在其中找到FrameworksOtherFrameworksSharedFrameworks

There is a tool class-dump that can generate headers from the Xcode app bundle. With the class names and methods, you can call NSClassFromString to get the class from the name.

有一个工具类转储 ,可以从Xcode应用程序捆绑包生成标头。 使用类名和方法,可以调用NSClassFromString从名称中获取类。

Xmas中令人费解的DVTBezelAlertPanel框架 (Swizzling DVTBezelAlertPanel framework in Xmas)

Christmas has always given me a special feeling, so I decided to make Xmas, which shows a random Christmas picture instead of the default alert view. The class used to render that view is DVTBezelAlertPanel inside the DVTKit framework. My article on building that plugin is here.

圣诞节总是给我一种特殊的感觉,所以我决定制作Xmas ,它显示随机的圣诞节图片而不是默认的警报视图。 用于渲染视图类是DVTBezelAlertPanel的DVTKit框架内。 我有关构建该插件的文章在这里。

With Objective C Runtime, there is a technique called swizzling, which can change and switch implementation and method signature of any running classes and methods.

使用Objective C Runtime,有一种称为swizzling的技术,它可以更改和切换任何正在运行的类和方法的实现以及方法签名。

Here, in order to change the content of that alert view, we need to swap the initialiser initWithIcon:message:parentWindow:duration: with our own method. We do that early by listening to NSApplicationDidFinishLaunchingNotification which is notified when a macOS plugin, in this case Xcode, launches.

在这里,为了更改该警报视图的内容,我们需要使用我们自己的方法交换初始化程序 initWithIcon:message:parentWindow:duration: 我们通过侦听NSApplicationDidFinishLaunchingNotification此操作,当macOS插件(在本例中为Xcode)启动时会收到通知。

class func swizzleMethods() {    guard let originalClass = NSClassFromString("DVTBezelAlertPanel") as? NSObject.Type else {        return    }
do {        try originalClass.jr_swizzleMethod("initWithIcon:message:parentWindow:duration:",            withMethod: "xmas_initWithIcon:message:parentWindow:duration:")    }    catch {        Swift.print("Swizzling failed")    }}

I initially liked to do everything in Swift. But it’s tricky to use the swizzle init method in Swift, so the quickest way is to do that in Objective C. Then we simply traverse the view hierarchy to find the NSVisualEffectView inside NSPanel to update the image.

我最初喜欢在Swift中做所有事情。 但是在Swift中使用s wizzle init方法很棘手,因此最快的方法是在Objective C中做到这一点。 然后,我们只需遍历视图层次结构, NSVisualEffectViewNSPanel找到NSPanel来更新图像。

与XcodeColorSense中的DVTSourceTextView进行交互 (Interacting with DVTSourceTextView in XcodeColorSense)

I work mostly with hex colors and I want a quick way to see the color. So I built XcodeColorSense — it supports hex color, RGBA, and named color.

我主要处理十六进制颜色,我想快速查看颜色。 因此,我构建了XcodeColorSense-它支持十六进制颜色,RGBA和命名的颜色。

The idea is simple. Parse the string to see if the user is typing something related to UIColor, and show a small overlay view with that color as background. The text view that Xcode uses is of type DVTSourceTextView in DVTKit framework. We also need to listen to NSTextViewDidChangeSelectionNotification which is triggered whenever any NSTextView content is changed.

这个想法很简单。 解析字符串以查看用户是否正在输入与UIColor相关的内容,并显示一个以该颜色为背景的小叠加视图。 该Xcode使用文本视图是一个类型的DVTSourceTextViewDVTKit框架。 我们还需要收听NSTextViewDidChangeSelectionNotification ,只要更改任何NSTextView内容,就会触发该事件。

func listenNotification() {  NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(handleSelectionChange(_:)), name: NSTextViewDidChangeSelectionNotification, object: nil)}
func handleSelectionChange(note: NSNotification) {  guard let DVTSourceTextView = NSClassFromString("DVTSourceTextView") as? NSObject.Type,    object = note.object where object.isKindOfClass(DVTSourceTextView.self),    let textView = object as? NSTextView  else { return }
self.textView = textView}

I had a Matcher architecture so we can detect different kinds of UIColor constructions — for example HexMatcher .

我有一个Matcher架构,因此我们可以检测到不同种类的UIColor构造-例如HexMatcher

public struct HexMatcher: Matcher {
func check(line: String, selectedText: String) -> (color: NSColor, range: NSRange)? {    let pattern1 = "\"#?[A-Fa-f0-9]{6}\""    let pattern2 = "0x[A-Fa-f0-9]{6}"
let ranges = [pattern1, pattern2].flatMap {      return Regex.check(line, pattern: $0)    }
guard let range = ranges.first      else { return nil }
let text = (line as NSString).substringWithRange(range).replace("0x", with: "").replace("\"", with: "")    let color = NSColor.hex(text)
return (color: color, range: range)  }}

To render the overlay, we use NSColorWell which is good for showing a view with background. The position is determined by calling firstRectForCharacterRange and some point conversions with convertRectFromScreen and convertRect .

要渲染叠加层,我们使用NSColorWell ,它非常适合显示带有背景的视图。 通过调用firstRectForCharacterRange并通过convertRectFromScreenconvertRect一些点转换来确定位置。

在XcodeWay中使用NSTask和IDEWorkspaceWindowController (Using NSTask and IDEWorkspaceWindowController in XcodeWay)

Finally, my beloved XcodeWay.

最后,我心爱的XcodeWay 。

I found myself needing to go to different places from Xcode with the context of the current project. So I built XcodeWay as a plugin that adds lots of handy menu options under Window.

我发现自己需要根据当前项目的上下文从Xcode转到其他地方。 因此,我将XcodeWay构建为插件,在Window下添加了许多方便的菜单选项。

Since the plugin runs in the same Xcode process, it has access to the main menu NSApp.mainMenu?.itemWithTitle(“Window”) . There we can alter the menu. XcodeWay is designed to easily extend functionalities through its Navigator protocol.

由于插件在相同的Xcode进程中运行,因此可以访问主菜单NSApp.mainMenu?.itemWithTitle(“Window”) 。 在那里我们可以更改菜单。 XcodeWay旨在通过其Navigator协议轻松扩展功能。

@objc protocol Navigator: NSObjectProtocol {  func navigate()  var title: String { get }}

For folders with a static path like Provisioning Profile ~/Library/MobileDevice/Provisioning Profiles or User data Developer/Xcode/UserData , we can just construct the URL and call NSWorkspace.sharedWorkspace().openURL . For dynamic folders that vary depending on the current project, more work needs to be done.

对于具有静态路径的文件夹,例如Provisioning ~/Library/MobileDevice/Provisioning Profiles或User data Developer/Xcode/UserData ,我们可以仅构造URL并调用NSWorkspace.sharedWorkspace().openURL 。 对于根据当前项目而变化的动态文件夹,需要做更多的工作。

How do we open the folder for the current project in Finder? The information for the current project path is kept inside IDEWorkspaceWindowController . This is a class that manages workspace windows in Xcode. Take a look at EnvironmentManager where we use objc_getClass to get the class definition from a string.

我们如何在Finder中打开当前项目的文件夹? 当前项目路径的信息保存在IDEWorkspaceWindowController 。 这是一个管理Xcode中的工作区窗口的类。 看一下EnvironmentManager ,我们在其中使用objc_getClass从字符串获取类定义。

self.IDEWorkspaceWindowControllerClass = objc_getClass("IDEWorkspaceWindowController");
NSArray *workspaceWindowControllers = [self.IDEWorkspaceWindowControllerClass valueForKey:@"workspaceWindowControllers"];
id workSpace = nil;
for (id controller in workspaceWindowControllers) {  if ([[controller valueForKey:@"window"] isEqual:[NSApp keyWindow]]) {    workSpace = [controller valueForKey:@"_workspace"];  }}
NSString * path = [[workSpace valueForKey:@"representingFilePath"] valueForKey:@"_pathString"];

Finally, we can utilise valueForKey to get the value for any property that we think exists. This way not only do we get the project path, we also get the path to the opening file. So we can call activateFileViewerSelectingURLs on NSWorkspace to open Finder with that file selected. This is handy as users don’t need to look for that file in Finder.

最后,我们可以利用valueForKey来获取我们认为存在的任何属性的值。 这样,我们不仅可以获取项目路径 ,还可以获取打开文件的路径。 因此,我们可以在NSWorkspace上调用activateFileViewerSelectingURLs来打开选择了该文件的Finder。 这很方便,因为用户不需要在Finder中查找该文件。

Many times we want to execute some Terminal commands on the current project folder. To achieve that, we can use NSTask with launch pad /usr/bin/open and arguments [@”-a”, @”Terminal”, projectFolderPath] . iTerm, if configured probably, will open this in a new tab.

很多时候,我们想在当前项目文件夹中执行一些终端命令。 为此,我们可以将NSTask与启动板/usr/bin/open和参数[@”-a”, @”Terminal”, projectFolderPath] 。 iTerm(如果已配置)将在新选项卡中将其打开。

The documents for iOS 7 apps are placed in the fixed location iPhone Simulator inside Application Support. But, from iOS 8, every app has a unique UUID and their document folders are hard to predict.

iOS 7应用程序的文档放在应用程序支持内的固定位置iPhone Simulator 。 但是,从iOS 8开始,每个应用程序都具有唯一的UUID,并且其文档文件夹很难预测。

~/Library/Developer/CoreSimulator/Devices/1A2FF360-B0A6-8127-95F3-68A6AB0BCC78/data/Container/Data/Application/

We can build a map and perform tracking to find the generated ID for the current project, or to check the plist inside each folder to compare the bundle identifier.

我们可以构建地图并执行跟踪以找到当前项目的生成ID,或者检查每个文件夹中的plist以比较包标识符。

The quick solution that I came up with was to search for the most recent updated folder. Every time we build the project, or make changes inside the app, their document folder is updated. That is where we can make use of NSFileModificationDate to find the folder for the current project.

我想到的快速解决方案是搜索最近更新的文件夹。 每次我们构建项目或在应用程序中进行更改时,其文档文件夹都会更新。 那是我们可以使用NSFileModificationDate查找当前项目的文件夹的地方。

There are many hacks when working with Xcode plugins, but the results are rewarding. Every few minutes we save each day end up saving a lot of time overall.

使用Xcode插件时有很多技巧,但结果是可喜的。 我们每天节省的每一分钟最终节省了很多时间。

安全与自由 (Security and freedom)

With great power comes great responsibility. The fact that plugins can do whatever they want rings an alert to security. In late 2015, there was a malware attack by distributing a modified version of Xcode, called XcodeGhost, which injects malicious code into any apps built with Xcode Ghost. The malware is believed to use the plugin mechanism among other things.

拥有权利的同时也被赋予了重大的责任。 插件可以做任何他们想做的事情,这一事​​实提醒了安全性。 在2015年末,通过分发Xcode的修改版本XcodeGhost进行了恶意软件攻击,该代码将恶意代码注入到使用Xcode Ghost构建的任何应用程序中。 据信该恶意软件除其他外还使用了插件机制。

Like the iOS apps we download from the Appstore, macOS apps like Xcode are signed by Apple when we download them from the Mac Appstore or through official Apple download links.

就像我们从Appstore下载的iOS应用程序一样,当我们从Mac Appstore或通过Apple官方下载链接下载macOS应用程序(如Xcode)时,它们也会由Apple 签名 。

Code signing your app assures users that it is from a known source and the app hasn’t been modified since it was last signed. Before your app can integrate app services, be installed on a device, or be submitted to the App Store, it must be signed with a certificate issued by Apple

对您的应用程序进行代码签名可确保用户来自已知来源,并且自上次签名以来未对其进行过修改。 您的应用程序可以集成应用程序服务,安装在设备上或提交到App Store之前,必须使用Apple发行的证书进行签名

To avoid potential malware like this, at WWDC 2016 Apple announced the Xcode Source Editor Extension as the only way to load third party extensions into Xcode. This means that, from Xcode 8, plugins can’t be loaded.

为了避免此类潜在的恶意软件,Apple在WWDC 2016上宣布了Xcode Source Editor Extension ,这是将第三方扩展加载到Xcode中的唯一方法。 这意味着无法从Xcode 8加载插件。

源代码编辑器扩展 (Source Editor Extension)

Extension is the recommended approach to safely add functionalities in restricted ways.

建议使用扩展来以受限方式安全添加功能。

App extensions give users access to your app’s functionality and content throughout iOS and macOS. For example, your app can now appear as a widget on the Today screen, add new buttons in the Action sheet, offer photo filters within the Photos app, or display a new system-wide custom keyboard.

应用程序扩展使用户可以在整个iOS和macOS中访问您应用程序的功能和内容。 例如,您的应用现在可以在“今日”屏幕上显示为小部件,在“操作”表中添加新按钮,在“照片”应用中提供照片滤镜或显示新的系统范围的自定义键盘。

For now, the only extension to Xcode is Source Editor, which allows us to read and modify contents of a source file, as well as read and modify the current text selection within the editor.

目前,Xcode的唯一扩展是Source Editor,它使我们能够读取和修改源文件的内容,以及读取和修改编辑器中的当前文本选择。

Extension is a new target and runs in a different process than Xcode. This is good in that it can’t alter Xcode in any ways other than conforming to XCSourceEditorCommand to modify the current document content.

扩展是一个新的目标,并且在与Xcode不同的过程中运行。 这样做的好处是,除了遵循XCSourceEditorCommand来修改当前文档内容外,它无法以其他任何方式更改Xcode。

protocol XCSourceEditorCommand {
func perform(with invocation: XCSourceEditorCommandInvocation, completionHandler: @escaping (Error?) -&gt; Void)}

Xcode 8 has lots of improvements like the new code completion features, Swift image and color literals, and snippets. This led to the deprecation of many Xcode plugins. For some indispensable plugins like XVim, this is unbearable for some people. Some old plugin features can’t be achieved with the current Source Editor Extension system.

Xcode 8进行了许多改进,例如新的代码完成功能,Swift图像和颜色文字以及代码片段。 这导致了许多Xcode插件的弃用。 对于某些不可缺少的插件(例如XVim),这对于某些人来说是无法忍受的。 当前的Source Editor Extension系统无法实现某些旧的插件功能。

除非您辞职Xcode (Unless you resign Xcode)

A workaround to bypass the restriction from Xcode 8 for plugins, is to replace the existing Xcode signature by a technique called resign. Resigning is very easy — we just need to create a self-signed certificate and call the codesign command. After this, Xcode should be able to load plugins.

绕过插件的Xcode 8限制的一种解决方法是,使用称为resign的技术替换现有的Xcode签名。 签名非常容易-我们只需要创建一个自签名证书并调用codesign命令即可。 此后,Xcode应该能够加载插件。

codesign -f -s MySelfSignedCertificate /Applications/Xcode.app

It is, however, not possible to submit apps built with resigned Xcode as the signature does not match the official version of Xcode. One way is to use two Xcodes: one official for distribution and one resigned for development.

但是,由于签名与Xcode的正式版本不匹配, 因此无法提交使用已签名的Xcode构建的应用程序。 一种方法是使用两个Xcode:一个用于发布的官方文件,一个用于开发的辞职文件。

移至Xcode扩展 (Moving to Xcode extension)

Xcode extension is the way to go, so I started moving my plugins to extension. For Xmas, since it modifies view hierarchy, it can’t become an extension.

Xcode扩展是必经之路,因此我开始将插件移至扩展。 对于Xmas,由于它修改了视图层次结构,因此不能成为扩展。

XcodeColorSense2中的颜色文字 (Color literal in XcodeColorSense2)

For the color sense, I rewrote the extension from scratch, and called it XcodeColorSense2. This, of course, can’t show an overlay over the current editor view. So I chose to utilize the new Color literal found in Xcode 8+.

对于颜色,我从头开始重写了扩展名,并将其命名为XcodeColorSense2 。 当然,这不能在当前编辑器视图上显示覆盖。 因此,我选择使用Xcode 8+中新的Color literal

The color is shown in a small box. It may be hard to distinguish similar colors, so that’s why I also include the name. The code is simply about inspecting selections and parsing to find the color declaration.

颜色显示在一个小框中。 区分相似的颜色可能很困难,因此这就是为什么我也要包括该名称的原因。 该代码仅涉及检查selections和解析以查找颜色声明。

func perform(with invocation: XCSourceEditorCommandInvocation, completionHandler: @escaping (Error?) -> Void ) -> Void {    guard let selection = invocation.buffer.selections.firstObject as? XCSourceTextRange else {      completionHandler(nil)      return    }
let lineNumber = selection.start.line
guard lineNumber < invocation.buffer.lines.count,      let line = invocation.buffer.lines[lineNumber] as? String else {      completionHandler(nil)      return    }
guard let hex = findHex(string: line) else {      completionHandler(nil)      return    }
let newLine = process(line: line, hex: hex)
invocation.buffer.lines.replaceObject(at: lineNumber, with: newLine)
completionHandler(nil)  }}

Most of the functionality is embedded inside my framework Farge, but I can’t find a way to use the framework inside Xcode extension.

大多数功能都嵌入在我的框架Farge中 ,但是我找不到在Xcode扩展中使用该框架的方法。

Since the extension feature is only accessible through the Editor menu, we can customise a key binding to invoke this menu item. For example I choose Cmd+Ctrl+S to show and hide color information.

由于只能通过“编辑器”菜单访问扩展功能,因此我们可以自定义按键绑定来调用此菜单项。 例如,我选择Cmd+Ctrl+S来显示和隐藏颜色信息。

This is, of course, not intuitive compared to the original plugin, but it’s better than nothing.

与原始插件相比,这当然不直观,但是总比没有好。

如何调试Xcode扩展 (How to debug Xcode extensions)

Working and debugging extensions is straightforward. We can use Xcode to debug Xcode. The debugged version of Xcode has a gray icon.

工作和调试扩展很简单。 我们可以使用Xcode调试Xcode。 Xcode的调试版本带有灰色图标。

如何安装Xcode扩展 (How to install Xcode extensions)

The extension must have an accompanying macOS app. This can be distributed to Mac Appstore or self-signed. I’ve written an article on how to do this.

该扩展程序必须具有随附的macOS应用程序。 它可以分发到Mac Appstore或自签名。 我写了一篇有关如何做到这一点的文章 。

All extensions for an app need to be explicitly enabled through “System Preferences”.

必须通过“系统偏好设置”显式启用应用程序的所有扩展。

The Xcode extension only works with editor for now, so we must open a source file for the Editor menu to have effect.

Xcode扩展名目前仅适用于编辑器,因此我们必须打开源文件以使“ Editor菜单生效。

XcodeWay中的AppleScript (AppleScript in XcodeWay)

In Xcode extensions, NSWorkspace, NSTask and private class construction don’t work anymore. Since I have used Finder Sync Extension in FinderGo, I thought I could try the same AppleScript scripting for Xcode extension.

在Xcode扩展中, NSWorkspaceNSTask和私有类构造不再起作用。 由于我已经在FinderGo中使用了Finder Sync Extension, 因此我认为我可以为Xcode扩展尝试相同的AppleScript脚本。

AppleScript is a scripting language created by Apple. It allows users to directly control scriptable Macintosh applications, as well as parts of macOS itself. You can create scripts — sets of written instructions — to automate repetitive tasks, combine features from multiple scriptable applications, and create complex workflows.

AppleScript是Apple创建的一种脚本语言。 它允许用户直接控制可编写脚本的Macintosh应用程序以及macOS本身的一部分。 您可以创建脚本(一组书面说明)来自动化重复的任务,组合来自多个可编写脚本的应用程序中的功能以及创建复杂的工作流程。

To try AppleScript, you can use the app Script Editor built inside macOS to write prototype functions. Function declaration starts with on and ends with end . To avoid potential conflicts with system functions, I usually use my as a prefix. Here is how I rely on System Events to get the home directory.

要尝试AppleScript,可以使用macOS内置的应用程序脚本编辑器编写原型函数。 函数声明以on开头,以end 。 为了避免与系统功能发生潜在冲突,我通常使用my作为前缀。 这是我依靠系统事件获取主目录的方式。

User interface scripting terminology is found in the “Processes Suite” of the “System Events” scripting dictionary. This suite includes terminology for interacting with most types of user interface elements, including:

用户界面脚本术语可在“系统事件”脚本字典的“进程套件”中找到。 该套件包括用于与大多数类型的用户界面元素进行交互的术语,包括:

  • windows
    视窗
  • buttons
    纽扣
  • checkboxes
    复选框
  • menus
    菜单
  • radio buttons
    单选按钮
  • text fields.
    文本字段。

In System Events, the process class represents a running app.

在系统事件中, process类表示一个正在运行的应用程序。

Many good citizen apps support AppleScript by exposing some of their functionalities, so these can be used by other apps. Here is how I get the current song from Spotify in Lyrics.

许多优秀的公民应用程序通过公开其某些功能来支持AppleScript,因此它们可以被其他应用程序使用。 这是我从Spotify的Lyrics中获取当前歌曲的方式。

tell application "Spotify"  set trackId to id of current track as string  set trackName to name of current track as string  set artworkUrl to artwork url of current track as string  set artistName to artist of current track as string  set albumName to album of current track as string  return trackId & "---" & trackName & "---" & artworkUrl & "---" & artistName & "---" & albumNameend tell

To get all the possible commands of a certain app, we can open the dictionary in Script Editor. There we can learn about which functions and parameters are supported.

要获取某个应用程序的所有可能命令,我们可以在脚本编辑器中打开字典。 在这里,我们可以了解支持哪些功能和参数。

If you think Objective C is hard, AppleScript is much harder. The syntax is verbose and error-prone. For your reference, here is the whole script file that powers XcodeWay.

如果您认为Objective C很难,AppleScript就会困难得多。 语法冗长且容易出错。 供您参考,这是为XcodeWay提供支持的整个脚本文件 。

To open a certain folder, tell Finder using POSIX file. I refactor every functionality into function for better code reuse.

要打开某个文件夹,请使用POSIX file告诉Finder 。 我将每个功能重构为功能,以实现更好的代码重用。

on myOpenFolder(myPath)tell application "Finder"activateopen myPath as POSIX fileend tellend myOpenFolder

Then, to run AppleScript inside a macOS app or extension, we need to construct an AppleScript descriptor with the correct process serial number and event identifiers.

然后,要在macOS应用程序或扩展中运行AppleScript,我们需要使用正确的进程序列号和事件标识符构造一个AppleScript描述符。

func eventDescriptior(functionName: String) -> NSAppleEventDescriptor {  var psn = ProcessSerialNumber(highLongOfPSN: 0, lowLongOfPSN: UInt32(kCurrentProcess))  let target = NSAppleEventDescriptor(    descriptorType: typeProcessSerialNumber,    bytes: &psn,    length: MemoryLayout<ProcessSerialNumber>.size  )
let event = NSAppleEventDescriptor(    eventClass: UInt32(kASAppleScriptSuite),    eventID: UInt32(kASSubroutineEvent),    targetDescriptor: target,    returnID: Int16(kAutoGenerateReturnID),    transactionID: Int32(kAnyTransactionID)  )
let function = NSAppleEventDescriptor(string: functionName)  event.setParam(function, forKeyword: AEKeyword(keyASSubroutineName))
return event}

Other tasks, like checking the current Git remote, are a bit trickier. Many times I want to share the link of the file I’m debugging to my remote teammate, so they know what file I’m referencing. This is doable by using shell script inside AppleScript .

其他任务,例如检查当前的Git遥控器,则有些棘手。 很多时候,我想与远程队友共享正在调试的文件的链接,以便他们知道我正在引用的文件。 这可以通过在AppleScript使用shell script来实现。

on myGitHubURL()set myPath to myProjectPath()set myConsoleOutput to (do shell script "cd " & quoted form of myPath & "; git remote -v")set myRemote to myGetRemote(myConsoleOutput)set myUrl to (do shell script "cd " & quoted form of myPath & "; git config --get remote." & quoted form of myRemote & ".url")set myUrlWithOutDotGit to myRemoveSubString(myUrl, ".git")end myGitHubURL

We can use quoted and string concatenation to form strings. Luckily we can expose Foundation framework and certain classes. Here is how I expose NSString to take advantage of all existing functionalities. Writing string manipulation from scratch using plain AppleScript will take lots of time.

我们可以使用quoted和字符串串联来形成字符串。 幸运的是,我们可以公开Foundation框架和某些类。 这是我公开NSString以利用所有现有功能的方式。 使用普通的AppleScript从头开始编写字符串操作将花费大量时间。

use scripting additionsuse framework "Foundation"property NSString : a reference to current application's NSString

With this we can build our other functions for string handling.

这样我们就可以构建其他用于字符串处理的函数。

on myRemoveLastPath(myPath)set myString to NSString's stringWithString:myPathset removedLastPathString to myString's stringByDeletingLastPathComponentremovedLastPathString as textend myRemoveLastPath

One cool feature that XcodeWay supports is the ability to go to the document directory for the current app in the simulator. This is handy when we need to inspect a document to check saved or cached data. The directory is dynamic so it’s hard to detect. We can, however, sort the directory for the most recently updated. Below is how we chain multiple shell scripts commands to find the folder.

XcodeWay支持的一项很酷的功能是能够转到模拟器中当前应用程序的文档目录。 当我们需要检查文档以检查保存或缓存的数据时,这非常方便。 该目录是动态的,因此很难检测到。 但是,我们可以对最新更新的目录进行排序。 下面是我们如何链接多个shell scripts命令来查找文件夹的方法。

on myOpenDocument()set command1 to "cd ~/Library/Developer/CoreSimulator/Devices/;"set command2 to "cd `ls -t | head -n 1`/data/Containers/Data/Application;"set command3 to "cd `ls -t | head -n 1`/Documents;"set command4 to "open ."do shell script command1 & command2 & command3 & command4end myOpenDocument

This feature helped me a lot when developing Gallery to check whether videos and downloaded images are saved in the correct place.

开发Gallery来检查视频和下载的图像是否保存在正确的位置时,此功能对我有很大帮助。

However, none of the scripts seem to work. Scripting has always been part of macOS since 1993. But, with the advent of the Mac Appstore and security concerns, AppleScript finally got restricted in mid 2012. That was when App Sandbox was enforced.

但是,这些脚本似乎都不起作用。 自1993年以来,脚本一直是macOS的一部分。但是,随着Mac Appstore的问世和安全方面的考虑,AppleScript终于在2012年中期受到限制。

应用沙箱 (App Sandbox)

App Sandbox is an access control technology provided in macOS, enforced at the kernel level. It is designed to contain damage to the system and the user’s data if an app becomes compromised. Apps distributed through the Mac App Store must adopt App Sandbox.

App Sandbox是macOS中提供的一种访问控制技术,在内核级别实施。 如果应用程序被盗用,它旨在包含对系统和用户数据的破坏。 通过Mac App Store分发的应用程序必须采用App Sandbox 。

For an Xcode extension to be loaded by Xcode, it must also support App Sandbox.

对于要由Xcode加载的Xcode扩展,它还必须支持App Sandbox。

At the beginning of App Sandbox enforcement, we could use App Sandbox Temporary Exception to temporarily grant our app access to Apple Script.

在应用程序沙箱强制执行开始时,我们可以使用应用程序沙箱临时异常临时授予我们的应用程序对Apple Script的访问权限。

This is now not possible.

现在这是不可能的。

The only way for AppleScript to run is if it resides inside ~/Library/Application Scripts folder.

AppleScript的唯一运行方式是驻留在~/Library/Application Scripts文件夹中。

如何安装自定义脚本 (How to install custom scripts)

macOS apps or extensions can’t just install scripts into the Application Scripts by themselves. They need user consent.

macOS应用程序或扩展不能仅将脚本自身安装到应用程序脚本中。 他们需要用户的同意。

One possible way to do that is to enable Read/Write and show a dialog using NSOpenPanel to ask user to select the folder to install our scripts.

一种可行的方法是启用“ Read/Write并使用NSOpenPanel显示对话框,要求用户选择文件夹来安装我们的脚本。

For XcodeWay, I choose to provide an install shell script so the user has a quick way to install scripts.

对于XcodeWay,我选择提供安装Shell脚本,以便用户快速安装脚本。

#!/bin/bash
set -euo pipefail
DOWNLOAD_URL=https://raw.githubusercontent.com/onmyway133/XcodeWay/master/XcodeWayExtensions/Script/XcodeWayScript.scptSCRIPT_DIR="${HOME}/Library/Application Scripts/com.fantageek.XcodeWayApp.XcodeWayExtensions"
mkdir -p "${SCRIPT_DIR}"curl $DOWNLOAD_URL -o "${SCRIPT_DIR}/XcodeWayScript.scpt"

AppleScript is very powerful. All of this is made explicit so the user has complete control over which things can be done.

AppleScript非常强大。 所有这些都是明确的,因此用户可以完全控制哪些事情可以完成。

Like an extension, a script is done asynchronously in a different process using XPC for inter process communication. This enhances security as a script has no access to the address space to our app or extension.

像扩展一样,使用XPC在进程间进行通信的不同进程中异步完成脚本。 由于脚本无法访问我们的应用或扩展程序的地址空间,因此可以提高安全性。

macOS Mojave中的更高安全性 (More security in macOS Mojave)

This year, at WWDC 2018, Apple introduced macOS Mojave which focuses on lots of security enhancements. In the Your Apps and the Future of macOS Security we can learn more about new security requirement for macOS apps. One of them is the usage description for AppleEvents.

今年,在WWDC 2018上,Apple推出了macOS Mojave,它专注于许多安全性增强功能。 在您的应用程序和macOS安全性的未来中,我们可以了解有关macOS应用程序的新安全性要求的更多信息。 其中之一是AppleEvents的用法说明。

unable to load info.plist exceptions (egpu overrides)

无法加载info.plist异常(egpu覆盖)

We used to declare usage description for many permissions in iOS, like photo library, camera, and push notifications. Now we need to declare the usage description for AppleEvents.

我们曾经在iOS中声明许多权限的使用情况描述,例如照片库,相机和推送通知。 现在我们需要声明AppleEvents的用法描述。

The first time our extension tries to execute some AppleScript commands, the above dialog is shown to ask for user consent. User can grant or deny permission, but for Xcode please say yes ?

我们的扩展程序第一次尝试执行某些AppleScript命令时,将显示以上对话框,要求用户同意。 用户可以授予或拒绝权限,但是对于Xcode,请说是?

The fix for us is to declare NSAppleEventsUsageDescription in our app target. We only need to declare in the app target, not in the extension target.

我们的解决方法是在应用程序目标中声明NSAppleEventsUsageDescription 。 我们只需要在应用程序目标中声明,而无需在扩展目标中声明。

<key>NSAppleEventsUsageDescription</key><string>Use AppleScript to open folders</string>

从这往哪儿走 (Where to go from here)

Huff huff, whew! Thanks for following such a long journey. Making frameworks and tools take lots of time, especially plugins and extensions — we have to continuously change to adapt them to new operating systems and security requirements. But it is a rewarding process, as we’ve learned more and have some tools to save our precious time.

呼呼,呼呼! 感谢您经过如此漫长的旅程。 制作框架和工具需要花费大量时间,尤其是插件和扩展-我们必须不断进行更改以使它们适应新的操作系统和安全性要求。 但这是一个有益的过程,因为我们已经了解了更多,并拥有一些工具来节省宝贵的时间。

For your reference, here are my extensions which are fully open source.

供您参考,这是我的扩展程序,它们是完全开源的。

  • XcodeWay

    XcodeWay

  • XcodeColorSense2

    XcodeColorSense2

I hope you find something useful in the post. Here are some resources to help explore Xcode extensions further:

希望您在帖子中找到有用的信息。 以下是一些资源,可帮助您进一步探索Xcode扩展:

  • Xcode Plugins by NSHipster

    NSHipster的Xcode插件

  • Writing Xcode plugin in Swift

    在Swift中编写Xcode插件

  • Xcode 8 Plugins (Alcatraz) — The end of an era

    Xcode 8插件(恶魔岛)—时代的终结

  • Using and Extending the Xcode Source Editor

    使用和扩展Xcode源代码编辑器

  • Why do I need to resign Xcode to use XVim2

    为什么我需要辞职Xcode才能使用XVim2

If you like this post, consider visiting my other articles and apps ?

如果您喜欢这篇文章,请考虑访问我的其他文章和应用程序 ?

翻译自: https://www.freecodecamp.org/news/how-to-convert-your-xcode-plugins-to-xcode-extensions-ac90f32ae0e3/

xcode扩展

xcode扩展_如何将Xcode插件转换为Xcode扩展名相关推荐

  1. safari的java扩展_用 Web 技术为 Safari 编写扩展

    原标题:用 Web 技术为 Safari 编写扩展 作者:希德,iOS 开发者,前"有经验的前端开发工程师",就职于网易严选.正在写书<Thinkable SwiftUI&g ...

  2. python维度扩展_在TensorFlow中实现矩阵维度扩展

    一般TensorFlow中扩展维度可以使用tf.expand_dims().近来发现另一种可以直接运用取数据操作符[]就能扩展维度的方法. 用法很简单,在要扩展的维度上加上tf.newaxis就行了. ...

  3. python 扩展_用Python方法对Cursor进行扩展

    用Python方法对Cursor进行扩展.为了方便使用,所以我对MySQLdb的Cursor进行了扩展,使其能够支持带?以及:xxx的查询方式.是因为以下原因. 目前的MySQLdb中的sql的语法, ...

  4. java spi 扩展_【扩展和解耦】JAVA原生SPI实现插件扩展

    Java极客  |  作者  /  铿然一叶 这是Java极客的第 81 篇原创文章 相关阅读: 1. 什么是插件 通俗的讲插件有以下特征: 1.增加或者替换已有能力 2.不影响原有功能 3.对原有系 ...

  5. 扩展的应用范围 ios_使用插件扩展iOS应用

    扩展的应用范围 ios App extensions or plug-ins are very common for desktop apps. Web browsers offer a big se ...

  6. 511遇见易语言乐玩插件找字扩展FindStrEx和快速找字扩展FindStrFastEx

    找字扩展FindStrEx和快速找字扩展FindStrFastEx是相对于找字和快速找字的,模块结构并没有什么不同,主要是返回的是找到的多个字的坐标,我们需要分割数组分别获取,不能使用GetX和Get ...

  7. 【Android Gradle 插件】Extension 扩展类型 ( Module 引入插件类型 | application 插件 | library 插件 | Variants 变体列表 )

    文章目录 一.Module 引入插件类型 1.com.android.application 插件 2.com.android.library 插件 二.Extension 扩展类型 三.applic ...

  8. vscode 显示多个文件_优秀的 VS Code 前端开发扩展

    原文作者: Ankur Biswas 翻译:weakish@LeanCloud 我留意到,网上有不少关于 VS Code 的文章,然而这些文章提到的扩展大部分都让我失望.所以我决定编写我个人使用的插件 ...

  9. joomla tab 扩展_如何使用扩展功能扩展Joomla网站的功能

    joomla tab 扩展 Joomla被独立评论网站CMS Critic评为2018年度最佳免费CMS ,它是全球最受欢迎的网站内容管理系统(CMSes)之一. 与WordPress一样, Joom ...

最新文章

  1. OCR光学字符识别方法汇总(附开源代码)
  2. nowcoder OI 周赛 最后的晚餐(dinner) 解题报告
  3. [iOS]应用内支付(内购)的个人开发过程及坑!
  4. shell脚本和python脚本和go脚本的区别_Nodejs中调用系统命令、Shell脚本和Python脚本的方法和实例-Go语言中文社区...
  5. 数据安全管理:RSA加密算法,签名验签流程详解
  6. taz文件_我们将赠送LulzBot Taz 6 3D打印机
  7. linux mint xmind运行,linux安装Xmind的经验
  8. 自动驾驶OS市场的现状及未来
  9. java URL和URI
  10. 百度php获取当前经纬度,百度地图获取经纬度的示例
  11. 新玺配资:板块轮动加快 多看少动均衡配置为主
  12. unity3D导入fbx模型,并实现fbx模型的自由旋转源码详解
  13. 外汇EA量化交易特点
  14. 实战讲解网关接口统一认证SpringCloudGateway(图+文)
  15. Cisco Encrypted Traffic Analysis(ETA)
  16. luci编程 openwrt_Luci流程分析(openwrt下)
  17. 电商业务Alipay支付实战(当面付实现)
  18. Eclipse中没有Servers或Dynamic Web Project的解决方案
  19. 各地政府开放平台_8个视频让您对开放政府感到兴奋
  20. 数据可视化之旅:常用图表对比

热门文章

  1. oppoJava面试题,腾讯社招三面多久联系
  2. db2 c语言游标名称可以是变量,mysql c语言 游标能取多行吗
  3. 实现技术3次作业 谢筱 1101220759
  4. java常用设计模式一:单例模式
  5. 面向对象编程思想-观察者模式
  6. imx6 mac地址设置
  7. Sharepoint 2013设置customErrors
  8. Tomcat配置JNDI数据源
  9. KM 最优匹配 讲解
  10. 使用Spring Task完成定时任务