Главная
Блог разработчиков phpBB
 
+ 17 предустановленных модов
+ SEO-оптимизация форума
+ авторизация через соц. сети
+ защита от спама

Objective-C Runtime. Теория и фактическое использование

Anna | 2.07.2014 | нет комментариев
В данном посте я хочу обратиться к теме, о которой многие начинающие iPhone-разработчики Зачастую имеют смутное представление: Objective-C Runtime. Многие знают, что он существует, но каковы его вероятности и как его применять на практике?
Испробуем разобраться в базовых функциях этой библиотеки. Материал основан на лекциях, которые мы вCoalla используем для обучения работников.

Что такое Runtime?

Objective-C задумывался как надстройка над языком C, добавляющая к нему поддержку объектно-ориентированной парадигмы. Реально, с точки зрения синтаксиса, Objective-C — это довольно маленький комплект ключевых слов и руководящих конструкций над обыкновенным C. Именно Runtime, библиотека времени выполнения, предоставляет тот комплект функций, которые вдыхают в язык жизнь, реализуя его динамические вероятности и обеспечивая функционирование ООП.

Базовые конструкции данных

Функции и конструкции Runtime-библиотеки определены в нескольких заголовочных файлах: objc.h,runtime.h и message.h. Вначале обратимся к файлу objc.h и посмотрим, что представляет из себя объект с точки зрения Runtime:

typedef struct objc_class *Class;
typedef struct objc_object {
    Class isa;
} *id; 

Мы видим, что объект в процессе работы программы представлен обыкновенной C-конструкцией. Всякий Objective-C объект имеет ссылку на свой класс — так называемый isa-указатель. Думаю, все видели его при просмотре конструкции объектов во время отладки приложений. В свою очередь, класс также представляет из себя аналогичную конструкцию:

struct objc_class {
    Class isa;
};

Класс в Objective-C — это полновесный объект и у него тоже присутствует isa-указатель на «класс класса», так называемый метакласс в терминах Objective-C. Подобно, С-конструкции определены и для других сущностей языка:

typedef struct objc_selector *SEL;
typedef struct objc_method *Method;
typedef struct objc_ivar *Ivar;
typedef struct objc_category *Category;
typedef struct objc_property *objc_property_t;
Функции Runtime-библиотеки

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

  • Манипулирование классами: class_addMethodclass_addIvarclass_replaceMethod
  • Создание новых классов: class_allocateClassPairclass_registerClassPair
  • Интроспекция: class_getNameclass_getSuperclassclass_getInstanceVariableclass_getProperty,class_copyMethodListclass_copyIvarListclass_copyPropertyList
  • Манипулирование объектами: objc_msgSendobjc_getClassobject_copy
  • Работа с ассоциативными ссылками
Пример 1. Интроспекция объекта

Разглядим пример применения Runtime библиотеки. В одном из наших планов модель данных представляет собой plain old Objective-C объекты с некоторым комплектом свойств:

@interface COConcreteObject : COBaseObject

@property(nonatomic, strong) NSString *name;
@property(nonatomic, strong) NSString *title;
@property(nonatomic, strong) NSNumber *quantity;

@end

Для комфорта отладки хотелось бы, Дабы при итоге в лог печаталась информация о состоянии свойств объекта, а не что-то как бы <COConcreteObject: 0x71d6860>. От того что модель данных довольно разветвленная, с огромным числом разных подклассов, неугодно писать для всякого класса обособленный способ description, в котором вручную собирать значения его свойств. На поддержка приходит Objective-C Runtime:

@implementation COBaseObject

- (NSString *)description {
    NSMutableDictionary *propertyValues = [NSMutableDictionary dictionary];
    unsigned int propertyCount;
    objc_property_t *properties = class_copyPropertyList([self class], &propertyCount);
    for (unsigned int i = 0; i < propertyCount; i  ) {
        char const *propertyName = property_getName(properties[i]);
        const char *attr = property_getAttributes(properties[i]);
        if (attr[1] == '@') {
            NSString *selector = [NSString stringWithCString:propertyName encoding:NSUTF8StringEncoding];
            SEL sel = sel_registerName([selector UTF8String]);
            NSObject * propertyValue = objc_msgSend(self, sel);
            propertyValues[selector] = propertyValue.description;
        }
    }
    free(properties);
    return [NSString stringWithFormat:@"%@: %@", self.class, propertyValues];
}

@end

Способ, определенный в всеобщем суперклассе объектов модели, получает список всех свойств объекта с поддержкой функции class_copyPropertyList. После этого значения свойств собираются в NSDictionary, тот, что и применяется при построении строкового представления объекта. Данный алгорифм раработает только со свойствами, которые являются Objective-C объектами. Проверка типа осуществляется с применением функции property_getAttributes. Итог работы способа выглядит приблизительно так:

