Objective-C Runtime для Си-шников. Часть 3

в 17:26, , рубрики: iOS, objective-c, OS X, разработка под iOS, Разработка под OS X

image

Всем привет. Сегодня я продолжу рассказывать вам о внутреннем устройстве Objective-C Runtime, а конкретно — о его реализации на уровне языка C.

В прошлых статьях мы с вами подробно разобрались с селекторами и механизмом посылки сообщений объектам и классам. Сегодня я хотел бы закончить с сообщениями и рассказать о принципах работы некоторых встроенных возможностей языка Objective C.

Тем, с кем мы ещё не знакомы, я предлагаю для начала прочитать первую и вторую части, а прочитавших и заинтересовавшихся прошу под кат.

Ещё немного об objc_msgSend()

Документация невозбранно лжёт нам, что существует целых четыре функции objc_msgSend():

  • objc_msgSend()
  • objc_msgSend_stret()
  • objc_msgSendSuper()
  • objc_msgSendSuper_stret()

Переведу кусочек этой документации:

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

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

.long	_objc_msgSend
.long	_objc_msgSend_fpret
.long	_objc_msgSend_stret
.long	_objc_msgSendSuper
.long	_objc_msgSendSuper_stret

, а для arm64 всего лишь так:

.quad   _objc_msgSend
.quad   _objc_msgSendSuper
.quad   _objc_msgSendSuper2

Функции с суффиксом «stret» используются для тех методов, которые возвращают переменные сложного типа (структуры), в то время как функции без такого суффикса возвращают значения простых типов. В качестве примера можно привести следующий код:

#import <Foundation/Foundation.h>
#import <objc/runtime.h>

struct TestStruct {
  long firstValue;
  long secondValue;
  long thirdValue;
};

@interface TestClass : NSObject
@end

@implementation TestClass
+ (struct TestStruct)someMethod {
  struct TestStruct * s = malloc(sizeof(struct TestStruct));
  return *s; // returns a whole struct
}
+ (struct TestStruct *)anotherMethod {
  struct TestStruct * s = malloc(sizeof(struct TestStruct));
  return s; // returns just a pointer
}
@end

int main(int argc, const char * argv[]) {
  // objc_msgSend_stret()
  struct TestStruct s = [TestClass someMethod];
  
  // objc_msgSend()
  struct TestStruct * ps = [TestClass anotherMethod];
  
  return 0;
}

Функции с суффиксом «Super» используются при вызове методов родительских классов, например:

#import <Foundation/Foundation.h>
#import <objc/runtime.h>

@interface TestClass : NSObject
@end

@implementation TestClass
- (NSString *)description {
  // objc_msgSendSuper2()
  return [super description];
}
@end

int main(int argc, const char * argv[]) {
  TestClass * myObj = [[TestClass alloc] init];
  [myObj description];
  return 0;
}

И, наконец, функции с суффиксом «fpret» используются там, где нужно вернуть простой тип, но который не умещается в регистр процессора, как например «long double»:

#import <Foundation/Foundation.h>
#import <objc/runtime.h>

@interface TestClass : NSObject
@end

@implementation TestClass
+ (long double)someMethod {
  return 0.0;
}
@end

int main(int argc, const char * argv[]) {
  // objc_msgSend_fpret()
  [TestClass someMethod];
  return 0;
}

Конечно, всегда можно рассказать о механизме сообщений Objective C еще что-то, но мне хотелось бы закончить с ним именно сейчас. Поэтому, если у вас возникли какие-либо вопросы, вы всегда можете узнать ответы на них в исходных кодах Objective C Runtime. А теперь давайте двигаться дальше.

Применяем полученные знания и делаем выводы

Можно долго рассуждать на тему того, тратим мы с вами время впустую или действительно занимаемся делом. Однако, например, теперь реализация удалённого вызова процедур на Objective C для нас с вами является достаточно просто задачей. По крайней мере, согласитесь, вы сразу мысленно представили как это реализовать:

  1. Получаем по сети имя метода
  2. Берем селектор
  3. Вызываем нужную функцию
  4. PROFIT!!!!

Однако, наше дело — разбираться дальше и не задавать лишних вопросов, кроме одного: а можно ли добавлять методы к классам прямо во время выполнения? Конечно можно!

Те, кто уже имеет опыт разработки приложений на Objective C, сразу вспомнили такой механизм как категории. Категории позволяют расширять функциональность любых классов даже не имея их исходных файлов. Например, вот таким вот образом можно добавлять новые методы к классу NSObject:

#import <Foundation/Foundation.h>

@interface NSObject (APObjectMapping)
+ (NSMutableDictionary *)objectMapping;
- (instancetype)initWithDictionary:(NSDictionary *)dictionary;
- (NSDictionary *)mapToDictionary;
@end

Очевидно, что во время исполнения новые методы просто будут добавлены в dispatch table класса NSObject. А значит все остальные классы, наследуемые от NSObject, также получат добавленные нами методы. Красота да и только!

Давайте попробуем сделать то же самое без использования категорий, исключительно средствами языка C. Для этого воспользуемся функцией class_addMethod:

#import <Foundation/Foundation.h>
#import <objc/runtime.h>

void appleSecret(id self, SEL _cmd) {
  NSLog(@"Tim Cook is so gay...");
}

int main(int argc, const char * argv[]) {
  class_addMethod([NSObject class], @selector(appleSecret), (IMP)appleSecret, "v@:");
  NSObject * myObj = [[NSObject alloc] init];
  [myObj performSelector:@selector(appleSecret)];
  return 0;
}

С первыми тремя параметрами функции class_addMethod() все и так понятно, а вот четвертый — это спецификация аргументов нашей функции. В данном случае «v» означает, что тип возвращаемого значения — void, "@" — первый параметр функции типа «объект», и ":" — второй параметр функции типа «селектор». Например, если бы наша функция принимала еще один параметр типа int, то её спецификация выглядела бы так: «v@:i».

Подводим итоги

Механизм сообщений это сердце языка Objective C. Именно этот механизм предоставляет в языке уровня самого обычного Си все те прелести, к которым мы так привыкли в Java, C#, Python и т.д. Разобравшись с этим механизмом, мы стали понимать как работают те или иные возможности Objective C, такие как категории. Мы так же при желании можем понять каким образом устроены протоколы.

На этом я предлагаю временно закончить с механизмом сообщений и разобраться в других базовых механизмах моего любимого языка, о которых пойдёт речь в следующих статьях этого цикла.

Автор: aperechnev

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js