Создание сменной архитектуры

Много приложений получают преимущества от наличия сменной архитектуры — способ расширить приложение с помощью новых функций, не изменяя код главного приложения. В этом разделе описывается создать сменную архитектуру для приложения Какао.

Создание архитектуры

Первый шаг в реализации сменной архитектуры решает то, что формируется, Вы хотите, чтобы плагины взяли. Для инструкций по тому, как решить, какой механизм Вы хотите использовать, считайте Сменный Проект Архитектуры.

Архитектура Какао естественно поддерживает три выбора:

Во всех трех случаях Вы предоставляете стандартный интерфейс сменным разработчикам, и они пишут сменные основные классы для соответствия интерфейса. Наиболее удобный способ сделать это должно обеспечить интерфейс как платформу для сменных разработчиков для соединения против.

Следующие разделы описывают, как опубликовать три различных типов сменных интерфейсов.

Публикация формального интерфейса плагина протокола

Для использования сменного протокола, Вы определяете, сменному разработчику только нужен заголовочный файл, содержащий определение протокола. Самый простой способ опубликовать интерфейс состоит в том, чтобы просто распределить этот заголовок.

Заголовок протокола для плагина графического фильтра мог бы посмотреть что-то как Перечисление 1:

Перечисление 1  Формальный протокол для сменной архитектуры

/*
   MyGreatImageApp
   Graphics Filter Interface version 0
   MyAppBitmapGraphicsFiltering.h
*/
 
#import <Cocoa/Cocoa.h>
 
@protocol MyGreatImageAppBitmapGraphicsFiltering
 
// Returns the version of the interface you're implementing.
// Return 0 here or future versions may look for features you don't have!
- (unsigned)interfaceVersion;
 
// Returns what to display in the Filter menu.
- (NSString *)menuItemString;
 
// The main worker bee: filters the bitmap and returns a modified version.
- (NSBitmapImageRep *)filteredImageRep:(NSBitmapImageRep *)imageRep;
 
// Returns the window controller for the settings configuration window.
- (NSWindowController *)configurationWindowController;
 
@end

Заметьте, что этот пример включает метод версии, таким образом, приложение может идентифицировать версию используемого интерфейса. Если Вы просто распределяете заголовочный файл, это - лучший способ гарантировать, чтобы будущие версии Вашего приложения избежали отправлять сообщения в старые плагины, что они не могут обработать. Можно также запросить плагин для того, какие сообщения он отвечает на, как описано в Проверке Плагинов.

Публикация неофициального интерфейса плагина протокола

Используя неофициальный протокол для сменной архитектуры несколько более хитро, чем использование формального протокола. Поскольку сменный разработчик может выбрать, какие методы реализовать, Ваше приложение должно проверить перед использованием метода, если плагин фактически реализует его. Если некоторые методы являются дополнительными, и некоторые требуются, несомненно, ясно дадут понять это в документации для интерфейса.

Можно распределить единственный заголовочный файл для определения неофициального протокола, так же, как с формальным протоколом. Перечисление 2 является альтернативной реализацией Перечисления 1, с помощью неофициального протокола вместо формального протокола, эффективно делая некоторые методы дополнительными. Как большинство неофициальных протоколов, этот реализован как категория на NSObject.

Перечисление 2  Неофициальный протокол для сменной архитектуры

/*
   MyGreatImageApp
   Graphics Filter Interface version 0
   MyAppBitmapGraphicsFiltering.h
*/
 
#import <Cocoa/Cocoa.h>
 
@interface NSObject(MyGreatImageAppBitmapGraphicsFiltering)
 
// REQUIRED
// Returns the version of the interface you're implementing.
// Return 0 here or future versions may look for features you don't have!
- (unsigned)interfaceVersion;
 
// OPTIONAL
// Returns what to display in the Filter menu. Defaults to the plug-in
// filename without the extension.
- (NSString *)menuItemString;
 
// REQUIRED
// The main worker bee: filters the bitmap and returns a modified version.
- (NSBitmapImageRep *)filteredImageRep:(NSBitmapImageRep *)imageRep;
 
// OPTIONAL
// Returns the window controller for the settings configuration window.
// If this method is not implemented, no Settings option is provided.
- (NSWindowController *)configurationWindowController;
 
@end

Публикация интерфейса плагина базового класса

Если Вы хотите обеспечить совместно использованную функциональность для всех плагинов, можно хотеть обеспечить базовый класс для сменных основных классов для наследования от. Например, интерфейс экранной заставки OS X предоставлен как платформа, содержащая базовый класс ScreenSaverView, обрабатывающий много подробных данных управления для плагинов экранной заставки. Для распределения базового класса лучшее решение состоит в том, чтобы упаковать его как платформу для сменных разработчиков для соединения против.

Перечисление 3 показывает интерфейс для гипотетической встраивающей сменной архитектуры, и Перечисление 4 показывает свою реализацию. Вместо протокола, интерфейс предоставлен как базовый класс, обеспечивающий некоторую функциональность, в этом примере довольно простой подкласс NSView, возвращающего информацию о версии, поддерживает URL, содержащий источник данных, и рисует белый фон. Обратите внимание на то, что класс содержит три зарезервированных указателя. Они позволяют базовому классу добавлять новые задействованные данные, не изменяя размер объекта, таким образом позволяя разработчику хост-приложения добавить опции, не создавая двоичные несовместимости.

  Базовый класс перечисления 3 взаимодействует через интерфейс для сменной архитектуры

#import <Cocoa/Cocoa.h>
 
@interface MyAppEmbeddingView : NSView
{
@private
    NSURL *_URL;
    void *_reserved1;
    void *_reserved2;
    void *_reserved3;
}
 
- (id)initWithFrame:(NSRect)frameRect URL:(NSURL)URL;
- (unsigned)interfaceVersion;
- (NSURL *)URL;
- (void)setURL:(NSURL *)URL;
 
@end

  Реализация Базового класса перечисления 4 для сменной архитектуры

#import "MyAppEmbeddingView.h"
 
@implementation MyAppEmbeddingView
 
- (id)initWithFrame:(NSRect)frameRect URL:(NSURL)URL
{
    self = [self initWithFrame:frameRect];
    [self setURL:URL];
    return self
}
 
- (unsigned)interfaceVersion
{
    return 0;
}
 
- (void)drawRect:(NSRect)rect
{
    NSEraseRect(rect);
}
 
- (NSURL *)URL
{
    return _URL;
}
 
- (void)setURL:(NSURL *)URL
{
    [URL retain];
    [_URL release];
    _URL = URL;
}
 
@end

Как только Вы закончили свой базовый класс, необходимо упаковать скомпилированную реализацию с заголовочным файлом в платформе для сменных разработчиков для использования. Для создания платформы используйте шаблон проекта Платформы Какао XCode. Удостоверьтесь, что Вы определяете частные и общедоступные заголовочные файлы, как Вы предназначаете в разделе Build Phases> Headers области Target Settings. Для получения дополнительной информации о создании платформ, см. “Платформы создания и Библиотеки” в Справке XCode.

Загрузка плагинов

Для использования плагинов приложение должно перейти через этот процесс:

  1. Найдите доступные плагины в стандартных расположениях

  2. Загрузите исполняемый код для каждого плагина

  3. Проверьте соответствие каждого плагина к сменному интерфейсу

  4. Инстанцируйте допустимых плагинов

Определенные подробные данные открытия, загрузки и инстанцирования плагинов совпадают с описанный в Загружающихся Пакетах Какао с NSBundle. Дополнительный шаг для сменной архитектуры должен проверить тот каждый плагин, соответствует интерфейсу, который Вы опубликовали для сменных разработчиков для использования.

Проверка немного отличается в зависимости от того, использует ли Ваша сменная архитектура формальный протокол, неофициальный протокол или базовый класс.

Проверка плагинов

Для формального протокола Вы запрашиваете класс, чтобы видеть, реализует ли он протокол. Для сейфа необходимо также выполнить проверку в реальных условиях, чтобы видеть, что она действительно реализует методы, которые она утверждает, что реализовала. Перечисление 5 показывает реализацию для метода, проверяющего, соответствует ли основной класс плагина MyGreatImageAppBitmapFiltering протокол, определенный в Перечислении 1, и дополнительно, проверяет, чтобы удостовериться, что это реализует все необходимые методы экземпляра.

  Проверка Плагина перечисления 5 (формальный протокол)

