十個很不錯的迷惑新手的Cocoa,OC開發問題總結,適合你們進行注意和學習。 php
在你繼續深刻學習以前,請停下腳步弄清這些問題。若是你是新手,這個教程不要但願一次能看的很是透徹,學必定階段反回來再看看又會有新的體會的。對於徹底看不懂的同窗,也不要強求本身。找來《計算機組成原理》《數據結構》《操做系統》《編譯原理》《計算機網絡》之類的大學課本看看吧。這些基礎不可能在這個論壇學到的,至少不可能系統的學到。
1. language background
首先c, c++語言背景,必須。
不少人問 「沒有任何語言基礎,我不想學c直接學objective-c」
這裏簡單幾句,objc 90%代碼是c、衆多開源代碼是c,c++。你不學好c在unix世界裏只能是個二流開發者!也許說得過於嚴厲,不過本身斟酌把。
接着English必須。
須要英語!須要英語!須要英語!蘋果不會把它們文檔都寫成中文的。「什麼,有人翻譯?」 等有人閒着翻譯出來了的時候,你們都已經賣了不少軟件了。你也是跟着人家屁股後面作開發。
2. Runtime(運行時)
Objective-c是動態語言, 不少新手或者開發人員經常被Runtime這個東西所迷惑。而偏偏這是一個很是重要的概念。 爲何重要呢!?我能夠這麼問:「若是讓你(設計、)實現一個計算機語言,你要如何下手?」 不多程序員這麼思考過。可是這麼一問,就會強迫你從更高層次思考(1)之前的問題了。 注意我這句話‘設計’括起來了,稍微次要點,關鍵是實現。
我把實現分紅3鐘不一樣的層次:
html
1. 傳統的面向過程的語言開發,例如c語言。實現c語言編譯器很簡單,只要按照語法規則實現一個LALR語法分析器就能夠了,編譯器優化是很是難的topic,不在這裏討論範圍內,忽略。 這裏咱們實現了編譯器其中最最基礎和原始的目標之一就是把一份代碼裏的函數名稱,轉化成一個相對內存地址,把調用這個函數的語句轉換成一個jmp跳轉指令。在程序開始運行時候,調用語句能夠正確跳轉到對應的函數地址。 這樣很好,也很直白,可是。。。太死板了。everything is pre-determined
2. 咱們但願靈活,因而須要開發面向對象的語言,例如c++。 c++在c的基礎上增長了類的部分。但這到底意味着什麼呢?咱們再寫它的編譯器要如何考慮呢?其實,就是讓編譯器多繞個彎,在嚴格的c編譯器上增長一層類處理的機制,把一個函數限制在它處在的class環境裏,每次請求一個函數調用,先找到它的對象, 其類型,返回值,參數等等,肯定了這些後再jmp跳轉到須要的函數。這樣不少程序增長了靈活性一樣一個函數調用會根據請求參數和類的環境返回徹底不一樣的結果。增長類機制後,就模擬了現實世界的抽象模式,不一樣的對象有不一樣的屬性和方法。一樣的方法,不一樣的類有不一樣的行爲! 這裏你們就能夠看到做爲一個編譯器開發者都作了哪些進一步的思考。可是。。。仍是死板, 咱們仍然叫c++是static language。
3. 但願更加靈活! 因而咱們徹底把上面哪一個類的實現部分抽象出來,作成一套完整運行階段的檢測環境。此次再寫編譯器甚至保留部分代碼裏的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。
如今說一下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(同步),由於1. There is no 100% deadlock detection algorithm. If there is, no deadlock at all. 2. 每每這類錯誤很隱晦,靜態分析很難找到。 3. 抽象度較高須要經驗去把握。
整體來講,我見到的在這方面的問題能夠分爲一下幾點:
1. 不知道多線程開發的幾個基點,看別人代碼越看越糊塗的。一會NSThread、一會Grand Central Dispatch、block等等。。。Apple封裝了不少線程的api, down to core多線程的結構基本是:
能夠看到在多線程開發中你能夠選擇這上面這4種不一樣的方式。
Mach是核心的操做系統部分。其實這個我也不是很是熟悉,至少我尚未讀到過直接使用mach作多線程的代碼。
pthread(POSIX Threads)是傳統的多線程標準,靈活、輕巧,可是須要理論基礎,開發複雜。須要注意一點,根據apple文檔提示,在Cocoa下使用pthread須要先啓動至少一個NSThread,肯定進入多線程環境後才能夠。
NSThread是Mac OS 10.0後發佈的多線程API較爲高層,可是缺少靈活性,並且和pthread相比效率低下。
Grand Central Dispatch 是10.6後引入的開源多線程庫,它介於pthread和NSThread之間。比NSThread更靈活、小巧,而且不須要像pthread同樣考慮不少lock的問題。同時objective-c 2.0發佈的新語法特性之一blocks,也正是根據Grand Central Dispatch需求推出的。
因此在你寫多線程代碼或者閱讀多線程代碼時候,心理要先明確使用哪一種技術。
2. thread和Reference Counting內存管理形成的問題。
線程裏面的方法都要放到NSAutoreleasePool裏面嗎這類問題很常見,主要緣由是 NSAutoreleasePool 究竟是幹什麼用得不明白。 NSAutoreleasePool跟thread其實關係並不顯著,它提供一個臨時內存管理空間,比如一個沙箱,確保不會有不當的內存分配泄露出來,在這個空間內新分配的對象要向這個pool作一下注冊告訴:「pool,我新分配一塊空間了」。當pool drain掉或者release,它裏面分配過的內存一樣釋放掉。可見和thread沒有很大關係。可是,咱們閱讀代碼的時候常常會看到,新開線程的函數內老是以NSAutoreleasePool開始結束。這又是爲何呢!? 由於thread內剛好是最適合須要它的地方! 線程函數應該計算量大,時間長(supposed to be heavy)。在線程裏面可能會有大量對象生成,這時使用autoreleasepool管理更簡潔。因此這裏的答案是,不必定非要在線程裏放NSAutoreleasePool,相對的在cocoa環境下任意地方均可以使用NSAutoreleasePool。若是你在線程內不使用NSAutoreleasePool,要記得在內部alloc和relase配對出現保證沒有內存泄露。
3. 線程安全
每一個程序都有一個主線程(main thread),它負責處理事件相應,和UI更新。
更新UI問題。不少新手會由於這個問題,致使程序崩潰或出現各類問題。並且逛論壇會看到因此人都會這麼告訴你:「不要在後臺線程更新你的UI」。其實這個說法不嚴密,在多線程環境裏處理這個問題須要謹慎,並且要了解線程安全特性。
首先咱們須要把「UI更新」這個詞作一個說明,它能夠有2個層次理解,首先是繪製,其次是顯示。這裏繪製是能夠在任何線程裏進行,可是要向屏幕顯示出來就須要在主線程操做了。我舉個例子說明一下,例如如今咱們有一個NSImageView,裏面設置了一個NSImage, 這時我想給NSImage加個變色濾鏡,這個過程就能夠理解爲繪製。那麼我徹底能夠再另一個線程作這個比較費時的操做,濾鏡增長完畢再通知NSImageView顯示一下。另外一個例子就是,Twitter客戶端會把每一條微博顯示成一個cell,可是速度很快,這就是由於它先對cell作了offscreen的渲染,而後再拿出來顯示。
因此經過這點咱們也能夠獲得進一步的認識,合理設計view的更新是很是重要的部分。不少新手寫得代碼片斷沒錯,只是由於放錯了地方就致使整個程序出現各類問題。
根據蘋果線程安全摘要說明,再其它線程更新view須要使用lockFocusIfCanDraw和unlockFocus鎖定,確保不會出現安全問題。
另外還要知道經常使用容器的線程安全狀況。immutable的容器是線程安全的,而mutable容器則不是。例如NSArray和NSMutableArray。
5. Asynchronous(異步) vs. Synchronous(同步)
引用
我在一個view要顯示多張web圖片,我想問一下,我是應該採用異步一個一個下載的方式,仍是應該採用多線程同時下載的方式,仍是2個都用,那種方式好呢?
實際上單獨用這2個方法都很差。並非簡單的用了更多線程就提升速度。這個問題同時涉及客戶端和服務器的狀況。
處理這種類型的程序,比較好的結構應該是:非主線程創建一個隊列(至關於Asynchronous任務),隊列裏同時啓動n個下載任務(至關於Synchronous任務)。這裏的n在2~8左右就夠了。這個結構下能夠認爲隊列裏面每n個任務之間是異步關係,可是這n個任務之間又是同步關係,每次同時下載2~8張圖片。這樣處理基本能夠知足速度要求和各種服務器限制。
(很是抱歉,以前質疑問問題的人理解錯誤。實際上是我本身暈了,問得問題很好。)
4. runloop
如今說說runloop爲什麼會成爲cocoa開發中迷惑的點。由於不少新手沒有從動態角度看它。 首先回想一下第2點介紹的runtime的概念。 接着我出一個題思考一下。
如今我有一個程序片斷以下:
複製代碼
- - (void)myThread:(id)sender
- {
- NSAutoreleasePool *pool=[[NSAutoreleasePool alloc] init];
- while (TRUE) {
-
- //do some jobs
- //break in some condition
-
- usleep(10000);
-
- [pool drain];
- }
-
- [pool release];
- }
|
如今要求,作某些設計,使得當這個線程運行的同時,還能夠從其它線程裏往它裏面隨意增長或去掉不一樣的計算任務。 這,就是NSRunloop的最原始的開發初衷。讓一個線程的計算任務更加靈活。 這個功能在c, c++裏也許能夠作到可是很是難,最主要的是由於語言能力的限制,之前的程序員不多這麼去思考。
好,如今咱們對上面代碼作一個很是簡單的進化:
複製代碼
- 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];
- }
|
注意,這裏沒有作線程安全處理,記住Mutable container is not thread safe.
這個簡單的擴展,讓咱們看到了如何利用runtime能力讓線程靈活起來。當咱們從另外線程向targetQueue和actionQueue同時加入對象和方法時候,這個線程函數就有了執行一個額外代碼的能力。
但,有人會問,哪裏有runloop? 那個是 nsrunloop? 看不出來啊。
複製代碼
- while (TRUE) {
- //break in some condition
- }
|
一個線程內這個結構就叫線程的runloop, 它和NSRunloop這個類雖然名字很像,但徹底不是一個東西。之前在使用靜態語言開始時候,程序員沒有什麼迷惑,由於沒有NSRunloop這個東西。 我接着來講,這個NSRunloop是如何來得。
第二段擴展代碼裏面確實沒有NSRunloop這個玩意兒,咱們接着作第3次改進。 此次咱們的目的是把其中動態部分抽象出來。
複製代碼
-
- @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
|
複製代碼
- @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
|
複製代碼
- @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結構抽象出來了。例如我有一個MyNSThread實例,myThread1。我能夠給這個實例的線程添加須要的任務,而myThread1內部的MyNSRunloop對象會管理好這些任務。
複製代碼
- 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];
|
當你看懂了上面的代碼也許會感嘆,‘原來是這麼回事啊!爲何把這麼簡單的功能搞這麼複雜呢?’ 其實就是這麼回事,把Runloop抽象出來可使得線程任務管理更加loose coupling,給設計模式提供更大的空間。這樣第三方開發者不須要過深刻的涉及線程內部代碼而輕鬆管理線程任務。另外請注意,這裏MyNSRunloop, MyNSTimer等類是我寫得一個模擬狀況,真實的NSRunloop實現確定不是這麼簡單。這裏只爲了說明一個思想。這種思想貫穿整個cocoa framework從界面更新到event管理。
5. delegate, protocol
這個會列出來由於,我感受問它的數量僅此於內存管理部分,它們用得很頻繁,而且它們是多鍾設計模式的重要組成部分。
6. event responder
使用過Xcode的開發者都知道Interface Builder這個開發組件,在Xcode4版本之後該組件已經和xcode整合到一塊兒。它是蘋果軟件開發中很是重要的部分。ib爲開發者減輕了很大一部分界面設計工做。可是其中有一個東西讓新接觸ib的開發者一頭霧水,那就是First Responder, 它是什麼東西,爲什麼它會有那麼多Actions。這節我會詳細介紹如何理解Responder和Cocoa下的事件響應鏈。
First Responder在IB屬性爲Placeholders,這意味着它屬於一個虛擬實例。就比如TextField裏面的string placeholder同樣,只是臨時顯示一下。真正的first responder會被其它對象代替。實際上,任何派生自NSResponder類的對象均可以替代First Responder。而First Responder裏面的全部Actions就是NSResponder提供的接口函數,固然你也能夠定義本身的響應函數。
MacOS在系統內部會維護一個稱爲「The Responder Chain」的鏈表。該列表內容爲responder對象實例,它們會對各類系統事件作出響應。最上面的哪一個對象就叫作first responder,它是最早接收到系統事件的對象。若是該對象不處理該事件,系統會將這個事件向下傳遞,直到找到響應事件的對象,咱們能夠理解爲該事件被該這個對象截取了。
The Responder Chain基本結構以下圖所示:
在理解了上面的概念以後,我但願使用一個例子讓你們對responder有更加具體的認識。你們都知道NSTextField這個控件,它是最多見的控件之一。它最基本功能是顯示一個字符串,若是啓用可選,那麼用戶能夠選中文本,拷貝文本,若是開啓編輯選項,還能夠運行用戶編輯文本,等等基本操做。
下面展現給你們的例子是建立一個咱們本身建立的簡單textfield叫LXTextField。它不屬於NSTextField而是派生自NSView,具備功能顯示字符串,全選字符串,響應用戶cmd+c的拷貝操做,三個基本功能。注意NSView派生自NSResponder。
複製代碼
- @interface LXTextField : NSView
- {
- NSString *stringValue;
- BOOL selectAll;
- }
- @property(retain,nonatomic) NSString *stringValue;
-
- @end
-
-
- @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
|
運行實例,能夠看到隨着LXTextField收到系統發送的becomeFirstResponder消息,LXTextField變成responder chain中的frist responder, 這時候能夠理解爲IB裏的哪一個First Responder虛擬實例被該LXTextField取代。這時候mainMenu上哪些菜單項,例如:全選(cmd+a), 拷貝(cmd+a)等事件都會最早發給當前這個LXTextField。一旦你的LXTextField實現了NSResponder的哪些默認函數,那麼該對象就會截取系統事件。固然這些事件具體如何實現仍是須要你本身寫代碼實現。例如這裏的 - (IBAction)copy:(id)sender; 顯然我手動實現了textfield的copy能力。
注意上述代碼中我實現了一個空函數- (void)keyDown:(NSEvent *)theEvent 這意味着咱們但願LXTextField截取鍵盤事件而再也不傳遞給responder chain後續對象。固然,若是咱們但願LXTextField響應特定鍵盤事件,而其餘事件繼續傳給其餘響應對象,咱們能夠編寫以下代碼。
複製代碼
- - (void)keyDown:(NSEvent *)theEvent
- {
- if(condition){
- do something;
- }else{
- [super keyDown:theEvent];
- }
- }
|
7. mem-ory management
這個也許是問得最多的問題了吧。全部這些問題每每來源於3個地方,一、不瞭解底層機制;二、沒有吃透規則; 三、不瞭解經常使用container的Reference Counting特性,或着說沒有下功夫去看對應文檔。
1. 底層機制
你們是否知道從舊時代的RC到ARC機制到底意味着什麼呢? 爲何ARC從開發速度,到執行速度和穩定性都要優於rc?
開發速度不言而喻,你少寫不少release代碼,甚至不多去操心這部分。
執行速度呢?這個還要從runtime提及,還記得我在第2點說得一句話麼:「Runtime is everything between your each function call.」
RC是一個古老的內存管理哲學:
誰分配誰釋放。 經過counting來肯定一個資源有幾個使用者。道理很簡單,可是每每簡單的東西人卻會犯錯。歷來沒有一個程序員能夠充滿信心的說,我寫得代碼歷來沒有過內存泄露。這樣來看,咱們就更須要讓程序能夠本身處理這個管理機制,這就須要把這個機制放到runtime裏。
因此RC->ARC就是把內存管理部分從普通開發者的函數中移到了函數外的runtime中。由於runtime的開發原型簡單,邏輯層次更高,因此作這個開發和管理出錯的機率更小。實際上編譯器開發人員對這部分通過無數次測試,因此能夠說用arc幾乎不會出錯。另外因爲編譯的額外優化,使得這個部分比程序員本身寫得代碼要快速不少。並且對於一些通用的開發模式,例如autorelease對象,arc有更優秀的算法保證autoreleasepool裏的對象更少。
因此RC->ARC就是把內存釋放語句從普通開發者的代碼中去掉,而後交給編譯器讓它在編譯階段自動增長該有的釋放語句。 由於是編譯器分析,因此出錯的機率更小。實際上編譯器開發人員對這部分通過無數次測試,因此能夠說用arc幾乎不會出錯。另外因爲交給編譯器處理,使得這個部分比程序員本身寫得代碼要快速不少。
ARC另外一個特色就是,強制程序員遵照owner規則。例如, 舊時不少程序員開發,喜歡在須要用的時候臨時創建一個windowController,窗口關閉後controller本身release掉。可是這個思路在arc下面是行不通的。由於這種模式打破了owner規則(一個實例須要至少有1個擁有者)一個臨時創建的windowController若是沒有owner,那麼它會在當前函數退出前就被arc釋放掉。有些程序員認爲這是arc很是討厭的地方,固然也有一些程序員認爲這樣更規範。
2. RC規則
首先說一下rc是什麼,r-Reference參照,引用 c-counting計數, rc就是引用計數。俗話說就是記錄使用者的數量。 例如如今我有一個房間空着,你們能夠進去隨意使用,可是你進門前,須要給門口的計數牌子+1, 出門時候-1。 這時候這個門口的牌子就是該房間裏的人數。一但這個牌子變爲0我就能夠把房間關閉。
這個規則可讓NSObject決定是否是要釋放內存。當一個對象alloc時候,系統分配其一塊內存而且object自動計數retainCount=1 這時候每當[object retain]一次retainCount+1(這裏雖然簡寫也是rc不過是巧合或者當時開發人員故意選的retain這個詞吧)每次[object release]時候retainCount-1 當retainCount==0時候object就真正把這快內存還給系統。
3. 經常使用container的Reference Counting特性
這個規則很簡單把。可是這塊確實讓新手最頭疼的地方。 問題出在,新手總想去驗證rc規則,又老是發現和本身的指望不符合。
無數次看到有人寫下以下句子
複製代碼
- NSLog(@"%d",[object retainCount]);
|
複製代碼
- while([object retainCount]>0){
- [object release];
- }
|
固然了,我也作過相似的動做,那種但願一切盡在掌握中的心態。可是你會看到其餘人告訴這麼作徹底沒有意義。rc does not work this way. 也許這樣的暴力釋放會起做用,可是retainCount並非用來作這個的。每一個數字意味着有其它對象引用該資源,這樣的暴力釋放很容易致使程序崩潰。這個數字也許並非你心目中的哪一個。由於你很難跟蹤到底哪些對象引用的該資源。你用代碼創建的資源不光只有你的代碼纔會用到,你調用的各類Framework,Framework調用的Framework,都有可能改變這個資源的retainCount. 因此去驗證rc規則不是明智之舉。
你能作的就是理解規則,使用規則,讀文檔瞭解container的引用特性。或者乾脆移到arc上面,讓runtime環境處理這些問題。
引用
我有一個NSMutableArray裏面保存了1000個NSString對象,我在release的時候須要循環釋放1000個string麼?仍是隻須要release NSMutableArray。
就像上面提到的,若是你瞭解container的引用特性,這個問題天然就解決了。「NSMutableArray在添加、插入objects時會作retain操做。」 經過這一句話就分析出,用戶不否須要幫助NSMutableArray釋放1000個string。回憶上面提到的管理哲學,「誰分配誰釋放」 編寫NSMutableArray的程序員很是熟悉這個規則,NSMutableArray內部retain了,NSMutableArray天然要負責release。可是NSMutableArray纔不會管你在外面什麼地方引用了這1000個string,它只管理好內部的rc就夠了。因此若是你在NSMutableArray外面對1000個string retain了,你天然須要release。相應的,你做爲建立這個NSMutableArray的程序員,你只管release這個NSMutableArray就能夠了。
最後說一下不用arc的狀況。目前狀況來看,有很多第三方的庫並未支持arc,因此若是你的舊項目使用了這些庫,請檢查是否做者發佈了新版本,或者你須要本身修正支持arc。
8. class heritage and category
在這節裏說明2個比較重要的問題,第一是subclass和category的區別。第二是新手最容易忽略的學習步驟。
Objective-C的Object-oriented programming特性提供subclass和category這2個比較很是重要的部分。subclass應該反覆被各類編程書籍介紹過。它是oop繼承特性的關鍵語法,它給類添加了延續而且多樣化本身的方法。能夠說沒有繼承就沒有oop這玩意。而category相對於subclass就不那麼出名了。其實category思想出世於smalltalk,因此它不能算是一個新生事物。
先說一下這2個特性最主要的區別。簡單能夠這麼理解,subclass體現了類的上下級關係,而category是類間的平級關係。
如上圖所示,左側是subclass,能夠看到class, subclass1, subclass2是遞進關係。同時下面的子類徹底繼承父類的方法,而且能夠覆蓋父類的方法。subclass2擁有function1,function2,function3三個函數方法。function1的執行代碼來自subclass1, function2的執行代碼來自於subclass2。
右側是category。能夠看到,不管如何擴展類的category,最終就只有一個類class。category能夠說是類的不一樣方法的小集合,它把一個類的方法劃分紅不一樣的區塊。請注意觀察,每一個category塊內的方法名稱都沒有重複的。這正是category的重要要求。
通過上面簡單解釋瞭解了這2點的基本區別,如今深刻說一下category。
在Objective-c語言設計之初一個主要的哲學觀點就是儘可能讓一個程序員維護龐大的代碼集。(對於龐大的項目‘原則’和‘協議’是很是重要的東西。甚至編寫良好的文件名都是很是重要的開發技巧)根據結構化程序設計的經驗出發,把一個大塊代碼劃分紅一些小塊的代碼更便於程序員管理。因而objc借用了smalltalk的categories概念。容許程序員把一系列功能相近的方法組織到一個單獨的文件內,使得這些代碼更容易識別。
更進一步的,和c,c++這種靜態語言相比。objc把class categories功能集成到了run-time裏面。所以,objc的categories容許程序員爲已經存在的類添加新的方法而不須要從新編譯舊的類。一旦一個category加入,它能夠訪問該類全部方法和實例變量,包括私有變量。
category不只能夠爲原有class添加方法,並且若是category方法與類內某個方法具備一樣的method signature,那麼category裏的方法將會替換類的原有方法。這是category的替換特性。利用這個特性,category還能夠用來修復一些bugs。例如已經發布的Framework出現漏洞,若是不便於從新發布新版本,可使用category替換特性修復漏洞。另外,因爲category有run-time級別的集成度,因此使得cocoa程序安全性有所降低。許多黑客就是利用這個特性(和posting技術2)劫持函數、破解軟件,或者爲軟件增長新功能。一個很典型的例子就是,我發佈的QQ表情管理器。
值得注意的一點是,因爲一個類的categories之間是平級關係。因此若是不一樣categories擁有相同的方法,這個調用結果是未知的。因此:
引用
Category methods should not override existing methods (class or instance).
Two different categories implementing the same method results in undefined behavior
Objc中Categories有其侷限的部分,就是你不能爲原有的class添加變量,只能添加方法。固然方法裏能夠添加局部變量。在這個侷限基礎上就有其它語言作了進一步改進,例如TOM語言就爲Categories增長了添加類變量的能力。
總上所屬,若是你開發時候遇到不管如何都須要爲類添加變量的狀況,最好的選擇就是subclass。相反若是你只但願增長一些函數簇。Categories是最好的選擇。
而且我我的給你們的建議是,每一個Cocoa程序員都應該收集整理本身的一套NS類函數的Categories擴展庫。這對你從此程序開發效率和掌控狀況都有很大提升。
9. drawing Issues
你們都知道,MacOS是一個很是注重UI的系統。因此在MacOS編程裏繪製是一個很是重要的部分。第10部分,我會從2點介紹MacOS下繪製編程。首先是繪製技術分類;其次是繪製代碼結構。
從繪製技術分類上看,Cocoa程序員能接觸的幾種繪製技術列表以下:
1. Cocoa Drawing(NS-prefix)
2. Core Graphics(CG-prefix, called Quazrtz 2D)
3. Core Animation
4. Core Image
5. OpenGL
在這裏我不打算給你們介紹每一種都是如何繪製具體的圖像。只是介紹一下,它們大概長什麼樣子,而且有什麼優點和限制。
###Cocoa Drawing
Cocoa Drawing應該是學習Cocoa程序開發最早接觸的繪製技術。也是目前大多數MacOS程序所使用的繪製技術,其底層使用Quazrtz 2D(Core Graphics)。蘋果對應文檔爲 [Cocoa Drawing Guide](
https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/CocoaDrawingGuide/Introduction/Introduction.html)。Cocoa Drawing並無統一的繪製函數,全部繪製函數分散在幾個主要的NS類的下面。例如, NSImage, NSBezierPath, NSString, NSAttributedString, NSColor, NSShadow,NSGradient …
因此很簡單,當你看到以下代碼就能夠判斷,使用的是Cocoa Drawing方法
複製代碼
- [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];
|
這種代碼多出如今NSView的drawRect函數內。Cocoa Drawing 的渲染上下文是 NSGraphicsContext,我不斷的看到不少新手把 NSGraphicsContext 和 CoreGraphics 的 CGContextRef 搞混。雖然它們很像而且也確實是有關係的,不過若是你不瞭解當繪製時候的 render context 不少時候將獲得一個空白頁面的結果。
###Core Graphics
Core Graphics 是 Cocoa Drawing layer 的底層技術,在 iOS 開發中很是廣泛,由於 iOS 系統中並不存在 Cocoa layer 因此網上能夠找到的可能是 Core Graphics 繪製代碼段子,這給那些不瞭解 Mac 開發的新手來講形成了很大困擾。Cocoa 是 Mac OS 下的 application framework 而 iOS 下的 application framework 則是 UIKit.framework又叫 Cocoa Touch,它們分享部分代碼基礎但又不徹底同樣。例如,Cocoa Touch 下的 UIView 的渲染上下文會使用 UIGraphicsGetCurrentContext() 取得,它獲得的是一個 CGContextRef 指針,而在 NSView 裏多用 [NSGraphicsContext currentContext] 取得渲染上下文。它獲得的是一個 NSGraphicsContext 對象。固然 NSView 裏也能夠經過 CGContextRef ctx = [[NSGraphicsContext currentContext] graphicsPort]; 來取得一個 Core Graphics 渲染上下文。 可見 Mac OS 下的開發更爲靈活一些。由於 iOS 中的 UIKit 開發初期就瞄準了顯卡硬件加速,全部 UIView 都是默認 layer-backed 的。iOS 開發者必須使用 Core Graphics 和 Core Animation 這幾個相對底層的繪製技術。
請看下面等價代碼,做用是繪製一個白色矩形。可是分別使用 Core Graphics 和 Cocoa Drawing:
複製代碼
- 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種*風格徹底不一樣*的繪製技術。Cocoa Drawing 是分散式的繪製函數,而 Core Graphics 是傳統的相似 OpenGL 的集成式的繪製方式。其實 Cocoa Drawing 下層是 Core Graphics, Core Graphics 的下層是 OpenGL。 ###Core Animation 若是說 Core Graphics 和 Cocoa Drawing 是通用的 UI 繪製框架的話,那麼 CA 顯然是界面動畫繪製的高級技術。 Core Animation 的對應 Cocoa Animation 部分應該是 NSAnimation 和 NSViewAnimation,但這2個差距比較大。NSAnimation 出現與 OS X 10.4,Core Animation 是 10.5 後出現的。NSViewAnimation 功能和使用相對簡單。 簡單來講,Core Animation 的做用對象是 CALayer, NSAnimation 的做用對象是 NSView。