- PVSM.RU - https://www.pvsm.ru -

До чего доводит идея (Objective-C) — target-action на блоках и много рантайма

Пришла мне как-то в голову идея, а можно ли взять блок и отдать для target-action?
Есть готовые решения, как к примеру BlocksKit [1] и другие библиотеки, однако их решение заключается в сохранении блока, установкой таргета и вызова блока из указанного селектора.

Зачем тогда нужна эта статья?

Я захотел создать способ генерации селектора, по которому будет вызван блок. Что здесь сложного, скажете вы? imp_implementationWithBlock + class_addMethod и дело закрыто. Но при этом подходе есть одно серьезное требование, это первый аргумент блока — владелец метода.

Как обойти это требование и сделать такое?

    [button addTarget:self action:[self ax_lambda:^(UIButton *sender, UIEvent *event){
        NSLog(@"click on button %@, event = %@", sender, event);
    }] forControlEvents:UIControlEventTouchUpInside];

    [button addTarget:self action:[self ax_lambda:^{
        NSLog(@"click");
    }] forControlEvents:UIControlEventTouchUpInside];

Или даже вот так

    __block NSInteger sum = 0;
    [self performSelector:[self ax_lambda:^(NSNumber *argA, NSNumber *argB) {
        sum = [argA integerValue] + [argB integerValue];
    }] withObject:@(2) withObject:@(3)];
    //sum — 5

    SEL selSum = [self ax_lambda:^NSInteger(NSInteger argA, NSInteger argB){
        return argA + argB;
    }];
    NSInteger(*funcSum)(id, SEL, NSInteger, NSInteger) = (NSInteger(*)(id, SEL, NSInteger, NSInteger))objc_msgSend;
    NSInteger sum2 = funcSum(self, selSum, 2, 3);
    //sum2 — 5

Реализация оказалась настолько интересной, что я решил написать об этом.

По сути, основная задача это избавиться от первого аргумента self в вызове блока. Это корневая проблема всего решения (жаль, что не единственная).
Ранее я уже немного писал о блоках [2], и отметил, что блок — это объект, а значит вызов будет происходить через NSInvocation.
Если получить момент вызова блока и в NSInvocation убрать аргумент self (сдвинув аргументы), то тогда я получу желаемый результат.

Дальше надо будет разбираться по ходу дела.

AXProxyBlock

Вопрос, как вклиниться в момент вызова блока? Как вообще получить момент вызова блока?
Очень часто я пишу эту фразу, но блок — это объект. Объект в objc в конечном виде это структура. Раз id — это указатель на структуру, позволено и обратное (__bridge, привет) [3].
Получается, можно создать фейковый блок. Ну или прокси для блока.

Интерфейс моего класса получился следующий:

typedef void(^AXProxyBlockInterpose)(NSInvocation *invocation);


@interface AXProxyBlock : NSProxy

+ (instancetype)initWithBlock:(id)block;

- (void)setBeforeInvoke:(AXProxyBlockInterpose)beforeInvoke;

- (NSString *)blockSignatureStringCTypes;

@end

Как можно догадаться, setBeforeInvoke принимает блок, в котором можно делать «магические» преобразования аргументов блока.
blockSignatureStringCTypes возвращает сигнатуру проксируемого блока. Зачем он в заголовочном файле? Об этом позднее.

ссылка на страницу документации о том, что сейчас начнется [4]

Для начала по документации создадим структуры блока и перечисление с нашими наименованиями.

typedef struct AXBlockStruct_1 {
    unsigned long int reserved;
    unsigned long int size;
    
    void (*copy_helper)(void *dst, void *src);
    void (*dispose_helper)(void *src);
    
    const char *signature;
} AXBlockStruct_1;

typedef struct AXBlockStruct {
    void *isa;
    int flags;
    int reserved;
    void (*invoke)(void *, ...);
    struct AXBlockStruct_1 *descriptor;
} AXBlockStruct;


typedef NS_ENUM(NSUInteger, AXBlockFlag) {
    AXBlockFlag_HasCopyDispose = (1 << 25),
    AXBlockFlag_HasCtor = (1 << 26),
    AXBlockFlag_IsGlobal = (1 << 28),
    AXBlockFlag_HasStret = (1 << 29),
    AXBlockFlag_HasSignature = (1 << 30)
};

