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にカテゴリを追加せずに辞書でドット表記を使用することはできないようです。ただし、この特定の NSDictionary インスタンスがその表記法に準拠していることを示す<> 構文の参照は、 Apple のドキュメントでは見当たりませんでした。

また、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 オブジェクトの構文に多少似ています。

「ちょっと似てる?」「同じ」はどうですか?

そしてその辞書はプロトコルに準拠しています

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 に答える