Was die Idee bringt (Objective-C) - Zielwirkung auf Blöcke und viel Laufzeit

  • Tutorial
Irgendwie kam mir eine Idee in den Sinn, aber ist es möglich, einen Block zu nehmen und ihn als Ziel zu verwenden?
Es gibt vorgefertigte Lösungen wie BlocksKit und andere Bibliotheken. Ihre Lösung besteht jedoch darin, den Block zu speichern, das Ziel festzulegen und den Block über den angegebenen Selektor aufzurufen.

Warum brauchen Sie dann diesen Artikel?

Ich wollte eine Möglichkeit schaffen, einen Selektor zu generieren, mit dem der Block aufgerufen wird. Was ist so kompliziert, sagst du? imp_implementationWithBlock + class_addMethod und der Fall ist geschlossen. Bei diesem Ansatz gibt es jedoch eine wichtige Anforderung: Dies ist das erste Argument des Blocks - der Eigentümer der Methode.

Wie kann man diese Anforderung umgehen und dies tun?
    [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];

Oder auch so
    __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

Die Umsetzung erwies sich als so interessant, dass ich mich entschied, darüber zu schreiben.

Tatsächlich besteht die Hauptaufgabe darin, das erste Argument von self im Blockaufruf zu beseitigen. Dies ist das Grundproblem der gesamten Lösung (schade, dass es nicht das einzige ist).
Zuvor habe ich ein wenig über Blöcke geschrieben und festgestellt, dass ein Block ein Objekt ist, was bedeutet, dass der Aufruf über NSInvocation erfolgt.
Wenn Sie in dem Moment, in dem der Block aufgerufen wird, das Argument self in NSInvocation entfernen (indem Sie die Argumente verschieben), erhalte ich das gewünschte Ergebnis.

Weiter wird es notwendig sein, auf dem Weg zu verstehen.


AXProxyBlock


Die Frage ist, wie man sich im Moment des Blockaufrufs einschaltet. Wie bekomme ich den Blockaufruf überhaupt?
Sehr oft schreibe ich diesen Satz, aber ein Block ist ein Objekt. Ein Objekt in seiner endgültigen Form ist eine Struktur. Da id ein Zeiger auf eine Struktur ist, ist auch das Gegenteil zulässig (__bridge, hallo) .
Es stellt sich heraus, dass Sie einen gefälschten Block erstellen können. Na ja, oder ein Proxy für den Block.

Die Schnittstelle meiner Klasse ist wie folgt:
typedef void(^AXProxyBlockInterpose)(NSInvocation *invocation);
@interface AXProxyBlock : NSProxy
+ (instancetype)initWithBlock:(id)block;
- (void)setBeforeInvoke:(AXProxyBlockInterpose)beforeInvoke;
- (NSString *)blockSignatureStringCTypes;
@end



Wie Sie vielleicht erraten haben, akzeptiert setBeforeInvoke einen Block, in dem Sie die Blockargumente „magisch“ konvertieren können.
blockSignatureStringCTypes gibt die Signatur des Proxy-Blocks zurück. Warum ist es in der Header-Datei? Darüber später.

Link zur Dokumentationsseite, die jetzt beginnt

Zunächst erstellen wir Blockstrukturen und eine Aufzählung mit unseren Namen.
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)
};


Und jetzt nehmen wir unsere Klasse.
Es ist notwendig, die entsprechende Struktur zu erstellen:
@interface AXProxyBlock ()  {
//  isa поле уже имеется в реализации NSProxy, а остальные поля добавим
    int _flags;
    int _reserved;
    IMP _invoke;
    AXBlockStruct_1 *_descriptor;
//  готово, а теперь те поля, которые нужны для класса    
    AXProxyBlockInterpose _beforeInvoke;
    id _block;
    NSMethodSignature *_blockMethodSignature;
    IMP _impBlockInvoke;
}
@end


Nun ist es notwendig, dass zum Zeitpunkt des Aufrufs die Klasse den empfangenen Block nachahmt:
Übereinstimmende Feldwerte
- (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);
...


Die Beschreibung des Zwecks dieser Felder finden Sie auf derselben Dokumentationsseite . Jetzt entsprechen die Felder dem Block zum Zeitpunkt des Aufrufs.

Aber ich habe 2 sehr wichtige ivar, die ich oben nicht unter den Spoiler aufgenommen habe, da sie sich bereits auf den Blockaufruf beziehen und ich näher darauf eingehen möchte.

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


