10个迷惑新手的CocoaObjective-c开发问题
?
本文轉(zhuǎn)載至 ?http://blog.csdn.net/lvxiangan/article/details/27964733首先請(qǐng)諒解我可能使用很多英文,畢竟英文資料將來(lái)會(huì)是你的主要資料來(lái)源。
這篇教程將描述一些我見(jiàn)到的眾多Cocoa開(kāi)發(fā)新手遇到的問(wèn)題和障礙。并不會(huì)手把手教你:“這個(gè)函數(shù)什么意思,哪個(gè)函數(shù)如何使用”,而是站在一定高度,統(tǒng)觀(guān)各種技術(shù)所處的角色,讓你不會(huì)迷失在各種技術(shù)細(xì)節(jié)中。在你繼續(xù)深入學(xué)習(xí)MacOS編程之前,請(qǐng)停下腳步看清楚這些問(wèn)題。如果你是新手,這個(gè)教程不要希望一次能看的非常透徹,學(xué)一定階段反回來(lái)再看看又會(huì)有新的體會(huì)的。
?
?
1. language background
首先c語(yǔ)言背景,必須。 很多人問(wèn):“沒(méi)有任何語(yǔ)言基礎(chǔ),我不想學(xué)c直接學(xué)objective-c。是否可以?” 這里我簡(jiǎn)單說(shuō)幾句,objc是c的超集,也就是說(shuō)大部分objc代碼其實(shí)是c、而且眾多傳統(tǒng)開(kāi)源項(xiàng)目都是c寫(xiě)成的。你不學(xué)好c在unix世界里只能是個(gè)二流開(kāi)發(fā)者!也許說(shuō)得過(guò)于嚴(yán)厲,不過(guò)自己斟酌把。c++呢大概了解一下即可,因?yàn)樗嬰s了。
接著English,必須。 蘋(píng)果不會(huì)把它們文檔都寫(xiě)成中文的。“什么,有人翻譯?” 等有人閑著翻譯出來(lái)了的時(shí)候,大家都已經(jīng)賣(mài)了很多軟件了。你也是跟著人家屁股后面做開(kāi)發(fā)。
?
2. runtime(運(yùn)行時(shí))
Objective-c是動(dòng)態(tài)語(yǔ)言, 很多新手或者開(kāi)發(fā)人員常常被runtime這個(gè)東西所迷惑。而恰恰這是一個(gè)非常重要的概念。我可以這么問(wèn):“如果讓你(設(shè)計(jì))實(shí)現(xiàn)一個(gè)計(jì)算機(jī)語(yǔ)言,你要如何下手?” 很少程序員這么思考過(guò)。但是這么一問(wèn),就會(huì)強(qiáng)迫你從更高層次思考1以前的問(wèn)題了。 注意我這句話(huà)‘設(shè)計(jì)’括起來(lái)了,稍微次要點(diǎn),關(guān)鍵是實(shí)現(xiàn)。
我把實(shí)現(xiàn)分成3種不同的層次:
第一種是傳統(tǒng)的面向過(guò)程的語(yǔ)言開(kāi)發(fā),例如c語(yǔ)言。實(shí)現(xiàn)c語(yǔ)言編譯器很簡(jiǎn)單,只要按照語(yǔ)法規(guī)則實(shí)現(xiàn)一個(gè)LALR語(yǔ)法分析器就可以了,編譯器優(yōu)化是非常難的topic,不在這里討論范圍內(nèi),忽略。 這里我們實(shí)現(xiàn)了編譯器其中最最基礎(chǔ)和原始的目標(biāo)之一就是把一份代碼里的函數(shù)名稱(chēng),轉(zhuǎn)化成一個(gè)相對(duì)內(nèi)存地址,把調(diào)用這個(gè)函數(shù)的語(yǔ)句轉(zhuǎn)換成一個(gè)jmp跳轉(zhuǎn)指令。在程序開(kāi)始運(yùn)行時(shí)候,調(diào)用語(yǔ)句可以正確跳轉(zhuǎn)到對(duì)應(yīng)的函數(shù)地址。 這樣很好,也很直白,但是太死板了。Everything is predetermined.
我們希望語(yǔ)言更加靈活,于是有了第二種改進(jìn),開(kāi)發(fā)面向?qū)ο蟮恼Z(yǔ)言,例如c++。 c++在c的基礎(chǔ)上增加了類(lèi)的部分。但這到底意味著什么呢?我們?cè)賹?xiě)它的編譯器要如何考慮呢?其實(shí),就是讓編譯器多繞個(gè)彎,在嚴(yán)格的c編譯器上增加一層類(lèi)處理的機(jī)制,把一個(gè)函數(shù)限制在它處在的class環(huán)境里,每次請(qǐng)求一個(gè)函數(shù)調(diào)用,先找到它的對(duì)象, 其類(lèi)型,返回值,參數(shù)等等,確定了這些后再jmp跳轉(zhuǎn)到需要的函數(shù)。這樣很多程序增加了靈活性同樣一個(gè)函數(shù)調(diào)用會(huì)根據(jù)請(qǐng)求參數(shù)和類(lèi)的環(huán)境返回完全不同的結(jié)果。增加類(lèi)機(jī)制后,就模擬了現(xiàn)實(shí)世界的抽象模式,不同的對(duì)象有不同的屬性和方法。同樣的方法,不同的類(lèi)有不同的行為! 這里大家就可以看到作為一個(gè)編譯器開(kāi)發(fā)者都做了哪些進(jìn)一步的思考。雖然面相對(duì)象語(yǔ)言有所改進(jìn),但還是死板, 我們?nèi)匀唤衏++是static language.
希望更加靈活!于是我們完全把上面哪個(gè)類(lèi)的實(shí)現(xiàn)部分抽象出來(lái),做成一套完整運(yùn)行階段的檢測(cè)環(huán)境,形成第三種,動(dòng)態(tài)語(yǔ)言。這次再寫(xiě)編譯器甚至保留部分代碼里的sytax名稱(chēng),名稱(chēng)錯(cuò)誤檢測(cè),runtime環(huán)境注冊(cè)所以全局的類(lèi),函數(shù),變量等等信息等等,我們可以無(wú)限的為這個(gè)層增加必要的功能。調(diào)用函數(shù)時(shí)候,會(huì)先從這個(gè)運(yùn)行時(shí)環(huán)境里檢測(cè)所以可能的參數(shù)再做jmp跳轉(zhuǎn)。這,就是runtime。編譯器開(kāi)發(fā)起來(lái)比上面更加彎彎繞。但是這個(gè)層極大增加了程序的靈活性。 例如當(dāng)調(diào)用一個(gè)函數(shù)時(shí)候,前2種語(yǔ)言,很有可能一個(gè)jmp到了一個(gè)非法地址導(dǎo)致程序crash, 但是在這個(gè)層次里面,runtime就過(guò)濾掉了這些可能性。 這就是為什么dynamic langauge更加強(qiáng)壯。 因?yàn)榫幾g器和runtime環(huán)境開(kāi)發(fā)人員已經(jīng)幫你處理了這些問(wèn)題。
好了上面說(shuō)著這么多,我們?cè)俜祷貋?lái)看objective-c的這些語(yǔ)句:
?
| 1 2 3 4 5 6 7 8 9 10 | 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]; |
?
看似很簡(jiǎn)單的語(yǔ)句,但是為了讓語(yǔ)言實(shí)現(xiàn)這個(gè)能力,語(yǔ)言開(kāi)發(fā)者要付出很多努力實(shí)現(xiàn)runtime環(huán)境。這里運(yùn)行時(shí)環(huán)境處理了弱類(lèi)型、函數(shù)存在檢查工作。runtime會(huì)檢測(cè)注冊(cè)列表里是否存在對(duì)應(yīng)的函數(shù),類(lèi)型是否正確,最后確定下來(lái)正確的函數(shù)地址,再進(jìn)行保存寄存器狀態(tài),壓棧,函數(shù)調(diào)用等等實(shí)際的操作。
?
| 1 2 3 | id knife=[Knife grateKnife]; NSArray *monsterList=[NSArray array]; [monsterList makeObjectsPerformSelector:@selector(killMonster:) withObject:knife]; |
?
用c,c++完成這個(gè)功能還是比較非常麻煩的,但是動(dòng)態(tài)語(yǔ)言處理卻非常簡(jiǎn)單并且這些語(yǔ)句讓objc語(yǔ)言更加intuitive。
在Objc中針對(duì)對(duì)象的函數(shù)調(diào)用將不再是普通的函數(shù)調(diào)用,[obj function1With:var1];?這樣的函數(shù)調(diào)用將被運(yùn)行時(shí)環(huán)境轉(zhuǎn)換成objc_msgSend(target,@selector(function1With:),var1);。Objc的runtime環(huán)境是開(kāi)源的,所以我們可以拿出一下實(shí)現(xiàn)做簡(jiǎn)單介紹,可以看到objc_msgSend由匯編語(yǔ)言實(shí)現(xiàn),我們甚至不必閱讀代碼,只需查看注釋就可以了解,運(yùn)行時(shí)環(huán)境在函數(shù)調(diào)用前做了比較全面的安全檢查,已確保動(dòng)態(tài)語(yǔ)言函數(shù)調(diào)用不會(huì)導(dǎo)致程序crash。對(duì)于希望深入學(xué)習(xí)的朋友可以自行下載Objc-runtime源代碼來(lái)閱讀,這里就不再深入講解。
?
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | /******************************************************************** * 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 |
?
現(xiàn)在說(shuō)一下runtime的負(fù)面影響: 1. 關(guān)于執(zhí)行效率問(wèn)題。 “靜態(tài)語(yǔ)言執(zhí)行效率要比動(dòng)態(tài)語(yǔ)言高”,這句沒(méi)錯(cuò)。因?yàn)橐徊糠謈pu計(jì)算損耗在了runtime過(guò)程中,而從上面的匯編代碼也可以看出,大概損耗在哪些地方。而靜態(tài)語(yǔ)言生成的機(jī)器指令更簡(jiǎn)潔。正因?yàn)橹肋@個(gè)原因,所以開(kāi)發(fā)語(yǔ)言的人付出很大一部分努力為了保持runtime小巧上。所以objecitve-c是c的超集+一個(gè)小巧的runtime環(huán)境。 但是,換句話(huà)說(shuō),從算法角度考慮,這點(diǎn)復(fù)雜度不算差別的,Big O notation結(jié)果不會(huì)有差別。( It’s not log(n) vs n2) 2. 另外一個(gè)就是安全性。動(dòng)態(tài)語(yǔ)言由于運(yùn)行時(shí)環(huán)境的需求,會(huì)保留一些源碼級(jí)別的程序結(jié)構(gòu)。這樣就給破解帶來(lái)的方便之門(mén)。一個(gè)現(xiàn)成的說(shuō)明就是,java,大家都知道java運(yùn)行在jre上面。這就是典型的runtime例子。它的執(zhí)行文件.class全部可以反編譯回近似源代碼。所以這里的額外提示就是如果你需要寫(xiě)和安全有關(guān)的代碼,離objc遠(yuǎn)點(diǎn),直接用c去。
簡(jiǎn)單理解:“Runtime is everything between your each function call.”
但是大家要明白,第二點(diǎn)我提到runtime并不只是因?yàn)樗鼛?lái)了這些簡(jiǎn)便的語(yǔ)言特性。而是這些簡(jiǎn)單的語(yǔ)言特性,在實(shí)際運(yùn)用中需要你從完全不同的角度考慮和解決問(wèn)題。只是計(jì)算1+1,很多語(yǔ)言都是一樣的,但是隨著問(wèn)題的復(fù)雜,項(xiàng)目的增長(zhǎng),靜態(tài)語(yǔ)言和動(dòng)態(tài)語(yǔ)言就會(huì)演化出完全不同的風(fēng)景。
?
3. thread
“thread synchronization another notorious trouble!”
記得上學(xué)時(shí)候?qū)W操作系統(tǒng)這門(mén)課,里面都會(huì)有專(zhuān)門(mén)一章介紹任務(wù)調(diào)度和生產(chǎn)者消費(fèi)者的問(wèn)題。 這就是為今后使用進(jìn)程、線(xiàn)程開(kāi)發(fā)打基礎(chǔ)。概念很簡(jiǎn)單,但難點(diǎn)在synchronization(同步),因?yàn)樗梨i檢測(cè)算法不是100%有效,否則就根本沒(méi)有死鎖這個(gè)說(shuō)法了。另一個(gè)原因是往往這類(lèi)錯(cuò)誤很隱晦,靜態(tài)分析很難找到。同時(shí)多線(xiàn)程開(kāi)發(fā)抽象度較高需要經(jīng)驗(yàn)去把握。
總體來(lái)說(shuō),我見(jiàn)到的在這方面的問(wèn)題可以分為一下幾點(diǎn):
1. 對(duì)系統(tǒng)整體結(jié)構(gòu)認(rèn)識(shí)模糊
不知道多線(xiàn)程開(kāi)發(fā)的幾個(gè)基點(diǎn),看別人代碼越看越糊涂的。一會(huì)NSThread,一會(huì)Grand Central Dispatch、block,一會(huì)又看到了pthread等等。Apple封裝了很多線(xiàn)程的API, 多線(xiàn)程開(kāi)發(fā)的基本結(jié)構(gòu)入下圖:
Mac OS Thread Architecture
可以看到在多線(xiàn)程開(kāi)發(fā)中你可以選擇這上面這4種不同的方式。
Mach是核心的操作系統(tǒng)部分。其實(shí)這個(gè)我也不是非常熟悉,至少我還沒(méi)有讀到過(guò)直接使用mach做多線(xiàn)程的代碼。
pthread(POSIX Threads)是傳統(tǒng)的多線(xiàn)程標(biāo)準(zhǔn),靈活、輕巧,但是需要理論基礎(chǔ),開(kāi)發(fā)復(fù)雜。需要注意一點(diǎn),根據(jù)apple文檔提示,在Cocoa下使用pthread需要先啟動(dòng)至少一個(gè)NSThread,確定進(jìn)入多線(xiàn)程環(huán)境后才可以。
NSThread是Mac OS 10.0后發(fā)布的多線(xiàn)程API較為高層,但是缺乏靈活性,而且和pthread相比效率低下。
Grand Central Dispatch?是10.6后引入的開(kāi)源多線(xiàn)程庫(kù),它介于pthread和NSThread之間。比NSThread更靈活、小巧,并且不需要像pthread一樣考慮很多l(xiāng)ock的問(wèn)題。同時(shí)objective-c 2.0發(fā)布的新語(yǔ)法特性之一blocks,也正是根據(jù)Grand Central Dispatch需求推出的。
所以在你寫(xiě)多線(xiàn)程代碼或者閱讀多線(xiàn)程代碼時(shí)候,心理要先明確使用哪種技術(shù)。
2. thread和Reference Counting內(nèi)存管理造成的問(wèn)題。
線(xiàn)程里面的方法都要放到NSAutoreleasePool里面嗎?
這類(lèi)問(wèn)題很常見(jiàn),迷惑的原因是不明白 NSAutoreleasePool 到底是干什么用的。NSAutoreleasePool跟thread其實(shí)關(guān)系并不顯著,它提供一個(gè)臨時(shí)內(nèi)存管理空間,好比一個(gè)沙箱,確保不會(huì)有不當(dāng)?shù)膬?nèi)存分配泄露出來(lái),在這個(gè)空間內(nèi)新分配的對(duì)象要向這個(gè)pool做一下注冊(cè)告訴:“pool,我新分配一塊空間了”。當(dāng)pool drain掉或者release,它里面分配過(guò)的內(nèi)存同樣釋放掉。可見(jiàn)和thread沒(méi)有很大關(guān)系。但是,我們閱讀代碼的時(shí)候經(jīng)常會(huì)看到,新開(kāi)線(xiàn)程的函數(shù)內(nèi)總是以NSAutoreleasePool開(kāi)始結(jié)束。這又是為什么呢!? 因?yàn)閠hread內(nèi)恰好是最適合需要它的地方! 線(xiàn)程函數(shù)應(yīng)該計(jì)算量大,時(shí)間長(zhǎng)(supposed to be heavy)。在線(xiàn)程里面可能會(huì)有大量對(duì)象生成,這時(shí)使用autoreleasepool管理更簡(jiǎn)潔。所以這里的答案是,不一定非要在線(xiàn)程里放NSAutoreleasePool,相對(duì)的在cocoa環(huán)境下任意地方都可以使用NSAutoreleasePool。如果你在線(xiàn)程內(nèi)不使用NSAutoreleasePool,要記得在內(nèi)部alloc和relase配對(duì)出現(xiàn)保證沒(méi)有內(nèi)存泄露。
3. 線(xiàn)程安全
每個(gè)程序都有一個(gè)主線(xiàn)程(main thread),它負(fù)責(zé)處理事件響應(yīng),和UI更新。
更新UI問(wèn)題。很多新手會(huì)因?yàn)檫@個(gè)問(wèn)題,導(dǎo)致程序崩潰或出現(xiàn)各種問(wèn)題。而且逛論壇會(huì)看到所以人都會(huì)這么告訴你:“不要在后臺(tái)線(xiàn)程更新你的UI”。其實(shí)這個(gè)說(shuō)法不嚴(yán)密,在多線(xiàn)程環(huán)境里處理這個(gè)問(wèn)題需要謹(jǐn)慎,而且要了解線(xiàn)程安全特性。
首先我們需要把“UI更新”這個(gè)詞做一個(gè)說(shuō)明,它可以有2個(gè)層次理解,首先是繪制,其次是顯示。這里繪制是可以在任何線(xiàn)程里進(jìn)行,但是要向屏幕顯示出來(lái)就需要在主線(xiàn)程操作了。我舉個(gè)例子說(shuō)明一下,例如現(xiàn)在我們有一個(gè)NSImageView,里面設(shè)置了一個(gè)NSImage, 這時(shí)我想給NSImage加個(gè)變色濾鏡,這個(gè)過(guò)程就可以理解為繪制。那么我完全可以再另外一個(gè)線(xiàn)程做這個(gè)比較費(fèi)時(shí)的操作,濾鏡增加完畢再通知NSImageView顯示一下。另一個(gè)例子就是,Twitter客戶(hù)端會(huì)把每一條微博顯示成一個(gè)cell,但是速度很快,這就是因?yàn)樗葘?duì)cell做了offscreen的渲染,然后再拿出來(lái)顯示。
所以通過(guò)這點(diǎn)我們也可以得到進(jìn)一步的認(rèn)識(shí),合理設(shè)計(jì)view的更新是非常重要的部分。很多新手寫(xiě)得代碼片段沒(méi)錯(cuò),只是因?yàn)榉佩e(cuò)了地方就導(dǎo)致整個(gè)程序出現(xiàn)各種問(wèn)題。
根據(jù)蘋(píng)果線(xiàn)程安全摘要說(shuō)明,再其它線(xiàn)程更新view需要使用lockFocusIfCanDraw和unlockFocus鎖定,確保不會(huì)出現(xiàn)安全問(wèn)題。
另外還要知道常用容器的線(xiàn)程安全情況。immutable的容器是線(xiàn)程安全的,而mutable容器則不是。例如NSArray和NSMutableArray。
4. Asynchronous(異步) vs. Synchronous(同步)
我在一個(gè)view要顯示多張web圖片,我想問(wèn)一下,我是應(yīng)該采用異步一個(gè)一個(gè)下載的方式,還是應(yīng)該采用多線(xiàn)程同時(shí)下載的方式,還是2個(gè)都用,那種方式好呢?
實(shí)際上單獨(dú)用這2個(gè)方法都不好。并不是簡(jiǎn)單的用了更多線(xiàn)程就提高速度。這個(gè)問(wèn)題同時(shí)涉及客戶(hù)端和服務(wù)器的情況。
處理這種類(lèi)型的程序,比較好的結(jié)構(gòu)應(yīng)該是:非主線(xiàn)程建立一個(gè)隊(duì)列(相當(dāng)于Asynchronous任務(wù)),隊(duì)列里同時(shí)啟動(dòng)n個(gè)下載任務(wù)(相當(dāng)于Synchronous任務(wù))。這里的n在2~8左右就夠了。這個(gè)結(jié)構(gòu)下可以認(rèn)為隊(duì)列里面每n個(gè)任務(wù)之間是異步關(guān)系,但是這n個(gè)任務(wù)之間又是同步關(guān)系,每次同時(shí)下載2~8張圖片。這樣處理基本可以滿(mǎn)足速度要求和各類(lèi)服務(wù)器限制。
5. thread和run-loop
runloop是線(xiàn)程里的一部分,但我覺(jué)得有必要單獨(dú)拿出來(lái)寫(xiě),是因?yàn)樗婕暗臇|西比較容易誤解,而說(shuō)明它的文章又不多。
4. run-loop
thread和runloop在以前,開(kāi)發(fā)者根本不太當(dāng)成一個(gè)問(wèn)題。因?yàn)闆](méi)有靜態(tài)語(yǔ)言里runloop就是固定的線(xiàn)程執(zhí)行l(wèi)oop。而現(xiàn)在Cocoa新手搞不明白的太多了,因?yàn)闆](méi)有從動(dòng)態(tài)角度看它,首先回想一下第2點(diǎn)介紹的runtime概念,接著出一個(gè)思考題。
現(xiàn)在有一個(gè)程序片段如下:
?
| 1 2 3 4 5 6 7 8 9 10 11 | - (void)myThread:(id)sender { ????NSAutoreleasePool *pool=[[NSAutoreleasePool alloc] init]; ????while (TRUE) { ????????//do some jobs ????????//break in some condition ????????usleep(10000); ????????[pool drain]; ????} ????[pool release]; } |
?
現(xiàn)在要求,做某些設(shè)計(jì),使得當(dāng)這個(gè)線(xiàn)程運(yùn)行的同時(shí),還可以從其它線(xiàn)程里往它里面隨意增加或去掉不同的計(jì)算任務(wù)。 這,就是NSRunloop的最原始的開(kāi)發(fā)初衷。讓一個(gè)線(xiàn)程的計(jì)算任務(wù)更加靈活。 這個(gè)功能在c, c++里也許可以做到但是非常難,最主要的是因?yàn)檎Z(yǔ)言能力的限制,以前的程序員很少這么去思考。
好,現(xiàn)在我們對(duì)上面代碼做一個(gè)非常簡(jiǎn)單的進(jìn)化:
?
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | NSMutableArray *targetQueue; NSMutableArray *actionQueue; - (void)myThread:(id)sender { ????NSAutoreleasePool *pool=[[NSAutoreleasePool alloc] init]; ????while (TRUE) { ????????//do some jobs ????????//break in some condition ????????int n=[targetQueue count]; ????????assert(n==[actionQueue count]); ????????for(int i=0;i<n;i++){ ????????????id target=[targetQueue objectAtIndex:i]; ????????????SEL action=NSSelectorFromString([actionQueue objectAtIndex:i]); ????????????if ([target respondsToSelector:action]) { ????????????????[target performSelector:action withObject:nil]; ????????????} ????????} ????????usleep(10000); ????????[pool drain]; ????} ????[pool release]; } |
?
注意,這里沒(méi)有做線(xiàn)程安全處理,記住Mutable container is not thread safe. 這個(gè)簡(jiǎn)單的擴(kuò)展,讓我們看到了如何利用runtime能力讓線(xiàn)程靈活起來(lái)。當(dāng)我們從另外線(xiàn)程向targetQueue和actionQueue同時(shí)加入對(duì)象和方法時(shí)候,這個(gè)線(xiàn)程函數(shù)就有了執(zhí)行一個(gè)額外代碼的能力。
有人會(huì)問(wèn),哪里有runloop? 那個(gè)是nsrunloop? 看不出來(lái)啊。
?
| 1 2 3 | while (TRUE) { ????//break in some condition } |
?
這個(gè)結(jié)構(gòu)就叫線(xiàn)程的runloop, 它和NSRunloop這個(gè)類(lèi)雖然名字很像,但完全不是一個(gè)東西。以前在使用靜態(tài)語(yǔ)言開(kāi)始時(shí)候,程序員沒(méi)有什么迷惑,因?yàn)闆](méi)有NSRunloop這個(gè)東西。 我接著來(lái)說(shuō),這個(gè)NSRunloop是如何來(lái)得。
第二段擴(kuò)展代碼里面確實(shí)沒(méi)有NSRunloop這個(gè)玩意兒,我們接著做第3次改進(jìn)。 這次我們的目的是把其中動(dòng)態(tài)部分抽象出來(lái)。
?
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 | @interface MyNSTimer : NSObject { ????id target; ????SEL action; ????float interval; ????CFAbsoluteTime lasttime; } - (void)invoke; @end @implementation MyNSTimer - (void)invoke; { ????if ([target respondsToSelector:action]) { ????????[target performSelector:action withObject:nil]; ????} } @end #!objc @interface MyNSRunloop : NSObject { ????NSMutableArray *timerQueue; } - (void)addTimer:(MyNSTimer*)t; - (void)executeOnce; @end @implementation MyNSRunloop - (void)addTimer:(MyNSTimer*)t; { ????@synchronized(timerQueue){ ????????[timerQueue addObject:t]; ????} } - (void)executeOnce; { ????CFAbsoluteTime currentTime=CFAbsoluteTimeGetCurrent(); ????@synchronized(timerQueue){ ????????for(MyNSTimer *t in timerQueue){ ????????????if(currentTime-t.lasttime>t.interval){ ????????????????t.lasttime=currentTime; ????????????????[t invoke]; ????????????} ????????} ????} } @end #!objc @interface MyNSThread : NSObject { ????MyNSRunloop *runloop; } - (void)main:(id)sender; @end @implementation MyNSThread - (void)main:(id)sender { ????NSAutoreleasePool *pool=[[NSAutoreleasePool alloc] init]; ????while (TRUE) { ????????//do some jobs ????????//break in some condition ????????[runloop executeOnce]; ????????usleep(10000); ????????[pool drain]; ????} ????[pool release]; } @end |
?
走到這里,我們就算是基本把Runloop結(jié)構(gòu)抽象出來(lái)了。例如我有一個(gè)MyNSThread實(shí)例,myThread1。我可以給這個(gè)實(shí)例的線(xiàn)程添加需要的任務(wù),而myThread1內(nèi)部的MyNSRunloop對(duì)象會(huì)管理好這些任務(wù)。
?
| 1 2 3 4 5 | MyNSTimer *timer1=[MyNSTimer scheduledTimerWithTimeInterval:1 target:obj1 selector:@selector(download1:)]; [myThread1.runloop addTimer:timer1]; MyNSTimer *timer2=[MyNSTimer scheduledTimerWithTimeInterval:2 target:obj2 selector:@selector(download2:)]; [myThread1.runloop addTimer:timer2]; |
?
當(dāng)你看懂了上面的代碼也許會(huì)感嘆,‘原來(lái)是這么回事啊!為什么把這么簡(jiǎn)單的功能搞這么復(fù)雜呢?’ 其實(shí)就是這么回事,把Runloop抽象出來(lái)可以使得線(xiàn)程任務(wù)管理更加loose coupling,給設(shè)計(jì)模式提供更大的空間。這樣第三方開(kāi)發(fā)者不需要過(guò)深入的涉及線(xiàn)程內(nèi)部代碼而輕松管理線(xiàn)程任務(wù)。另外請(qǐng)注意,這里MyNSRunloop, MyNSTimer等類(lèi)是我寫(xiě)得一個(gè)模擬情況,真實(shí)的NSRunloop實(shí)現(xiàn)肯定不是這么簡(jiǎn)單。這里為了說(shuō)明一個(gè)思想。這種思想貫穿整個(gè)cocoa framework,從界面更新到event管理。
?
5. delegate, protocol
這個(gè)會(huì)列出來(lái)因?yàn)?#xff0c;我感覺(jué)問(wèn)它的數(shù)量?jī)H此于內(nèi)存管理部分,它們用得很頻繁,并且這些是設(shè)計(jì)模式的重要組成部分。
待寫(xiě)…
?
6. event respon-der
Interface Builder First Responder
使用過(guò)Xcode的開(kāi)發(fā)者都知道Interface Builder這個(gè)開(kāi)發(fā)組件,在Xcode4版本以后該組件已經(jīng)和xcode整合到一起。它是蘋(píng)果軟件開(kāi)發(fā)中非常重要的部分。ib為開(kāi)發(fā)者減輕了很大一部分界面設(shè)計(jì)工作。但是其中有一個(gè)東西讓新接觸ib的開(kāi)發(fā)者一頭霧水,那就是First Responder, 它是什么東西,為何它會(huì)有那么多Actions。這節(jié)我會(huì)詳細(xì)介紹如何理解Responder和Cocoa下的事件響應(yīng)鏈。
First Responder在IB屬性為Placeholders,這意味著它屬于一個(gè)虛擬實(shí)例。就好比TextField里面的string placeholder一樣,只是臨時(shí)顯示一下。真正的first responder會(huì)被其它對(duì)象代替。實(shí)際上,任何派生自NSResponder類(lèi)的對(duì)象都可以替代First Responder。而First Responder里面的所有Actions就是NSResponder提供的接口函數(shù),當(dāng)然你也可以定義自己的響應(yīng)函數(shù)。
MacOS在系統(tǒng)內(nèi)部會(huì)維護(hù)一個(gè)稱(chēng)為“The Responder Chain”的鏈表。該列表內(nèi)容為responder對(duì)象實(shí)例,它們會(huì)對(duì)各種系統(tǒng)事件做出響應(yīng)。最上面的哪個(gè)對(duì)象就叫做first responder,它是最先接收到系統(tǒng)事件的對(duì)象。如果該對(duì)象不處理該事件,系統(tǒng)會(huì)將這個(gè)事件向下傳遞,直到找到響應(yīng)事件的對(duì)象,我們可以理解為該事件被該這個(gè)對(duì)象截取了。
The Responder Chain基本結(jié)構(gòu)如下圖所示:
The Responder Chain
在理解了上面的概念之后,我希望使用一個(gè)例子讓大家對(duì)responder有更加具體的認(rèn)識(shí)。大家都知道NSTextField這個(gè)控件,它是最常見(jiàn)的控件之一。它最基本功能是顯示一個(gè)字符串,如果啟用可選,那么用戶(hù)可以選中文本,拷貝文本,如果開(kāi)啟編輯選項(xiàng),還可以運(yùn)行用戶(hù)編輯文本,等等基本操作。
下面展示給大家的例子是創(chuàng)建一個(gè)我們自己創(chuàng)建的簡(jiǎn)單textfield叫LXTextField。它不屬于NSTextField而是派生自NSView,具有功能顯示字符串,全選字符串,響應(yīng)用戶(hù)cmd+c的拷貝操作,三個(gè)基本功能。注意NSView派生自NSResponder。
?
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 | // //??LXTextField.h //??lxtextfield // //??Created by xu lian on 12-03-09. //??Copyright (c) 2012 Beyondcow. All rights reserved. // #import <AppKit/AppKit.h> @interface LXTextField : NSView { ????NSString *stringValue; ????BOOL selectAll; } @property(retain,nonatomic) NSString *stringValue; @end #!objc // //??LXTextField.m //??lxtextfield // //??Created by xu lian on 12-03-09. //??Copyright (c) 2012 Beyondcow. All rights reserved. // #import "LXTextField.h" @implementation LXTextField @synthesize stringValue; - (void)awakeFromNib { ????selectAll = NO; } - (id)initWithFrame:(NSRect)frameRect { ????if( self = [super initWithFrame:frameRect] ){ ????????selectAll = NO; ????} ????return self; } - (BOOL)acceptsFirstResponder { ????return YES; } - (BOOL)becomeFirstResponder { ????return YES; } - (BOOL)resignFirstResponder { ????selectAll=NO; ????[self setNeedsDisplay:YES]; ????return YES; } - (void)setStringValue:(NSString *)string{ ????stringValue = string; ????[self setNeedsDisplay:YES]; } - (void)drawRect:(NSRect)dirtyRect { ????if (selectAll) { ????????NSRect r = NSZeroRect; ????????r.size = [stringValue sizeWithAttributes:nil]; ????????[[NSColor selectedControlColor] set]; ????????NSRectFill(r); ????} ????[stringValue drawAtPoint:NSZeroPoint withAttributes:nil]; } - (IBAction)selectAll:(id)sender; { ????selectAll=YES; ????[self setNeedsDisplay:YES]; } - (IBAction)copy:(id)sender; { ????NSPasteboard *pasteBoard = [NSPasteboard generalPasteboard]; ????[pasteBoard declareTypes:[NSArray arrayWithObjects:NSStringPboardType, nil] owner:nil]; ????[pasteBoard setString:stringValue forType:NSStringPboardType]; } - (void)mouseDown:(NSEvent *)theEvent { ????if ([theEvent clickCount]>=2) { ????????selectAll=YES; ????} ????[self setNeedsDisplay:YES]; } - (void)keyDown:(NSEvent *)theEvent { } @end |
?
運(yùn)行實(shí)例,可以看到隨著LXTextField收到系統(tǒng)發(fā)送的becomeFirstResponder消息,LXTextField變成responder chain中的frist responder, 這時(shí)候可以理解為IB里的哪個(gè)First Responder虛擬實(shí)例被該LXTextField取代。這時(shí)候mainMenu上哪些菜單項(xiàng),例如:全選(cmd+a), 拷貝(cmd+a)等事件都會(huì)最先發(fā)給當(dāng)前這個(gè)LXTextField。一旦你的LXTextField實(shí)現(xiàn)了NSResponder的哪些默認(rèn)函數(shù),那么該對(duì)象就會(huì)截取系統(tǒng)事件。當(dāng)然這些事件具體如何實(shí)現(xiàn)還是需要你自己寫(xiě)代碼實(shí)現(xiàn)。例如這里的 – (IBAction)copy:(id)sender; 顯然我手動(dòng)實(shí)現(xiàn)了textfield的copy能力。
注意上述代碼中我實(shí)現(xiàn)了一個(gè)空函數(shù)- (void)keyDown:(NSEvent *)theEvent 這意味著我們希望LXTextField截取鍵盤(pán)事件而不再傳遞給responder chain后續(xù)對(duì)象。當(dāng)然,如果我們希望LXTextField響應(yīng)特定鍵盤(pán)事件,而其他事件繼續(xù)傳給其他響應(yīng)對(duì)象,我們可以編寫(xiě)如下代碼。
?
| 1 2 3 4 5 6 7 8 | - (void)keyDown:(NSEvent *)theEvent { ????if(condition){ ????????do something; ????}else{ ????????[super keyDown:theEvent]; ????} } |
?
待寫(xiě)…
?
7. mem-ory management
內(nèi)存管理問(wèn)題,也許是問(wèn)得最多的問(wèn)題了吧。
開(kāi)發(fā)速度不言而喻,你少寫(xiě)很多release代碼,甚至很少去操心這部分。
執(zhí)行速度呢?這個(gè)還要從runtime說(shuō)起,還記得我在第2點(diǎn)說(shuō)得一句話(huà)么:“Runtime is everything between your each function call.”
RC有一個(gè)古老的內(nèi)存管理哲學(xué):誰(shuí)分配誰(shuí)釋放。?通過(guò)counting來(lái)確定一個(gè)資源有幾個(gè)使用者。道理很簡(jiǎn)單,但是往往簡(jiǎn)單的東西人卻會(huì)犯錯(cuò)。從來(lái)沒(méi)有一個(gè)程序員可以充滿(mǎn)信心的說(shuō),我寫(xiě)得代碼從來(lái)沒(méi)有過(guò)內(nèi)存泄露。這樣來(lái)看,我們就更需要讓程序可以自己處理這個(gè)管理機(jī)制,這就需要把這個(gè)機(jī)制放到runtime里。
所以RC->ARC就是把內(nèi)存管理部分從普通開(kāi)發(fā)者的函數(shù)中移到了函數(shù)外的runtime中。因?yàn)閞untime的開(kāi)發(fā)原型簡(jiǎn)單,邏輯層次更高,所以做這個(gè)開(kāi)發(fā)和管理出錯(cuò)的概率更小。實(shí)際上編譯器開(kāi)發(fā)人員對(duì)這部分經(jīng)過(guò)無(wú)數(shù)次測(cè)試,所以可以說(shuō)用arc幾乎不會(huì)出錯(cuò)。另外由于編譯的額外優(yōu)化,使得這個(gè)部分比程序員自己寫(xiě)得代碼要快速很多。而且對(duì)于一些通用的開(kāi)發(fā)模式,例如autorelease對(duì)象,arc有更優(yōu)秀的算法保證autoreleasepool里的對(duì)象更少。
這個(gè)規(guī)則可以讓NSObject決定是不是要釋放內(nèi)存。當(dāng)一個(gè)對(duì)象alloc時(shí)候,系統(tǒng)分配其一塊內(nèi)存并且object自動(dòng)計(jì)數(shù)retainCount=1 這時(shí)候每當(dāng)[object retain]一次retainCount+1(這里雖然簡(jiǎn)寫(xiě)也是rc不過(guò)是巧合或者當(dāng)時(shí)開(kāi)發(fā)人員故意選的retain這個(gè)詞吧)每次[object release]時(shí)候retainCount-1 當(dāng)retainCount==0時(shí)候object就真正把這快內(nèi)存還給系統(tǒng)。
常用container的Reference Counting特性 這個(gè)規(guī)則很簡(jiǎn)單把。但是這塊確實(shí)讓新手最頭疼的地方。問(wèn)題出在,新手總想去驗(yàn)證rc規(guī)則,又總是發(fā)現(xiàn)和自己的期望不符合。 無(wú)數(shù)次看到有人寫(xiě)下如下句子
NSLog(@”%d”,[object retainCount]);
while([object retainCount]>0){ [object release]; }
當(dāng)然了,我也做過(guò)類(lèi)似的動(dòng)作,那種希望一切盡在掌握中的心態(tài)。但是你會(huì)看到其他人告訴這么做完全沒(méi)有意義,RC規(guī)則并不是這么用的。
首先,這個(gè)數(shù)字也許并不是你心目中的哪個(gè)。因?yàn)楹茈y跟蹤到底哪些地方引用的該資源。你建立的資源不光只有你的代碼才會(huì)用到,你調(diào)用的各種Framework,Framework調(diào)用的Framework,都有可能改變這個(gè)資源的retainCount。
其次,這里每個(gè)數(shù)字意味著有其它對(duì)象引用該資源,這樣的暴力釋放很容易導(dǎo)致程序崩潰。就好比,其它人也許可以翻牌子把門(mén)口哪個(gè)牌子上的數(shù)字改變,但是這會(huì)出現(xiàn)問(wèn)題。還有很多人在里面,把牌子變成0房間鎖了結(jié)果誰(shuí)也出不來(lái)。又或者,減少牌子上的數(shù)字,人進(jìn)的過(guò)多房間變得過(guò)于擁擠。
所以去驗(yàn)證rc規(guī)則,或者單純的改變r(jià)etainCount并不是明智之舉。你能做的就是理解規(guī)則,使用規(guī)則,讀文檔了解container的引用特性。或者干脆移到 Automatic Reference Counting (ARC) 上面。
我有一個(gè)NSMutableArray里面保存了1000個(gè)NSString對(duì)象,我在release的時(shí)候需要循環(huán)釋放1000個(gè)string么?還是只需要release NSMutableArray。
就像上面提到的,如果你了解container的引用特性,這個(gè)問(wèn)題自然就解決了。“NSMutableArray在添加、插入objects時(shí)會(huì)做retain操作。” 通過(guò)這一句話(huà)就分析出,用戶(hù)不否需要幫助NSMutableArray釋放1000個(gè)string。回憶上面提到的管理哲學(xué),“誰(shuí)分配誰(shuí)釋放” 編寫(xiě)NSMutableArray的程序員非常熟悉這個(gè)規(guī)則,NSMutableArray內(nèi)部retain了,NSMutableArray自然要負(fù)責(zé)release。但是NSMutableArray才不會(huì)管你在外面什么地方引用了這1000個(gè)string,它只管理好內(nèi)部的rc就夠了。所以如果你在NSMutableArray外面對(duì)1000個(gè)string retain了,你自然需要release。相應(yīng)的,你作為創(chuàng)建這個(gè)NSMutableArray的程序員,你只管release這個(gè)NSMutableArray就可以了。
最后說(shuō)一下不用arc的情況。目前情況來(lái)看,有不少第三方的庫(kù)并未支持arc,所以如果你的舊項(xiàng)目使用了這些庫(kù),請(qǐng)檢查是否作者發(fā)布了新版本,或者你需要自己修正支持arc。
?
8. class heritage, category and extensions
Objective-C 的 OOP 特性提供 subclass 和 category 這2個(gè)非常重要的部分。subclass 應(yīng)該反復(fù)被各種編程書(shū)籍介紹過(guò)。它是 OOP 繼承特性的關(guān)鍵語(yǔ)法,它給類(lèi)添加了延續(xù)并且多樣化自己的方法。可以說(shuō)沒(méi)有繼承就沒(méi)有 OOP 這玩意。而 category 相對(duì)于 subclass 就不那么出名了。其實(shí) category 思想出世于 smalltalk,所以它不能算是一個(gè)新生事物。
先說(shuō)一下這2個(gè)特性最主要的區(qū)別。簡(jiǎn)單可以這么理解,subclass 體現(xiàn)了類(lèi)的上下級(jí)關(guān)系,而 category 是類(lèi)間的平級(jí)關(guān)系。
Subclass and Category
如上圖所示,左側(cè)是subclass,可以看到class, subclass1, subclass2是遞進(jìn)關(guān)系。同時(shí)下面的子類(lèi)完全繼承父類(lèi)的方法,并且可以覆蓋父類(lèi)的方法。subclass2擁有function1,function2,function3三個(gè)函數(shù)方法。function1的執(zhí)行代碼來(lái)自subclass1, function2的執(zhí)行代碼來(lái)自于subclass2。
右側(cè)是category。可以看到,無(wú)論如何擴(kuò)展類(lèi)的category,最終就只有一個(gè)類(lèi)class。category可以說(shuō)是類(lèi)的不同方法的小集合,它把一個(gè)類(lèi)的方法劃分成不同的區(qū)塊。請(qǐng)注意觀(guān)察,每個(gè)category塊內(nèi)的方法名稱(chēng)都沒(méi)有重復(fù)的。這正是category的重要要求。
經(jīng)過(guò)上面簡(jiǎn)單解釋了解了這2點(diǎn)的基本區(qū)別,現(xiàn)在深入說(shuō)一下category。
在Objective-c語(yǔ)言設(shè)計(jì)之初一個(gè)主要的哲學(xué)觀(guān)點(diǎn)就是盡量讓一個(gè)程序員維護(hù)龐大的代碼集。(對(duì)于龐大的項(xiàng)目‘原則’和‘協(xié)議’是非常重要的東西。甚至編寫(xiě)良好的文件名都是非常重要的開(kāi)發(fā)技巧)根據(jù)結(jié)構(gòu)化程序設(shè)計(jì)的經(jīng)驗(yàn)出發(fā),把一個(gè)大塊代碼劃分成一些小塊的代碼更便于程序員管理。于是objc借用了smalltalk的categories概念。允許程序員把一系列功能相近的方法組織到一個(gè)單獨(dú)的文件內(nèi),使得這些代碼更容易識(shí)別。
更進(jìn)一步的,和c,c++這種靜態(tài)語(yǔ)言相比。objc把class categories功能集成到了run-time里面。因此,objc的categories允許程序員為已經(jīng)存在的類(lèi)添加新的方法而不需要重新編譯舊的類(lèi)。一旦一個(gè)category加入,它可以訪(fǎng)問(wèn)該類(lèi)所有方法和實(shí)例變量,包括私有變量。
category不僅可以為原有class添加方法,而且如果category方法與類(lèi)內(nèi)某個(gè)方法具有同樣的method signature,那么category里的方法將會(huì)替換類(lèi)的原有方法。這是category的替換特性。利用這個(gè)特性,category還可以用來(lái)修復(fù)一些bugs。例如已經(jīng)發(fā)布的Framework出現(xiàn)漏洞,如果不便于重新發(fā)布新版本,可以使用category替換特性修復(fù)漏洞。另外,由于category有run-time級(jí)別的集成度,所以使得cocoa程序安全性有所下降。許多黑客就是利用 category、posting2、Method Swizzling 等方法破解軟件,或者為軟件增加新功能。一個(gè)很典型的例子就是,我原來(lái)發(fā)布的QQ表情管理器(目前已經(jīng)不再維護(hù))。
值得注意的一點(diǎn)是,由于一個(gè)類(lèi)的categories之間是平級(jí)關(guān)系。所以如果不同categories擁有相同的方法,這個(gè)調(diào)用結(jié)果是未知的:
Category methods should not override existing methods (class or instance). Two different categories implementing the same method results in undefined behavior.
(因?yàn)閜osting、Method Swizzling這個(gè)話(huà)題有些深入,本文里我就不介紹了。有興趣自行Google)
Objc中Categories有其局限的部分,就是你不能為原有的class添加變量,只能添加方法。當(dāng)然方法里可以添加局部變量。在這個(gè)局限基礎(chǔ)上就有其它語(yǔ)言做了進(jìn)一步改進(jìn),例如TOM語(yǔ)言就為Categories增加了添加類(lèi)變量的能力。
自從Objc 2.0以后,語(yǔ)言引入了一個(gè)新的特性叫做 Class Extensions, 它可以看做是一類(lèi)特殊的 category,可以給原有類(lèi)增加新的屬性和方法。
通過(guò)http://developer.apple.com/library/ios/documentation/cocoa/conceptual/ProgrammingWithObjectiveC/CustomizingExistingClasses/CustomizingExistingClasses.html介紹我們可以看出,如果 categories 是為類(lèi)增加外部方法的話(huà),那么 extensions 就是用做類(lèi)的內(nèi)部拓展。
Class extensions 的外觀(guān)很簡(jiǎn)單,就是一個(gè) Category 后面括號(hào)內(nèi)的名字為空:
?
| 1 2 | @interface ClassName () @end |
?
接下來(lái),你就可以給你的類(lèi)里添加屬性,方法了:
?
| 1 2 3 4 5 6 | @interface XYZPerson (){ ????id _someCustomInstanceVariable; } @property NSObject *extraProperty; - (void)assignUniqueIdentifier; @end |
?
Class extensions 常用來(lái)定義類(lèi)的私有變量和方法。
總上所屬,如果你開(kāi)發(fā)時(shí)候遇到無(wú)論如何都需要為類(lèi)添加變量的情況,最好的選擇就是subclass。相反如果你只希望增加一些函數(shù)簇。Categories是最好的選擇。而類(lèi)內(nèi)部需要用到的私有變量和方法則最好寫(xiě)在 Class extensions 里。
Categories關(guān)注的重心是代碼設(shè)計(jì),把不同功能的方法分離開(kāi)。在Objc里因?yàn)镃ategories是runtimes級(jí)別的特性,所以這種分離不僅體現(xiàn)在源碼結(jié)構(gòu)上,同時(shí)體現(xiàn)在運(yùn)行時(shí)過(guò)程中。這意味著一個(gè)category里的方法在程序運(yùn)行中如果沒(méi)有被調(diào)用,那么它就不會(huì)被加載到內(nèi)存中。所以合理的使用categories會(huì)減少你的程序內(nèi)存消耗。
所以我個(gè)人給大家的建議是,每個(gè)Cocoa程序員都應(yīng)該收集整理自己的一套NS類(lèi)函數(shù)的Categories擴(kuò)展庫(kù)。這對(duì)你今后程序開(kāi)發(fā)效率和掌控情況都有很大提高。
?
9. Drawing Issues
大家知道,MacOS 是一個(gè)非常注重UI的系統(tǒng)。所以在 MacOS 編程里繪制是一個(gè)非常重要的部分。第9部分,我會(huì)介紹 MacOS 下繪制編程。
從繪制技術(shù)分類(lèi)上看,Cocoa程序員能接觸的幾種繪制技術(shù)列表如下:
在這里我不打算給大家介紹如何繪制具體的按鈕或者表格。只是介紹一下,它們的代碼風(fēng)格,優(yōu)勢(shì)和限制。
Cocoa Drawing
Cocoa Drawing應(yīng)該是學(xué)習(xí)Cocoa程序開(kāi)發(fā)最先接觸的繪制技術(shù)。也是目前大多數(shù)MacOS程序所使用的繪制技術(shù),其底層使用Quazrtz 2D(Core Graphics)。蘋(píng)果對(duì)應(yīng)文檔為?Cocoa Drawing Guide。Cocoa Drawing并沒(méi)有統(tǒng)一的繪制函數(shù),所有繪制函數(shù)分散在幾個(gè)主要的NS類(lèi)的下面。例如, NSImage, NSBezierPath, NSString, NSAttributedString, NSColor, NSShadow,NSGradient …
所以很簡(jiǎn)單,當(dāng)你看到如下代碼就可以判斷,使用的是Cocoa Drawing方法
?
| 1 2 3 4 5 6 7 | [anImage drawInRect:rect fromRect:NSZeroRect operation:NSCompositeSourceOver fraction:1.0]; [@"some text" drawAtPoint:NSZeroPoint withAttributes:attrs]; NSBezierPath *p=[NSBezierPath bezierPathWithRect:rect]; [[NSColor redColor] set]; [p fill]; |
?
這種代碼多出現(xiàn)在NSView的drawRect函數(shù)內(nèi)。Cocoa Drawing 的渲染上下文是 NSGraphicsContext,我不斷的看到很多新手把 NSGraphicsContext 和 CoreGraphics 的 CGContextRef 搞混。雖然它們很像并且也確實(shí)是有關(guān)系的,不過(guò)如果你不了解當(dāng)繪制時(shí)候的 render context 很多時(shí)候?qū)⒌玫揭粋€(gè)空白頁(yè)面的結(jié)果。
Core Graphics
Core Graphics 是 Cocoa Drawing layer 的底層技術(shù),在 iOS 開(kāi)發(fā)中非常普遍,因?yàn)?iOS 系統(tǒng)中并不存在 Cocoa layer 所以網(wǎng)上可以找到的多是 Core Graphics 繪制代碼段子,這給那些不了解 Mac 開(kāi)發(fā)的新手來(lái)說(shuō)造成了很大困擾。Cocoa 是 Mac OS 下的 application framework 而 iOS 下的 application framework 則是 UIKit.framework又叫 Cocoa Touch,它們分享部分代碼基礎(chǔ)但又不完全一樣。例如,Cocoa Touch 下的 UIView 的渲染上下文會(huì)使用 UIGraphicsGetCurrentContext() 取得,它得到的是一個(gè) CGContextRef 指針,而在 NSView 里多用 [NSGraphicsContext currentContext] 取得渲染上下文。它得到的是一個(gè) NSGraphicsContext 對(duì)象。當(dāng)然 NSView 里也可以通過(guò) CGContextRef ctx = [[NSGraphicsContext currentContext] graphicsPort]; 來(lái)取得一個(gè) Core Graphics 渲染上下文。 可見(jiàn) Mac OS 下的開(kāi)發(fā)更為靈活一些。因?yàn)?iOS 中的 UIKit 開(kāi)發(fā)初期就瞄準(zhǔn)了顯卡硬件加速,所有 UIView 都是默認(rèn) layer-backed 的。iOS 開(kāi)發(fā)者必須使用 Core Graphics 和 Core Animation 這幾個(gè)相對(duì)底層的繪制技術(shù)。
請(qǐng)看下面等價(jià)代碼,作用是繪制一個(gè)白色矩形。但是分別使用 Core Graphics 和 Cocoa Drawing:
?
| 1 2 3 4 5 6 7 | const CGFloat white[]={ 1.0, 1.0, 1.0, 1.0 }; CGContextSetFillColor(cgContextRef, white); CGContextSetBlendMode(cgContextRef, kCGBlendModeNormal); CGContextFillRect(cgContextRef, CGRectMake(0, 0, width, height)); [[NSColor whiteColor] set]; NSRectFillUsingOperation(NSMakeRect(0, 0, width, height), NSCompositeSourceOver); |
?
可以看出,這是2種風(fēng)格完全不同的繪制技術(shù)。Cocoa Drawing 是分散式的繪制函數(shù),而 Core Graphics 是傳統(tǒng)的類(lèi)似 OpenGL 的集成式的繪制方式。其實(shí) Cocoa Drawing 下層是 Core Graphics, Core Graphics 的下層是 OpenGL。
在 OSX 下 NSGraphicsContext 和 CGContextRef 大部分時(shí)候是可以相互轉(zhuǎn)換的。
NSGraphicsContext 到 CGContextRef:
?
| 1 2 3 4 | CGContextRef ctx = [[NSGraphicsContext currentContext] graphicsPort]; CGContextSaveGState(ctx); //Core Graphic drawing code here CGContextRestoreGState(ctx); |
?
CGContextRef 到 NSGraphicsContext:
?
| 1 2 3 4 5 | NSGraphicsContext *ctx = [NSGraphicsContext graphicsContextWithGraphicsPort:cgContextRef flipped:NO]; [NSGraphicsContext saveGraphicsState]; [NSGraphicsContext setCurrentContext:ctx]; //cocoa drawing code here [NSGraphicsContext restoreGraphicsState]; |
?
大部分時(shí)候使用 Cocoa Drawing 可以繪制出需要的效果,但是某些特殊時(shí)候需要 Core Graphic 繪制,例如一些特殊的陰影,clip效果,自定義pattern phase,blending style等等。
Core Animation
如果說(shuō) Core Graphics 和 Cocoa Drawing 是通用的 UI 繪制框架的話(huà),那么 CA 顯然是界面動(dòng)畫(huà)繪制的高級(jí)技術(shù)。 Core Animation 的對(duì)應(yīng) Cocoa Animation 部分應(yīng)該是 NSAnimation 和 NSViewAnimation,但這2個(gè)差距比較大。NSAnimation 出現(xiàn)與 OS X 10.4,Core Animation 是 10.5 后出現(xiàn)的。NSViewAnimation 功能和使用相對(duì)簡(jiǎn)單。
簡(jiǎn)單來(lái)說(shuō),Core Animation 的作用對(duì)象是 CALayer, NSAnimation 的作用對(duì)象是 NSView。了解你的程序界面是在處理那種對(duì)象很重要。
Core Image
對(duì)于這個(gè)繪制技術(shù),這篇文章給了我很多啟示大家也可以看看。Notes on Rendering 2D Graphics on a Mac?雖然是一篇 note 但是,記錄了很多實(shí)際應(yīng)用中的經(jīng)歷,可以對(duì)個(gè)各種繪制技術(shù)有一個(gè)比較全面的解析。
根據(jù)此文的介紹。Core Image 適合處理小量大圖,而非常不適合處理大量小圖。因?yàn)?CI 利用 GPU 運(yùn)算,而數(shù)據(jù)到 GPU 的round-trip 時(shí)間數(shù)量級(jí)在 millisecond。這就意味著,1000 張小圖分別再 GPU 運(yùn)算,時(shí)間至少再 1000*1 ms。此文作者嘗試?yán)L制3000張小圖片,利用 Cocoa Drawing 原本耗時(shí) 750ms,但是改用CI后耗時(shí)猛增到3秒。
所以,這就是CI在osx繪制技術(shù)里所處的宏觀(guān)角色:單圖做實(shí)時(shí)處理。
openGL
待寫(xiě)…
?
10. design pattern
待寫(xiě)…
1:這里其實(shí)很有意思,為何我用“更高層次思考”,而不是“更底層次”。作為一個(gè)編譯器和語(yǔ)言開(kāi)發(fā)人員,面對(duì)的問(wèn)題確實(shí)更底層沒(méi)錯(cuò),但是他們思考的維度更高,更抽象,這樣子。一個(gè)不算恰當(dāng)?shù)谋确骄秃孟褚粋€(gè)三維世界的人處理二維世界的一條線(xiàn)的問(wèn)題。
2:Posting技術(shù)在10.5以后deprecated,并且64bit run-time也不再支持
from?http://lianxu.me/2012/11/10-cocoa-objc-newbie-problems/
總結(jié)
以上是生活随笔為你收集整理的10个迷惑新手的CocoaObjective-c开发问题的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 设置函数环境——setfenv
- 下一篇: Excel插件类库的设计思路