2013-05-04 15:54:01.992 Test[40675:11303] COConcreteObject: {
name = Foo;
quantity = 10;
title = bar;
}

Сообщения

Система вызова способов в Objective-C реализована через посылку сообщений объекту. Всякий вызов способа транслируется в соответствующий вызов функции objc_msgSend:

// Вызов способа
[array insertObject:foo atIndex:1];
// Соответствующий ему вызов Runtime-функции
objc_msgSend(array, @selector(insertObject:atIndex:), foo, 1);

Вызов objc_msgSent инициирует процесс поиска реализации способа, соответствующего селектору, переданному в функцию. Реализация способа ищется в так называемой таблице диспетчеризации класса. От того что данный процесс может быть довольно продолжительным, с всяким классом ассоциирован кеш способов. Позже первого вызова всякого способа, итог поиска его реализации будет закеширован в классе. Если реализация способа не обнаружена в самом классе, дальше поиск продолжается вверх по иерархии наследования — в суперклассах данного класса. Если же и при поиске по иерархии итог не достигнут, в дело вступает механизм динамического поиска — вызывается один из особых способов: resolveInstanceMethodлибо resolveClassMethod. Переопределение этих способов — одна из последних вероятностей повлиять на Runtime:

  (BOOL)resolveInstanceMethod:(SEL)aSelector {
    if (aSelector == @selector(myDynamicMethod)) {
        class_addMethod(self, aSelector, (IMP)myDynamicIMP, "v@:");
        return YES;
    }
    return [super resolveInstanceMethod:aSelector];
}

Тут вы можете динамически указать свою реализацию вызываемого способа. Если же данный механизм по каким-то причнам вас не устраивает — вы можете применять форвардинг сообщений.

Пример 2. Method Swizzling

Одна из особенностей категорий в Objective-C — способ, определенный в категории, всецело перекрывает способ базового класса. Изредка нам требуется не переопределить, а расширить функционал имеющегося способа. Пускай, скажем, по каким-то причинам нам хочется залогировать все добавления элементов в массив NSMutableArray. Стандартными средствами языка этого сделать не получится. Но мы можем применять прием под наименованием method swizzling:

@implementation NSMutableArray (CO)

  (void)load {
    Method addObject = class_getInstanceMethod(self, @selector(addObject:));
    Method logAddObject = class_getInstanceMethod(self, @selector(logAddObject:));
    method_exchangeImplementations(addObject, logAddObject);
}

- (void)logAddObject:(id)aObject {
    [self logAddObject:aObject];
    NSLog(@"Добавлен объект %@ в массив %@", aObject, self);
}

@end

Мы перегружаем способ load — это особый callback, тот, что, если он определен в классе, будет вызван во время инициализации этого класса — до вызова всякого из других его способов. Тут мы меняем местами реализацию базового способа addObject: и нашего способа logAddObject:. Обратите внимание на «рекурсивный» вызов в logAddObject: — это и есть обращение к перегруженной реализации основного способа.

Пример 3. Ассоциативные ссылки

Еще одним вестимым лимитацией категорий является неосуществимость создания в них новых переменных экземпляра. Пускай, скажем, вам требуется добавить новое качество к библиотечному классу UITableView — ссылку на «заглушку», которая будет показываться, когда таблица пуста:

@interface UITableView (Additions)

@property(nonatomic, strong) UIView *placeholderView;

@end

«Из коробки» данный код трудиться не будет, вы получите исключение во время выполнения программы. Эту загвоздку дозволено обойти, применяя функционал ассоциативных ссылок:

static char key;

@implementation UITableView (Additions)

-(void)setPlaceholderView:(UIView *)placeholderView {
    objc_setAssociatedObject(self, &key, placeholderView, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

-(UIView *) placeholderView {
    return objc_getAssociatedObject(self, &key);
}

@end

Всякий объект вы можете применять как ассоциативный массив, объединяя с ним другие объекты с поддержкой функции objc_setAssociatedObject. Для ее работы требуется ключ, по которому вы потом сумеете извлечь необходимый вам объект назад, применяя вызов objc_getAssociatedObject. При этом вы не можете применять скопированное значение ключа — это должен быть именно тот объект (в примере — указатель), тот, что был передан в вызове objc_setAssociatedObject.

Завершение

Сейчас вы располагаете базовым представлением о том, что такое Objective-C Runtime и чем он может быть пригоден разработчику на практике. Для желающих узнать вероятности библиотеки глубже, могу порекомендовать следующие добавочные источники:

Источник: programmingmaster.ru

Оставить комментарий
Форум phpBB, русская поддержка форума phpBB
Рейтинг@Mail.ru 2008 - 2017 © BB3x.ru - русская поддержка форума phpBB