_impBlockInvoke ist eine Blockaufruffunktion, Implementierung. Dies ist ein normaler Funktionszeiger und kann von Hand aufgerufen werden.
_blockMethodSignature ist eine Blocksignaturmethode. Worum es sich handelt, wird im Folgenden ausführlich erläutert.

So erhalten Sie NSMethodSignature für einen Block
- (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];
}



Wir nehmen unseren Block, holen einen Deskriptor daraus, bewegen uns dann zum gewünschten Wert, um die Signatur des Blocks (const char *) zu erhalten, und erstellen daraus NSMethodSignature. NSMethodSignature bestimmt die Anzahl und Art der Argumente, den Rückgabewert usw.
Es sieht nicht kompliziert aus, aber die Manipulation des Flags ist verwirrend: Je nach Blocktyp kann die Signatur auf unterschiedliche Weise lokalisiert werden. Beispielsweise muss der globale Block nicht über die Kopier- und Zerstörungsfunktionen hinaus verschoben werden.

Meine Klasse verfügt nicht über eine Methode zum Aufrufen des Blocks, daher wird forwardInvocation aufgerufen. Zuvor muss herausgefunden werden, welcher Typ von NSInvocation gebildet wird. Daher wird methodSignatureForSelector aufgerufen, in dem wir _blockMethodSignature zurückgeben.

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

Der Code hier sollte sehr klar sein (setzen Sie ein neues Ziel für den Aufruf, den Before-Block genannt, falls vorhanden), aber wo ist der [anInvocation-Aufruf] -Aufruf?!
Das ist schwarze Magie. Die invokeUsingIMP-Methode ist eine private API, die hier zu finden ist und vieles mehr


Stellen Sie das Puzzle zusammen, bevor Sie fortfahren


Ich halte Block Proxying für ein besonderes Material, und wenn wir direkt zur Lösung der zweiten Hälfte des Problems übergehen, lesen weniger Menschen den Artikel. Daher wird der Wrapper nun kurz als ein Puzzle mit vorgefertigten Lösungen betrachtet und am Ende wird die zweite Hälfte der Aufgabe sortiert. So können Sie sich etwas entspannen und das Material strukturierter sammeln.

Lassen Sie uns über die Methode sprechen, die am Anfang des Artikels aufgerufen wurde - ax_lambda. Dies ist nur eine Kategorie für NSObject, es ist ein Wrapper zum Aufrufen der Hauptfunktion, die so aussieht:
SEL ax_lambda(id obj, id block, NSMutableArray *lambdas);

Ich denke, es wird jetzt klarer, warum der Wrapper geschrieben ist. Und wenn das erste und zweite Argument keine Fragen aufwirft, dann bringt das dritte Sie zum Nachdenken. Zuerst werde ich über die Notwendigkeit des dritten Arguments sprechen und dann den Kategoriecode für Spoiler angeben.

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;
}

Dies ist die Hauptfunktion, das sehr zusammengesetzte Puzzle. Die AXProxyBlockWithSelf-Klasse wird später betrachtet. Im Moment stelle ich nur fest, dass dies ein Nachkomme der AXProxyBlock-Klasse ist, wie Sie wahrscheinlich vermutet haben.
Um einen Block zu einer Methode zu machen, werden ein Selektor, eine Implementierung und eine Zeichenfolgensignatur benötigt. Die Implementierung wird vom Proxy-Block empfangen, der Proxy gibt auch die Zeichenfolgensignatur aus (in AXProxyBlock ist dies die Signatur des Proxy-Blocks, in AXProxyBlockWithSelf ist dies jedoch anders und wird später erläutert). Es ist jedoch nicht schwierig, den Selektor zu generieren. Warum also der 3. Parameter?

Wenn imp_implementationWithBlock aufgerufen wird, wird das Blockkopieren (Block_copy) aufgerufen. Das copy_helper-Feld im Block ist ein Zeiger auf die Kopierfunktion des Blocks. Der Proxy-Block verfügt jedoch nicht über diese Funktion. Auch wenn ich eine Kopierfunktion der Form void (*) (void * dst, void * src) erstelle, kann ich das gewünschte Ergebnis nicht erzielen. Das Objekt, das nach src kopiert werden soll, kommt und dies ist keine Instanz meiner Klasse. Daher erhöht der Aufruf von imp_implementationWithBlock nicht den Referenzzähler für das proxyBlock-Objekt (und proxyBlock wird nach Beendigung der Funktion zerstört). Um dies zu verhindern, verwende ich eine Sammlung, die den internen Referenzzähler erhöht. Es stellt sich heraus, dass die Lebensdauer des Blocks von der Lebensdauer der Sammlung abhängt, in der er gespeichert ist. Bei der Kategorie ist die Lebensdauer eines Blocks durch die Lebensdauer des Eigentümers begrenzt.

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