А теперь займемся нашим классом.

Нужно сделать соответствующее строение:

@interface AXProxyBlock ()  {

//  isa поле уже имеется в реализации NSProxy, а остальные поля добавим
    int _flags;
    int _reserved;
    IMP _invoke;
    AXBlockStruct_1 *_descriptor;

//  готово, а теперь те поля, которые нужны для класса    
    AXProxyBlockInterpose _beforeInvoke;
    id _block;
    NSMethodSignature *_blockMethodSignature;
    IMP _impBlockInvoke;
}

@end

Теперь нужно чтобы на момент вызова класс имитировал принимаемый блок:

Соотвествие значений полей

- (instancetype)initWithBlock:(id)block {
    if (self != nil) {
        AXBlockStruct *blockRef = (__bridge AXBlockStruct *)block;
        _flags = blockRef->flags;
        _reserved = blockRef->reserved;
        _descriptor = calloc(1, sizeof(AXBlockStruct_1));
        _descriptor->size = class_getInstanceSize([self class]);
        
        BOOL flag_stret = _flags & AXBlockFlag_HasStret;
        _invoke = (flag_stret ? (IMP)_objc_msgForward_stret : (IMP)_objc_msgForward);
...

Описание назначения этих полей можно прочитать все на той же странице документации clang [4]. Теперь поля соответствуют блоку на момент вызова.

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

        _impBlockInvoke = (IMP)blockRef->invoke;
        _blockMethodSignature = [self blockMethodSignature];

_impBlockInvoke — это функция вызова блока, имплементация. Это обычный указатель на функцию и вызвать можно руками.
_blockMethodSignature это метод-сигнатура блока. Что это такое будет рассмотрено очень подробно далее.

Как получить NSMethodSignature для блока

- (NSMethodSignature *)blockMethodSignature {
    const char *signature = [[self blockSignatureStringCTypes] UTF8String];
    return [NSMethodSignature signatureWithObjCTypes:signature];
}

- (NSString *)blockSignatureStringCTypes {
    AXBlockStruct *blockRef = (__bridge AXBlockStruct *)_block;
    
    const int flags = blockRef->flags;
    
    void *signatureLocation = blockRef->descriptor;
    signatureLocation += sizeof(unsigned long int);
    signatureLocation += sizeof(unsigned long int);
    
    if (flags & AXBlockFlag_HasCopyDispose) {
        signatureLocation += sizeof(void(*)(void *dst, void *src));
        signatureLocation += sizeof(void (*)(void *src));
    }
    
    const char *signature = (*(const char **)signatureLocation);
    return [NSString stringWithUTF8String:signature];
}

Мы берем наш блок, получаем из него descriptor, потом смещаемся на нужную величину для получения сигнатуры блока (const char *) и через нее создаем NSMethodSignature. NSMethodSignature определяет кол-во и типы аргументов, возвращаемое значение и тп.
Выглядит не сложно, но манипуляции с флагом смущают: в зависимости от типа блока, его сигнатура может располагаться по-разному. К примеру, у глобального блока не нужно смещаться за функции копирования и разрушения.

Метода на вызов блока у моего класса нет, значит вызван будет forwardInvocation, а перед ним необходимо узнать какого типа будет сформирован NSInvocation, поэтому происходит вызов methodSignatureForSelector, в котором мы отдаем наш _blockMethodSignature.

forwardInvocation

- (void)forwardInvocation:(NSInvocation *)anInvocation {
    [anInvocation setTarget:_block];
    if (_beforeInvoke) {
        _beforeInvoke(anInvocation);
    }
    IMP imp = _impBlockInvoke;
    [anInvocation invokeUsingIMP:imp];
}

Код здесь должен быть очень понятен (установили новую цель на вызов, вызвали блок before если существует), но где вызов [anInvocation invoke]?!
Это черная магия. Метод invokeUsingIMP это private API, которое можно найти здесь, как и еще много чего [5]

Собираем пазл перед продолжением

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

Поговорим о методе, который вызывался в самом начале статьи — ax_lambda. Это всего лишь категория для NSObject, она является оберткой для вызова основной функции, которая выглядит следующим образом:

SEL ax_lambda(id obj, id block, NSMutableArray *lambdas);

Думаю теперь становится понятнее, для чего написана обертка. И если первый и второй аргумент не вызывает вопросов, то 3й заставляет задуматься. Сперва я расскажу о необходимости третьего аргумента, а потом уже приведу под спойлеры код категории.

SEL ax_lambda(id obj, id block, NSMutableArray *lambdas) {
    SEL selector = ax_generateFreeSelector(obj);
    
    AXProxyBlockWithSelf *proxyBlock = [AXProxyBlockWithSelf initWithBlock:block];
    [proxyBlock setBeforeInvoke:^(NSInvocation *invocation){
        ax_offsetArgInInvocation(invocation);
    }];
    [lambdas addObject:proxyBlock];
    
    IMP imp = imp_implementationWithBlock(proxyBlock);
    NSString *signatureString = [proxyBlock blockSignatureStringCTypes];
    class_addMethod([obj class], selector, imp, [signatureString UTF8String]);
    
    return selector;
}

Это и есть основная функция, тот самый собранный пазл. Класс AXProxyBlockWithSelf будет рассмотрен далее, пока только отмечу, что это потомок класса AXProxyBlock как наверняка догадались.
Чтобы сделать блок методом необходим селектор, имплементация и строковая сигнатура. Имплементация будет получена с проксиблока, строковую сигнатуру отдаст тоже прокси (в AXProxyBlock это сигнатура проксируемого блока, но в AXProxyBlockWithSelf она отличается и это будет рассмотрено далее), ну а селектор сгенерировать не сложно. Так зачем же 3й параметр?

При вызове imp_implementationWithBlock будет вызвано копирование блока (Block_copy). Поле copy_helper в блоке указатель на функцию копирования блока. Однако прокси блока не имеет такой возможности. Даже если я создам функцию копирования вида void (*)(void *dst, void *src), я не смогу получить желаемый результат. В src придет объект, в который нужно копировать и это будет не экземпляр моего класса. Поэтому вызов imp_implementationWithBlock не увеличит счетчик ссылок для объекта proxyBlock (и proxyBlock будет уничтожен после завершения функции). Чтобы этого не допустить, я использую коллекцию, которая увеличит внутренний счетчик ссылок. Получается срок жизни блока зависит от срока жизни коллекции хранящей его. В случае с категорией срок жизни блока ограничен сроком жизни владельца.

AXLambda.h

SEL ax_lambda(id obj, id block, NSMutableArray *lambdas);

@interface NSObject (AX_Lambda)

- (SEL)ax_lambda:(id)block;

@end

AXLambda.m

static char kAX_NSObjectAssociatedObjectKey;


@interface NSObject (_AX_Lambda)

@property (copy, nonatomic) NSMutableArray *ax_lambdas;

@end


@implementation NSObject (_AX_Lambda)

@dynamic ax_lambdas;

- (void)setAx_lambdas:(NSMutableArray *)lambdas {
    objc_setAssociatedObject(self, &kAX_NSObjectAssociatedObjectKey, lambdas, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (NSMutableArray *)ax_lambdas {
    NSMutableArray *marrey = objc_getAssociatedObject(self, &kAX_NSObjectAssociatedObjectKey);
    if (marrey == nil) {
        self.ax_lambdas = [NSMutableArray array];
    }
    return objc_getAssociatedObject(self, &kAX_NSObjectAssociatedObjectKey);
}

@end

@implementation NSObject (AX_Lambda)

- (SEL)ax_lambda:(id)block {
    return ax_lambda(self, block, self.ax_lambdas);
}

@end

Ну и функции, используемые в SEL ax_lambda(id obj, id block, NSMutableArray *lambdas);

SEL ax_generateFreeSelector(id obj)

SEL ax_generateFreeSelector(id obj) {
    SEL selector;
    NSMutableString *mstring = [NSMutableString string];
    do {
        [mstring setString:@"ax_rundom_selector"];
        u_int32_t rand = arc4random_uniform(UINT32_MAX);
        [mstring appendFormat:@"%zd", rand];
        selector = NSSelectorFromString(mstring);
    } while ([obj respondsToSelector:selector]);
    return selector;
}

void ax_offsetArgInInvocation(NSInvocation *invocation)

void ax_offsetArgInInvocation(NSInvocation *invocation) {
    void *foo = malloc(sizeof(void*));
    NSInteger arguments = [[invocation methodSignature] numberOfArguments];
    for (NSInteger i = 1; i < arguments-1; i++) { //i = 0 is self
        [invocation getArgument:foo atIndex:i+1];
        [invocation setArgument:foo atIndex:i];
    }
    
    free(foo);
}

Разбираемся с NSMethodSignature на примере комбинирования stringWithFormat и NSArray

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

Мне нужен был метод, позволяющий генерировать строку из формата и массива аргументов, к примеру вот так:

    NSString *format = @"%@, foo:%@, hello%@";
    NSArray *input = @[@(12), @(13), @" world"];
    NSString *result = [NSString ax_stringWithFormat:format array:input];
    //result — @"12, foo:13, hello world"

К сожалению, методы, которые я находил на SO не работали (первый [6], второй [7]). Возможно я не правильно пытался их использовать (у кого получилось — отпишите, пожалуйста) на ARC, но поскольку мне нужен был рабочий вариант, я написал свою реализацию.

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

Конечный вид метода выглядит так:

+ (instancetype)ax_stringWithFormat:(NSString *)format array:(NSArray *)arguments; 

Стандартный метод для создания строки по формату и параметрам выглядит следующим образом

- (instancetype)initWithFormat:(NSString *)format arguments:(va_list)argList NS_FORMAT_FUNCTION(1,0);

Но для использования (и самая проблема) нужно создать va_list (что это [8] и как использовать [9]).
Следующий метод отлично подходит

+ (instancetype)ax_string:(NSString *)format, ...

+ (instancetype)ax_string:(NSString *)format, ... {
    va_list list;
    va_start(list, format);
    NSString *str = [[NSString alloc] initWithFormat:format arguments:list];
    va_end(list);
    return str;
}

Теперь проблема как его вызвать с аргументами из NSArray.

NSInvocation [10] — это объект используемый для хранения и пересылки сообщения между объектами и/или между приложениями.
Однако при создании NSInvocation нужно иметь NSMethodSignature.
NSMethodSignature [11] позволяет определить сколько аргументов принимает метод, типы аргументов, смещения, тип возвращаемого значения. По этом очень логичным смотрится замечание из документации [10]

NSInvocation does not support invocations of methods with either variable numbers of arguments or union arguments.

Ведь не известно сколько аргументов и какого типа будет передано в функцию/метод с переменным кол-вом аргументов.

А если все же известно? Если я сам знаю эту информацию перед вызовом? Тогда я могу сказать, что в данном случае метод будет принимать к примеру 4 аргумента и тк функция принимает переменное кол-во аргументов, это сработает.
NSMethodSignature можно создать через генерируемую сигнатуру, если самому указать всю информацию выше. NSArray содержит только указатели и смещения всех параметров только на величину указателя, поэтому все довольно просто. Как я уже писал [12], в методе можно использовать self и _cmd потому что они в неявном виде передаются в метод.

+ (NSMethodSignature *)ax_generateSignatureForArguments:(NSArray *)arguments

+ (NSMethodSignature *)ax_generateSignatureForArguments:(NSArray *)arguments {
    NSInteger count = [arguments count];
    NSInteger sizeptr = sizeof(void *);
    NSInteger sumArgInvoke = count + 3; // self + _cmd + не забыть про то что в метод еще и формат будет передаваться
    NSInteger offsetReturnType = sumArgInvoke * sizeptr;
    
    NSMutableString *mstring = [[NSMutableString alloc] init];
    [mstring appendFormat:@"@%zd@0:%zd", offsetReturnType, sizeptr];
    for (NSInteger i = 2; i < sumArgInvoke; i++) {
        [mstring appendFormat:@"@%zd", sizeptr * i];
    }
    return [NSMethodSignature signatureWithObjCTypes:[mstring UTF8String]];
}

Стоит немного рассказать о том, что здесь происходит. Для начала надо посмотреть здесь [13] типы кодирования.
А теперь по порядку, я очень надеюсь, что вы посмотрели в таблицу.

На первом месте сигнатуры будет возвращаемый тип и его смещение (возвращаемый тип находится после всех аргументов, поэтому у него будет максимальное смещение, но пишется на первом). Предположим sizeof(void*) будет 8 и массив из 3х аргументов. Но включая self + _cmd + формат который будет передан и того получаем 6 аргументов. 6х8 = 48

@48
Затем следует self и _cmd. self на первом месте в аргументах, по этому

@48@0:8
Затем формат

@48@0:8@16
и аргументы

@48@0:8@16@24@32@40

Теперь, имея сигнатуру можно использовать NSInvocation

+ (instancetype)ax_stringWithFormat:(NSString *)format array:(NSArray *)arrayArguments

+ (instancetype)ax_stringWithFormat:(NSString *)format array:(NSArray *)arrayArguments {
    NSMethodSignature *methodSignature = [self ax_generateSignatureForArguments:arrayArguments];
    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSignature];
    [invocation setTarget:self];
    [invocation setSelector:@selector(ax_string:)];
    
    [invocation setArgument:&format atIndex:2];
    for (NSInteger i = 0; i < [arrayArguments count]; i++) {
        id obj = arrayArguments[i];
        [invocation setArgument:(&obj) atIndex:i+3];
    }
    
    [invocation invoke];
    
    __autoreleasing NSString *string;
    [invocation getReturnValue:&string];
    
    return string;
}

И теперь, если немного изменить метод выше, можно избавиться от метода + (instancetype)ax_string:(NSString *)format,…

Полный код под спойлером

+ (instancetype)ax_stringWithFormat:(NSString *)format array:(NSArray *)arrayArguments {
    NSMethodSignature *methodSignature = [self ax_generateSignatureForArguments:arrayArguments];
    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSignature];

    [invocation setTarget:self];
    [invocation setSelector:@selector(stringWithFormat:)];
    
    [invocation setArgument:&format atIndex:2];
    for (NSInteger i = 0; i < [arrayArguments count]; i++) {
        id obj = arrayArguments[i];
        [invocation setArgument:(&obj) atIndex:i+3];
    }
    
    [invocation invoke];
    
    __autoreleasing NSString *string;
    [invocation getReturnValue:&string];
    
    return string;
}

//https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html
+ (NSMethodSignature *)ax_generateSignatureForArguments:(NSArray *)arguments {
    NSInteger count = [arguments count];
    NSInteger sizeptr = sizeof(void *);
    NSInteger sumArgInvoke = count + 3; //self + _cmd + (NSString *)format
    NSInteger offsetReturnType = sumArgInvoke * sizeptr;
    
    NSMutableString *mstring = [[NSMutableString alloc] init];
    [mstring appendFormat:@"@%zd@0:%zd", offsetReturnType, sizeptr];
    for (NSInteger i = 2; i < sumArgInvoke; i++) {
        [mstring appendFormat:@"@%zd", sizeptr * i];
    }
    return [NSMethodSignature signatureWithObjCTypes:[mstring UTF8String]];
}

Решение второй половины задачи — как добавить еще 1 аргумент в блок незамеченно?

Был рассмотрен перехват момента вызова блока и смещение аргументов. Был рассмотрен код применения идеи и небольшие нюансы этого применения. Однако осталась проблема, которая мешает завершению.

Блок, принимаемый в imp_implementationWithBlock, должен принимать первым аргументов владельца. Получается, что сигнатура входного блока для функции ax_lambda отличается от положенной сигнатуры и в NSInvocation аргументы будут переданы совершенно не верно.

Класс AXProxyBlockWithSelf переделывает сигнатуру проксируемого блока, добавляя в нее дополнительно первый аргумент. Таким образом, вызов проксиблока будет совершен с правильными аргументами, а первый аргумент уже сместим перед вызовом самого блока.
Нужно переписать метод — (NSString *)blockSignatureStringCTypes

- (NSString *)blockSignatureStringCTypes

- (NSString *)blockSignatureStringCTypes {
    NSString *signature = [super blockSignatureStringCTypes];
    NSString *unformatObject = [signature ax_unformatDec];
    NSString *formatNewSignature = [self addSelfToFormat:unformatObject];
    
    NSArray *byteSignature = [signature ax_numbers];
    NSArray *byteNewSignature = [self changeByteSignature:byteSignature];
    
    return [NSString ax_stringWithFormat:formatNewSignature array:byteNewSignature];
}

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

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

- (NSString *)ax_unformatDec {
    NSCharacterSet *characterSet = [NSCharacterSet decimalDigitCharacterSet];
    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"length > 0"];
    NSArray *separated = [[self componentsSeparatedByCharactersInSet:characterSet] filteredArrayUsingPredicate:predicate];
    NSString *format = [separated componentsJoinedByString:@"%@"];
    if ([[self lastSubstring] isEqualToString:[format lastSubstring]] ) {
        return format;
    } else {
        return [format stringByAppendingString:@"%@"];
    }
}

- (NSString *)lastSubstring {
    NSInteger lastIndex = [self length] - 1;
    return [self substringFromIndex:lastIndex];
}

Далее надо посмотреть здесь [13] типы кодирования.

Добавляем аргумент "владелец" на первое место

- (NSString *)addSelfToFormat:(NSString *)format {
    NSMutableArray *marray = [[format componentsSeparatedByString:@"?"] mutableCopy];
    [marray insertObject:@"?%@@" atIndex:1];
    return [marray componentsJoinedByString:@""];
}

Получаем NSArray смещений аргументов для вызова

- (NSArray *)ax_numbers {
    NSString *pattern = @"\d+";
    NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:pattern options:NSRegularExpressionCaseInsensitive error:nil];
    NSRange fullRange = NSMakeRange(0, [self length]);
    NSArray *matches = [regex matchesInString:self options:NSMatchingReportProgress range:fullRange];
    
    NSMutableArray *numbers = [NSMutableArray array];
    for (NSTextCheckingResult *checkingResult in matches) {
        NSRange range = [checkingResult range];
        NSString *numberStr = [self substringWithRange:range];
        NSNumber *number = @([numberStr integerValue]);
        [numbers addObject:number];
    }
    
    return numbers;
}

