2012-08-25 44 views
1

對於我的遊戲的在線模式,我使用屬性GKScore,並且所有支持Game Center的設備都可以更新到iOS 5(即添加context屬性時) ,我要求context屬性可以在線播放。但是,我在執行此運行時檢查時遇到問題。我假設我可以使用[GKScore instancesRespondToSelector:@selector(setContext:)]來檢查它的存在,但是這會在iOS 5和5.1模擬器以及@selector(context)上返回false。爲什麼在這種情況下會發生這種情況,請執行此檢查的最乾淨和最正確的方法是什麼?檢查GKScore實例是否具有上下文屬性

+1

你的意思是說「因爲*不*所有支持Game Center的設備都可以升級到iOS 5嗎?」遊戲中心已添加到iOS 4.1中。我的第二代iPod touch卡在iOS 4.2.1上,所以有Game Center,但永遠不會運行iOS 5.0。 – Dondragmer

+0

哦,對,我知道,我認爲第二代iPod Touch將與iPhone 3G相同,但事實並非如此。感謝那些信息。 – jrtc27

回答

1

我不能完全解釋這一點,但GKScore類的一個實例對象返回YESrepondsToSelector(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)]應該返回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 
+0

另外,爲了記錄,iOS 6按預期行事。而且我幾乎認爲這需要得到NDA的保護:P我不會獎勵賞金,以防某人能夠解釋爲什麼。 – jrtc27

+0

沒問題。我真的很想知道自己這種奇怪行爲的原因。 – Dondragmer

+0

如果我對「iPod2,1」型號名稱/號碼進行了快速而髒的測試以確定iPod Touch 2G(並因此給iOS 4.1+上的所有其他設備發送不同的消息),那麼人們會不會對我皺眉?編輯:沒關係,只是使消息更通用。 – jrtc27

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:將返回NO(參見this question)。不知道內部細節,這是我可以建議的,但如果你只是想測試一個屬性的存在,上面的示例代碼將工作。順便提一下,如果你不想讓包含在Objective-C運行時中的include包含在內,你可能希望將此行爲封裝到一個類中,或者將它包裝在一個選擇器中,而不是僅僅將它們保存在某個地方。當然,這完全取決於你。

+0

'-respondsToSelector:'確實返回了'YES'的預期結果,但正如我在上面對上述答案的評論中提到的那樣,這兩個工作在iOS 6 beta版中都如預期的那樣工作。我曾經考慮過使用Objective-C運行時,但總是看起來不那麼優雅,而且,雖然我知道這不是真的,但它似乎很容易打破。無論如何感謝您的答案,但我認爲我會堅持只檢查分配的實例,因爲它似乎工作。 – jrtc27

+0

@ jrtc27所有的好:)只是想我會讓你知道它。另外,-respondsToSelector:本身就是運行時函數class_respondsToSelector(Class,SEL)的包裝,就像NSObject的大部分基本方法一樣。你可以在這裏看到NSObject的實現:http://www.opensource.apple.com/source/objc4/objc4-532/runtime/NSObject.mm – Ephemera

+0

啊,這是一個很好的鏈接 - 謝謝你!我想你對'class_respondsToSelector'說得很對,但是源代碼使得我的問題中概括的行爲比我原先想象的更加陌生......噢,我*假設*我可以去深入一些程序集調試,但是我的目前的解決方案的作品,所以我很高興:) – jrtc27

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使用完全相同的C函數實現instancesRespondToSelector:。但是,他們對respondsToSelector:使用不同的C函數實現。這意味着,GKScore覆蓋respondsToSelector:有自己的執行(但不會覆蓋instancesRespondToSelector

如果您發送相同的GKScore實例的respondsToSelector:你對於某些選擇(明顯不同的結果,不同的C實現,或者就不會有理由提供一個子類實現)。

看起來他們做了一件時髦的少數特殊的屬性,並提供了respondsToSelector:的覆蓋來處理特殊情況下,卻忘了關於確保instancesRespondToSelector:做了正確的事情。

如果你想通過彙編代碼來設置一個斷點,我相信你可以看到不同之處。

我沒有那樣做。

我個人的好奇心,只會把我至今:-)

對於你的情況,試圖探測代碼的方法實現,我建議創建一個臨時的GKScore對象做你的測試,緩存導致,並釋放臨時對象。

+0

是的,我可以證實這種行爲。此外,在iOS 6中,'instancesRespondToSelector:'實現已被覆蓋,解釋了爲什麼它按預期工作。如果你看到了問題的根源,我會獎賞你的賞金,但正確的答案應該保持原樣,作爲第一個答案,這也給了我想辦法忽略的解決辦法。 – jrtc27

1

我也遇到過這個問題,但在我的情況下與GKTurnBasedMatchParticipant。我做了一個快速轉儲,將#instancesRespondToSelector:發送給此類的每個屬性。

這裏的結果:

1 playerID   false 
2 lastTurnDate  false 
3 status    true 
4 matchOutcome  false 
5 matchOutcomeString true 
6 isWinner   true 
7 invitedBy   false 
8 inviteMessage  false 
9 internal   true 

注意有多少屬性的報告說,他們不能作爲選擇發送。但是,請注意另外還有一個「內部」屬性。現在查看查詢此內部對象是否會響應屬性選擇器的結果:

1 playerID   true 
2 lastTurnDate  true 
3 status    true 
4 matchOutcome  true 
5 matchOutcomeString false 
6 isWinner   false 
7 invitedBy   true 
8 inviteMessage  true 
9 internal   false 

因此,許多缺少的屬性都在此處。我想使用非文件化的「內部」功能來解決明顯的Apple錯誤並不是很安全,但仍然有趣。

編輯:經過一天的搗亂,我發現這裏的問題。這些流氓屬性實際上被設置爲轉發方法以轉發到「內部」對象。作爲ObjectiveC noob,我沒有意識到這是一個完全可以接受的事情。

在我的情況下,我不只是試圖檢測一個對象是否響應選擇器,但我實際上也想調用它。因此,應對轉發的一般解決方案是:

(a)檢查響應使用[實例#respondsToSelector:sel]而不是[[實例類] instanceRespondsToSelector:del]的可用性。

(二)要調用可能,也可能不會,被轉發的方法做到這一點:

NSMethodSignature *signature = [instance methodSignatureForSelector:sel]; 
if (!signature) { 
    // It's possible this is a message forwarding selector, so try this before giving up. 
    NSObject *fwd=[instance forwardingTargetForSelector:sel]; 
    if (fwd && (signature= [fwd methodSignatureForSelector:sel])) 
     // Redirect to the forwarding target 
     instance=fwd; 
    else { 
     // ERROR case - selector is really not supported 
    } 
} 

NSInvocation *invocation=[NSInvocation invocationWithMethodSignature:signature]; 

// Proceed with invocation setup 

我希望這是有用的,以防止其他人,因爲我對這個浪費太多的時間。