Nun, die in SEL ax_lambda verwendeten Funktionen (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 * -Aufruf)
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);
}




Grundlegendes zu NSMethodSignature durch Kombinieren von StringWithFormat und NSArray


Bevor Sie mit dem nächsten Teil beginnen, benötigen Sie grundlegende Kenntnisse zu NSInvocation und NSMethodSignature. Ich habe darüber nachgedacht, dies in einem separaten Artikel zu isolieren, bin jedoch zu dem Schluss gekommen, dass sich der Artikel als interessant und einfach (bei der Analyse eines konkreten Beispiels) herausstellen wird, aber nicht sehr umfangreich. Also habe ich mich entschlossen, gleich hier darüber zu schreiben.

Ich brauchte eine Methode, um einen String aus einem Format und einem Array von Argumenten zu generieren, zum Beispiel so:
    NSString *format = @"%@, foo:%@, hello%@";
    NSArray *input = @[@(12), @(13), @" world"];
    NSString *result = [NSString ax_stringWithFormat:format array:input];
    //result — @"12, foo:13, hello world"


Leider haben die Methoden, die ich auf SO gefunden habe, nicht funktioniert ( erstens , zweitens ). Vielleicht habe ich nicht versucht, sie korrekt in ARC zu verwenden (wer auch immer erfolgreich war - bitte abschreiben), aber da ich eine funktionierende Version brauchte, habe ich meine Implementierung geschrieben.

Die Lösung basiert vollständig auf dem Prinzip der Methoden, ohne dass es zu Umgehungen mit Zeigern oder Transformationen kommt.

Die endgültige Form der Methode sieht folgendermaßen aus:
+ (instancetype)ax_stringWithFormat:(NSString *)format array:(NSArray *)arguments; 


Die Standardmethode zum Erstellen einer Zeichenfolge nach Format und Parametern lautet wie folgt
- (instancetype)initWithFormat:(NSString *)format arguments:(va_list)argList NS_FORMAT_FUNCTION(1,0);

Aber um (und das Problem selbst) zu verwenden, müssen Sie eine va_list erstellen ( was ist das und wie wird es verwendet ).
Die folgende Methode ist großartig
+ (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;
}


Jetzt ist das Problem, wie man es mit Argumenten von NSArray aufruft.

NSInvocation ist ein Objekt zum Speichern und Weiterleiten einer Nachricht zwischen Objekten und / oder zwischen Anwendungen.
Beim Erstellen von NSInvocation muss jedoch NSMethodSignature vorhanden sein.
Mit NSMethodSignature können Sie bestimmen, wie viele Argumente eine Methode akzeptiert , welche Argumenttypen, Offsets und Rückgabetypen verwendet werden. Die Bemerkung aus der Dokumentation sieht in dieser Hinsicht sehr logisch aus
NSInvocation unterstützt keine Aufrufe von Methoden mit variablen Anzahlen von Argumenten oder Vereinigungsargumenten.

Schließlich ist nicht bekannt, wie viele Argumente und welcher Typ mit einer variablen Anzahl von Argumenten an die Funktion / Methode übergeben werden.

Und wenn Sie es noch wissen? Ob ich selbst diese Information kenne, bevor ich anrufe? Dann kann ich sagen, dass in diesem Fall die Methode beispielsweise 4 Argumente akzeptiert und die Funktion möglicherweise eine variable Anzahl von Argumenten akzeptiert. Dies funktioniert.
NSMethodSignature kann über die generierte Signatur erstellt werden, wenn Sie alle oben genannten Informationen selbst angeben. NSArray enthält nur Zeiger und Offsets aller Parameter, nur um die Größe des Zeigers, also ist alles ziemlich einfach. Wie ich bereits geschrieben habe , können Sie self und _cmd in der Methode verwenden, da sie implizit an die Methode übergeben werden.
+ (NSMethodSignature *) ax_generateSignatureForArguments: (NSArray *) Argumente
+ (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]];
}


