对于我的游戏的联机模式,我使用的是GKScore
的context
属性,并且由于所有支持游戏中心的设备都可以更新到iOS 5(此时添加了context
属性),因此我要求context
属性可用于联机游戏。然而,我在实现这个运行时检查时遇到了问题。我假设我可以使用[GKScore instancesRespondToSelector:@selector(setContext:)]
检查它的存在,但这在iOS 5和5.1模拟器以及@selector(context)
上都返回false。究竟为什么会发生这种情况,请问执行这种检查的最干净、最正确的方法是什么?
发布于 2012-08-29 13:37:58
我不能完全解释这一点,但是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)]
应该返回NO。
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
发布于 2012-09-02 20:49:22
这看起来像是GK实现中的一个bug。
考虑下面的代码...
// 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实现,那么对于某些选择器,您会得到不同的结果(显然,否则就没有理由提供一个子类实现)。
看起来他们为一些特殊的属性做了一些时髦的事情,并为respondsToSelector:
提供了一个重写来处理特殊情况,但忘记了确保instancesRespondToSelector:
做了正确的事情。
如果你想浏览汇编代码,设置一个断点,我相信你能看出来不同之处。
我没有那么做。
我个人的好奇心只能让我走到这一步:-)
对于试图在代码中检测方法实现的情况,我建议创建一个临时GKScore
对象来执行测试,缓存结果,并释放临时对象。
发布于 2012-08-31 08:24:05
如果您专门寻找某个属性的存在,则应该使用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实例似乎没有响应正确的选择器,我的想法是context属性可能被声明为@dynamic
,因此+instancesRespondToSelector:
和-respondsToSelector:
将返回NO
(参见this question)。不知道内部细节,这是我能建议的全部,但是如果你只是想测试一个属性的存在,上面的示例代码可以工作。
顺便说一句,如果你不想让Objective-C运行时的include到处浮动,你可能想把这种行为封装在一个类中,或者把它包装在选择器中,而不是一字不差地把它放在某个地方。当然,这完全取决于您。
https://stackoverflow.com/questions/12126501
复制相似问题