3

在 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 文档中看到任何对 <> 语法的引用来表明这个特定的 NSDictionary 实例符合该表示法。

Facebook 文档对这种包装的工作方式有点稀疏:

FBGraphUser 协议代表了 Facebook 用户对象最常用的属性。它可用于访问已用 FBGraphObject 外观包装的 NSDictionary 对象。

如果有人遵循 FBGraphObject 文档的这条线索,那么有一些方法可以返回符合这个“外观......”的字典,但没有进一步解释如何包装字典。

所以我想我的问题有几个:

  1. 使这种语法工作的底层代码是什么样的?
  2. 它为什么存在?
  3. 为什么 facebook 会以这种方式实现它,而不是仅仅制作一个他们可以将数据转换成的对象?

任何解释或见解将不胜感激!

4

2 回答 2

6

基本上,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;
    }
}
于 2013-08-07T23:20:34.807 回答
4

这种语法有点类似于语法 id object 语法

“有点像”?“一模一样”怎么办?

并且该字典符合协议

不,声明说你必须传入一个类是的对象,同时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的家伙。

于 2013-08-07T23:11:22.200 回答