1. 程式人生 > >iOS程式設計遇到的一些難點問題總結

iOS程式設計遇到的一些難點問題總結

最近做的一個專案,雖然不大,但剛開始接觸iOS程式設計,遇到和克服的問題還不少,記錄下來,溫習一下,或者對別人也會有一點幫助。

這個專案採用的是swift和objective C,CPP混合程式設計的方式,有些模組之前在別的平臺已經實現了,想直接拿過來用。另一方面是對swift不太熟悉,而且版本變化太快,很多時候按照教程例子來做,結果還是錯誤。感覺objective c相對穩定一點,所以主體還是以OC為主。

1. swift語言裡面的?和!問題

有幫助的網頁,網上查了很多,各說紛雲,看得一頭霧水,還好一個網頁講得算是明白一點。

http://joeyio.com/ios/2014/06/04/swift---/

補充一點我個人的理解,swift語言看起來簡單優雅,實際上裡面藏了很多玄機。之所以要用?和!,目的是為了讓程式碼更明確,同時給編譯器提供更多線索,發現更多潛在錯誤。它是Apple發行的語言,很符合Apple的性格,它要你把事情弄清楚再寫程式碼,而不是把模糊的問題丟給編譯器來做決定。

2. table view的邏輯

由於iOS系統是不開源的,因此我們要實現表格功能必須安裝系統預定好的套路來走。有幾個關鍵點,第一個是它的2個代理,其中一個代理負責提供資料,所謂提供資料,就是2個最主要的介面函式,第一個是告訴系統表格有多少行,第二個就是告訴系統每一行裡面的資料是什麼內容。第二個會被多次呼叫,假設一個頁面裡面有10行,那麼這個介面函式就會被呼叫10次。但不會超出一個頁面的次數,因為系統只會請求當前使用者看到的頁面的資料。假如表格的資料有100個,那麼它是通過不斷滾動的過程中丟棄舊的,填充新的資料這種方式來實現的。

這是關於view的一個代理,另外一個代理是關於controller的,就是當用戶點中表格的某一項時,程式要做些什麼,這裡面最常用的就是didSelected介面函式,開發者只需要在這裡面寫自己的實現程式碼即可。要注意不要寫到didDeselected裡面,這裡面的函式名很容易搞混,這個Deselected是某一項從選中變成不被選中時呼叫的介面。一開始我寫到這個裡面,總感覺哪裡不對,後來才發現。

使用customer型別的prototype時,如何訪問裡面的label物件?tag的使用。

常見的表格例子,表格裡面的cell只有最多2個標籤,但我的專案裡要放入5個標籤,那麼如何在程式碼裡面操作這些標籤呢?由於介面是在IB裡面拉進去的,而cell裡面的標籤卻不能通過ctrl drag的方式拉進程式碼裡面。後來看到一個例子是給每個標籤定一個tag值,然後在程式碼裡面用viewByTag

UILabel *name = [cell viewWithTag:TAG_NAME];
    UILabel *singer = [cell viewWithTag:TAG_SINGER];
    UILabel *code = [cell viewWithTag:TAG_CODE];
    UILabel *lang = [cell viewWithTag:TAG_LANG];
    UILabel *type = [cell viewWithTag:TAG_TYPE];

通過這樣的方式就能獲取到對於的label。

後來偶然的機會了解到,還是能通過直接的方式訪問,但麻煩一點,就是要先把cell用一個class定義好,然後在interface裡面手工建立好label的宣告,這個時候就可以storyboard裡面拉線到table view裡面了。

http://stackoverflow.com/questions/10176312/connect-outlet-of-a-cell-prototype-in-a-storyboard

這個頁面裡面討論了這個問題。

表格的配置不當,很容易導致app崩潰,而且經常會看到:

在這個地方崩潰:AppDelegate: UIResponder, UIApplicationDelegate