- (BOOL)plugInClassIsValid:(Class)plugInClass
{
    if([plugInClass
        conformsToProtocol:@protocol(MyGreatImageAppBitmapFiltering)])
    {
        if([plugInClass instancesRespondToSelector:
                        @selector(interfaceVersion)] &&
           [plugInClass instancesRespondToSelector:
                        @selector(menuItemString)] &&
           [plugInClass instancesRespondToSelector:
                        @selector(filteredImageRep)] &&
           [plugInClass instancesRespondToSelector:
                        @selector(configurationWindowController])
        {
            return YES;
        }
    }
 
    return NO;
}

Для неофициального протокола необходимо запросить класс для наблюдения, какие методы он реализует. Как правило, разработчики хост-приложения определяют определенные методы как требуется и других как дополнительные, таким образом, метод проверки должен различить два должным образом. Перечисление 6 дает реализацию для неофициальной версии сменного метода проверки. Метод удостоверяется, что плагин реализует все требуемые методы от неофициальной версии MyGreatImageAppBitmapFiltering данный в Перечислении 2. Можно проверить на дополнительные методы, поскольку они необходимы в другом месте в приложении.

  Проверка Плагина перечисления 6 (неофициальный протокол)

- (BOOL)plugInClassIsValid:(Class)plugInClass
{
    if([plugInClass instancesRespondToSelector:
                    @selector(interfaceVersion)] &&
       [plugInClass instancesRespondToSelector:
                    @selector(filteredImageRep)])
    {
        return YES;
    }
 
    return NO;
}

Плагин, наследовавшийся от базового класса, является самым простым проверить. Вы просто запрашиваете основной класс плагина, чтобы видеть, является ли это подкласс базового класса, как показано в Перечислении 7.

  Проверка Плагина перечисления 7 (базовый класс)

- (BOOL)plugInClassIsValid:(Class)plugInClass
{
    if([plugInClass isSubclassOfClass:[MyAppEmbeddingView class]])
    {
        return YES;
    }
 
    return NO;
}

Загрузка плагинов: пример кода

Перечисление 8 является немного измененной версией Перечисления 1, проверяющего плагины прежде, чем добавить их, обозначенный граничным комментарием // Validation. Для полного описания того, как код работает, посмотрите исходную версию.

  Реализация перечисления 8 для сменных методов загрузки

NSString *ext = @"plugin";
NSString *appSupportSubpath = @"Application Support/KillerApp/PlugIns";
 
// ...
 
- (void)loadAllPlugins
{
    NSMutableArray *instances;
    NSMutableArray *bundlePaths;
    NSEnumerator *pathEnum;
    NSString *currPath;
    NSBundle *currBundle;
    Class currPrincipalClass;
    id currInstance;
 
    bundlePaths = [NSMutableArray array];
    if(!instances)
    {
        instances = [[NSMutableArray alloc] init];
    }
 
    [bundlePaths addObjectsFromArray:[self allBundles]];
 
    pathEnum = [bundlePaths objectEnumerator];
    while(currPath = [pathEnum nextObject])
    {
        currBundle = [NSBundle bundleWithPath:currPath];
        if(currBundle)
        {
            currPrincipalClass = [currBundle principalClass];
            if(currPrincipalClass &&
               [self plugInClassIsValid:currPrincipalClass])  // Validation
            {
                currInstance = [[currPrincipalClass alloc] init];
                if(currInstance)
                {
                    [instances addObject:[currInstance autorelease]];
                }
            }
        }
    }
}
 
- (NSMutableArray *)allBundles
{
    NSArray *librarySearchPaths;
    NSEnumerator *searchPathEnum;
    NSString *currPath;
    NSMutableArray *bundleSearchPaths = [NSMutableArray array];
    NSMutableArray *allBundles = [NSMutableArray array];
 
    librarySearchPaths = NSSearchPathForDirectoriesInDomains(
        NSLibraryDirectory, NSAllDomainsMask - NSSystemDomainMask, YES);
 
    searchPathEnum = [librarySearchPaths objectEnumerator];
    while(currPath = [searchPathEnum nextObject])
    {
        [bundleSearchPaths addObject:
            [currPath stringByAppendingPathComponent:appSupportSubpath]];
    }
    [bundleSearchPaths addObject:
        [[NSBundle mainBundle] builtInPlugInsPath]];
 
    searchPathEnum = [bundleSearchPaths objectEnumerator];
    while(currPath = [searchPathEnum nextObject])
    {
        NSDirectoryEnumerator *bundleEnum;
        NSString *currBundlePath;
        bundleEnum = [[NSFileManager defaultManager]
            enumeratorAtPath:currPath];
        if(bundleEnum)
        {
            while(currBundlePath = [bundleEnum nextObject])
            {
                if([[currBundlePath pathExtension] isEqualToString:ext])
                {
                 [allBundles addObject:[currPath
                           stringByAppendingPathComponent:currBundlePath]];
                }
            }
        }
    }
 
    return allBundles;
}