2009-09-11 21 views
7

我是想一組矩形與相應的行動聯繫起來,所以我試圖做我可以在結構中放置一個Objective-C選擇器嗎?

struct menuActions { 
    CGRect rect; 
    SEL action; 
}; 

struct menuActions someMenuRects[] = { 
    { { { 0, 0 }, {320, 60 } }, @selector(doSomething) }, 
    { { { 0, 60}, {320, 50 } }, @selector(doSomethingElse) }, 
}; 

,但我得到的錯誤「初始元素不是常量」。是否有某種原因,我試圖做的事情一般是不允許的,或者在全球範圍內是不允許的,或者我有某種小標點符號錯誤?

+2

我不知道爲什麼'@ selector'不是常量,但是如果你可以把初始化放到一個函數中,你就不必擔心這個。 – Amok 2009-09-11 22:10:15

+0

我不想編寫像someMenuRects [0] .action = @doSomething這樣的代碼,因爲那麼我可能只是在運行時做同樣的事情,即如果(CGRectContainsPoint(someMenuRects [0],pt)){[self doSomething]} – 2009-09-11 22:35:32

+2

選擇器不是常量,因爲該值在運行時非常早才確定。因此,如果需要,可以在該處粘貼一個字符串並在運行時進行查找。 – bbum 2009-09-12 06:22:25

回答

23

這個答案是爲什麼"initializer element is not constant"

鑑於下面的例子:

SEL theSelector; // Global variable 

void func(void) { 
    theSelector = @selector(constantSelector:test:); 
} 

編譯爲像這樣的i386架構:

.objc_meth_var_names 
L_OBJC_METH_VAR_NAME_4: 
    .ascii "constantSelector:test:\0" 

    .objc_message_refs 
    .align 2 
L_OBJC_SELECTOR_REFERENCES_5: 
    .long L_OBJC_METH_VAR_NAME_4 

這部分定義了兩個本地(在彙編代碼方面)「的變量」(實際上標籤),L_OBJC_METH_VAR_NAME_4L_OBJC_SELECTOR_REFERENCES_5。文本.objc_meth_var_names.objc_message_refs,就在'變量'標籤之前,告訴彙編器將對象文件的哪一部分放在「後面的內容」中。這些部分對鏈接器有意義。 L_OBJC_SELECTOR_REFERENCES_5最初設置爲L_OBJC_METH_VAR_NAME_4的地址。

在執行加載時,程序開始執行之前,所述接頭做了大約是這樣的:

  • 迭代所.objc_message_refs 部中的每個條目。
  • 每個條目最初設置爲指向0終止的C字符串的指針。
  • 在我們的例子中,指針被初始設置爲 地址的L_OBJC_METH_VAR_NAME_4,其 包含ASCIIC"constantSelector:test:"
  • 然後執行 sel_registerName("constantSelector:test:") 並將返回值存儲在 L_OBJC_SELECTOR_REFERENCES_5。鏈接器, 知道私有實現細節, 可能不會字面上調用。

本質上的連接器在加載時間爲我們的例子中執行此:

L_OBJC_SELECTOR_REFERENCES_5 = sel_registerName("constantSelector:test:"); 

這就是爲什麼"initializer element is not constant" - 初始化元素必須是在編譯時間常數。在程序開始執行之前,該值實際上並不知道。即使這樣,您的struct聲明也存儲在不同的鏈接器部分,即.data部分。鏈接器只知道如何更新.objc_message_refs部分中的SEL值,並且沒有辦法將運行時計算的SEL值從.objc_message_refs「複製」到.data中的某個任意位置。

C源代碼

theSelector = @selector(constantSelector:test:); 

...變爲:

movl L_OBJC_SELECTOR_REFERENCES_5, %edx // The SEL value the linker placed there. 
    movl L_theSelector$non_lazy_ptr, %eax // The address of theSelector. 
    movl %edx, (%eax)      // theSelector = L_OBJC_SELECTOR_REFERENCES_5; 

由於鏈接做節目之前,其所有工作都在執行,L_OBJC_SELECTOR_REFERENCES_5包含你的值完全相同將得到,如果你打電話sel_registerName("constantSelector:test:")

theSelector = sel_registerName("constantSelector:test:"); 

區別在於這是一個函數調用,並且該函數需要執行查找選擇器(如果它已被註冊)的實際工作,或者執行分配新的SEL值以註冊選擇器的過程。這是相當慢,只是加載一個常數值。雖然這是'較慢',但它確實允許您傳遞任意的C字符串。在以下情況下,這可能很有用:

  • 選擇器在編譯時不知道。
  • 只有在調用之前,纔會知道選擇器。
  • 您需要在運行時動態改變選擇器。

所有選擇器都需要通過,這個選項只需要註冊一次SEL。這對於任何給定的選擇器都具有恰到好處的一個值的優點。雖然實現私人細節,SEL是「通常」,只是一個char *指向選擇器C字符串文本副本的指針。

現在你知道了。知道是戰鬥的一半!

+1

+1。很好的答案。 – 2009-09-12 05:17:01

+0

感謝您的詳細信息。我假設sel_registerName發生在編譯/鏈接時間,而不是在加載時間,對於一個常量選擇器...所以我猜sel_registerName真的只是「更慢」,如果你把它稱爲> 1次... – 2009-09-12 08:28:39

+0

哇.. 。不明白這一半,但聽起來你知道你在說什麼)+1 – 2013-10-01 20:47:43

4

如何:

struct menuActions { 
    CGRect rect; 
    const char *action; 
}; 

struct menuActions someMenuRects[] = { 
    { { { 0, 0 }, {320, 60 } }, "doSomething" }, 
    { { { 0, 60}, {320, 50 } }, "doSomethingElse" }, 
}; 

在運行時,註冊選擇:

int numberOfActions = 2; 
for (int i=0; i < numberOfActions; i++) 
    NSLog (@"%s", sel_registerName(someMenuRects[i].action)); 

輸出:

[Session started at 2009-09-11 16:16:12 -0700.] 
2009-09-11 16:16:14.527 TestApp[12800:207] @selector(doSomething) 
2009-09-11 16:16:14.531 TestApp[12800:207] @selector(doSomethingElse) 

更多在Objective-C 2.0 Runtime Reference

+0

巧妙的+1 – 2009-09-11 23:33:29

+0

是的,我意識到了sel_registerName,但原則上在編譯時確定在運行時執行此步驟似乎是錯誤的。也許我的問題的答案實際上只是「不,你不能」,我應該問另一個問題,例如「在Objective-C中構建函數調度表的最佳方式是什麼......」 – 2009-09-11 23:35:35

+1

我並不是指假設你無知。這是一個有趣的問題,但我不知道正確的答案。 – 2009-09-11 23:44:33

-1

看起來你在這裏重新創建NSCell。如果你想實現一個菜單,爲什麼不使用現有的UI類?

+1

iPhone有NSCell嗎? – 2009-09-12 15:39:51

相關問題