debug顯示的資訊是:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[UINavigationItem tableView:numberOfRowsInSection:]: unrecognized selector sent to instance 0x7fd9d1760d70'
這個原因其實是我在storyboard裡面看到table view的data source沒有關聯,就自己把資料關聯到table上面了。很多時候這種崩潰都說由於這個關聯的datasource不對,可以先斷開來試試。按照我的理解,因為這底層是看不到的,只能猜,就是它關聯了這個,就把另一個釋放了,從而有用的那個物件找不到,造成系統崩潰。這就是為什麼很多資深玩家不喜歡用stroyboard,因為不好掌控,有些隱藏的東西不容易發現。而用程式碼來實現介面的構造雖然麻煩一點,但勝在夠明白,寫成什麼樣就是什麼樣。而我現階段還是要依賴圖形的工具,我體會圖形的一點不好的地方就是2個專案的storyboard不可比較,曾經有些例子,完全按照教程來一步一步做,可是死活不對勁,但下載老師做好的專案又是正常的,比對程式碼也沒有差別,差別就在storyboard,可是storyboard裡面的差異非常大,根本看不出端倪,而且把對方的檔案直接覆蓋自己的也錯誤百出。

總的來說,還是自己功夫不夠深。以後還要深入把基礎打好才行。

3. search bar的邏輯

以前的search bar和search display之類的物件是分開的,後來Apple提供了整合在一起的方案,而且還把搜尋的演算法也封裝成物件,期望可以簡化開發者的工作。可是對於我這種開發者來說是恰恰相反。反而是最簡單的search bar更適合我,因為我只需要在searchbar的內容發生改變的時候執行一次查詢,然後把表格重新整理一次就行了。而如果呼叫那些捆綁的display controller,則需要實現更多的協議,而且還要提供一個輸出結果顯示的table view controller,真的搞得我頭暈。Apple官方的資料裡面也提供了一個實現的例子,可是這個例子本身就很複雜,涉及到多個view controller。

http://www.hcios.com/archives/1143

這個頁面裡面提供的方式比較適合我。

https://developer.apple.com/library/prerelease/ios/samplecode/TableSearch_UISearchController/Introduction/Intro.html

這個是官方提供的例子,是Apple主導的做法,他們已經剪除了舊的那種做法,現在推薦使用ui search controller的方式,但是不太清楚這樣改的背景原因是什麼?感覺也沒有那麼好用啊。

4. 用IB來做介面還是用程式碼裡面實現?

用程式碼來設定控制元件的大小有可能在適配不同的螢幕時出現問題,暫時還沒有去研究auto resize的部分要怎樣做。

但用程式碼要做介面其實沒有想象中那麼難,其實只有把介面中的控制元件當成物件來處理就可以了,建立物件,設定物件的熟悉,把物件add到view裡面,設定相關的delegate,然後就OK了。

5. bundle檔案的訪問方式

這個專案需要訪問外部的一個bin檔案,從這個檔案裡面讀取資料,但iOS與android不同,它有一個sandbox的機制,應用程式只能訪問自己的空間。所以不能把bin檔案放在sdcard目錄,而是一個叫bundle的目錄。

這個bundle說白了就是在xcode介面左邊專案列表裡的內容,我們要往裡面加入bin檔案,方法就是把這個bin拖動進去,當拖進去後,會提示是否要copy,選擇是,還有要選擇add to app,否則程式碼裡裡面會訪問不了。把bin檔案拖進去之後,程式碼要如何訪問呢?

-(void)loadIndexFile{
    NSString *path = @"/MEGIDX.bin";
    NSString *filePath=[[NSString alloc] initWithFormat:@"%@%@",[[NSBundle mainBundle]resourcePath],path];
    
    [self readIndexFile: filePath];
}
這樣就可以獲取到目標檔案的路徑,然後就可以像一般的檔案操作那樣對它進行open,read, seek了。
但是bundle是隻讀的,如果有要寫如的檔案,則需要通過

