Почему возвращаемое значение NSInvocation создает зомби? - PullRequest
1 голос
/ 07 июня 2019

Я пытаюсь создать связь между JavaScript и Native.Для этого мне нужно динамически выполнить метод для некоторого класса, когда JavaScript вызывает его.

У меня проблема с получением NSInvocation возвращаемого значения.При использовании getReturnValue приложение вылетает из-за зомби.Указывается, что зомби исходят из вызова, возвращаемого значением метода.

Если я закомментирую строку [invocation getReturnValue:&result];, приложение не прервется.

Тестовый метод, который я сейчас вызываю, возвращает и (NSString *) Если я заставлю реализацию вызванного метода селектора вернуть буквальную строку, например @ "firstsecond"), приложение также не прервется.

Зачем нужна ссылка на него, если метод вызова уже выполнен и возвращена строка?Если возвращенная строка не скопирована в id result.

- (void)userContentController:(nonnull WKUserContentController *)userContentController didReceiveScriptMessage:(nonnull WKScriptMessage *)message {


    if ([@"Native_iOS_Handler" isEqualToString: message.name]) {
        NSArray *arguments = [message.body valueForKey:@"arguments"];
        NSNumber *callbackID = [message.body valueForKey:@"callbackID"];
        NSString *APIName = [message.body valueForKey:@"APIName"];
        NSString *methodName = [message.body valueForKey:@"methodName"];

        id classAPI = [self.exposedAPIs objectForKey:APIName];

        SEL methodToRun = [classAPI getSelectorForJSMethod:methodName];

        NSMethodSignature *methodSignature = [classAPI methodSignatureForSelector:methodToRun];
        NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSignature];
        [invocation setTarget:classAPI];
        [invocation setSelector:methodToRun];

        id result;
        [invocation invoke];
        [invocation getReturnValue:&result];
        NSLog(@"%@", result);// output: firstsecond
    }
}

//the selector in this case is this
-(NSString*)getFoo{
    // Why is this a zombie????
    return [NSString stringWithFormat:@"%@%@", @"first", @"second"];
    // This works:
    //return @"fristsecond"
}

Хотя селектор в Instruments отличается, результат тот же.Из этой картины я понимаю, что я тебе сказал.У меня нет опыта работы с инструментами.

enter image description here

1 Ответ

1 голос
/ 14 июня 2019

Вы стали жертвой ARC, не зная, как NSInvocation работает, изменяя result косвенно через другой указатель.Это известная проблема, описанная здесь .

Что происходит, если результирующий объект косвенно становится равным result, но ARC не знает об этом и никогда не сохранит его.

Не вдаваясь в подробности NSString - это кластер класса .То, что это эффективно означает, является реализацией под изменениями, основанными на том, как строка создается и используется.Подробности об этом скрыты при взаимодействии с ним в obj-c, и Apple приложила много усилий, чтобы сделать его понятным для разработчиков iOS.Ваш случай несколько особенный.

Обычно вы будете получать:

  1. __NSCFConstantString (например, @"constant") - строковая константа для времени жизни приложения, для вашего случая это случается, что работает, но вы должны никогда полагаться на это
  2. NSTaggedPointerString (например, [[@"a"] mutableCopy] copy]) - оптимизированная короткая строка с внутренней справочной таблицей.
  3. __NSCFString (например, [@"longString" mutableCopy] copy]) длинное строковое представление CoreFoundation.

В любое время NSString может изменить реализацию ниже, так что вы никогда не должны делать предположений об этом.Случай 3 сразу же выйдет из области видимости после возврата и будет освобожден в следующем цикле выполнения, случай 1 никогда не будет освобожден, случай 2 (?), Но наверняка переживет следующий цикл выполнения.

Так что по сути ARC недостаточно умен, чтобы связать потенциально освобожденный объект с id result, и вы столкнетесь с проблемами.

Как это исправить?

Используйте один из них:

1.

void *tempResult;
[invocation getReturnValue:&tempResult];
id result = (__bridge id) tempResult;

2.

__unsafe_unretained id tempResult;
[invocation getReturnValue:&tempResult];
result = tempResult;

Редактировать

Как отметил @newacct в своем комментарии getReturnValue: не выполняет регистрацию weak указателей, следовательно, неуместно в этом случае.Указатель не будет обнуляться , когда объект будет освобожден.

3.

__weak id tempResult;
[invocation getReturnValue:&tempResult];
result = tempResult;

...