Es lohnt sich, ein wenig darüber zu sprechen, was hier passiert. Zuerst müssen Sie sich die Codierungsarten hier ansehen .
Und jetzt, in Ordnung, hoffe ich wirklich, dass Sie sich den Tisch angesehen haben.

An erster Stelle der Signatur stehen der Rückgabetyp und sein Offset (der Rückgabetyp steht nach allen Argumenten, hat also den maximalen Offset, wird aber in den ersten geschrieben). Angenommen, sizeof (void *) ist 8 und ein Array von 3 Argumenten. Aber einschließlich self + _cmd + das Format, das übergeben wird, und wir erhalten 6 Argumente. 6x8 = 48
@ 48
gefolgt von self und _cmd. self steht bei den Argumenten an erster Stelle, also
@ 48 @ 0: 8,
gefolgt von
@ 48 @ 0: 8 @ 16
und Argumenten
@ 48 @ 0: 8 @ 16 @ 24 @ 32 @ 40

Nachdem Sie eine Signatur haben, können Sie NSInvocation verwenden
+ (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;
}



Und jetzt, wenn Sie die obige Methode geringfügig ändern, können Sie die ax_string-Methode + (instancetype) loswerden: (NSString *) format, ...
Vollständiger Code unter dem Spoiler
+ (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]];
}



Die Lösung für die zweite Hälfte des Problems - wie fügt man dem Block unbemerkt 1 weiteres Argument hinzu?



Das Abfangen des Zeitpunkts, zu dem der Block aufgerufen wurde, und der Versatz der Argumente wurden berücksichtigt. Der Code für die Anwendung der Idee und die kleinen Nuancen dieser Anwendung wurden berücksichtigt. Es blieb jedoch ein Problem, das die Fertigstellung verhinderte.

Der in imp_implementationWithBlock akzeptierte Block muss zuerst die Eigentümerargumente annehmen. Es zeigt sich, dass die Signatur des Eingabeblocks für die Funktion ax_lambda von der vorgesehenen Signatur abweicht und die Argumente völlig falsch an NSInvocation übergeben werden.

Die AXProxyBlockWithSelf-Klasse macht die Signatur des Proxy-Blocks erneut und fügt ihm ein zusätzliches erstes Argument hinzu. Auf diese Weise wird der Proxy-Blockaufruf mit den richtigen Argumenten abgeschlossen, und das erste Argument wird bereits vor dem Blockaufruf verschoben.
Die Methode - (NSString *) blockSignatureStringCTypes muss neu geschrieben werden

- (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];
}


Es gibt also eine Blocksignatur mit Argumenttypen und Offset, Rückgabetyp usw.
Sie müssen ein zusätzliches Argument in die Signatur einfügen und die Argumente verschieben.
Ruft das Anfangsformat des Signaturzeichenfolgenformats ab
- (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];
}


Als nächstes müssen Sie sich die Codierungsarten hier ansehen .
Fügen Sie das Argument "Eigentümer" an erster Stelle ein
- (NSString *)addSelfToFormat:(NSString *)format {
    NSMutableArray *marray = [[format componentsSeparatedByString:@"?"] mutableCopy];
    [marray insertObject:@"?%@@" atIndex:1];
    return [marray componentsJoinedByString:@""];
}


Rufen Sie die NSArray-Argument-Offsets auf
- (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;
}


Wir ändern den Offset der Argumente unter Berücksichtigung des hinzugefügten Arguments in einen neuen Wert
- (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;
}



Erstellen Sie am Ende eine neue Signatur mit dem neuen Zeichenfolgenformat und NSArray mit einem neuen Offset. Wenn die Implementierung aufgerufen wird, wird der Eigentümer gemäß der Dokumentation als erstes Argument übertragen, aufgrund des Abfangens verschoben und der ursprüngliche Block wird aufgerufen.


Vollständiger Code hier. Es war nur ein Experiment, ich hatte keine Lust, diesen Code für die Verwendung in Projekten zu schreiben. Ich bin aber froh, dass ich dieses Geschäft erfolgreich abschließen konnte. Ich bin auch froh, dass ich vielleicht jemandem helfen konnte, indem ich eine Lösung zum Generieren von Strings mit NSArray auf SO auslegte.
Ich hoffe, es ist mir gelungen, das Material in verständlicher Form zu vermitteln und in Blöcke zu zerlegen.

Jetzt auch beliebt: