Почему возвращаемое значение NSInvocation создает зомби?

Я пытаюсь создать JavaScript для собственного общения. Для этого мне нужно динамически выполнять метод в некотором классе, когда его вызывает 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"
}

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

введите здесь описание изображения


person h3dkandi    schedule 07.06.2019    source источник


Ответы (1)


Вы стали жертвой 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;

person Kamil.S    schedule 14.06.2019
comment
Я думал, что это что-то вроде этого, но не мог найти способ обойти это. Ранее я пробовал ваши первые два предложения, но они не сработали. Однако тот, у которого была пустота, работал. В настоящее время у меня нет времени, чтобы правильно понять это и прочитать связанные ответы, но я обязательно сделаю это позже. Я только что попробовал исправить, и это сработало. - person h3dkandi; 17.06.2019
comment
Обновил ответ. - person Kamil.S; 17.06.2019
comment
getReturnValue: не регистрирует слабые указатели, поэтому вам не следует использовать __weak. - person newacct; 01.07.2019
comment
@newacct спасибо за ваше прекрасное понимание, я надеюсь, что после редактирования оно станет более точным. - person Kamil.S; 01.07.2019