2015-02-19 4 views
0

У меня есть странная проблема с NSInvocation. Я использую его в качестве обратного обратного вызова при завершении сетевой операции. Позвольте мне пояснить предыдущее предложение более подробно:NSInvocation получить цель вызывает EXC_BAD_ACCESS

Я использую настраиваемый сетевой протокол, который работает через сокет TCP, и у меня есть класс, который использует этот протокол и служит в качестве подключения к моему серверу. Теперь класс имеет метод позволяет сказать performNetworkRequestWithDelegate:, который реализуется следующим образом:

- (void)performNetworkRequestWithDelegate:(id<MyClassDelegate>)delegate 
{ 
    NSString *requestKey = [self randomUniqueString]; 
    id request = [self assembleRequestAndSoOnAndSoForth]; 
    [request setKey:requestKey]; 

    SEL method = @selector(callbackStatusCode:response:error:); 
    NSMethodSignature *signature = [delegate methodSignatureForSelector:method]; 
    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature]; 
    invocation.target = delegate; 
    invocation.selector = method; 

    delegateInvocationMap[requestKey] = invocation; //See below for an explanation what the delegateInvocationMap is 

    [self sendRequest:request]; 
} 

Ладно, так что я знаю, что есть некоторые вещи, которые должны быть объяснены. Во-первых, не беспокойтесь о чем-либо, связанном с запросом, кроме requestKey. Он работает так: ключ запроса возвращается обратно ко мне, когда я получаю ответ от сервера. Таким образом, это похоже на поле HTTP-заголовка, которое возвращается к вам, когда вы получаете ответ от сервера. Таким образом, я могу определить, какой запрос был сделан. delegateInvocationMap - это NSMutableDictionary, который держится за наши призывы, и мы можем получить правильный ответ, когда получим ответ и разобраем requestKey.

Теперь обработчик для ответа, как это:

- (void)processResponse:(id)response 
{ 
    //Check for errors and whatnot 

    NSString *requestKey = [response requestKey]; 
    if (!requestKey) return; //This never happens and is handled more correctly but keep it like this for the sake of simplicity 

    NSInvocation *invocation = delegateInvocationMap[requestKey]; 
    if (!invocation) return; 

    [delegateInvocationMap removeObjectForKey:requestKey]; 

    if (!invocation.target) return; //THIS LINE IS THE PROBLEM 

    [self setInvocationReturnParams:invocation fromResponse:response]; 
    [invocation invoke]; //This works when everything is fine 
} 

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

Когда цель вызова освобождена, я получаю EXC_BAD_ACCESS при попытке проверить, есть ли цель для моего вызова. Яблоко документы говорят:

The receiver’s target, or nil if the receiver has no target.

Как я могу проверить, если приемник был уже освобождаться? Это огромная боль.

EDIT: В комментариях ниже я обнаружил, что доступ к удаленному объекту всегда неизвестен. Я не знаю, есть ли официальная документация, указывающая это (я еще не проверял), но у меня есть обходная идея. Можно ли будет наблюдать цель вызова для вызова dealloc через KVO?

ответ

2

NSInvocationtarget недвижимость не является ARC weak ссылка; он определяется как assign. Если вы не будете ссылаться на этот объект, он будет освобожден, и вы начнете видеть EXC_BAD_ACCESS исключений.

@property(assign) id target 

АРК автоматически преобразует assign свойства unsafe_unretained вместо weak. Значение weak будет установлено в nil, когда объект будет освобожден; свойство unsafe_unretained будет продолжать указывать на адрес памяти, который будет мусором.

Вы можете обойти это, используя метод retainArguments.

[invocation retainArguments]; 

Из документации:

Если приемник еще не сделали этого, сохраняет цель и все аргументы объекта приемника и копии всех его C-строковых аргументов и блоков.

+0

Я не хочу, чтобы вызов поддерживал сильную ссылку, поскольку эти ссылки чаще всего ссылаются на контроллеры представлений. Я хочу проверить, существует ли объект, на который нацелен призыв, и если я просто хочу «забыть все». – Majster

+0

Вы используете ARC? –

+0

Да, я использую ARC. – Majster

0

Поскольку NSInvocation хочет сохранить цель, но вы по сути хотите сохранить слабую ссылку, используйте что-то вроде TPDWeakProxy. Прокси принимает ссылку и удерживает ее со слабым указателем, но прокси-сервер может удерживаться сильно.

Вот как я это сделал в OCMockito в методе категории NSInvocation:

- (void)mkt_retainArgumentsWithWeakTarget 
{ 
    if (self.argumentsRetained) 
     return; 
    TPDWeakProxy *proxy = [[TPDWeakProxy alloc] initWithObject:self.target]; 
    self.target = proxy; 
    [self retainArguments]; 
} 

Это заменяет цель с тем, что по существу слабой целью.

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