發表文章

目前顯示的是 七月, 2013的文章

vim 快速開啟目錄下的檔案

用過 XCode 的 "shift+command+o" 快速開啟檔案後, 一直很想在 vim 內用同樣的功能。按個快速鍵會跳出一個輸入框, 接著只要打部份字串, 就會濾出有可能的檔案。 看到 DKcmd 推薦用 ctrlp, 就來試用一下。試用後感覺相當不錯, 用法如下: 在 command mode 打 ctrl+p 打檔名 選定檔案, 按 ctrl+t 開在新分頁; 按 ctrl+v 開在目前分頁的新 window; 按 enter 開在目前分頁ctrlp 除介面好用外, 另一大優點是使用 pure vim script, 理論上有 vim 的平台都能用 ctrlp。不過最近用一陣子後發覺一些小問題, 微調之後才會更好用。 在 $HOME/.vimrc 中加上這幾行: let g:ctrlp_clear_cache_on_exit = 0 " 離開 vim 後不要清 cache let g:ctrlp_max_files = 1000000 " 加大 cache 索引的檔案數, 否則會漏找檔案 let g:ctrlp_user_command = 'find %s -type f' " 使用 find 加速建索引的速度備註: 無用的實驗觀察之前沒讀完官網, 漏看了參數 g:ctrlp_user_command, 只好自己手動建索引加速, 下面的心得就當紀念吧。 另外 cache 預設存放在 $HOME/.cache/ctrlp 下, 若嫌 ctrlp 建 cache 太慢, 可以自己手動建:$ cd /path/to/target $ index=$(echo `pwd` | sed 's#/#%#g' | sed 's#$#.txt#') $ find . | cut -b 3- | LC_ALL=C sort > $HOME/.cache/ctrlp/$index 自己建會快超多, 不過會比較不乾淨一點, ctrlp 似乎有濾掉不能開的檔案, 像是目錄或圖檔之類的。

gdb 顯示 STL container 的方法

問題描述如下列的程式碼:std::vector<int> numbers; for (int i = 0; i < 4; i++) { numbers.push_back(i * 3); } std::map<std::string, std::string> contacts; contacts["john"] = "0987-654321"; contacts["mary"] = ""; 在 gdb 中不易閱讀 numbers 和 contacts 的顯示結果:(gdb) p numbers $1 = {<std::_Vector_base<int, std::allocator<int> >> = {_M_impl = {<std::allocator<int>> = {<__gnu_cxx::new_allocator<int>> = {<No data fields>}, <No data fields>}, _M_start = 0x6060 10, _M_finish = 0x606020, _M_end_of_storage = 0x606020}}, <No data fields>} (gdb) p contacts $2 = {_M_t = {_M_impl = {<std::allocator<std::_Rb_tree_node<std::pair<std::basic_string<char, std::char_traits<char>, std::allocator<char> > const, std::basic_string<char, std::char_ traits<char>, std::allocator<char> > > > >> = {<__gnu_cxx::new_allocator<std::_Rb_tree_node<std::pair…

trace C/C++ function call 的方法

目的 了解特定模組或類別的使用方式。找出執行時間過長的函式方法一: 使用 gcc 參數 -finstrument-functions參考資料:Code Gen Options - Using the GNU Compiler Collection (GCC)Jserv's blog: GCC 函式追蹤功能 優點: 自動含蓋所有函式。 缺點: 可能增加太多執行負擔, 雖然可以配合 -finstrument-functions-exclude-file-list 去除不想觀察的函式, 對於大專案來說有些不方便。方法二: 自己寫 logger使用 C/C++ 或 gcc 提供的語法, 自己寫 logger 沒有想像中的麻煩: 可用 gcc 的 __PRETTY_FUNCTION__ 或標準的 __func__ 取得函式名稱 用 __LINE__ 取得行數 用 __FILE__ 取得檔名 於是可定個巨集:#define LOG_IT() MyFunctionLogger logger(__FILE__, __LINE__, __PRETTY_FUNCTION__) 然後用 vim 的全域取代::% s/^{/&\r LOG_IT();\r\r/c 這會逐個詢問是否取代下列字串:void foo() { // blah blah 為void foo() { LOG_IT(); // blah blah Btw, 由此可知, 函式的左大括號放在行首比放在行尾來得方便。 MyFunctionLogger 只是個 wrapper, 目的是利用 RAII 記錄進入和離開函式, 這樣即使函式中間有任何 return, 都不會漏記離開函式:class MyFunctionLogger { public: MyFunctionLogger(const char *file, int line, const char *func) : m_file(file), m_line(line), m_func(func) { MyLogger::Instance()->EnterFunctionCall(m_file, m_line, m_func); } ~MyFunctionLogger() { …

Linux 寫 code 確認某個 process 仍活著

shell script 直接用 ps PID | grep ... 檢查, 無須多說。寫 C/C++ 程式的話, 基於同樣的想法, 可以用 stat("/proc/PID/stat") 檢查檔案是否存在, 但這作法有一些不確定性, 有可能目標 process 掛了, 之後有新的 process 用了同樣的 pid, 結果誤判目標還活著。 若自己有權限送 signal 給目標 process 的話 (如 child 或 parent process), 可以用 kill 來檢查:bool IsAlive(pid_t pid) { return !(kill(pid, SIGCONT) == -1 && errno == ESRCH); } 送個無關緊要的 signal 給目標 process (若它真的有機會進入 STOP 狀態的話, 得挑別的 signal), 再檢查錯誤值判斷目標是否還活著。覺得用 kill 檢查的小技巧還滿有趣的, 備忘一下。2014-03-28 更新實際用了以後發現一個不方便的副作用。使用 gdb 連上 process 後, gdb 會在 process 收到 signal 時中斷。若採用送 signal 定期偵測 process 是否活著的話, gdb 會三不五時地中斷, 不方便除錯。 還是用 stat("/proc/PID/stat") 比較方便一些。或是觀察的 process 是 child process 的話, 可用 waitpid + WNOHANG 判斷。

在 iOS 刪除 App 後不會刪除 keychain 內的資料

不過理論上 App 不能存取別其它 App 的 keychain 資料, 所以這勉強不算安全漏洞吧。若希望使用者重新安裝 App 時, 不會用到之前存在 keychain 的資料, 可在安裝後第一次啟動時先刪除 keychain 資料:- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { //Clear keychain on first run in case of reinstallation if (![[NSUserDefaults standardUserDefaults] objectForKey:@"FirstRun"]) { // Delete values from keychain here NSArray *secItemClasses = @[(__bridge id)kSecClassGenericPassword, (__bridge id)kSecClassInternetPassword, (__bridge id)kSecClassCertificate, (__bridge id)kSecClassKey, (__bridge id)kSecClassIdentity]; for (id secItemClass in secItemClasses) { NSDictionary *spec = @{(__bridge id)kSecClass: (__bridge id)secItemClass}; SecItemDelete((__bridge CFDictionaryRef)spec); } [[NSUserDefaults standardUserDe…

VirtualBox 使用 Bridged Adapter 的方法

打從開始用 VirtualBox 到現在, 一直沒有成功地用過 Bridged Adapter, 所幸 cmd 很自然地使用 Bridged Adapter, 比對一下成功的設置方式, 總算明白我搞錯的地方。 Bridged Adapter 的設定幾乎和 NAT 一樣簡單, 在 guest OS 關機狀態下, 從 VM 的 Settings -> Network -> Attached to 選擇 Bridged Adapter 即可。若 host OS 是透過 DHCP 取得上網 IP (比方說家裡有裝 IP 分享器), 那 guest OS 無須設定, 開機後也會從 DHCP 取得 IP 上網。 若 host OS 是用 PPPoE 連上網路, guest OS 也需要設定 PPPoE 才能連上網。可以想成 guest OS 和 host OS 是連上同一個 LAN 的另一台機器。Btw, 可以參考這裡了解 Ubuntu 設定 PPPoE 的方法, 作法也很簡單。

shell script: 用 eval 執行內含單引號的變數

某些情況下需要在指令裡加入單引號, 但是會有問題。下面是一個簡化後沒意義的例子:cmd="ls '/etc'" echo "> Run $cmd" $cmd 執行結果如下:> Run ls '/etc' ls: cannot access '/etc': No such file or directory 但是實際在 bash 上打入 ls '/etc' 卻沒問題。從這裡得知, 解法是用 eval 執行:cmd="ls '/etc'" echo "> Run $cmd" eval $cmd 另外記得除錯時可以配合 set -x 顯示 bash 實際執行的指令, 或是用 bash -x 執行 script。

加速 gdb 載入 symbol 時間

gdb 7.4 以後支援 index, 可以加速載入 gdb symbol 的時間, 參考 Index Files - Debugging with GDB 的說明, 寫了個 script 接受一個 binary 檔, 會作以下的事: 用 gdb 載入 binary 的 symbol, 並存下 symbol index 到暫存檔 加入 symbol index 到原 binary symbol 太多時, 用 gdb index 可大幅減少載入時間, 不過每次重編又要重新產生 index 檔, 不知有沒有更好的方法, 可以在 linking 時直接在 binary 內加入 symbol index, 並且不會增加太多 linking 時間。2013-07-19 更新 依 Viller Hsiao 留言裡的建議, 試了 gold 的 --gdb-index。這功能還滿新的, Ubuntu 12.04 的 gold 沒這選項, 不過 12.10 的 gold 有。可以用 objdump -h BINARY | grep index 確認 link 時加上 --gdb-index 確實有效。 2013-07-23 更新 用 gold 編譯加上 --gdb-index 後踩到雷, 在目前的專案裡 100% 會在執行 backtrace 後卡住不動, 接著一直狂吃記憶體, 漲到 4G 以後顯示gdb virtual memory exhausted can't allocate 4064 bytes, 然後和使用者說 gdb 掛了, 你願意結束這個 debug session 嗎?交叉比對了下述三種情況: 用 gold link (不含 --gdb-index)用 gold link (不含 --gdb-index), 事後補 gdb index用 gold link + --gdb-index 結果只有第三種會中獎, 所以現在改回用第二種方法了。

Objective-C 的 scope 以及 property

今天要用又忘了, 寫下來備忘一下。 scopeinstance variable 有 scope, method 似乎沒有 scope。畢竟是 dynamic typing, method 加上 scope 也滿奇怪的。 instance variable 的 scope: 分為 @public, @protected, @private 和 @package (看不懂 @package, 要用到再研究, 我猜是指 scope 在「函式庫」內) @interface 預設 scope 是 @protected @implementation 預設 scope 是 @privateproperty宣告 @property Klass myObject 後, 可用 @synthesize 讓 compiler 產生基本的 getter 和 setter 實作。 或是自己在 @implementation 內實作-(void) setMyObject:(Klass*)object { } -(Klass*) myObject { return ...; } 個人不太喜歡 @property 的設計, 要找 property 相關存取程式時, 有點不方便。以 @property myObject 來說, 要先確定是否有用 synthesize, 若有的話, 要同時看 myObject、setMyObject 以及 synthesize 的 instance variable (如 _myObject), 才能確定沒有其它存取到這個屬性的地方。

MacPorts 基本操作

之前問朋友在 Mac OS X 下用那套工具, 結果強者們清一色地回答用 MacPorts, 不管它好不好用, 總之強者們意見如此一致, 跟著試就是了。 最近用一陣子後覺得還算順手, 記得依這裡的說明設 bash-completion, 關鍵的一步是要用 macports 裡的新版 bash 才能搭上 bash-completion, 但是 iTerm2 預設用系統的 bash, 要改一下 iTerm2 的偏好, 改用 macports 裝的 bash 才行。 目前只到下列幾個指令, man ports 裡都有: port search PATTERN port install PKG port info PKG port content PKG 美中不足的是, macports 沒有像 apt-file 這種從檔名反查套件的功能。不然, 有考慮日後改用 Macbook Pro 開發, 可兼顧日常使用應用程式, 以及用 terminal 開發的需求。

Optimizing the Critical Rendering Path 閱讀筆記

Optimizing the Critical Rendering Path - Velocity SC 2013 這篇滿有趣的, 看完順便筆記一下。 問題描述 使用者等超過一秒就會不耐煩 3G / 4G 網路 latency 很高以 3G 的情況來說, 假設 RTT 是 200ms, 去掉必須的 DNS lookup、TCP handshake、載入 html 檔 (用 http), 就只剩下 400ms 可以 render 網頁。 https 需要額外 1 ~ 2 個 RTT, 情況更糟 載入網頁時, 雖然 html 可以邊載入邊顯示, 但是需要載入外部 CSS 檔時會延後顯示網頁。CSS 需要載入完整個檔案才可使用, 無法邊載入邊用 外部 javascript 檔也會阻擋整個網頁顯示 ( ps. 網頁加速的基本知識可以參閱 Best Practices for Speeding Up Your Web Site, 還有用 Chrome Developer Tool 內建的功能檢查 ) 解法 要下參數避免 TCP slow start, 降低 RTT 一個 roundtrip 大概可下載 14KB 資料 (應該是以 MTU 1500 Bytes 來估吧), 盡量讓第一份 html 只含 14KB 以下的資料, 之後再補載入剩下內容 避免在第一次顯示時用到外部 CSS 檔, 有 Google 有提供模組讓 apache2 或 nginx "inline CSS" 避免在第一次顯示時用到外部 JS 檔

使用 iOS notification 的好處

先前在《iOS delegation vs. notification》提到我偏好 delegation, 這篇稍微修正一下對兩者的觀點。 開發 iOS 和 Android 以前我只有寫 Web 的經驗, 沒有寫圖形應用程式的經驗 (硬漢工程師就是要用 CLI 啊!!)。之前只覺得開發 Web 很容易上手, 不過跨瀏覽器很煩。最近多了一些 iOS 開發經驗, 才體會到為什麼 Web 這麼好寫, 個人認為的原因有二: 透過 AJAX 做 model 的操作, 預設就有成功或失敗的 callbackUI 明確知道目前 model 的狀態, 要不要反應是另一回事, 至少預設就有能力針對非同步操作提供 UI 回饋。 DOM 的架構很容易存取到所有 UI 元件。一來 DOM 是全域變數, 二來有 id、class 等屬性方便索引到目標 UI 元件, 任何時間任何一行程式碼都可以一行取得目標 UI。 換句話說, 寫網頁可以輕鬆得知 model 狀態變化, 得知變化時還可以輕鬆地改變任何相關的 UI 元件。 反觀寫圖形應用程式時, 離上述兩點相當地遙遠, 寫起來綁手綁腳的。或許是出於類似的需求, iOS 有提供 NSNotificationCenter 協助 observe 和 dispatch event。 於是, 透過 NSNotificationCenter 這個全域變數, 我們可以讓 model 透過 NSNotificationCenter 告知狀態變化, 或是寫一個 model delegate 做這件事。讓有需求的 UI 元件透過 NSNotificationCenter 觀察事件變化。 如此一來, 減輕不少實作負擔。不過如同 When to use Delegation, Notification, or Observation in iOS 所言, notification 過於彈性會造成一些問題。個人認為配合一些規範, 像是使用 header 檔定義 notification name 和 user info 的 key, 不要直接用 string, 應該是不錯的選擇。 備註: 為簡化起見, 上述關於網頁的連結是以 jQuery 為例。 漸漸覺得全域變數不是那麼糟的東西, 需要考量它的優缺點來使用, 而不是預設就想盡辦法避免全域變數。UIView …

GUI model 的介面設計心得

最近寫了稍微複雜一點的 iOS 介面, 多了一些心得。先前已寫過《非同步程式心得》包含 GUI 和 IO 的體會, 這篇則著重在 model 介面的設計。 如同《從需求出發理解背後技術的思考脈胳》的體會, 思考技術議題時不仿從需求切入, 比較容易掌握到必要項目, 需要在設計上做取捨時也有個依據。 先來看兩個生活中的例子: 電梯外側和內側有螢幕顯示電梯目前所在的樓層。 紅綠燈的秒數倒數。 這兩個例子裡, 不會因為多了螢幕顯示樓層或秒數而影響結果發生的時間, 但是會讓使用者明白現在發生什麼事, 比較不會失去耐心。相信體驗過沒提供樓層的電梯或沒秒數的紅綠燈的人, 應該更能體會沒有告知狀態變化的感覺。 回到程式開發的世界裡, model 時常會有非同步的操作, 比方說寫一個線上文書編輯器, 介面可以是 Web 或是 App UI, 但資料存在線上伺服器裡。 存檔的必要介面如下:void Document::Save(); void DocumentDelegate::OnSaveResult(bool ok); caller 產生一個 document, 將自己傳入作為 DocumentDelegate。呼叫 Save() 之後, 會觸發 callback OnSaveResult() 告知存檔是否成功。 乍看之下這樣的介面已滿足所有需求了, 使用者要求存檔, 介面也會回應存檔成功或失敗。但是, 若 Save() 和 OnSaveResult() 之間間隔過久, 使用者不知道剛才的操作是成功或失敗, 或是他沒有按到存檔鈕 (使用觸控面版特別容易有這樣的疑問), 甚至會懷疑應用程式是否當掉了。 或許我們會考慮在呼叫 Document::Save() 之後, 順便在 UI 上顯示訊息, 讓使用者明白已經進行 Save() 的操作。單就這例子來看沒有問題, 但是當 model 操作比較複雜時就有問題了。比方說, 後來支援了自動存檔, 有可能使用者沒有按「存檔」, 但 document 定期會執行 Save(), 介面無從反應現在是否正在存檔中。 反過來說, 若是介面改為:void Document::Save(); void DocumentDelegate::OnSaveResult(bool ok); void DocumentDelegate::OnSav…

iOS UIView 除錯

Refernece: Visual View Debugging | Cocoanetics 這篇實在是太實用了, 備忘一下文中提到的技巧。 取得 view 資訊NSLog(@"%@", view); 直接輸出 UIView 即可, 不用自己笨笨的輸出 frame 之類的資訊。 輸出 view 的巢狀結構NSLog(@"%@", [view recursiveDescription]); [UIView recursiveDescription] 是 private method, 不過 Objective-C 有 dynamic typing 的特性, 真的要呼叫的話, 系統也不會擋你, 只是編譯時有個 warning 而已。這功能相當實用, 可以確保相關的 view 是否有加進去, 了解 sub views 之間的順序 (後面的 view 會蓋掉前面的 view)。 在多個關鍵處輸出 recursive description 後, 才發覺我在某處不小心刪掉某個 view 沒加回去。若沒有這項功能, 執行後只知道沒出現這個 view, 到底是放錯位置、沒放進去或被覆蓋掉, 完全是一無所知啊。 畫出 view 的外框view.layer.borderColor = [UIColor redColor].CGColor; view.layer.borderWidth = 1.0; 使用上述程式的前置動作為: linking 加入 QuartzCore.framework 程式加上 #import <QuartzCore/QuartzCore.h>2014-07-23 更新[UIView recursiveDescription] 會顯示本身整層的結構, 若想看更上層的結構, 要往上存取 superview。但在 lldb 中打 po view.superview.superview 會失敗, 原因是 view.superview 傳回 id, 而 id 沒有型別, 無法直接呼叫 superview。所以要寫成 po ((UIView*)view.superview).superview。一但取得目標 UIView 的位置後 (例如 0x12345678), 再來就可以用 po [(UIView*)0x12…

High Performance Browser Networking ch5, ch6 心得

Chapter 5. Introduction to Wireless Networkshttp://chimera.labs.oreilly.com/books/1230000000545/ch05.html 影響傳輸頻寬的項目: C=BW×log2(1+S/N) C is the channel capacity, and is measured in bits per second BW is the available bandwidth, and is measured in hertz S is signal and N is noise, and are measured in watts 訊號的強弱就像聲音一樣: 若環境音很吵, 很難聽清楚對方說的話。 離愈遠愈不清楚。 頻段受法律影響, 不是想用那一段就可以用那一段。Chapter 6. Wi-Fihttp://chimera.labs.oreilly.com/books/1230000000545/ch06.html 802.11 (Wi-Fi) 基於 802.3 (Ethernet) 的修改而來的 Ethernet 的作法是 "先聽一聽, 確定沒人說話之後再說話"。若有人同時想說話, 各自隨機休息一段時間, 再重試一次。 Wi-Fi 的作法和 Ethernet 相似, 只是受到無線通訊硬體的限制, 送資料的時候無法偵測是否有人在說話, 所以要由接受者回應 ACK 得知是否傳送成功。 這個基於 Ethernet 而來的通訊協定只適用於低負擔的無線環境, 反之會很慘。 Wi-Fi 有重新傳輸 packet 的機制, TCP packet lost 比例通常不會因 physical layer 使用 Ehternet 或 Wi-Fi 而有差別, 但是使用 Wi-Fi 時很可能有較高且不同的延遲時間。 Wi-Fi 有分 2.4GHz 和 5GHz 兩個不同頻段區間。各區間有數個 channel, 若使用者使用不同 channel, 傳輸的訊號就不會打架, 這是提升傳輸品質的最好方法。這裡有 802.11b 不同 channel 對應到不同頻段的示意圖。使用 2.4GHz 的 Wi-Fi 協定時, 選用 1、6、11 最適當, 可減少和其它 channel 相衝。 5GHz 有比較多…