检查GKScore实例是否具有上下文属性

时间:2012-08-25 23:33:06

标签: objective-c ios game-center

对于我的游戏的在线模式,我使用context的{​​{1}}属性,并且所有支持Game Center的设备都可以更新到iOS 5(即{{1}时已添加属性),我要求GKScore属性可以在线播放。但是,我在实现此运行时检查时遇到问题。我假设我可以使用context来检查它的存在,但是这会在iOS 5和5.1模拟器以及context上返回false。为什么发生这种情况,请问最干净,最正确的方法是什么?

3 个答案:

答案 0 :(得分:4)

这看起来像是GK实施中的一个错误。

考虑以下代码......

// Get the C-functions that are really called when the selector message is sent...
typedef BOOL (*XX)(id, SEL, SEL);
XX classImpNSObject = (XX)[NSObject
    methodForSelector:@selector(instancesRespondToSelector:)];
XX classImpGKScore = (XX)[GKScore
    methodForSelector:@selector(instancesRespondToSelector:)];
XX instImpNSObject = (XX)[NSObject
    instanceMethodForSelector:@selector(respondsToSelector:)];
XX instImpGKScore = (XX)[GKScore
    instanceMethodForSelector:@selector(respondsToSelector:)];

// See that the same C function is called for both of these...
NSLog(@"instancesRespondToSelector: %p, %p", classImpNSObject, classImpGKScore);

// But, different functions are called for these...
NSLog(@"respondsToSelector: %p, %p", instImpNSObject, instImpGKScore);

// Invoke to C-Functions for instancesRespondToSelector:
NSLog(@"NSObject instancesRespondToSelector: context: %s",
    classImpNSObject(
        [NSObject class],
        @selector(instancesRespondToSelector:),
        @selector(context))
    ? "YES" : "NO");
NSLog(@"GKScore instancesRespondToSelector: context: %s",
    classImpGKScore(
        [GKScore class],
        @selector(instancesRespondToSelector:),
        @selector(context))
    ? "YES" : "NO");

// Invoke the C functions for respondsToSelector:
GKScore *gkScore = [[GKScore alloc] init];
NSLog(@"NSObject respondsToSelector: context: %s",
    instImpNSObject(
        gkScore,
        @selector(respondsToSelector:),
        @selector(context))
    ? "YES" : "NO");
NSLog(@"GKScore respondsToSelector: context: %s",
    instImpGKScore(
        gkScore,
        @selector(respondsToSelector:),
        @selector(context))
    ? "YES" : "NO");

基本上,我们只是提取了在响应这些消息时调用的C函数。

如您所见,NSObject和GKScore对instancesRespondToSelector:使用完全相同的C函数实现。但是,它们对respondsToSelector:使用不同的C函数实现。这意味着GKScore会使用自己的实现覆盖respondsToSelector:(但不会覆盖instancesRespondToSelector

如果将相同的GKScore实例发送到respondsToSelector:的不同C实现,则会为某些选择器获得不同的结果(显然,或者没有理由提供子类实现)。 / p>

看起来他们为一些特殊属性做了一些时髦的事情,并为respondsToSelector:提供了覆盖特殊情况的覆盖,但忘了确保instancesRespondToSelector:做对了。

如果你想通过汇编代码,设置一个断点,我相信你可以看到差异。

我没有这样做。

我个人的好奇只会带我到目前为止: - )

对于你的情况,尝试在代码中检测方法实现时,我建议创建一个临时的GKScore对象来进行测试,缓存该结果,并释放临时对象。

答案 1 :(得分:1)

我无法完全解释这一点,但是类GKScore实例化对象会将 YES 返回repondsToSelector(context),即使在课程中也是如此说它不会。如果没有其他解决方案可行,则构造一个GKScore对象只是为了查询它。


我想知道[[GKScore alloc] init]是否实际返回的类型不是GKScore。这可能发生。

GKScore *instantiatedScore = [[GKScore alloc] init]; // Add autorelease if using manual reference counting.
NSString* className = NSStringFromClass([instantiatedScore class]);
NSLog(@"instantiatedScore class name = %@", className);

但是,根据这个输出,它没有:

instantiatedScore class name = GKScore

我想知道GKSCore.h头文件中的编译器指令是否会影响这一点。它定义了两个仅在iOS 5.0或更高版本中可用的属性:contextshouldSetDefaultLeaderboard。也许那些编译器指令意味着该类不能保证它将支持这两个属性。

根据此假设,[GKScore instancesRepondToSelector:@selector(category)]应返回 YES ,但[GKScore instancesRepondToSelector:@selector(shouldSetDefaultLeaderboard)]应返回

GKScore *instantiatedScore = [[GKScore alloc] init]; // Add autorelease if using manual reference counting.
NSLog(@"GKScore category = %d", [GKScore instancesRespondToSelector:@selector(category)]);
NSLog(@"instantiatedScore category = %d", [instantiatedScore respondsToSelector:@selector(category)]);

NSLog(@"GKScore context = %d", [GKScore instancesRespondToSelector:@selector(context)]);
NSLog(@"instantiatedScore context = %d", [instantiatedScore respondsToSelector:@selector(context)]);

NSLog(@"GKScore shouldSetDefaultLeaderboard = %d", [GKScore instancesRespondToSelector:@selector(shouldSetDefaultLeaderboard)]);
NSLog(@"instantiatedScore shouldSetDefaultLeaderboard = %d", [instantiatedScore respondsToSelector:@selector(shouldSetDefaultLeaderboard)]);

但是,输出比那更奇怪:

GKScore category = 0
instantiatedScore category = 1
GKScore context = 0
instantiatedScore context = 1
GKScore shouldSetDefaultLeaderboard = 1
instantiatedScore shouldSetDefaultLeaderboard = 1

答案 2 :(得分:1)

如果您专门寻找属性的存在,则应使用Objective-C运行时函数:

class_getProperty(Class cls, const char *name)

要使用它,您必须导入:

#import <objc/runtime.h>

作为一个很小的测试示例,以下是测试特定属性是否存在的方法:

#import <objc/runtime.h>

//...

objc_property_t realP = class_getProperty([GKScore class], "context");
objc_property_t fakeP = class_getProperty([GKScore class], "fakeContext");


if (realP) {
    NSLog(@"context exists");
}
if (!fakeP) {
    NSLog(@"fakeContext does not exist");
}
// Both statements will log correctly.

至于为什么GKScore实例似乎没有响应正确的选择器,我的想法是上下文属性可能被声明为@dynamic,因此+instancesRespondToSelector:-respondsToSelector:将返回{ {1}}(见this question)。不知道内部细节,这是我可以建议的,但如果您只想测试属性的存在,上面的示例代码将起作用。

顺便提一下,如果你不想让一个include到Objective-C运行时浮动,你可能想要将这个行为封装在一个类中,或者将它包装在一个选择器中而不是简单地将它粘贴在某个地方。当然,这完全取决于你。