Изменяем смещения аргументов на новое значение с учетом добавленного аргумента

- (NSArray *)changeByteSignature:(NSArray *)byteSignature {
    NSInteger value = sizeof(void *);
    NSMutableArray *marray = [NSMutableArray array];
    for (NSNumber *number in byteSignature) {
        NSInteger offset = [number integerValue] + value;
        [marray addObject:@(offset)];
    }
    [marray insertObject:@0 atIndex:1];
    return marray;
}

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

Полный код здесь. [14] Это был всего лишь эксперимент, у меня не было желания написать этот код для использования в проектах. Но я рад, что смог завершить это дело успешно. Так же я рад, что возможно смог кому-то помочь выложив решение [15] генерации строки с использованием NSArray на SO.
Надеюсь, у меня получилось донести материал в понятной форме и разбить на блоки.

Автор: ajjnix

Источник [16]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/ios-development/112820

Ссылки в тексте:

[1] BlocksKit: https://github.com/zwaldowski/BlocksKit

[2] о блоках: https://habrahabr.ru/post/271255/

[3] (__bridge, привет): http://clang.llvm.org/docs/AutomaticReferenceCounting.html#bridged-casts

[4] ссылка на страницу документации о том, что сейчас начнется: http://clang.llvm.org/docs/Block-ABI-Apple.html

[5] private API, которое можно найти здесь, как и еще много чего: https://github.com/nst/iOS-Runtime-Headers

[6] первый: http://stackoverflow.com/questions/1058736/how-to-create-a-nsstring-from-a-format-string-like-xxx-yyy-and-a-nsarr

[7] второй: http://stackoverflow.com/questions/8273380/converting-nsarray-contents-to-a-varargs-with-arc-for-use-with-nsstring-initwi

[8] что это: http://www.cplusplus.com/reference/cstdarg/va_list/

[9] как использовать: http://www.cplusplus.com/reference/cstdarg/va_start/

[10] NSInvocation: https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSInvocation_Class/

[11] NSMethodSignature: https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSMethodSignature_Class/index.html#//apple_ref/doc/c_ref/NSMethodSignature

[12] писал: https://habrahabr.ru/post/270913/

[13] здесь: https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html

[14] Полный код здесь.: https://github.com/ajjnix/AXBlock

[15] решение: http://stackoverflow.com/a/35039384/4759124

[16] Источник: https://habrahabr.ru/post/276599/