2012年11月26日 星期一

複雜的心情

2012年7月27日我存下人生中的第一個PSD檔。

今天,2012年11月26日最新完成的照片標註介面:

我只想對自己說「喜歡嗎?」 (笑)

2012年11月21日 星期三

iOS分享_如何使用Xcode內建工具找到記憶體洩漏(Memory Leak)

不管你是自修或是到外面上課,書本或是講師一定會告訴你“管理記憶體”非常的重要,而記憶體管理對於有C語言底子的人可能非常的自然,但是對於其他程式語言起家的程式設計師(諸如我),倒是一件幾乎完全從零開始的事情;因為知道要管理,我們會在撰寫iOS時自以為地使用alloc分配記憶體,再在自覺得適當的時刻利用release將其釋放掉,當我們完成一個階段並且開始在模擬器或實機跑時,發現程式正常運作,那麼我們可能自我感覺良好的認為完成了一個很OK的程式。

但是其實有時候可能在某些呼叫的方法上,iOS會自己管理記憶體,並不需要你去觸碰,也有可能在不同function間傳遞時因為操作有誤,導致每次呼叫便使得retaincount不斷上升,而記憶體管理需要不斷地學習,瞭解需要管理與釋放的變數與釋放的時機。

本篇並沒有要提及如何寫出良好記憶體管理的方法,我也沒有能力寫出這樣偉大的文章,但即使我們沒有辦法一次就達到完善的記憶體管理,卻可以經過一些工具一步步向前。

截至目前為止,我所知道要尋找Memory Leak的方法有兩個,你應該針對你的程式先完成第一個測試,再進一步完成第二個,直至程式修改成你所接受的記憶體管理:

1. 使用Xcode的Analyze功能。

這個功能可以幫助你去尋找程式碼中,有建立記憶體空間卻沒有釋放的位置,我們將一個release註解來看看他的結果:

接著你可以對他點擊,以獲取更多的資訊。

這一個測試僅能以編譯的手段,去尋找那些你可能忘記釋放的地方,也就是你必須要釋放的那些變數,所以最基本的要求就是讓該測試沒有任何提醒。但即便該測試完成,也不代表該程式在執行階段不會發生Memory Leak的問題,原因是有可能程式需要上傳相片,那麼我們會使用[NSData dataWithContentsOfFile:filePath];的方式將檔案轉為NSData,再傳送出去,而在從檔案轉成NSData到上傳的過程中,會使用非常多變數與方法,其中一個該釋放但未釋放,則可能造成暫存該檔案的空間一直存在,所以當你不斷進行上傳的動作,會發現你的程式似乎越來越慢?直到Crash為止。

所以我們必須藉由第二個工具來輔助我們尋找程式運行中可能的記憶體洩漏。

2. 使用Xcode的Profile工具。

點擊後會新開一個名叫Instruments的程式,選Leaks後點擊Profile。

你的程式會開始運作,並且跑出一些好像在監測什麼的圖表?

接著你可以試著操作程式,會發現該圖表不斷的跳動,

而這個名為Allocations就是程式記憶體的分配,你可以藉由觀察下方表格的All Allocations*來得知目前的總使用量,若是你不斷地在頁面間來回反覆操作一樣的動作,而該記憶體用量只增不減,就代表在這個動作的過程中,應該有一些需要調整的地方。

當然,我們不能僅藉由這樣的方式來找尋應該要修改的地方,點選左邊的Leaks區域,會進到Leaks的監測畫面:

接著在繪圖區與表格區中間,有一個“田”Leaks,點選後選擇Call Tree:
最後將左側的Hide System Libraries與Invert Call Tree打勾(Hide System Libraries幫助你只檢視自己程式的問題,Invert Call Tree是將檢視的過程顛倒,讓你馬上可以點到出問題的最後一段程式碼),讓我們製造一些Leak的問題,可以看到該工具會將Leak列表:

雙擊列表兩下則可以得知“可能”出錯的位置:

最後,你可以嘗試的修復這些洩漏,直到程式運作過程完全沒有為止。

講起來有工具而且已經幫助你找到相關位置,理論上應該很容易解決這些問題?但實際上這些Memory Leak的問題通常會牽扯到不同函式間的呼叫,洩漏的位置往往不是問題的原因。連續一個星期都在處理Leak相關問題,這實在比平常在解決其他程式語言的問題時更加令人惱羞,更深深感覺每一個問題的解決背後真的需要很多知識的累積,近一兩年常有人問我要學什麼語言?學什麼最好?某語言好學嗎?我的答案總是讓人不以為然:

你有心學就可以學起來,實際打開電腦寫遠比一直想著要不要學有用多了,而每一種語言的特性都有可能讓你更瞭解別種語言,如果你要學的是if跟for,那什麼都可以學,我第一個學的是MATLAB。

