2015-09-19 3 views
1

Я хотел бы знать, если UIViewAlertForUnsatisfiableConstraints - это селектор, который я могу swizzle. Все, что я знаю до сих пор, это то, что UIViewAlertForUnsatisfiableConstraints является символом, который можно использовать для размещения символической точки останова. Но я не могу найти этот символ в любом публичном классе. Кто-нибудь знает, где он находится?Можно ли использовать UIViewAlertForUnsatisfiableConstraints?

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

ответ

3

Поскольку UIViewAlertForUnsatisfiableConstraints является функцией C, swizzling требует значительно больше работы, чем обычный метод Objective C.

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

В среде, не связанной с джейлбрейком, требуется расширенная манипуляция во время выполнения. По какой-то причине мне очень нравится выбрасывать часы, чтобы узнать, как скоро будет мертвый язык .. так что вот полное решение! Это link было огромной помощью.

#import <dlfcn.h> 
#import <objc/runtime.h> 
#include <sys/mman.h> 

int64_t originalOffset; 
int64_t *origFunc; 

void swizzled_UIViewAlertForUnsatisfiableConstraints(NSLayoutConstraint *offendingConstraint, NSArray *allConstraints) { 
    // inject swizzle code here 
    NSLog(@"swizzled!"); 

    // call the original function (if you want!) 
    if (origFunc) { 
     // replace jump instruction w/ the original memory offset 
     *origFunc = originalOffset; 
     ((void(*)(NSLayoutConstraint*, NSArray*))origFunc)(offendingConstraint, allConstraints); 
    } 
} 

static inline BOOL swizzleAlertForUnsatisfiableConstraints() { 

    // get the original function and hold onto it's memory offset 
    origFunc = dlsym(RTLD_DEFAULT, "UIViewAlertForUnsatisfiableConstraints"); 
    if (!origFunc) { 
     return NO; 
    } 
    originalOffset = *origFunc; 

    // define the swizzled implementation 
    int64_t *swizzledFunc = (int64_t*)&swizzled_UIViewAlertForUnsatisfiableConstraints; 

    // make the memory containing the original funcion writable 
    size_t pageSize = sysconf(_SC_PAGESIZE); 
    uintptr_t start = (uintptr_t)origFunc; 
    uintptr_t end = start + 1; 
    uintptr_t pageStart = start & -pageSize; 
    mprotect((void *)pageStart, end - pageStart, PROT_READ | PROT_WRITE | PROT_EXEC); 

    //Calculate the relative offset needed for the jump instruction 
    //Since relative jumps are calculated from the address of the next instruction, 
    // 5 bytes must be added to the original address (jump instruction is 5 bytes) 
    int64_t offset = (int64_t)swizzledFunc - ((int64_t)origFunc + 5 * sizeof(char)); 

    //Set the first instruction of the original function to be a jump 
    // to the replacement function. 
    //E9 is the x86 opcode for an unconditional relative jump 
    int64_t instruction = 0xe9 | offset << 8; 
    *origFunc = instruction; 

    return YES; 
} 
+0

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

+0

Я думаю, что использую это неправильно. Я получаю «Исключение: EXC_BAD_ACCESS (код = 1, адрес = 0x49656e69))». Похоже, что исходная функция правильно не вызывается. Я вижу «__swizzleAlertForUnsatisfiableConstraints_block_invoke» в трассировке стека, когда обычно вижу «AlertForUnsatisfiableConstraints». Однако похоже, что есть проблема с форматом функции замены или смещением памяти. Вы видели такую ​​проблему? –

+0

упс! Я тестировал это только на 32-битном симуляторе, а не на 64 бит. Если вы попробуете его на iPad 2 sim (32 бит), вы должны увидеть, что он работает. Я буду обновлять код завтра версией, которая работает как для 32/64 бит. – Casey

1

Я пробовал решение Кейси и отлично работает, но только на тренажере. Процессор ARM имеет режим Thumb, поэтому очень сложно подкачать, используя инструкцию ARM B, чем x86 JMP.

UIViewAlertForUnsatisfiableConstraints вызывается из нижнего метода UIView. Кажется, он определен в делегате внутреннего движка.

- (void)engine:(NSISEngine *)arg1 willBreakConstraint:(id <NSISConstraint>)arg2 dueToMutuallyExclusiveConstraints:(NSArray *)arg3; 

https://github.com/nst/iOS-Runtime-Headers/blob/6a384f6a219be448de8714f263a4c212516d52b6/Frameworks/UIKit.framework/UIView.h#L1108

https://github.com/nst/iOS-Runtime-Headers/blob/3dde29deb7000cfac59d3e5473a71557123f29ea/protocols/NSISEngineDelegate.h#L10

метод Swizzling ObjC намного проще, чем C FUNC.

Определить частный интерфейс:

@interface UIView (UIConstraintBasedLayout_EngineDelegate_PrivateInterface) 

- (void)engine:(id /* NSISEngine */)engine willBreakConstraint:(NSLayoutConstraint *)breakConstraint dueToMutuallyExclusiveConstraints:(NSArray<NSLayoutConstraint *> *)mutuallyExclusiveConstraints; 

@end 

Тогда Swizzle ObjC метод:

+ (void)swizzleAutoLayoutAlertMethod 
{ 
    Class class = [UIView class]; 
    Method originalMethod = class_getInstanceMethod(class, @selector(engine:willBreakConstraint:dueToMutuallyExclusiveConstraints:)); 
    if (!originalMethod) { 
     [NSException raise:NSInternalInconsistencyException format:@"This platform does not support Auto Layout or interface has been changed."]; 
    } 
    Method swizzledMethod = class_getInstanceMethod(class, @selector(swizzled_engine:willBreakConstraint:dueToMutuallyExclusiveConstraints:)); 
    method_exchangeImplementations(swizzledMethod, originalMethod); 
} 

ПРИМЕЧАНИЕ: Я создал библиотеку ворса для Автокомпоновка с этой идеей, см https://github.com/ypresto/AutoLayoutLint.

Смежные вопросы