2013年7月27日 星期六

vim 快速開啟目錄下的檔案

用過 XCode 的 "shift+command+o" 快速開啟檔案後, 一直很想在 vim 內用同樣的功能。按個快速鍵會跳出一個輸入框, 接著只要打部份字串, 就會濾出有可能的檔案。

看到 DKcmd 推薦用 ctrlp, 就來試用一下。試用後感覺相當不錯, 用法如下:

  1. 在 command mode 打 ctrl+p
  2. 打檔名
  3. 選定檔案, 按 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 似乎有濾掉不能開的檔案, 像是目錄或圖檔之類的。

2013年7月20日 星期六

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<std::basic_string<char, std::char_traits<char>, std::allocator<char> > const, std
::basic_string<char, std::char_traits<char>, std::allocator<char> > > > >> = {<No data fields>}, <No data fields>}, _M_key_compare = {<std::binary_function<std::basic_string<char, std
::char_traits<char>, std::allocator<char> >, std::basic_string<char, std::char_traits<char>, std::allocator<char> >, bool>> = {<No data fields>}, <No data fields>}, _M_header = {_M_co
lor = std::_S_red, _M_parent = 0x606080, _M_left = 0x606080, _M_right = 0x606120}, _M_node_count = 2}}}

解法一: 土法煉鋼

參考這篇的作法, 先取得目標位址再取值:

(gdb) p numbers
$5 = {<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 = 0x60601
0, _M_finish = 0x606020, _M_end_of_storage = 0x606020}}, <No data fields>}
(gdb) ptype numbers._M_impl
type = struct std::_Vector_base<int, std::allocator<int> >::_Vector_impl
    : public std::allocator<int> {
    std::allocator<int>::pointer _M_start;
    std::allocator<int>::pointer _M_finish;
    std::allocator<int>::pointer _M_end_of_storage;
  public:
    void _Vector_impl(void);
    void _Vector_impl(
    const std::_Vector_base<int, std::allocator<int> >::_Vector_impl::_Tp_alloc_type &);
}
(gdb) ptype numbers._M_impl._M_start
type = int *
(gdb) p numbers._M_impl._M_start[1]
$7 = 3
(gdb) p *(numbers._M_impl._M_start)@numbers.size()
$12 = {0, 3, 6, 9}

先用 ptype 逐步了解各欄位的型別, 繼而找到儲存資料的欄位 numbers._M_impl._M_start。 由於 _M_start 是 int*, 倒數第二個指令直接取出 numbers[1] 的值, 最後一個指令則是用 print P@N 印出從位置 P 開始 N 筆資料。

嫌麻煩的話, 可以定義新的 gdb command:

(gdb) define pv
Type commands for definition of "pv".
End with a line saying just "end".
>if $argc == 1
 >p *($arg0._M_impl._M_start)@$arg0.size()
 >end
>if $argc == 2
 >p $arg0._M_impl._M_start[$arg1]
 >end
>end
(gdb) pv numbers
$21 = {0, 3, 6, 9}
(gdb) pv numbers 1
$22 = 3

將上述的 define pv ... end 寫入 /.gdbinit, 之後就不用重寫一次。

解法二: 使用別人寫好的 pretty-printers

土法煉鋼的目的是讓我們有辦法應付日後各種需求, 但是針對 STL 這種大家都有的需求, 已經有善心人士提供完整的套件了, 見 STLSupport - GDB Wiki 的第一點。

摘要作法如下:

$ cd /path/to/gdb_script/
$ svn co svn://gcc.gnu.org/svn/gcc/trunk/libstdc++-v3/python
$ cat <<EOF >> ~/.gdbinit
python
import sys
sys.path.insert(0, '/path/to/gdb_script/python')
from libstdcxx.v6.printers import register_libstdcxx_printers
register_libstdcxx_printers (None)
end
EOF

載入 pretty-printers 後, 輸出變數的結果如下:

(gdb) p numbers
$1 = std::vector of length 4, capacity 4 = {0, 3, 6, 9}
(gdb) p contacts
$2 = std::map with 2 elements = {["john"] = "0987-654321", ["mary"] = ""}

有需要的時候, 也可以參考 python/libstdcxx/v6/printers.py 學習如何用 python 寫 gdb script

trace C/C++ function call 的方法

目的

方法一: 使用 gcc 參數 -finstrument-functions

參考資料:

優點:

  • 自動含蓋所有函式。

缺點:

  • 可能增加太多執行負擔, 雖然可以配合 -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()
  {
    MyLogger::Instance()->LeaveFunctionCall(m_file, m_line, m_func);
  }

private:
  const char *m_file;
  int m_line;
  const char *m_func;
};

MyLogger 可以做的事很多, 比方說:

  • 用 file 做為 tag 分類輸出。
  • 在 EnterFunctionCall() 和 LeaveFunctionCall() 記錄層級, 輸出 function call 時, 視 function call 的深度做對應的縮排。
  • 在 EnterFunctionCall() 和 LeaveFunctionCall() 記錄目前時間, 可在 LeaveFunctionCall() 時計算執行時間, 協助 profiling。

若 MyLogger 會被多個 thread 使用, 別忘了將 MyLogger 寫成 thread-safe, 不過以觀察或除錯的角度來說, 偷懶不作應該也OK吧。