2012年11月13日 星期二

iOS學習_如何較易找到exc_bad_access錯誤的源頭

"exc_bad_access"是一個令人惱羞的錯誤訊息,出現的原因是“使用了已經釋放記憶體的變數”,如果你很希望好操作程式的記憶體,那就很可能在觀念不清楚的狀態下一直遇到這一個錯誤。

而之所以說很令人惱羞,log並不會顯示任何資訊來供我們判斷,這時可在環境變數中增加一個參數(NSZombieEnabled),來增加判斷的資訊。

步驟:
1. 於選單Product選擇Edit Scheme。

2. 於Arguments的Environment Variables增加參數NSZombieEnabled,並設定為YES。

這時就可以看到錯誤時會多出一句相關錯誤的log。(EX:[__NSArrayM count]: message sent to deallocated instance 0x9bb5620)
然後再配合Xcode左側的Thread列表,來查找可能的位置與原因。
方法

iOS學習_返回NSMutableArray的函式要正確release的方法(ola的猜測)

本文章為筆者自行觀念的瞭解,無法保證一定正確。 ------------------------------------------- NSMutableArray是一個非常好用的型態,我們可以將他看作一個物件陣列(操作方法),慢慢的我們會將處理NSMutableArray獨立出一個一個方法,類似下面的程式碼:
-(NSMutableArray *)selectReportingMain_All
而函式的內容不外乎alloc一個NSMutableArray,然後將適當的物件放進去,在return NSMutableArray:
-(NSMutableArray *)selectReportingMain_All { NSMutableArray *items = [[NSMutableArray alloc] init]; if ([db open]) { FMResultSet *rs = [db executeQuery:@"SELECT * From reportingMain"]; while ([rs next]) { NSString *reportingID = [rs stringForColumn:@"reportingID"]; NSString *title = [rs stringForColumn:@"title"]; NSString *note = [rs stringForColumn:@"note"]; NSString *xValue = [rs stringForColumn:@"xValue"]; NSString *yValue = [rs stringForColumn:@"yValue"]; NSString *time = [rs stringForColumn:@"time"]; NSString *status = [rs stringForColumn:@"status"]; [items addObject:[NSDictionary dictionaryWithObjectsAndKeys: reportingID , @"reportingID", title , @"title", note , @"note", xValue , @"xValue", yValue , @"yValue", time , @"time", status , @"status", nil]]; } } [db close]; return items; }
上述程式碼為SQLite查詢的操作,這樣就可以簡單的將資料庫操作與界面做一個簡單的切割,但是觀察上面的程式碼可能會覺得有點詭異?
我們並沒有釋放items,難道每一次地使用就會不斷佔用記憶體嗎?這豈不是慢性自殺?

所以必須在適當的時機點進行釋放,所謂適當的時機就是“立刻”。 return [items autorelease];
如此,我們即可確保該NSMutableArray有被進行釋放。但通常會去接這一個NSMutableArray的介面都會準備一個全域變數,來承接吐出來的結果。
dbOperating *dbO = [[dbOperating alloc] init]; itembag = [dbO selectReportingMain_All]; NSLog(@"items retain:%d",items.retainCount); [dbO release]; 上述程式碼itembag可以很順利地取得結果,但是當該介面的其他函示需要進行操作時,則會產生“exc_bad_access”的錯誤,也就是說你使用了已經釋放的變數。

解決方法: dbOperating *dbO = [[dbOperating alloc] init]; itembag = [dbO selectReportingMain_All]; [itembag retain]; NSLog(@"items retain:%d",items.retainCount); [dbO release]; 手動增加itembag的retainCount,這時如果將retainCount輸出,會看到在上述程式碼內retainCount為2,但因回傳的變數是使用[items autorelease],所以隨即retainCount會降為1,並且也可以正常的讀取使用itembag裡面的值。

Ola的Photoshop學堂Part2-將icon轉為手繪懷舊風格

閱讀本文章注意事項:
1. 筆者為僅會小畫家且完全不曾接觸過Photoshop之程式設計師。
2. 本文章使用Photoshop CS5,筆者沒使用過其他版本。
3. 若是本文章幫助你完成該效果,請留言:「Ola國小時貼在布告欄展示一天就被撕掉的海報我覺得很棒!」。

-------------------------------------------------

為了讓整體風格相似,我需要一個手繪風格的icon,來放到嘔心瀝血的介面上:

步驟:
1. 先準備一張你所需要改變風格的圖片,我所使用的是Android上一個非常好用的記帳軟體的圖示。

2. 接著將該圖片調整為黑白色系。

3. 複製一個相同的圖到另一個圖層。

4. 將原本圖層加上顏色覆蓋,選擇咖啡色並且將透明度調整至30%。

5. 對複製的圖層加上濾鏡效果:風格化→找尋邊緣

