Getting started with xcode plug-ins
For xcode 5, this article seems outdated in some places. Xcode 5 is now fully switched to arc, so the initialization settings of the plug-in have actually changed. In addition, thanks to a large number of excellent plug-ins (see the link at the bottom of the article), many great gods have gradually joined the ranks of plug-in development. Therefore, a simple template is necessary. This repo on GitHub contains a template project for the xcode 5 plug-in, saving you the trouble of creating a plug-in project from the beginning every time. You can download and use it directly.
It is also worth mentioning that in xcode 5, Apple added a uuid check mechanism to prevent expired plug-ins from causing the IDE crash after xcode upgrade. Only when the uuid is declared and adapted can it be correctly loaded by xcode. The above project also contains more detailed instructions in this regard. For details, refer.
Other ideas and common methods for plug-in development in this article are still effective in the new xcode.
This article describes the basic steps and common methods required to create an xcode4 plug-in. Note that creating a plug-in for xcode does not have any official support. Therefore, the methods and information described in this article may become invalid as Apple changes in xcode. In addition, since private APIs are used to create plug-ins, xcode plug-ins cannot be submitted to Mac App Store for sale.
This article is based on xcode 4.6 (4h127), but it should be applicable to any version of xcode 4.x. I put the vvplugindemo project file on GitHub. If you need it, you can download it from here and use it as a reference and start point.
Summary
Xcode itself, as an IDE, can be regarded as excellent, but it still has many missing functions. In addition, it creates some convenient ide plug-ins for its own development needs during development, it will definitely speed up development. Because Apple does not provide any technical and Documentation Support for xcode plug-ins, it may be difficult for most developers to get started. Although there is no official support, it is possible to develop and use plug-ins in xcode, and it is also acquiesced. When xcode is started, xcode will look ~ The suffix in the/library/Application Support/developer/shared/xcode/plug-ins folder is. the bundle of xcplugin is loaded as a plug-in (the executable files in it are run), which allows us to effectively and reasonably inject our code (although this word is a bit unpleasant) xcode, and run it. Therefore, to create the xcode plug-in, we need to create a bundle project and put the compiled bundle in the directory of the plug-in mentioned above. This is the principle of the xcode plug-in.
It should be noted that, because xcode will load your plug-in at startup, it is equivalent to your code having the opportunity to inject xcode. As long as your plug-in is loaded successfully, it will share a process with xcode, that is, when your code crash, xcode will also crash. In the same case, it may occur due to compatibility issues when the xcode version is updated (because the plug-in may use private APIs, Apple is not obligated to maintain the availability of these Apis ). In this case, you can directly Delete the problematic xcplugin file under the plug-in directory.
Your first plug-in
I will compile a simple demo plug-in to describe how to compile the xcode plug-in. This plug-in will add a project named "What is selected" in the edit menu of xcode, when you click this menu command, a warning box is displayed, prompting you to select the content in the editor. I believe that this example can contain most of the steps and some useful methods required in Plug-in creation. As I am only a half-hanging developer, And the level is very limited, I would like to ask you to forgive me for mistakes and help me correct them. So start ..
Create a bundle Project
Create a project, OSX, framework & library, select bundle, and click Next.
On the project information page, enter the plug-in name. In this example, demoplugin is called, and framework uses the default cocoa. In addition, remember to remove the check box before use automatic reference counting. Because the plug-in can only use GC for memory management, arc is not required.
Project Settings
Plug-in projects are different from general projects. Special settings are required to ensure correct compilation of plug-in bundle.
First, add the following three boolean values to edit the info. plist file of the project (directly edit the plist file or modify the info of the target under targets:
XCGCReady = YES XCPluginHasUI = NO XC4Compatible = YES
This will tell the compiler project that GC has been used, there is no other UI and it is compatible with xcode4, otherwise your plug-in will not be loaded. Next, set bundle setting:
Installation build products location is set to $ {home}
- Root directory of Product
Set installation directory
- /Library/Application Support/developer/shared/xcode/plug-ins
- Here, the plug-in installation location is specified, so that after the build, the plug-in will be directly thrown to the plug-ins directory. You can copy and paste it each time. Note that this is not an absolute path, but based on the above $ {home} path.
Deployment location is set to Yes
- Tell xcode not to use the build location in the setup, but to use installation directory to determine where to put the build.
Wrapper extension is set to xcplugin
- Change the product suffix to xcplugin. Otherwise, xcode will not load the plug-in.
As mentioned in the beginning, xcode will search for the plug-in directory and load it each time it starts, the purpose of the above settings is that after each build, you only need to restart xcode to see the effect of the re-compiled plug-in, instead of searching for the product and then copying & paste.
In addition, you also need to add a key-Value Pair in user-defined:
- Gccenableobjc_gc is set to supported
Now that all the configuration is complete, you can finally implement the plug-in ~
Hello World
Create a new class named vvplugindemo (of course, it is acceptable if it is not heavy) and inherit from nsobject (do not forget to write xcode plug-in for iOS development, you need to use the objective-C class template in the cocoa of OS X, instead of the cocoa touch template ..). Open vvplugindemo. M and add the following code:
+(void)pluginDidLoad:(NSBundle *)plugin { NSLog(@"Hello World"); }
Build (for OS X 10.8 sdks, there may be a warning that the GC has been discarded. You don't need to worry about it. xcode itself is GC, And the arc plug-in cannot be loaded ), open the console (Control + space input console) and restart xcode. We should be able to see the output of our plug-in the console:
Great. There is a saying that writing a hello World indicates that you have mastered half of it... Then, the remaining half of the content will introduce the problems that may be faced during plug-in development and some common means.
Create a plug-in singleton and listen to events
Continue with our plug-ins. Do you still remember our purpose? Add a project named "What is selected" to the edit menu of xcode. When you click this menu command, a warning box is displayed, it prompts you to select the content in the editor. In general, we hope that the plug-in can exist throughout the entire xcode lifecycle (do not forget that xcode, which is actually used to write cocoa, is also a cocoa program ). The best way is to initialize the singleton in + plugindidload: as follows:
+ (void) pluginDidLoad: (NSBundle*) plugin { [self shared]; }+(id) shared { static dispatch_once_t once; static id instance = nil; dispatch_once(&once, ^{ instance = [[self alloc] init]; }); return instance; }
In this way, in other classes, we can simply access the plug-in instance through [vvplugindemo shared.
In init, add an event listener after the program is started. After the program is started, add the required menu items in edit in the menu bar, this operation should be performed after xcode is fully started to avoid potential risks and conflicts. In addition, to display the content displayed in the editor when you press the button, we may need to listen to the nstextviewdidchangeselectionnotification event (WTF, why do you know what to listen. Don't worry. I will talk about it later, first demo first)
- (id)init { if (self = [super init]) { [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidFinishLaunching:) name:NSApplicationDidFinishLaunchingNotification object:nil]; } return self; } - (void) applicationDidFinishLaunching: (NSNotification*) noti { [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(selectionDidChange:) name:NSTextViewDidChangeSelectionNotification object:nil]; NSMenuItem *editMenuItem = [[NSApp mainMenu] itemWithTitle:@"Edit"]; if (editMenuItem) { [[editMenuItem submenu] addItem:[NSMenuItem separatorItem]]; NSMenuItem *newMenuItem = [[NSMenuItem alloc] initWithTitle:@"What is selected" action:@selector(showSelected:) keyEquivalent:@""]; [newMenuItem setTarget:self]; [newMenuItem setKeyEquivalentModifierMask: NSAlternateKeyMask]; [[editMenuItem submenu] addItem:newMenuItem]; [newMenuItem release]; } } -(void) selectionDidChange:(NSNotification *)noti { //Nothing now. Just in case of crash. } -(void) showSelected:(NSNotification *)noti { //Nothing now. Just in case of crash. }
Now build and restart xcode. If everything goes well, you should be able to see the changes on the menu bar:
Complete demo plug-in
The rest is simple. After receiving the changeselection notification of textview, update the selected text and click the button to display a dialog box containing the stored text. Let's do it ~
First, add the property declaration in the. M file (for personal habits, you can also use Ivar ). Add the following between # import and @ implementation:
@interface VVPluginDemo() @property (nonatomic,copy) NSString *selectedText; @end
Synthesis does not need to write because of the Automatic Binding of new attributes. (For more information about this, see my blog post ). Then-selectiondidchange: And-showselected are completed as follows:
-(void) selectionDidChange:(NSNotification *)noti { if ([[noti object] isKindOfClass:[NSTextView class]]) { NSTextView* textView = (NSTextView *)[noti object]; NSArray* selectedRanges = [textView selectedRanges]; if (selectedRanges.count==0) { return; } NSRange selectedRange = [[selectedRanges objectAtIndex:0] rangeValue]; NSString* text = textView.textStorage.string; self.selectedText = [text substringWithRange:selectedRange]; } //Hello, welcom to OneV‘s Den }-(void) showSelected:(NSNotification *)noti { NSAlert *alert = [[[NSAlert alloc] init] autorelease]; [alert setMessageText: self.selectedText]; [alert runModal]; }
Build, restart xcode, select a piece of text, and click what is selected in edit. Oy ~ Complete ~
Now you have mastered the basic xcode plug-in creation method. The next step is to practice it based on your needs ~ But before that, you may be interested in some important techniques and common methods.
Useful techniques for developing plug-ins
As there is no document to guide plug-in development, debugging can only be performed by logging, so it is very difficult. It will be helpful to master some common skills and methods.
I need all communications!
A good method is to listen to the desired message and respond to the message. Like nstextviewdidchangeselectionnotification in the demo. For kids shoes familiar With iOS or Mac development, many types of notifications should also be exposed in daily development, because plug-in development does not have documentation, therefore, we need to find the notification we want to listen to and receive. In the nsnotificationcenter document, the addobserver: selector: Name: object: Method for adding observer. When the name parameter is assigned a value of nil, all notifications can be monitored:
Icationicationname: the name of the notification for which to register the observer; that is, only notifications with this name are delivered to the observer. if you pass nil, the notification center doesn't use a notification's name to decide whether to deliver it to the observer.
Therefore, you can use it to monitor all the notifications and find what you need for processing:
-(id)init { if (self = [super init]) { [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(notificationListener:) name:nil object:nil]; } return self; } -(void)notificationListener:(NSNotification *)noti { NSLog(@" Notification: %@", [noti name]); }
Output after the compilation is restarted on the console:
Of course, it may not be helpful to print the name. You may need to obtain more information from the noti object or userinfo. It is a good method to print by conditions and search with the console.
Hack private API
With the dynamic features of OC, you can do a lot of things, such as replacing an xcode method at runtime. Remember that xcode itself is also a cocoa program, which is essentially no different from the program we developed using xcode. Therefore, if you know how xcode performs some operations, you can change this method to the method we implemented at runtime and execute our own method. This is the method swizzling (or monkey patch) in the runtime. This is a very interesting and convenient method in the Smalltalk class language, for more details, I have previously written an article on the characteristics of OC runtime. The method swizzling method mentioned at that time does not check the function of the exchange, and its versatility is also relatively poor. Now there is a mature method exchange mechanism for OC, among which the famous ones are zoozsch jrswizzle and OC community methodswizzling implementation.
With the method exchange method, you need to find the method to be exchanged. All libraries used by xcode are included in three folders: xcode. APP/contents/frameworks, sharedframeworks, and otherframeworks. Among them, idekit and idefoundation in frameworks are the most direct and important relationships with xcode, as well as dvtkit and dvtfoundation in sharedframeworks. The prefix of DVS indicates developer toolkit. Classes in IDE and idefoundation are subclasses of classes in DVS. These four frameworks are the most important thing we need to deal with when developing xcode plug-ins that change the default behavior of xcode. In addition, if you want to inject IB, you may also need to use ibautolayoutfoundation under frameworks (to be determined ). For private APIs in these frameworks, you can use Class-dump to easily extract header files. Of course, some people have completed this job for the lazy, and the xcode-class-dump of probablycorey contains most Class header files.
As a demo, we will simply complete a method exchange: when completing the code, we will simply output a log.
Methodswizzle
To exchange methods, you can use the ready-made methodswizzle. Methodswizzle can be found here. Import. h and. m to the plug-in project ~
Find the corresponding API
By searching, the completion code function is defined in the dvttextcompletioncontroller class in dvkit. One of the methods is-(bool) acceptcurrentcompletion. It is used to determine whether the returned Boolean value accepts the current completion result. Because these are private APIs, We need to declare them in our project. In the new file C and C ++, select the header file, add a header file to the project, and add the following code:
@interface DVTTextCompletionController : NSObject - (BOOL)acceptCurrentCompletion; @end
Then you need to set the dvkit. add the framework to the project in/applications/xcode. find dvtkit in APP/contents/sharedframeworks. to any normally accessible directory, and then add the framework to build phases of the plug-in project. Hmm? You said you couldn't find dvtkit. framework? Sorry, the private framework cannot be found. Click Add other... and then go to the place just copied to find it ..
The last step is to add a method exchange ~ Create a dvttextcompletioncontroller category named plugindemo
Add the following implementation to the header and methodswizzle. h defined before import in dvttextcompletioncontroller + plugindemo. M:
+ (void)load{ MethodSwizzle(self, @selector(acceptCurrentCompletion), @selector(swizzledAcceptCurrentCompletion));}- (BOOL)swizzledAcceptCurrentCompletion { NSLog(@"acceptCurrentCompletion is called by %@", self); return [self swizzledAcceptCurrentCompletion]; }
+ The load method is executed when each nsobject class or subclass is called. It can be used to configure the current class at runtime. The acceptcurrentcompletion method of dvttextcompletioncontroller and the swizzledacceptcurrentcompletion method we implemented are exchanged here. In swizzledacceptcurrentcompletion, a log is printed and an instance of the corresponding method is output. Next, it seems that you have called yourself, but in fact the swizzledacceptcurrentcompletion method has already been exchanged with the original acceptcurrentcompletion method. Therefore, the actual call here will be the original method. So what this Code does is to change xcode's attempt to call the original acceptcurrentcompletion to printing a log first, and then calling the original acceptcurrentcompletion.
Compile and restart xcode. Open a project and enter something for completion. The output in the console meets our expectation:
Great. With the injection of private APIs, the tasks that can be done are greatly extended.
View hierarchy of xcode
Another common plug-in behavior is to modify some interfaces. Once again, xcode is a standard cocoa program and everything is so familiar (if you are developing for cocoa or cocoatouch, you should be familiar with it ). Obtain the window of the entire app and print the subview recursively. Stackoverflow has a uiview version. You can obtain an nsview version after a slight change. Create an nsview dumping category and add the following implementation:
-(void)dumpWithIndent:(NSString *)indent { NSString *class = NSStringFromClass([self class]); NSString *info = @""; if ([self respondsToSelector:@selector(title)]) { NSString *title = [self performSelector:@selector(title)]; if (title != nil && [title length] > 0) { info = [info stringByAppendingFormat:@" title=%@", title]; } } if ([self respondsToSelector:@selector(stringValue)]) { NSString *string = [self performSelector:@selector(stringValue)]; if (string != nil && [string length] > 0) { info = [info stringByAppendingFormat:@" stringValue=%@", string]; } } NSString *tooltip = [self toolTip]; if (tooltip != nil && [tooltip length] > 0) { info = [info stringByAppendingFormat:@" tooltip=%@", tooltip]; } NSLog(@"%@%@%@", indent, class, info); if ([[self subviews] count] > 0) { NSString *subIndent = [NSString stringWithFormat:@"%@%@", indent, ([indent length]/2)%2==0 ? @"| " : @": "]; for (NSView *subview in [self subviews]) { [subview dumpWithIndent:subIndent]; } } }
When appropriate (such as clicking a button), call the following code to print the current xcode structure, which is very convenient. This is helpful for understanding the composition of xcode and how to build a complicated program like xcode ~
[[[NSApp mainWindow] contentView] dumpWithIndent:@""];
The output results in the result console are similar to the following:
Go to the corresponding view as needed ~ Then, in combination with the exchange of methods, you can do what you want to do.
Last little bonus
/Applications/xcode. APP/contents/frameworks/idekit. framework/versions/A/resources has a lot of pictures used on the xcode interface, PDF, PNG, And Tiff formats, and you want to customize run, the stop button, or you want to change the breakpoint marker from a blue block to a robot cat portrait or something... It should be possible ~
/Applications/xcode. the app/contents/Plugins directory contains many xcode built-in "official version" plug-ins. Obviously, you can write plug-ins for xcode plug-ins through the class-dump and injection methods... hmm ~ For example, you can change the behavior of a debugger or make the plist editor smarter.
I hope Apple can provide support for compiling xcode plug-ins. It is interesting to explore everything, but it also takes time.
In addition, many plug-ins written by great gods on code hosting websites such as GitHub are released open-source. These must be the best teaching materials and references written by the Learning plug-in:
- Mneorr/Alcatraz xcode package management plug-in to manage plug-ins of other plug-ins
- Onevcat/vv1_enter-xcode plug-in that helps you quickly write document comments and automatically extract parameter return values.
- OMZ/colorsense-for-xcode displays the corresponding color on uicolor/nscolor
- OMZ/dash-plugin-for-xcode integrates dash in xcode for easy reading of documents
- OMZ/minixcode hides xcode's bloated toolbar for greater visual space
- Ksuther/ksimagenamed-xcode automatically fills in the image name when entering imagenamed
- Jugglershu/xvim converts the xcode editor into Vim
- Davekeck/Xcode-4-Fixins fixes some xcode bugs (should be no longer so useful)
- 0 xced/clitool-infoplist facilitates the modification of info. plist as the CLI target plug-in
- Questbeat/Lin shows completion for nslocalizedstring
- Stefanceriu/scxcodeminimap Show Code map on the side
Now, let's go here. I put the vvplugindemo project file on GitHub. If you need it, you can download it from here and use it as a reference and start point. Thank you for reading this long article. As I said at the beginning, my own skills are very limited. Therefore, I would like to ask you to forgive me for mistakes and help me correct them. Thank you again ~
Xcode plugin Creation