NSString *filePath=[[NSString alloc]initWithFormat:@"%@/MEGIDX",NSHomeDirectory()];

6. 容易導致程式崩潰的原因

由於某些物件會被自動釋放,所以當某些物件不能被訪問到時,就會導致崩潰。解決的辦法是遮蔽掉一些地方,看問題是否仍然出現。有些物件如果只有指標,而沒有例項化,也會導致崩潰,特別是列表的array,需要用alloc init這樣的方式分配好空間,甚至要預先賦值,否則當程式需要訪問時會導致崩潰。

有些又IB建立的物件,也很容易因為配置不當而導致程式崩潰,比如說某個物件在IB裡刪除了,後來又新增一個同名的物件,這個時候它之前殘留的outlet會仍然連線到程式碼裡,但這個時候會有可能導致出錯,正確的方法是同時刪除全部重新做一次。

7. 對介面卡模式的理解

看了一個極客學院的視訊,對介面卡模式的講解比較好,老師舉的例子是實際生活中手機的電源介面卡,就是不管外界的電壓是220V還是110V,通過介面卡之後可以轉換成統一的5V電壓。這樣用在程式碼中,是為了降低程式間的耦合,使靈活性提高。當缺點就是可讀性降低,提高了學習成本,對於沒有這個概念的程式設計師來說可能根本看不懂設計者的用意所在。

8. 對segue的理解,新舊版本ios的變遷

segue是在storyboard裡面連線2個檢視控制器的那個箭頭,它起到承接2個物件之間切換的功能,它需要把前一個物件裡面的資訊傳遞到下一個物件去的這麼一個功能角色。

- (void)prepareForSegue:(UIStoryboardSegue *)segue
                 sender:(id)sender

這個方法用來在介面切換時執行一些程式碼。傳進來2個引數都是有用的。

9. 二進位制字串的處理,如何解決顯示亂碼的問題。

對於中文,需要定義一個NSStringEncoding物件,然後在init NSString的時候傳進來,

NSStringEncoding
if ([lang isEqualToString:@"CHINESE"] == true) {
            NSStringEncoding enc = CFStringConvertEncodingToNSStringEncoding(kCFStringEncodingGB_18030_2000);
            name = [[NSString alloc] initWithCString:songName encoding:enc];
            singer = [[NSString alloc] initWithCString:singerName encoding:enc];
        }else if([lang isEqualToString:@"VIETNAM"] || [lang isEqualToString:@"REMIX"]){
            unichar temUni[128];
            int len = (int)strlen(songName);
            len = StringUtils::CP1258ToUnicode((unsigned char *)songName, temUni, len);
            name = [[NSString alloc] initWithCharacters:temUni length:len];
            
            len = (int)strlen(singerName);
            len = StringUtils::CP1258ToUnicode((unsigned char*)singerName, temUni, len);
            singer = [[NSString alloc] initWithCharacters:temUni length:len];
        }
        else{
            name = [[NSString alloc] initWithCString:songName encoding:NSUTF8StringEncoding];
            singer = [[NSString alloc] initWithCString:singerName encoding:NSUTF8StringEncoding];
        }

對於越南文,則需要在底層轉換成unicode編碼,然後用initWithCharacters這個函式來給NSString初始化。

對於英文,則需要用NSUTF8StringEncoding來初始化字串。

總結,iOS系統內部以unicode為主,其他的編碼最終會被轉換成unicode處理。NSStringEncoding物件提供了一個函式入口給NSString的初始化方法,它其實是先用這個轉換函式把別的格式的編碼轉換成為iOS統一使用的unicode編碼,然後再初始化一次。

10. cpp程式碼移植到iOS專案遇到的問題,關於過載。

cpp支援同名的構造方法,而iOS匯入這樣的同名方法會報錯,解決的辦法是把另外的構造方法改一個名字。