6. 再對已經找尋邊緣的圖做濾鏡效果:素描→蠟筆紋理

7. 將5、6步驟的圖層設為覆蓋。

8. 最後將圖另存出來:


當然,你可以在步驟6時不要使用素描→蠟筆紋理的濾鏡,將可以營造出各式各樣不同風格的圖示;或是一開始不要使用黑白,或以別的顏色當底,就可以變化出很多不同的感覺,但能不能拿到好的icon來做風格改變又是另一回事了。

iOS學習_客制化Navigation Part4_Navigation的Title文字顏色

最近花超多時間在處理美工的問題,總體來說要產出好看友善的界面,需要大量的設計與圖片,而第一步莫過於瞭解什麼樣的物件可以作出什麼效果的客制化,整體來說最容易可以拿來參考的客制化界面,就是內建在iPad裡面的程式,行事曆與備忘錄這兩個已經透露出許多物件可變化的可能性。

首先就是處理幾乎每個程式都會使用的UINavigation,需要客制化的地方大致可分為四項:
1. 右上角的功能按鈕。
2. 左上角的返回鍵。
3. Navigation的底圖。
4. Navigation的Title文字。


本篇講第四點:Navigation的Title文字顏色。

最為單純,程式碼只需要一句:
navigator.navigationBar.titleTextAttributes = [NSDictionary dictionaryWithObject:[UIColor whiteColor] forKey:UITextAttributeTextColor]; 效果:

到此連續四篇,應該可以完全客制化Navigation的外觀,但能不能做出適宜且好看的圖,又是另一個需要努力的地方。

iOS學習_客制化Navigation Part3_改變Navigation的底圖

最近花超多時間在處理美工的問題,總體來說要產出好看友善的界面,需要大量的設計與圖片,而第一步莫過於瞭解什麼樣的物件可以作出什麼效果的客制化,整體來說最容易可以拿來參考的客制化界面,就是內建在iPad裡面的程式,行事曆與備忘錄這兩個已經透露出許多物件可變化的可能性。

首先就是處理幾乎每個程式都會使用的UINavigation,需要客制化的地方大致可分為四項:
1. 右上角的功能按鈕。
2. 左上角的返回鍵。
3. Navigation的底圖。
4. Navigation的Title文字。


本篇講第三點:Navigation的底圖。

雖然第三點跟第四點都相當的單純,但為了日後好尋找還是分成兩篇,做法:
UIImage *image = [UIImage imageNamed:@"mapview_bar1"]; [navigator.navigationBar setBackgroundImage:image forBarMetrics:UIBarMetricsDefault]; 效果:

雖然設置底圖非常簡單,但製作底圖就非常困難了,要如何跟下面的內容融為一體,真的是需要很多的巧思。

iOS學習_客制化Navigation Part2_改變返回按鈕

最近花超多時間在處理美工的問題,總體來說要產出好看友善的界面,需要大量的設計與圖片,而第一步莫過於瞭解什麼樣的物件可以作出什麼效果的客制化,整體來說最容易可以拿來參考的客制化界面,就是內建在iPad裡面的程式,行事曆與備忘錄這兩個已經透露出許多物件可變化的可能性。

首先就是處理幾乎每個程式都會使用的UINavigation,需要客制化的地方大致可分為四項:
1. 右上角的功能按鈕。
2. 左上角的返回鍵。
3. Navigation的底圖。
4. Navigation的Title文字。


本篇講第二點:左上角的返回鍵。

做法其實跟第一篇一樣,但是因為在self.navigationItem後面除了本篇要使用的leftBarButtonItem以外,還有一個會令人誤會的backBarButtonItem,依照字面上我們理論上可以使用backBarButtonItem屬性簡單地將自己的UIBarButtonItem指給他,完成客制化的動作。

但backBarButtonItem與leftBarButtonItem或是rightBarButtonItem不同,backBarButtonItem所設定的按鈕並不是目前所見的View,而是設定目前View push出的下一個View的返回鍵,也就是說我們重A轉移到B,希望B上面的返回鍵可以做些改變,那麼必須在A設定backBarButtonItem。

所以很快的我們在首頁進行設定,就可以改變下一個頁面的返回鍵:
UIBarButtonItem *backBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"回去" style:UIBarButtonItemStylePlain target:nil action:nil]; self.navigationItem.backBarButtonItem = backBarButtonItem; 效果:

非常簡單的將原本的文字改成自己想要的文字,但這不符合我們的需求,主要還是希望整個按鈕的風格都換掉,所以可以嘗試這樣:
UIBarButtonItem *backButton = [[UIBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"btn_back_un"] style:UIBarButtonItemStylePlain target:nil action:nil]; self.navigationItem.backBarButtonItem = backButton; 效果:

基本上這個效果非常的蠢,完全不符合需求。

這時我們會想到那我就學上一篇客制化按鈕的方式就OK了?很不幸的不管怎麼樣設定似乎都不會改變backBarButtonItem。

所以,最終還是必須回到最原始,以客制化leftBarButtonItem,並加上相對應的返回事件來完成:
UIImage *imgItem_BACK = [UIImage imageNamed:@"btn_back_un"]; UIImage *imgItem_BACK_click = [UIImage imageNamed:@"btn_back_click"]; UIButton *btnimg_BACK = [UIButton buttonWithType:UIButtonTypeCustom]; [btnimg_BACK setBackgroundImage:imgItem_BACK forState:UIControlStateNormal]; [btnimg_BACK setBackgroundImage:imgItem_BACK_click forState:UIControlStateHighlighted]; [btnimg_BACK addTarget:self action:@selector(btn_back_click) forControlEvents:UIControlEventTouchUpInside]; btnimg_BACK.frame = CGRectMake(0.0f, 0.0f, imgItem_BACK.size.width, imgItem_BACK.size.height); UIBarButtonItem *imageBACKButton = [[UIBarButtonItem alloc] initWithCustomView:btnimg_BACK]; self.navigationItem.leftBarButtonItem = imageBACKButton; [imageBACKButton release];
效果:

iOS學習_客制化Navigation Part1_放置多個圖片按鈕

最近花超多時間在處理美工的問題,總體來說要產出好看友善的界面,需要大量的設計與圖片,而第一步莫過於瞭解什麼樣的物件可以作出什麼效果的客制化,整體來說最容易可以拿來參考的客制化界面,就是內建在iPad裡面的程式,行事曆與備忘錄這兩個已經透露出許多物件可變化的可能性。

首先就是處理幾乎每個程式都會使用的UINavigation,需要客制化的地方大致可分為四項:
1. 右上角的功能按鈕。
2. 左上角的返回鍵。
3. Navigation的底圖。
4. Navigation的Title文字。


本篇講第一點:右上角的功能按鈕。

之前有發過一篇文章“iOS學習_於UINavigation上增加多個按鈕”,最終的效果是會出現預設顏色的按鈕,如果放置有顏色的小圖示則會自動變成灰階,預設的其實已經不錯看,但離美化距離就有一點遠。


做法與以上篇是相同的方法,但這次我們設定按鈕的預設圖片與被按下去的圖片:

UIImage *imgItem_GPS = [UIImage imageNamed:@"btn_gps_un"]; UIImage *imgItem_GPS_click = [UIImage imageNamed:@"btn_gps_click"]; UIButton *btnimg_GPS = [UIButton buttonWithType:UIButtonTypeCustom]; [btnimg_GPS setBackgroundImage:imgItem_GPS forState:UIControlStateNormal]; [btnimg_GPS setBackgroundImage:imgItem_GPS_click forState:UIControlStateHighlighted]; [btnimg_GPS addTarget:self action:@selector(btn_whereIam_Click) forControlEvents:UIControlEventTouchUpInside]; btnimg_GPS.frame = CGRectMake(0.0f, 0.0f, imgItem_GPS.size.width, imgItem_GPS.size.height); UIBarButtonItem *imageGPSButton = [[UIBarButtonItem alloc] initWithCustomView:btnimg_GPS];
上面可以看到我們使用btn_gps_un與btn_gps_click作為效果圖,利用setBackgroundImage方法指定給按鈕,並設定他們應該出現的狀態,最後裝到一個UIBarButtonItem中。

以同樣的步驟,在準備兩個名為imageLayerButton與imageSearchButton的UIBarButtonItem,將其裝到一個NSMutableArray中,並設定給TranslucentToolbar,最後以UIBarButtonItem方法中initWithCustomView來初始化,然後指定給要放置的Navigation(EX:self.navigationItem.rightBarButtonItem)。

//將按鈕加到NSMutableArray當中。 NSMutableArray* buttons = [[NSMutableArray alloc] initWithCapacity:3]; [buttons addObject:imageGPSButton]; [buttons addObject:imageLayerButton]; [buttons addObject:imageSearchButton]; [imageGPSButton release]; [imageLayerButton release]; [imageSearchButton release]; //建立一個UIToolbar來裝載剛剛建立的NSMutableArray TranslucentToolbar *toolbar = [[TranslucentToolbar alloc]initWithFrame:CGRectMake(0, 0, 240, 44)]; [toolbar setItems:buttons animated:NO]; [buttons release]; UIBarButtonItem *navigationRightBarButton = [[UIBarButtonItem alloc] initWithCustomView:toolbar]; self.navigationItem.rightBarButtonItem = navigationRightBarButton; [toolbar release]; [navigationRightBarButton release];
如此就可以放置上一個底色為透明,又帶有美編圖的功能列表。

效果: