Pull to refresh

Пишем свой Xcode plugin

Reading time 3 min
Views 8.7K
Зачастую возникают ситуации, когда функционал используемой IDE хочется расширить. Везет, если разработчику предоставлены средства и документация для того, чтобы это сделать. К сожалению, в случае c Xcode это не так. Документирование возможностей остановилось на версии Xcode 3.0, так что никто не гарантирует, что в следующей версии написанный вами плагин заработает.

Примечание: за основу для написания данного топика был взят плагин ColorSense-for-Xcode.

Как я уже говорил, официально, Xcode не предоставляет публичного API для написания плагинов. При старте приложения, Xcode просматривает папку с плагинами (~/Library/Application Support/Developer/Shared/Xcode/Plug-ins) и загружает найденные (.xcplugin).

На самом деле, простенький плагин пишется за несколько часов, что вы и увидите далее.

Создаем новый Xcode проект

Плагин — всего навсего OS X бандл, создадим новый проект с типом 'bundle'.



При создании проекта, нужно убедиться, что ARC выключен, так как Xсode работает под управлением garbage collector, это же распространяется и на плагин.

Открываем таргет плагина и выставляем следующие настройки:

  • XC4Compatible = YES
  • XCPluginHasUI = NO
  • XCGCReady = YES
  • Principal class = {название главного класса плагина}




Конфигурируем Build Settings

Идем в build settings и выставляем следующие настройки:
  • Installation Build Products Location = ${HOME}
  • Installation Directory = /Library/Application Support/Developer/Shared/Xcode/Plug-ins
  • Deployment Location = YES
  • Wrapper extension = xcplugin


Также, нужно добавить несколько user-definded settings:
  • GCC_ENABLE_OBJC_GC = supported
  • GCC_MODEL_TUNING = G5


Мы указали, куда должен устанавливаться наш плагин после сборки. Важно: с отладкой плагинов все печально, и вам придется перезапускать Xcode после каждого билда.

Пишем плагин

Создадим новый класс и назовем его тем именем, что укзали в настройке Principal class. Когда Xcode загружает плагин, будет вызван метод + (void) pluginDidLoad: (NSBundle*) plugin, в котором можно произвести начальную настройку плагина (как правило, плагин — это синглтон).

+ (void) pluginDidLoad: (NSBundle*) plugin {
	static id sharedPlugin = nil;
	static dispatch_once_t once;
	dispatch_once(&once, ^{
		sharedPlugin = [[self alloc] init];
	});
}

- (id)init {
	if (self = [super init]) {
		[[NSNotificationCenter defaultCenter] addObserver:self 
                        selector:@selector(applicationDidFinishLaunching:) 
                            name:NSApplicationDidFinishLaunchingNotification 
                          object:nil];
	}
	return self;
}

в обработчике applicationDidFinishLaunching: мы можем непосредственно исполнять логику плагина. В нашем случае, мы подпишемся на нотификации изменения положения курсора в редакторе, а также добавим новый пункт в меню Edit.

- (void)applicationDidFinishLaunching:(NSNotification*)notification {
    [[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:@"Show autoresizing masks"
                                                             action:@selector(toggleMasks:)
                                                      keyEquivalent:@"m"];
        [newMenuItem setTarget:self];
        [newMenuItem setKeyEquivalentModifierMask:NSAlternateKeyMask];
        [[editMenuItem submenu] addItem:newMenuItem];
        [newMenuItem release];
    }
}

Для примера, будем выводить по изменению позиции курсора строку:

- (void)selectionDidChange:(NSNotification*)notification {
    if ([[notification object] isKindOfClass:[NSTextView class]]) {
        NSTextView* textView = (NSTextView *)[notification object];
        
        if (![[NSUserDefaults standardUserDefaults] boolForKey:kDLShowSizingsPreferencesKey]) {
            return;
        }
        
        NSArray* selectedRanges = [textView selectedRanges];
		if (selectedRanges.count >= 1) {
			NSRange selectedRange = [[selectedRanges objectAtIndex:0] rangeValue];
			NSString *text = textView.textStorage.string;
			NSRange lineRange = [text lineRangeForRange:selectedRange];
			NSString *line = [text substringWithRange:lineRange];
                 }
                        NSAlert *alert = [[[NSAlert alloc] init] autorelease];
                        [alert setMessageText:line];
                        [alert runModal];
}

Простейший, ничего полезного не делающий плагин готов!

Замечания

Как я уже говорил выше, отладка плагина возможно только при помощи перезапуска Xcode (когда я писал плагин, я везде расставлял NSAlert и выводил нужную информацию). Из-за того, что документации как таковой нет, чтобы найти нужный вид в иерархии, или узнать какие нотификации отсылются, необходимо выполнить тот же самый трюк: вывести информацию либо в лог, либо в алерт. Если плагин падает, то Xcode не запустится, а плагин нужно удалить из ‘~/Library/Application Support/Developer/Shared/Xcode/Plug-ins’.

Более функциональный пример

Предпосылкой для написания статьи стал написанный мной небольшой плагин, который отображает маски авторазмера для UIView:



Исходник на github.

Спасибо за внимание!
Tags:
Hubs:
+28
Comments 10
Comments Comments 10

Articles