对于我的游戏的在线模式,我使用context
的{{1}}属性,并且所有支持Game Center的设备都可以更新到iOS 5(即{{1}时已添加属性),我要求GKScore
属性可以在线播放。但是,我在实现此运行时检查时遇到问题。我假设我可以使用context
来检查它的存在,但是这会在iOS 5和5.1模拟器以及context
上返回false。为什么发生这种情况,请问最干净,最正确的方法是什么?
答案 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或更高版本中可用的属性:context
和shouldSetDefaultLeaderboard
。也许那些编译器指令意味着该类不能保证它将支持这两个属性。
根据此假设,[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运行时浮动,你可能想要将这个行为封装在一个类中,或者将它包装在一个选择器中而不是简单地将它粘贴在某个地方。当然,这完全取决于你。