若目標模組是純 C 的程式, 可用 gcc 的 cleanup attribute 做出 RAII 的效果, 達到和 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 判斷。

2013年7月19日 星期五

在 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 standardUserDefaults] setValue:@"1strun" forKey:@"FirstRun"];
    }

    //...Other stuff that usually happens in didFinishLaunching
}

參考資料:

2013年7月18日 星期四

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 檔, 會作以下的事:

  1. 用 gdb 載入 binary 的 symbol, 並存下 symbol index 到暫存檔
  2. 加入 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
結果只有第三種會中獎, 所以現在改回用第二種方法了。

2013年7月17日 星期三

Objective-C 的 scope 以及 property

今天要用又忘了, 寫下來備忘一下。

scope

instance variable 有 scope, method 似乎沒有 scope。畢竟是 dynamic typing, method 加上 scope 也滿奇怪的。

instance variable 的 scope:

  • 分為 @public, @protected, @private 和 @package (看不懂 @package, 要用到再研究, 我猜是指 scope 在「函式庫」內)
  • @interface 預設 scope 是 @protected
  • @implementation 預設 scope 是 @private

property

宣告 @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 開發的需求。

2013年7月15日 星期一

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 檔

2013年7月14日 星期日

使用 iOS notification 的好處

先前在《iOS delegation vs. notification》提到我偏好 delegation, 這篇稍微修正一下對兩者的觀點。

開發 iOS 和 Android 以前我只有寫 Web 的經驗, 沒有寫圖形應用程式的經驗 (硬漢工程師就是要用 CLI 啊!!)。之前只覺得開發 Web 很容易上手, 不過跨瀏覽器很煩。最近多了一些 iOS 開發經驗, 才體會到為什麼 Web 這麼好寫, 個人認為的原因有二:

換句話說, 寫網頁可以輕鬆得知 model 狀態變化, 得知變化時還可以輕鬆地改變任何相關的 UI 元件。

反觀寫圖形應用程式時, 離上述兩點相當地遙遠, 寫起來綁手綁腳的。或許是出於類似的需求, iOS 有提供 NSNotificationCenter 協助 observe 和 dispatch event。

於是, 透過 NSNotificationCenter 這個全域變數, 我們可以

如此一來, 減輕不少實作負擔。不過如同 When to use Delegation, Notification, or Observation in iOS 所言, notification 過於彈性會造成一些問題。個人認為配合一些規範, 像是使用 header 檔定義 notification name 和 user info 的 key, 不要直接用 string, 應該是不錯的選擇。

備註:

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::OnSaving();  // NEW

UI 不會漏掉這個變化。即使有多個 UI 元件關心 Document 是否正在存檔、存檔成功或失敗, 也可透過 DocumentDelegate 讓這些 UI 元件和 Document 相依, 而不是某個 UI 元件通知其它 UI 元件, 減輕 UI 元件之間的相依性。

總結來說, 對於所有的非同步操作, model 至少需要告知下述三種狀態變化:

  • 正在執行中
  • 執行成功
  • 執行失敗

這樣 UI 才有能力完成必要的功能。即使 UI 用不到也無所謂, 實作 model 時原本就需要考慮好可能的狀態變化, 提供對應的 callback 只是舉手之勞。反之, 實作 UI 時發覺綁手綁腳, 要再回頭思考該改 model 還是 UI (或是偷懶不做 ...), 會比較辛苦。

2013年7月8日 星期一

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*)0x12345678 recursiveDescription] 得到目標的結構。
  • 承上, 更方便的作法是在 app 產生 UIWindow 的時候設中斷點, 取得 UIWindow 的位置 (例如 0x87654321), 之後隨時想看目前整個畫面的結構, 就按 XCode 的「暫停鍵」中斷程式進入 lldb, 再用 [(UIView*)0x87654321 recursiveDescription] 取得目前畫面的 UIView 結構。很快地了解 UIView 結構後, 要了解程式行為就容易多了。而且從 recursiveDescription 得知所有 subview 的位置後, 也可以配合轉型直接輸出各個 view 的其它屬性, 像是 [(UIScrollView*)0x12341234 zoomScale]。

2013年7月7日 星期日

High Performance Browser Networking ch5, ch6 心得

Chapter 5. Introduction to Wireless Networks

http://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-Fi

http://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 有比較多 channel, 因此不容易和別人相衝。但是頻率愈高, 使用範圍愈窄。此外, 較舊的手機 (如 iPhone 4S 以前的 iPhone 機種) 只支援 2.4GHz。
  • 總之, Wi-Fi 沒有保證傳輸的頻寬以及延遲時間。
  • 可用 inSSIDer 觀察週圍的無線品質, Android、Windows、Mac OS X 上有軟體可用, Android 上有免費軟體。
  • 開發應用程式時, 可考慮監控過去的流量變化, 決定傳輸內容的品質。不過也要留意監控的結果是否夠即時、穩定, 若變化過大的話, 動態內容的品質有可能反而幫倒忙。

C++ 能否用 memcpy 複製 class / struct 的資料?

答案是: POD (plain old data) type 可以。POD type 可和 C 互通, CPP Reference POD Type 的介紹: Specifies that the type is POD (Plain Old Data) type. Thi...