Пользователь
0,0
рейтинг
25 декабря 2012 в 02:58

Разработка → Пишем свой Xcode plugin tutorial

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

Спасибо за внимание!
Денис Лебедев @garnett
карма
22,2
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Спецпроект

Самое читаемое Разработка

Комментарии (10)

  • +2
    Огонь! Спасибо! Пошел писать свой плагин)
    • 0
      А с отладчиком как-нибудь взаимодействовать возможно?
      • 0
        Печально, но никак, это указано в статье.
  • 0
    Было-бы здорово получить API, не встречали нигде такого?
    • 0
      К сожалению, не встречал, так как по факту документации появится неоткуда — apple не поддерживает плагины официально.
  • –1
    Вот прямо сегодня утром думал, как было бы здорово сразу в коде посмотреть на маску…
  • 0
    @property (retain) DLMaskView *maskView;
    @property (retain) DLSpringsStrutsView *sizingView;
    ..
    self.maskView = [[DLMaskView alloc] initWithFrame:NSZeroRect];
    ...
    self.sizingView = [[DLSpringsStrutsView alloc] initWithFrame:NSZeroRect];
    

    После такого счетчики ссылок созданных объектов будут равны 2.

    - (void)dealloc
    {   [_sizingView release];
        [_maskView release];
        [super dealloc];
    }
    

    После этого — уменьшатся на 1.

    Поэтому лучше так:

    _maskView = [[DLMaskView alloc] initWithFrame:NSZeroRect];
    ...
    _sizingView = [[DLSpringsStrutsView alloc] initWithFrame:NSZeroRect];
    ...
    - (void)dealloc
    {
        self.sizingView = nil;
        self.maskView = nil;
        [super dealloc];
    }
    


    Чуть не забыл, огромное спасибо за статью :)
    • 0
      Спасибо, привык уже к ARC, поправил.
      • 0
        ARC развращает :)
        • 0
          Не соглашусь, но это уже тема для отдельного топика.

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.