Introspect параметър от тип: id, за да реши дали е клас или протокол

Имам следния метод:

-(void)SomeMethod:(id)classOrProtocol;

Ще се нарича така:

[self someMethod:@protocol(SomeProtocol)];

Or

[self someMethod:[SomeClass class]];

В тялото на метода трябва да реша дали |classOrProtocol| е:

Всеки клас (клас) ИЛИ всеки протокол (протокол) ИЛИ нещо друго

[[classOrProtocol class] isKindOfClass: [Protocol class]]

Резултати от грешка (компилация):

„Протоколът“ на приемника е препратен клас и съответният @интерфейс може да не съществува

И така, как мога да различа протокол от клас от нещо друго?


person Undistraction    schedule 21.09.2011    source източник


Отговори (2)


В Objective-C 2 (т.е. освен ако не използвате 32-битово време за изпълнение на OS X) Protocol се дефинира като просто преден клас, вижте /usr/include/objc/runtime.h. Реалният интерфейс никъде не е деклариран. Можете да опитате да включите /usr/inlcude/objc/Protocol.h като кажете

#import <objc/Protocol.h>

но както е написано там, нито един метод не се поддържа публично за екземпляр на Protocol. Единственият приет начин за работа с Protocol екземпляри е да се използват функции за изпълнение, дадени в Справочник за изпълнение на Objective-C. Дори не е публично дефинирано дали Protocol е подклас на нещо и дори не е посочено, че прилага NSObject протокол. Така че не можете да извикате никакъв метод на него.

Разбира се, можете да използвате изходния код на средата за изпълнение, за да видите какво се случва. Protocol наследява от Object (което е остатък от NeXTSTep преди OpenStep), а не от NSObject. Така че не можете да използвате познатите методи за NSObject-извлечени обекти, включително Class от NSObject-извлечени обекти. Вижте реализациите с отворен код на Protocol.h и Protocol.m. Както виждате там, самият клас Protocol не прави нищо, защото всеки метод просто прехвърля self към protocol_t и извиква функция. Всъщност, както може да се види от функцията _read_images и други в objc-runtime-new.mm, указателят isa на Protocol обект се задава ръчно, когато изпълнимият файл и библиотеките се зареждат и никога не се използват.

Така че не се опитвайте да проверявате дали id е Protocol или не.

Ако наистина трябва да направите това, можете да използвате

id foo=...;
if(foo->isa==class_getClass("Protocol")){
    ...
}

Но, сериозно, не го правете.

person Yuji    schedule 21.09.2011
comment
Благодаря за изчерпателния отговор. Използвам този метод в код, свързан с инжектиране на зависимост, и в този контекст мисля, че има смисъл. Но може би трябва да го разделя на два метода. - person Undistraction; 21.09.2011
comment
Добавена е малко повече информация за вътрешността. Беше забавно да чета изходния код, благодаря за хубавия въпрос, 1ndivisible! - person Yuji; 21.09.2011

Това не е проблем, причинен от невъзможност да се определи дали е клас или протокол. Грешката е причинена от липсващ интерфейс на Protocol клас. Уверете се, че импортирате Protocol.m в горната част на вашия файл за внедряване, където тествате типа на аргумента.

Можете също да опитате да използвате функцията NSClassFromString(), която ще върне Class обект или nil. Имайте предвид обаче, че ако се върне nil, това не означава, че този аргумент е протокол. Това просто означава, че може да бъде и недефиниран клас!

Има и метод NSProtocolFromString, който връща подходящи резултати - Протокол за протокол и nil за недефиниран протокол.

person Eimantas    schedule 21.09.2011
comment
Благодаря за отговора. Ако се опитам да импортирам Protocol.m (или Protocol.h), получавам грешка „Файлът не е намерен“. Също така мога да използвам протокол като тип параметър в същия файл без импортиране. - person Undistraction; 21.09.2011
comment
Добре, реших, че е малко глупост, тъй като Protocol е клас за действителен тип протокол, който няма файлове Protocol.(h|m) като такива. Съжалявам, че ви подвеждам. - person Eimantas; 21.09.2011