Пишем свой Xcode plugin

от автора

Зачастую возникают ситуации, когда функционал используемой 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.

Спасибо за внимание!

ссылка на оригинал статьи http://habrahabr.ru/post/163795/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *