Objective-C: performSelector: vs. forwardInvocation:

довольно новичок в objective-c, но я столкнулся с довольно распространенной ситуацией: я хочу, чтобы ClassA попросил ClassB выполнить метод для объекта, о котором знает только ClassB (а также с использованием метода, неизвестного ClassA ).

Я нашел два способа сделать это: performSelector: и forwardInvocation: - но я хотел бы узнать больше и закрепить свое понимание каждого из них. Я нашел эту заметку в документации разработчика Apple:

Аргумент aSelector [в performSelector:] должен идентифицировать метод, который не принимает аргументов. Для методов, которые возвращают что-либо, кроме объекта, используйте NSInvocation.

.. означает ли это, что методы, которые начинаются с - (id) methodName, будут использовать performSelector:, а скажем, - (int) nonObjectMethodName будет использовать forwardInvocation:?

также как насчет методов, возвращающих (void)? или методы, которые возвращают объекты, не являющиеся идентификаторами, например (NSString)?


person toblerpwn    schedule 07.07.2012    source источник
comment
Обратите внимание, что -performSelector: withObject: также существует.   -  person NSResponder    schedule 08.07.2012


Ответы (3)


Нет. -forwardInvocation: используется как часть механизма пересылки сообщений. Не беспокойтесь о пересылке сообщений, так как на самом деле она используется только прокси-объектами, и велика вероятность, что вам никогда не понадобится ее использовать и вы будете знать, что вы ее используете.

-performSelector: предполагает, что тип возвращаемого сообщения id или совместим, поэтому это небезопасно, если оно используется для отправки сообщения, возвращаемый тип которого отличается (например, шире, чем указатель, такой как long long, в 32-битных системах, или возвращается через другой регистр / адрес, например float или большой struct.)

Если вы хотите косвенно отправить подобное сообщение, вы можете создать экземпляр класса NSInvocation, а затем отправить его -invoke. Затем возвращаемое значение сохраняется в объекте вызова и может быть доступно через него. -forwardInvocation: никогда не используется вами в этом сценарии.

Вообще говоря, если вы обнаружите, что используете -performSelector:, вы, вероятно, имеете дело с антипаттерном. В этом случае вы пытаетесь отправить сообщение, о котором ClassA официально не знает. Альтернативное решение - раскрыть эти частные методы.


Если у вас есть и ClassA, и ClassB, вы можете создать «частный» заголовок для ClassB, который включает в себя частные методы, которые вы хотите использовать. Если кто-то другой (например, Apple) владеет ClassB, вы имеете дело с недокументированными API-интерфейсами и, возможно, вам придется искать другой подход, поскольку Apple отклонит приложение, использующее такие API-интерфейсы.

Чтобы создать частный заголовок, войдите в Xcode и создайте новый файл заголовка. Назовите его как-нибудь вроде «ClassB + Internal.h» или «ClassB + PrivateMethodsForMeOnly.h». Относитесь к нему как к частному для вашего проекта - никто не сможет использовать его, если только они не являются коллегами (тот же подпроект, библиотека или компонент) с ClassB. В этот новый заголовок добавьте следующее:

#import "ClassB.h" // so we get the original class definition

@interface ClassB (PrivateMethodsForMeOnly)
- (double)someMethod;
- (const struct low_level_c_type_t)otherMethod:(int)i;
// etc. etc. etc.
@end

И в ClassA.m (не ClassA.h, если вы не хотите предоставлять эти методы всем, кто использует ClassA!) Добавьте следующую строку в свой раздел include:

#import "ClassB+PrivateMethodsForMeOnly.h"

ClassA после этого получит доступ к этим методам в новой категории.

person Jonathan Grynspan    schedule 07.07.2012
comment
Приватный танец заголовков - прекрасная вещь, когда вы создаете фреймворк или библиотеку. В одном проекте это немного глупо, поскольку все классы имеют полный доступ друг к другу. Тем не менее, обеспечение такой жесткости заставляет вас немного больше думать об иерархии классов, и это никогда не будет пустой тратой времени. - person Jonathan Grynspan; 08.07.2012
comment
Я владею обоими классами, но основная причина этого вопроса действительно в поисках правильной архитектуры mvc или mvc + store - в моем случае ClassA - это настраиваемый класс UITableViewCell, который «вызывается» классом UITableViewController (ClassB). управляемый объект - это простой класс NSObject, но в моем приложении пока только ClassB имеет доступ к этому классу NSObject, и я хочу сохранить его таким образом. - person toblerpwn; 09.07.2012

Хорошо, это несколько вопросов.

Прежде всего, забудьте о "forwardInvocation" - это практически метод, который вам нужен только для создания прокси-объектов.

Вызовы создаются с помощью [NSInvocation invocationWithMethodSignature ...], затем заполняются селектором, аргументами и целевым объектом, и в конечном итоге вы просто "вызываете" его. Вот как вы можете отправить (и даже сохранить) любой вызов метода.

performselector существует в нескольких вариантах, некоторые из которых принимают параметры, вызывают его после задержки или запускают метод в другом потоке. Это более «прямой» вызов - NSInvocation - это объект, который вы можете хранить, как любой другой объект, пока он вам не понадобится, тогда как «performselector» почти сразу запускает метод.

Что касается возвращаемых значений, я обычно придерживаюсь того, что написано в документации; если он сообщает мне, что метод должен возвращать идентификатор, я возвращаю объект (любой указатель на объект в порядке, поэтому NSString * в порядке. Возврат nil также в порядке, что в значительной степени покрывает случай 'void'), а не void и не int.

Также имейте в виду, что вызов performselector не совсем обычный; в большинстве случаев вы просто жестко кодируете вызов, как в "[MyObject SomeMethod: SomeParameter];". Вызов метода в стиле performSelector предназначен для особых случаев - материал target / action в объектах GUI является одним из таких случаев, когда вызываемый селектор хранится в объекте, поэтому его, очевидно, нельзя жестко запрограммировать.

person Christian Stieber    schedule 07.07.2012

Цель -forwardInvocation: - позволить объекту попытаться отправить сообщение, которое он не распознает, вместо того, чтобы просто отказаться. NSInvocation содержит не только селектор, но и все параметры сообщения. NSProxy использует его, но, если я правильно помню, у нас он был до распределенных объектов. Это один из способов реализовать делегирование «всего, что не может обработать этот класс».

Вам следует прочитать раздел о пересылке сообщений в документации по Objective-C.

person NSResponder    schedule 08.07.2012