NSDictionary‹FBGraphUser› * объяснение синтаксиса пользователя

В Facebook iOS SDK запросы возвращаются со следующим обработчиком:

 ^(FBRequestConnection *connection, 
       NSDictionary<FBGraphUser> *user, 
       NSError *error) { }

Затем к пользовательской переменной можно получить доступ с помощью таких вызовов...

   self.userNameLabel.text = user.name;
   self.userProfileImage.profileID = user.id;

Этот синтаксис несколько похож на синтаксис id <protocolDelegate> object, который является общим объявлением свойства, за исключением того, что NSDictionary явно является объектом id, и этот словарь соответствует протоколу? Но откуда берется точечный синтаксис и как можно утверждать, что произвольный объект NSFoundation соответствует протоколу, не создавая подкласса самого объекта и не приводя его в соответствие?

Я провел дополнительное исследование о точечной записи и NSDictionary, и оказалось, что это невозможно использовать запись через точку в словаре без добавления категории в NSDictionary. Однако я не видел ссылок на синтаксис ‹> в Apple Documentation, чтобы указать, что этот конкретный экземпляр NSDictionary соответствует этой нотации.

И документация Facebook немного скудна о том, как работает эта упаковка:

Протокол FBGraphUser представляет наиболее часто используемые свойства объекта пользователя Facebook. Его можно использовать для доступа к объекту NSDictionary, который был обернут фасадом FBGraphObject.

Если следовать этому пути к документации FBGraphObject, то есть методы, которые возвращают словари, соответствующие этому "фасаду...", но нет дальнейших объяснений того, как оборачивать словарь.

Итак, я думаю, что у меня несколько вопросов:

  1. Как должен выглядеть базовый код, чтобы такой синтаксис работал?
  2. Почему он существует?
  3. Зачем Facebook реализовывать это таким образом, а не просто создавать объект, в который они могут конвертировать данные?

Любое объяснение или понимание будут очень признательны!


person AdamG    schedule 07.08.2013    source источник


Ответы (2)


По сути, NSDictionary<FBGraphUser> *user подразумевает объект, который наследуется от NSDictionary, добавляя функциональные возможности (в частности, типизированный доступ), объявленные протоколом FBGraphUser.

Причины такого подхода достаточно подробно описаны в FBGraphObject. документации (протокол FBGraphUser расширяет протокол FBGraphObject). Что может сбить вас с толку, так это то, что FBGraphObject — это протокол (описанный здесь) и класс (описанный здесь), который наследуется от NSMutableDictionary.

С точки зрения внутренней реализации, это довольно продвинутая динамическая магия Objective-C, о которой вы, вероятно, не хотите беспокоиться. Все, что вам нужно знать, это то, что вы можете рассматривать объект как словарь, если хотите, или использовать дополнительные методы в протоколе. Если вы действительно хотите узнать подробности, вы можете посмотреть на исходный код для FBGraphObject, в частности, эти методы:

#pragma mark -
#pragma mark NSObject overrides

// make the respondsToSelector method do the right thing for the selectors we handle
- (BOOL)respondsToSelector:(SEL)sel
{
    return  [super respondsToSelector:sel] ||
    ([FBGraphObject inferredImplTypeForSelector:sel] != SelectorInferredImplTypeNone);
}

- (BOOL)conformsToProtocol:(Protocol *)protocol {
    return  [super conformsToProtocol:protocol] ||
    ([FBGraphObject isProtocolImplementationInferable:protocol 
                           checkFBGraphObjectAdoption:YES]);
}

// returns the signature for the method that we will actually invoke
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
    SEL alternateSelector = sel;

    // if we should forward, to where?
    switch ([FBGraphObject inferredImplTypeForSelector:sel]) {
        case SelectorInferredImplTypeGet:
            alternateSelector = @selector(objectForKey:);
            break;
        case SelectorInferredImplTypeSet:
            alternateSelector = @selector(setObject:forKey:);
            break;
        case SelectorInferredImplTypeNone:
        default:
            break;
    }

    return [super methodSignatureForSelector:alternateSelector];
}

