首先請諒解我可能使用很多英文,畢竟英文資料將來會是你的主要資料來源。
這篇部落格將描述一些我見到的眾多Cocoa開發新手遇到的問題和障礙。在你繼續深入學習MacOS編程之前,請停下腳步弄清這些問題。如果你是新手,這個教程不要希望一次能看的非常透徹,學一定階段反回來再看看又會有新的體會的。
1. language background
首先c, c++語言背景,必須。 很多人問:”沒有任何語言基礎,我不想學c直接學objective-c。“ 這裡我簡單說幾句,objc是c的超集,也就是說大部分objc代碼其實是c、而且眾多開原始碼是c,c++寫成的。你不學好c在unix世界裡只能是個二流開發人員!也許說得過於嚴厲,不過自己斟酌把。
接著English,必須。 蘋果不會把它們文檔都寫成中文的。“什麼,有人翻譯?” 等有人閑著翻譯出來了的時候,大家都已經賣了很多軟體了。你也是跟著人家屁股後面做開發。
2. runtime(運行時)
Objective-c是動態語言, 很多新手或者開發人員常常被runtime這個東西所迷惑。而恰恰這是一個非常重要的概念。我可以這麼問:“如果讓你(設計、)實現一個電腦語言,你要如何下手?” 很少程式員這麼思考過。但是這麼一問,就會強迫你從更高層次思考1以前的問題了。 注意我這句話‘設計’括起來了,稍微次要點,關鍵是實現。
我把實現分成3種不同的層次:
第一種是傳統的面向過程的語言開發,例如c語言。實現c語言編譯器很簡單,只要按照文法規則實現一個LALR文法分析器就可以了,編譯器最佳化是非常難的topic,不在這裡討論範圍內,忽略。 這裡我們實現了編譯器其中最最基礎和原始的目標之一就是把一份代碼裡的函數名稱,轉化成一個相對記憶體位址,把調用這個函數的語句轉換成一個jmp跳轉指令。在程式開始運行時候,調用語句可以正確跳轉到對應的函數地址。 這樣很好,也很直白,但是太死板了。Everything is predetermined.
我們希望語言更加靈活,於是有了第二種改進,開發物件導向的語言,例如c++。 c++在c的基礎上增加了類的部分。但這到底意味著什麼呢?我們再寫它的編譯器要如何考慮呢?其實,就是讓編譯器多繞個彎,在嚴格的c編譯器上增加一層類處理的機制,把一個函數限制在它處在的class環境裡,每次請求一個函數調用,先找到它的對象, 其類型,傳回值,參數等等,確定了這些後再jmp跳轉到需要的函數。這樣很多程式增加了靈活性同樣一個函數調用會根據請求參數和類的環境返回完全不同的結果。增加類機制後,就類比了現實世界的抽象模式,不同的對象有不同的屬性和方法。同樣的方法,不同的類有不同的行為! 這裡大家就可以看到作為一個編譯器開發人員都做了哪些進一步的思考。雖然面相對象語言有所改進,但還是死板, 我們仍然叫c++是static language.
希望更加靈活!於是我們完全把上面哪個類的實現部分抽象出來,做成一套完整運行階段的檢測環境,形成第三種,動態語言。這次再寫編譯器甚至保留部分代碼裡的sytax名稱,名稱錯誤偵測,runtime環境註冊所以全域的類,函數,變數等等資訊等等,我們可以無限的為這個層增加必要的功能。調用函數時候,會先從這個運行時環境裡檢測所以可能的參數再做jmp跳轉。這,就是runtime。編譯器開發起來比上面更加彎彎繞。但是這個層極大增加了程式的靈活性。 例如當調用一個函數時候,前2種語言,很有可能一個jmp到了一個非法地址導致程式crash, 但是在這個層次裡面,runtime就過濾掉了這些可能性。 這就是為什麼dynamic langauge更加強壯。 因為編譯器和runtime環境開發人員已經幫你處理了這些問題。
好了上面說著這麼多,我們再返回來看objective-c的這些語句:
id obj=self; if ([obj respondsToSelector:@selector(function1:)) { } if ([obj isKindOfClass:[NSArray class]] ) { } if ([obj conformsToProtocol:@protocol(myProtocol)]) { } if ([[obj class] isSubclassOfClass:[NSArray class]]) { } [obj someNonExistFunction];
看似很簡單的語句,但是為了讓語言實現這個能力,語言開發人員要付出很多努力實現runtime環境。這裡運行時環境處理了弱類型、函數存在檢查工作。runtime會檢測註冊列表裡是否存在對應的函數,類型是否正確,最後確定下來正確的函數地址,再進行儲存寄存器狀態,壓棧,函數調用等等實際的操作。
id knife=[Knife grateKnife]; NSArray *monsterList=[NSArray array]; [monsterList makeObjectsPerformSelector:@selector(killMonster:) withObject:knife];
用c,c++完成這個功能還是比較非常麻煩的,但是動態語言處理卻非常簡單並且這些語句讓objc語言更加intuitive。
在Objc中針對對象的函數調用將不再是普通的函數調用,[obj function1With:var1]; 這樣的函數調用將被運行時環境轉換成objc_msgSend(target, @selector(function1With:), var1);。Objc的runtime環境是開源的,所以我們可以拿出一下實現做簡單介紹,可以看到objc_msgSend由組合語言實現,我們甚至不必閱讀代碼,只需查看注釋就可以瞭解,運行時環境在函數調用前做了比較全面的安全檢查,已確保動態語言函數調用不會導致程式crash。對於希望深入學習的朋友可以自行下載Objc-runtime原始碼來閱讀,這裡就不再深入講解。
/******************************************************************** * id objc_msgSend(id self, * SEL op, * ...) * * On entry: a1 is the message receiver, * a2 is the selector ********************************************************************/ ENTRY objc_msgSend # check whether receiver is nil teq a1, #0 moveq a2, #0 bxeq lr # save registers and load receiver's class for CacheLookup stmfd sp!, {a4,v1-v3} ldr v1, [a1, #ISA] # receiver is non-nil: search the cache CacheLookup a2, LMsgSendCacheMiss # cache hit (imp in ip) - prep for forwarding, restore registers and call teq v1, v1 /* set nonstret (eq) */ ldmfd sp!, {a4,v1-v3} bx ip # cache miss: go search the method lists LMsgSendCacheMiss: ldmfd sp!, {a4,v1-v3} b _objc_msgSend_uncached LMsgSendExit: END_ENTRY objc_msgSend .text .align 2 _objc_msgSend_uncached: # Push stack frame stmfd sp!, {a1-a4,r7,lr} add r7, sp, #16 SAVE_VFP # Load class and selector ldr a1, [a1, #ISA] /* class = receiver->isa */ # MOVE a2, a2 /* selector already in a2 */ # Do the lookup MI_CALL_EXTERNAL(__class_lookupMethodAndLoadCache) MOVE ip, a1 # Prep for forwarding, Pop stack frame and call imp teq v1, v1 /* set nonstret (eq) */ RESTORE_VFP ldmfd sp!, {a1-a4,r7,lr} bx ip
現在說一下runtime的負面影響:
1. 關於執行效率問題。 “靜態語言執行效率要比動態語言高”,這句沒錯。因為一部分cpu計算損耗在了runtime過程中,而從上面的彙編代碼也可以看出,大概損耗在哪些地方。而靜態語言產生的機器指令更簡潔。正因為知道這個原因,所以開發語言的人付出很大一部分努力為了保持runtime小巧上。所以objecitve-c是c的超集+一個小巧的runtime環境。 但是,換句話說,從演算法角度考慮,這點複雜度不算差別的,Big O notation結果不會有差別。( It’s not log(n) vs n2 )
2. 另外一個就是安全性。動態語言由於運行時環境的需求,會保留一些源碼層級的程式結構。這樣就給破解帶來的方便之門。一個現成的說明就是,java,大家都知道java運行在jre上面。這就是典型的runtime例子。它的執行檔案.class全部可以反編譯回近似原始碼。所以這裡的額外提示就是如果你需要寫和安全有關的代碼,離objc遠點,直接用c去。
簡單理解:“Runtime is everything between your each function call.”
但是大家要明白,第二點我提到runtime並不只是因為它帶來了這些簡便的語言特性。而是這些簡單的語言特性,在實際運用中需要你從完全不同的角度考慮和解決問題。只是計算1+1,很多語言都是一樣的,但是隨著問題的複雜,項目的增長,靜態語言和動態語言就會演化出完全不同的風景。
3. thread
“thread synchronization another notorious trouble!”
記得上學時候學作業系統這門課,裡面都會有專門一章介紹任務調度和生產者消費者的問題。 這就是為今後使用進程、線程開發打基礎。概念很簡單,但痛點在synchronization(同步),因為死結檢測演算法不是100%有效,否則就根本沒有死結這個說法了。另一個原因是往往這類錯誤很隱晦,靜態分析很難找到。同時多線程開發抽象度較高需要經驗去把握。