Загрузка пакетов

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

Загрузка пакетов какао с NSBundle

Класс NSBundle обеспечивает методы для загрузки исполняемого кода и ресурсов от пакетов Какао. Это обрабатывает все подробности загрузки, включая взаимодействие с Мужественным загрузчиком dyld и загрузка символов Objective C во время выполнения Objective C.

Для получения информации об использовании NSBundle для загрузки ресурсов некода см. Руководство по программированию Ресурса.

Загрузка пакетов Какао состоит из пяти основных шагов:

  1. Найдите пакет.

  2. Создайте объект NSBundle представлять пакет.

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

  4. Запросите пакет для его основного класса.

  5. Инстанцируйте объекта основного класса.

Следующие разделы покрывают каждый из этих шагов подробно.

Определение местоположения пакетов

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

Загружаемые пакеты, упаковывающиеся с Вашими приложениями, обычно включаются в комплекте приложений в Contents/PlugIns. Для получения сменного каталога для пакета главного приложения используйте NSBundle’s builtInPlugInsPath метод.

Этот фрагмент кода показывает, как использовать NSBundle для получения сменного каталога приложения, который можно назвать PlugIns или Plug-ins (прежний заменяет последнего):

NSBundle *appBundle;
NSString *plugInsPath;
 
appBundle = [NSBundle mainBundle];
plugInsPath = [appBundle builtInPlugInsPath];

Несмотря на то, что это не стандартное расположение, можно получить некоторое удобство путем хранения загружаемых пакетов в комплекте приложений Resources каталог. Тогда можно использовать NSBundle’s pathsForResourcesOfType:inDirectory: метод для нахождения их. Этот фрагмент кода находит все файлы и каталоги с расширением .bundle в приложении Resources/PlugIns каталог:

NSBundle *appBundle;
NSArray *bundlePaths;
 
appBundle = [NSBundle mainBundle];
bundlePaths = [appBundle pathsForResourcesOfType:@"bundle"
               inDirectory:@"PlugIns"];

Ваше приложение может также поддерживать пакеты в каталогах поддержки приложений в Library каталог в многократных доменах: специфичный для пользователя (~/Library), в масштабе всей системы (/Library), сеть (/Network/Library). Для поиска этих и других стандартных каталогов используйте NSSearchPathForDirectoriesInDomains функция.

Этот фрагмент кода создает массив путей поиска для Вашего приложения для нахождения пакетов, которые можно тогда искать отдельные плагины:

NSString *appSupportSubpath = @"Application Support/KillerApp/PlugIns";
NSArray *librarySearchPaths;
NSEnumerator *searchPathEnum;
NSString *currPath;
NSMutableArray *bundleSearchPaths = [NSMutableArray array];
 
// Find Library directories in all domains except /System
librarySearchPaths = NSSearchPathForDirectoriesInDomains(
    NSLibraryDirectory, NSAllDomainsMask - NSSystemDomainMask, YES);
 
// Copy each discovered path into an array after adding
// the Application Support/KillerApp/PlugIns subpath
searchPathEnum = [librarySearchPaths objectEnumerator];
while(currPath = [searchPathEnum nextObject])
{
    [bundleSearchPaths addObject:
        [currPath stringByAppendingPathComponent:appSupportSubpath]];
}

Создание объекта NSBundle

Для создания NSBundle возражают для пакета, который Вы хотите загрузить, любой выделяет объект и использует initWithPath: инициализатор или использование метод создания удобства bundleWithPath:. Если экземпляр уже существует для пакета, оба из этих методов возвращают существующий экземпляр вместо того, чтобы создать новый.

Этот фрагмент кода получает пакет, расположенный в fullPath:

NSString *fullPath; // Assume this exists.
NSBundle *bundle;
 
bundle = [NSBundle bundleWithPath:fullPath];

Загрузка кода

Для загрузки исполняемого кода пакета используйте NSBundle’s load метод. Этот метод возвраты YES если код был уже загружен, и, если загрузка была успешна или NO иначе.

Этот фрагмент кода загружает код для пакета в fullPath:

NSString *fullPath; // Assume this exists.
NSBundle *bundle;
 
bundle = [NSBundle bundleWithPath:fullPath];
[bundle load];

Получение основного класса

Каждый пакет Какао содержит код для основного класса, обычно служащего точкой входа приложения в пакет. Вы получаете основной класс пакета с NSBundle’s principalClass метод, загружающий пакет, если это уже не загружается. Этот фрагмент кода получает основной класс для пакета, расположенного в fullPath:

NSString *fullPath; // Assume this exists.
NSBundle *bundle;
Class principalClass;
 
bundle = [NSBundle bundleWithPath:fullPath];
principalClass = [bundle principalClass];
 

Можно также получить объекты класса по имени с classNamed: метод. Этот фрагмент кода получает класс KillerAppController от пакета в fullPath:

NSString *fullPath; // Assume this exists.
NSBundle *bundle;
Class someClass;
 
bundle = [NSBundle bundleWithPath:fullPath];
someClass = [bundle classNamed:@"KillerAppController"];

Инстанцирование основного класса

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

Этот фрагмент кода получает основной класс пакета в fullPath и создает экземпляр основного класса:

NSString *fullPath; // Assume this exists.
NSBundle *bundle;
Class principalClass;
id instance;
 
bundle = [NSBundle bundleWithPath:fullPath];
principalClass = [bundle principalClass];
instance = [[principalClass alloc] init];

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

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

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

NSString *ext = @"bundle";
NSString *appSupportSubpath = @"Application Support/KillerApp/PlugIns";
 
// ...
 
- (void)loadAllBundles
{
    NSMutableArray *instances;                                         // 1
    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]];               // 2
 
    pathEnum = [bundlePaths objectEnumerator];
    while(currPath = [pathEnum nextObject])
    {
        currBundle = [NSBundle bundleWithPath:currPath];               // 3
        if(currBundle)
        {
            currPrincipalClass = [currBundle principalClass];          // 4
            if(currPrincipalClass)
            {
                currInstance = [[currPrincipalClass alloc] init];      // 5
                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;
}

Вот то, как работает код:

  1. instances массив содержит все объекты, которые инстанцируют от основных классов обнаруженных пакетов. Этот объект показан в методе для ясности, но обычно был бы переменной экземпляра класса контроллера.

  2. loadAllBundles вызовы метода allBundles метод для получения всех файлов, заканчивающихся расширением .bundle. allBundles метод просто перечисляет через все стандартные пути для загружаемых пакетов (в комплекте приложений и в пользователе, локальном, и сетевом Library каталоги).

  3. Для каждого возвращенного пути создается объект NSBundle. Если файл с a .bundle расширение не было фактически допустимым пакетом, возвратами NSBundle nil и остальная часть итерации пропускается.

  4. Эта строка получает основной класс текущего пакета. Вызов principalClass неявно загружает код сначала.

  5. Наконец, метод инстанцирует основного класса. Пока init не возвращается nil, новый экземпляр добавляется к instances массив. Если Вы пишете приложение со сменной архитектурой (в противоположность приложению с несколькими известными загружаемыми пакетами), необходимо выполнить некоторую проверку на плагинах прежде, чем создать экземпляр основного класса.

Загрузка пакетов некакао с CFBundle

В некоторых случаях Вы, возможно, должны загрузить пакеты некакао из приложения Какао. Вы используете подпрограммы CFBundle в Базовой Основе для загрузки пакетов некакао: CFBundleCreate создать объекты CFBundle; CFBundleLoadExecutable загрузить исполняемый код пакета; и CFBundleGetFunctionPointerForName искать адрес загруженной подпрограммы. Посмотрите, что Базовая Основа Программирует Руководство по программированию Пакета Темы для получения дополнительной информации об этих методах и других методах, предоставленных CFBundle.

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

Перечисление 2 показывает интерфейс для класса обертки Какао для CFBundle, и Перечисление 3 показывает свою реализацию. Объяснение следует за каждым перечислением.

  Загрузка перечисления 2 и использование кода от пакета некакао

#import <CoreFoundation/CoreFoundation.h>
 
typedef long (*DoSomethingPtr)(long);                                  // 1
typedef void (*DoSomethingElsePtr)(void);
 
@interface MyBundleWrapper : NSObject
{
    DoSomethingPtr doSomething;                                        // 2
    DoSomethingElsePtr doSomethingElse;
 
    CFBundleRef cfBundle;                                              // 3
}
 
- (long)doSomething:(long)arg;                                         // 4
- (void)doSomethingElse;
 
@end

Интерфейс содержит четыре элемента:

  1. Введите определения для указателей функции, один для каждой функции в пакете

  2. Переменные экземпляра указателя функции

  3. A CFBundleRef переменная экземпляра

  4. Методы Objective C для обертывания функций C

  Загрузка перечисления 3 и использование кода от пакета некакао

#import "MyBundleWrapper.h"
 
@implementation MyBundleWrapper
 
- (id)init
{
    NSString *bundlePath;
    NSURL *bundleURL;
 
    self = [super init];
 
    bundlePath = [[[NSBundle mainBundle] builtInPlugInsPath]           // 1
                    stringByAppendingPathComponent:@"MyCFBundle.bundle"];
    bundleURL = [NSURL fileURLWithPath:bundlePath];
    cfBundle = CFBundleCreate(kCFAllocatorDefault, (CFURLRef)bundleURL);
 
    return self;
}
 
- (void)dealloc
{
    CFRelease(cfBundle);
}
 
- (long)doSomething:(long)arg
{
    if(!doSomething)                                                   // 2
    {
        doSomething = CFBundleGetFunctionPointerForName(cfBundle,
                                           CFSTR("DoSomething"));
    }
    return doSomething(arg);                                           // 3
}
 
- (void)doSomethingElse
{
    if(!doSomethingElse)                                               // 2
    {
        doSomethingElse = CFBundleGetFunctionPointerForName(cfBundle,
                                           CFSTR("DoSomethingElse"));
    }
    doSomethingElse();                                                 // 3
}
 
@end

Вот то, что делает реализация:

  1. Инициализирует cfBundle переменная экземпляра с URL к пакету в каталоге плагинов приложения. Пакет может находиться где угодно на диске; каталог плагинов является просто типичным расположением для встроенных загружаемых пакетов.

  2. Когда метод вызывают, лениво инициализирует указатель функции, связанный с методом. Вызов к CFBundleGetFunctionPointerForName неявно загружает исполняемый код пакета перед поиском указателя функции.

  3. Возвращает значение, возвращенное загруженной функцией.