Версия SudzC ARC - вызов objc_msgSend вызывает EXC_BAD_ACCESS с использованием 64-битной архитектуры

Изменить - я отслеживал проблему ниже для проблемы с 64-разрядной и 32-разрядной архитектурой... см. мой ответ на вопрос, как я разрешил

Я использовал SudzC для создания SOAP-кода для веб-службы. Они снабжают вас примером приложения, которое я смог успешно использовать, как на устройстве, так и на симуляторе.

Затем я начал создавать свое приложение. Я импортировал созданные файлы SudzC в новый проект XCode, используя пустой шаблон приложения (с поддержкой CoreData и ARC).

Я получил первый запрос SOAP и все работает в симуляторе, а затем я начал выполнять свой первый тест на устройстве (iPhone 5S под управлением iOS 7.02). Устройство выдает ошибку EXC_BAD_ACCESS каждый раз, когда выполняется запрос SOAP.

Я проследил это до файла SoapRequest.m, в частности метода connectionDidFinishLoading. Этот метод использует вызов objc_msgSend для отправки данных ответа SOAP обратно методу обработчика в другом классе (в данном случае, в моем контроллере представлений). Здесь код:

SoapRequest.m:

- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
    NSError* error;
    if(self.logging == YES) {
        NSString* response = [[NSString alloc] initWithData: self.receivedData encoding: NSUTF8StringEncoding];
        NSLog(@"%@", response);
    }

    CXMLDocument* doc = [[CXMLDocument alloc] initWithData: self.receivedData options: 0 error: &error];
    if(doc == nil) {
        [self handleError:error];
        return;
    }

    id output = nil;
    SoapFault* fault = [SoapFault faultWithXMLDocument: doc];

    if([fault hasFault]) {
        if(self.action == nil) {
            [self handleFault: fault];
        } else {
            if(self.handler != nil && [self.handler respondsToSelector: self.action]) {
                objc_msgSend(self.handler, self.action, fault);
            } else {
                NSLog(@"SOAP Fault: %@", fault);
            }
        }
    } else {
        CXMLNode* element = [[Soap getNode: [doc rootElement] withName: @"Body"] childAtIndex:0];
        if(deserializeTo == nil) {
            output = [Soap deserialize:element];
        } else {
            if([deserializeTo respondsToSelector: @selector(initWithNode:)]) {
                element = [element childAtIndex:0];
                output = [deserializeTo initWithNode: element];
            } else {
                NSString* value = [[[element childAtIndex:0] childAtIndex:0] stringValue];
                output = [Soap convert: value toType: deserializeTo];
            }
        }
        if(self.action == nil) { self.action = @selector(onload:); }
        if(self.handler != nil && [self.handler respondsToSelector: self.action]) {
            objc_msgSend(self.handler, self.action, output);
        } else if(self.defaultHandler != nil && [self.defaultHandler respondsToSelector:@selector(onload:)]) {
            [self.defaultHandler onload:output];
        }

    }
    conn = nil;
}

Итак, строка objc_msgSend(self.handler, self.action, output);, кажется, там, где моя проблема. self.handler указывает на мой контроллер просмотра, а self.action указывает на этот метод:

TasksViewController.m:

- (void) findItemHandler: (id) value {

    // Handle errors
    if([value isKindOfClass:[NSError class]]) {
        NSLog(@"%@", value);
        return;
    }

    // Handle faults
    if([value isKindOfClass:[SoapFault class]]) {
        NSLog(@"%@", value);
        return;
    }

    // Do something with the id result
    NSLog(@"FindItem returned the value: %@", value);
}

Повторный вход в этот метод - это то, где я выхожу из строя. Похоже, что (id)value не делает это из класса SoapRequest. Я предполагаю, что он освобождается от ARC. Я протестировал вызов, заменив (id)value на int:

objc_msgSend(self.handler, self.action, 1);

и

- (void)findItemHandler:(int)value

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

@property (nonatomic, strong) id value;

Затем передано это:

self.value = output;
objc_msgSend(self.handler, self.action, self.value);

Такая же проблема. Я также пробовал то же самое с экземпляром SoapRequest... Теперь я смог обойти проблему, создав свойство в контроллере представления и установив это свойство в значение, но мне действительно интересно, как исправить это, используя исходный код. Я вернулся к примеру приложения, загруженного из SudzC, чтобы посмотреть, как это работает, и оказывается, что ARC не включен для этого проекта (!).

Может ли кто-нибудь сказать мне:

1) Если я прав в своем предположении, что output освобождается, что приводит к тому, что метод обработчика ссылается на неправильный адрес памяти?

2) Почему это работает на симуляторе? Я предполагаю, что это связано с тем, что у сима намного больше доступной памяти, поэтому она не так агрессивно с отключением ARC...

3) Как я могу это исправить, если я хочу сохранить вызов objc_msgSend? Я хочу узнать, как/почему это происходит.

4) Если SudzC правилен в использовании здесь objc_msgSend, так как я понимаю, что это плохая практика называть это напрямую, за исключением редких случаев?

Спасибо!

Ответ 1

ОК - так после того, как мы вытащили волосы и научились, мне стало ясно, что это может быть проблема с 64-битными и 32-разрядными версиями. Это было первое приложение, над которым я работал, начиная с обновления до нового Xcode. Я пошел в "Настройки сборки" и изменил архитектуру "Стандартные архитектуры (включая 64-разрядные) (armv7, armv7s, arm64)" на "Стандартные архитектуры (armv7, armv7s)". Это исправило проблему!

Затем я вернулся и исследовал, почему это происходит. Я нашел это руководство для 64-битного перехода Apple: https://developer.apple.com/library/content/documentation/General/Conceptual/CocoaTouch64BitGuide/ConvertingYourAppto64-Bit/ConvertingYourAppto64-Bit.html

В документе упоминается следующее:

Отправка Objective-C Сообщения с использованием прототипа методов Прототип исключение из правила литья, описанное выше, когда вы звоните функция objc_msgSend или любые другие подобные функции в Objective-C время выполнения, которое отправляет сообщения. Хотя прототип для функции сообщений имеют вариационную форму, функция метода, которая вызванный приложением Objective-C, не использует один и тот же прототип. Среда выполнения Objective-C напрямую отправляется на функцию, которая реализует метод, поэтому вызывающие соглашения несовместимы, так как описанных ранее. Поэтому вы должны указать objc_msgSend для прототипа, который соответствует вызываемой функции метода.

В листинге 2-14 показана правильная форма отправки сообщения на объект с использованием низкоуровневых функций сообщений. В этом примере doSomething: метод принимает один параметр и не имеет вариационная форма. Он запускает функцию objc_msgSend, используя прототип функции метода. Обратите внимание, что функция метода всегда принимает идентификатор переменная и селектор в качестве первых двух параметров. После Функция objc_msgSend передается в указатель функции, вызов отправленный через тот же самый указатель функции.

Используя эту информацию, я изменил следующую строку:

objc_msgSend(self.handler, self.action, self.value);

в

id (*response)(id, SEL, id) = (id (*)(id, SEL, id)) objc_msgSend;
response(self.handler, self.action, output);

И все работает!

Надеюсь, это поможет кому-то еще...