// forwards otherwise missing selectors that match the FBGraphObject convention
- (void)forwardInvocation:(NSInvocation *)invocation {
    // if we should forward, to where?
    switch ([FBGraphObject inferredImplTypeForSelector:[invocation selector]]) {
        case SelectorInferredImplTypeGet: {
            // property getter impl uses the selector name as an argument...
            NSString *propertyName = NSStringFromSelector([invocation selector]);
            [invocation setArgument:&propertyName atIndex:2];
            //... to the replacement method objectForKey:
            invocation.selector = @selector(objectForKey:);
            [invocation invokeWithTarget:self];
            break;
        }
        case SelectorInferredImplTypeSet: {
            // property setter impl uses the selector name as an argument...
            NSMutableString *propertyName = [NSMutableString stringWithString:NSStringFromSelector([invocation selector])];
            // remove 'set' and trailing ':', and lowercase the new first character
            [propertyName deleteCharactersInRange:NSMakeRange(0, 3)];                       // "set"
            [propertyName deleteCharactersInRange:NSMakeRange(propertyName.length - 1, 1)]; // ":"

            NSString *firstChar = [[propertyName substringWithRange:NSMakeRange(0,1)] lowercaseString];
            [propertyName replaceCharactersInRange:NSMakeRange(0, 1) withString:firstChar];
            // the object argument is already in the right place (2), but we need to set the key argument
            [invocation setArgument:&propertyName atIndex:3];
            // and replace the missing method with setObject:forKey:
            invocation.selector = @selector(setObject:forKey:);
            [invocation invokeWithTarget:self]; 
            break;
        } 
        case SelectorInferredImplTypeNone:
        default: 
            [super forwardInvocation:invocation];
            return;
    }
}
person jbat100    schedule 07.08.2013
comment
jbat100 могу ли я использовать FBGraphObject другими способами, которые выходят за рамки SDK Facebook, поскольку эта функция весьма полезна. Я создал вопрос здесь. Спасибо - person otakuProgrammer; 13.08.2014

Этот синтаксис чем-то похож на синтаксис синтаксиса объекта id.

"Несколько похоже"? Как насчет "идентичных"?

и этот словарь соответствует протоколу

Нет, в объявлении сказано, что вы должны передать объект класса NSDictionary, который в то же время соответствует протоколу FBGraphUser.

Но откуда взялся точечный синтаксис

Я этого не понимаю. Оно исходит от программиста, написавшего рассматриваемый фрагмент кода. И это возможно, потому что протокол FBGraphUser объявляет некоторые свойства, к которым затем можно получить доступ через точечную нотацию.

и как можно утверждать, что произвольный объект NSFoundation соответствует протоколу, не создавая подкласса самого объекта и не приводя его в соответствие?

Он называется не "NSFoundation", а просто Foundation. И не объект не "соответствует" (а скорее "соответствует") протоколу, а его класс. И вы только что сами показали синтаксис для этого.

И как это реализовано? Просто: категория.

#import <Foundation/Foundation.h>

@protocol Foo
@property (readonly, assign) int answer;
@end

@interface NSDictionary (MyCategory) <Foo>
@end

@implementation NSDictionary (MyCategory)

- (int)answer
{
    return 42;
}

@end

int main()
{
    NSDictionary *d = [NSDictionary dictionary];
    NSLog(@"%d", d.answer);
    return 0;
}

Это SSCCE, т.е. е. он компилируется и работает как есть, попробуйте!

Как должен выглядеть базовый код, чтобы такой синтаксис работал?

Ответил выше.

Почему он существует?

Потому что язык определен так.

Зачем Facebook реализовывать это таким образом, а не просто создавать объект, в который они могут конвертировать данные?

Не знаю, спросите у парней из Facebook.

person Community    schedule 07.08.2013