天天看點

iOS開發系列—Objective-C之Foundation架構概述Foundation概述常用結構體日期字元串數組字典裝箱和拆箱反射拷貝檔案操作歸檔

我們前面的章節中就一直建立cocoa class,那麼cocoa到底是什麼,它和我們前面以及後面要講的内容到底有什麼關系呢?objective-c開發中經常用到nsobject,那麼這個對象到底是誰?它為什麼又出現在objective-c中間呢?今天我們将揭開這層面紗,重點分析在ios開發中一個重要的架構foundation,今天的主要内容有:

<a href="http://www.cnblogs.com/kenshincui/p/3885689.html#foundation">foundation概述</a>

<a href="http://www.cnblogs.com/kenshincui/p/3885689.html#struct">常用結構體</a>

<a href="http://www.cnblogs.com/kenshincui/p/3885689.html#date">日期</a>

<a href="http://www.cnblogs.com/kenshincui/p/3885689.html#string">字元串</a>

<a href="http://www.cnblogs.com/kenshincui/p/3885689.html#array">數組</a>

<a href="http://www.cnblogs.com/kenshincui/p/3885689.html#dictionary">字典</a>

<a href="http://www.cnblogs.com/kenshincui/p/3885689.html#boxing">裝箱和拆箱</a>

<a href="http://www.cnblogs.com/kenshincui/p/3885689.html#reflector">反射</a>

<a href="http://www.cnblogs.com/kenshincui/p/3885689.html#copy">拷貝</a>

<a href="http://www.cnblogs.com/kenshincui/p/3885689.html#io">檔案操作</a>

<a href="http://www.cnblogs.com/kenshincui/p/3885689.html#archiver">歸檔</a>

為什麼前面說的内容中建立一個類的時候我們都是選擇cocoa class呢?cocoa是什麼呢?

cocoa不是一種程式設計語言(它可以運作多種程式設計語言),它也不是一個開發工具(通過指令行我們仍然可以開發cocoa程式),它是建立mac os x和ios程式的原生面向對象api,為這兩者應用提供了程式設計環境。

我們通常稱為“cocoa架構”,事實上cocoa本身是一個架構的集合,它包含了衆多子架構,其中最重要的要數“foundation”和“uikit”。前者是架構的基礎,和界面無關,其中包含了大量常用的api;後者是基礎的ui類庫,以後我們在ios開發中會經常用到。這兩個架構在系統中的位置如下圖:

iOS開發系列—Objective-C之Foundation架構概述Foundation概述常用結構體日期字元串數組字典裝箱和拆箱反射拷貝檔案操作歸檔

其實所有的mac os x和ios程式都是由大量的對象構成,而這些對象的根對象都是nsobject,nsobject就處在foundation架構之中,具體的類結構如下:

iOS開發系列—Objective-C之Foundation架構概述Foundation概述常用結構體日期字元串數組字典裝箱和拆箱反射拷貝檔案操作歸檔
iOS開發系列—Objective-C之Foundation架構概述Foundation概述常用結構體日期字元串數組字典裝箱和拆箱反射拷貝檔案操作歸檔
iOS開發系列—Objective-C之Foundation架構概述Foundation概述常用結構體日期字元串數組字典裝箱和拆箱反射拷貝檔案操作歸檔

通常我們會将他們分為幾類:

值對象

集合

作業系統服務:檔案系統、url、程序通訊

通知

歸檔和序列化

表達式和條件判斷

objective-c語言服務

uikit主要用于界面構架,這裡我們不妨也看一下它的類結構:

iOS開發系列—Objective-C之Foundation架構概述Foundation概述常用結構體日期字元串數組字典裝箱和拆箱反射拷貝檔案操作歸檔

在foundation中定義了很多常用結構體類型來簡化我們的日常開發,這些結構體完全采用objective-c定義,和我們自己定義的結構體沒有任何差別,之是以由架構為我們提供完全是為了簡化我們的開發。常用的結構體有nsrange、nspoint、nssize、nsrect等

可以看到對于常用結構體在foundation架構中都有一個對應的make方法進行建立,這也是我們日後比較常用的操作;而且與之對應的還都有一個nsstringfromxx方法來進行字元串轉換,友善我們調試。上面也提到nssize其實就是cgsize,nsrect其實就是cgrect,我們可以通過檢視代碼進行确認,例如nssize定義:

iOS開發系列—Objective-C之Foundation架構概述Foundation概述常用結構體日期字元串數組字典裝箱和拆箱反射拷貝檔案操作歸檔

繼續檢視cgsize的代碼:

iOS開發系列—Objective-C之Foundation架構概述Foundation概述常用結構體日期字元串數組字典裝箱和拆箱反射拷貝檔案操作歸檔

接下來熟悉一下foundation架構中日期的操作

在objc中字元串操作要比在c語言中簡單的多,在下面的例子中你将看到字元串的初始化、大小寫轉化、字尾字首判斷、字元串比較、字元串截取、字元串轉換等,通過下面的例子我們基本可以掌握常用的字元串操作(注意這些内容雖然基本,但卻是十分常用的操作,需要牢記):

注意:上面代碼注釋中提到的需要釋放記憶體指的是在mrc下的情況,當然本質上在arc下也需要釋放,隻是這部分代碼編譯器會自動建立。

在objc中路徑、檔案讀寫等操作是利用字元串來完成的,這裡通過幾個簡單的例子來示範(首先在桌面上建立一個test.txt檔案,裡面存儲的内容是”hello world,世界你好!”)

注意:在上面的例子中我們用到了可變數組,下面會專門介紹。

我們知道在字元串操作過程中我們經常希望改變原來的字元串,當然這在c語言中實作比較複雜,但是objc為我們提供了新的可變字元串類nsmutablestring,它是nsstring的子類。

下面将示範常用的數組操作:初始化、數組對象的方法執行、數組元素的周遊、在原有數組基礎上産生新數組、數組排序等

需要注意幾點:

nsarray中隻能存放對象,不能存放基本資料類型,通常我們可以通過在基本資料類型前加@進行轉換;

數組中的元素後面必須加nil以表示資料結束;

makeobjectsperformselector執行數組中對象的方法,其參數最多隻能有一個;

上面數組操作中無論是數組的追加、删除、截取都沒有改變原來的數組,隻是産生了新的數組而已;

對象的比較除了使用系統自帶的方法,我們可以通過自定義比較器的方法來實作;

下面看一下可變數組的内容:

可變數組中的元素後面必須加nil以表示資料結束;

往一個可變數組中添加一個對象,此時這個對象的引用計數器會加1,當這個對象從可變數組中移除其引用計數器減1。同時當整個數組銷毀之後會依次調用每個對象的releaes方法。

在不可變數組中無論對數組怎麼排序,原來的數組順序都不會改變,但是在可變數組中如果使用sortusingselector:排序原來的數組順序就發生了變化。

字典在我們日常開發中也是比較常用的,通過下面的代碼我們看一下在objc中的字典的常用操作:初始化、周遊、排序

注意:同數組一樣,不管是可變字典還是不可變字典初始化元素後面必須加上nil以表示結束。

其實從上面的例子中我們也可以看到,數組和字典中隻能存儲對象類型,其他基本類型和結構體是沒有辦法放到數組和字典中的,當然你也是無法給它們發送消息的(也就是說有些nsobject的方法是無法調用的),這個時候通常會用到裝箱(boxing)和拆箱(unboxing)。其實各種進階語言基本上都有裝箱和拆箱的過程,例如c#中我們将基本資料類型轉化為object就是一個裝箱的過程,将這個object對象轉換為基本資料類型的過程就是拆箱,而且在c#中裝箱的過程可以自動完成,基本資料類型可以直接指派給object對象。但是在objc中裝箱的過程必須手動實作,objc不支援自動裝箱。

在objc中我們一般将基本資料類型裝箱成nsnumber類型(當然它也是nsobject的子類,但是nsnumber不能對結構體裝箱),調用其對應的方法進行轉換:

+(nsnumber *)numberwithchar:(char)value;

+(nsnumber *)numberwithint:(int)value;

+(nsnumber *)numberwithfloat:(float)value;

+(nsnumber *)numberwithdouble:(double)value;

+(nsnumber *)numberwithbool:(bool)value;

+(nsnumber *)numberwithinteger:(nsinteger)value;

拆箱的過程就更加簡單了,可以調用如下方法:

-(char)charvalue;

-(int)intvalue;

-(float)floatvalue;

-(double)doublevalue;

-(bool)boolvalue;

簡單看一個例子

上面我們看到了基本資料類型的裝箱和拆箱過程,那麼結構體呢?這個時候我們需要引入另外一個類型nsvalue,其實上面的nsnumber就是nsvalue的子類,它包裝了一些基本資料類型的常用裝箱、拆箱方法,當要對結構體進行裝箱、拆箱操作我們需要使用nsvalue,nsvalue可以對任何資料類型進行裝箱、拆箱操作。

事實上對于常用的結構體foundation已經為我們提供好了具體的裝箱方法:

+(nsvalue *)valuewithpoint:(nspoint)point;

+(nsvalue *)valuewithsize:(nssize)size;

+(nsvalue *)valuewithrect:(nsrect)rect;

對應的拆箱方法:

-(nspoint)pointvalue;

-(nssize)sizevalue;

-(nsrect)rectvalue;

那麼如果是我們自定義的結構體類型呢,這個時候我們需要使用nsvalue如下方法進行裝箱:

+(nsvalue *)valuewithbytes:(const void *)value objctype:(const char *)type;

調用下面的方法進行拆箱:

-(void)getvalue:(void *)value;

通過前面的介紹大家都知道無論在數組還是在字典中都必須以nil結尾,否則數組或字典無法判斷是否這個數組或字典已經結束(與c語言中的字元串比較類似,c語言中定義字元串後面必須加一個”\0”)。但是我們有時候确實想在資料或字典中存儲nil值而不是作為結束标記怎麼辦呢?這個時候需要使用nsnull,這個類是一個單例,隻有一個null方法。簡單看一下:

我們知道在objc中很多關鍵字前都必須加上@符号,例如@protocol、@property等,當然objc中的字元串必須使用@符号,還有就是%@可以表示輸出一個對象。其實@符号在新版的objc中還有一個作用:裝箱。

相信聰明的童鞋在前面的例子中已經看到了,這裡簡單的介紹一下(在下面的示範中你也将看到很多objc新特性)。

由于objc動态性,在objc中實作反射可以說是相當簡單,下面代碼中示範了常用的反射操作,具體作用也都在代碼中進行了注釋說明:

account.h

account.m

person.h

person.m

main.m

對象拷貝操作也比較常見,在objc中有兩種方式的拷貝:copy和mutablecopy,這兩種方式都将産生一個新的對象,隻是後者産生的是一個可變對象。在objc中如果要想實作copy或者mutablecopy操作需要實作nscopy或者nsmutablecopy協定,拷貝操作産生的新的對象預設引用計數器是1,在非arc模式下我們應該對這個對象進行記憶體管理。在熟悉這兩種操作之前我們首先需要弄清兩個概念:深複制(或深拷貝)和淺複制(或淺拷貝)。

淺複制:在執行複制操作時,對于對象中每一層(對象中包含的對象,例如說屬性是某個對象類型)複制都是指針複制(如果從引用計數器角度出發,那麼每層對象的引用計數器都會加1)。

深複制:在執行複制操作時,至少有一個對象的複制是對象内容複制(如果從引用計數器角度出發,那麼除了對象内容複制的那個對象的引用計數器不變,其他指針複制的對象其引用計數器都會加1)。

注: 指針拷貝:拷貝的是指針本身(也就是具體對象的位址)而不是指向的對象内容本身。 對象複制:對象複制指的是複制内容是對象本身而不是對象的位址。 完全複制:上面說了深複制和淺複制,既然深複制是至少一個對象複制是對象内容複制,那麼如果所有複制都是對象内容複制那麼這個複制就叫完全複制。

對比copy和mutablecopy其實前面我們一直還用到一個操作是retain,它們之間的關系如下:

retain:始終采取淺複制,引用計數器會加1,傳回的對象和被複制對象是同一個對象1(也就是說這個對象的引用多了一個,或者說是指向這個對象的指針多了一個);

copy:對于不可變對象copy采用的是淺複制,引用計數器加1(其實這是編譯器進行了優化,既然原來的對象不可變,複制之後的對象也不可變那麼就沒有必要再重新建立一個對象了);對于可變對象copy采用的是深複制,引用計數器不變(原來的對象是可變,現在要産生一個不可變的當然得重新産生一個對象);

mutablecopy:無論是可變對象還是不可變對象采取的都是深複制,引用計數器不變(如果從一個不可變對象産生一個可變對象自然不用說兩個對象絕對不一樣肯定是深複制;如果從一個可變對象産生出另一個可變對象,那麼當其中一個對象改變自然不希望另一個對象改變,當然也是深複制)。

可變對象:當值發生了改變,那麼位址也随之發生改變; 不可變對象:當值發生了改變,内容首位址不發生變化; 引用計數器:用于計算一個對象有幾個指針在引用(有幾個指針變量指向同一個記憶體位址);

為了友善大家了解上面的代碼,這裡以圖形畫出str1、str2、str3、str4在記憶體中的存儲情況:

iOS開發系列—Objective-C之Foundation架構概述Foundation概述常用結構體日期字元串數組字典裝箱和拆箱反射拷貝檔案操作歸檔

從上面可以清楚的看到str1和str3同時指向同一個對象,是以這個對象的引用計數器是2(可以看到兩箭頭指向那個對象),str2和str4都是兩個新的對象;另外objc引入對象拷貝是為了改變一個對象不影響另一個對象,但是我們知道nsstring本身就不能改變那麼即使我重新複制一個對象也沒有任何意義,是以為了性能着想如果通過copy方法産生一個nsstring時objc不會再複制一個對象而是将新變量指向同一個對象。

注意網上很多人支招在arc模式下可以利用_objc_rootretaincount()或者cfgetretaincount()取得retaincount都是不準确的,特别是在對象拷貝操作之後你會發現二者取值也是不同的,是以如果大家要檢視retaincount最好還是暫時關閉arc。

要想支援copy或者mutablecopy操作那麼對象必須實作nscoping協定并實作-(id)copywithzone:(nszone*)zone方法,在foundation中常用的可複制對象有:nsnumber、nsstring、nsmutablestring、nsarray、nsmutablearray、nsdictionary、nsmutabledictionary。下面看一下如何讓自定義的類支援copy操作:

在上面的代碼中重點說一下test2這個方法,在test2方法中我們發現當修改了person2.name屬性之後person1.name也改變了,這是為什麼呢?我們可以看到在person.m中自定義實作了copy方法,同時實作了一個淺拷貝。之是以說是淺拷貝主要是因為我們的name屬性參數是直接指派完成的,同時由于name屬性定義時采用的是assign參數(預設為assign),是以當通過copy建立了person2之後其實person2對象的name屬性和person1指向同一個nsmutablestring對象。通過圖形表示如下:

iOS開發系列—Objective-C之Foundation架構概述Foundation概述常用結構體日期字元串數組字典裝箱和拆箱反射拷貝檔案操作歸檔

上面test2的寫法純屬為了讓大家了解複制的原理和本質,實際開發中我們很少會遇到這種情況,首先我們一般定義name的話可能用的是nsstring類型,根本也不能修改;其次我們定義字元串類型的話一般使用(copy)參數,同樣可以避免這個問題(因為nsmutablestring的copy是深複制)。那麼如果我們非要使用nsmutabestring同時不使用屬性的copy參數如何解決這個問題呢?答案就是使用深複制,将-(id)copywithzone:(nszone *)zone方法中person1.name=_name改為,person1.name=[_name copy];或person1.name=[_name mutablecopy]即可,這樣做也正好滿足我們上面對于深複制的定義。

在好多語言中字元串都是一個特殊的對象,在objc中也不例外。nsstring作為一個對象類型存儲在堆中,多數情況下它跟一般的對象類型沒有差別,但是這裡我們需求強調一點那就是字元串的引用計數器。

看完上面的例子如果不了解nsstring的處理你也許會有點奇怪(注意上面的代碼請在xcode5下運作)?請看下面的解釋

str1是一個字元串常量,它存儲在常量區,系統不會對它進行引用計數,是以無論是初始化還是做retain操作其引用計數器均為-1;

str3、str4、str5建立的對象同一般對象類似,存儲在堆中,系統會對其進行引用計數;

采用stringwithstring定義的變量有些特殊,當後面的字元串是字元串常量,則它本身就作為字元串常用量存儲(str2),類似于str1;如果後面的參數是通過類似于str3、str4、str5的定義,那麼它本身就是一個普通對象,隻是後面的對象引用計數器預設為1,當給它指派時會做一次拷貝操作(淺拷貝),引用計數器加1,所有str2_1引用計數器為2;

str6其實和str1類似,雖然定義的是可變數組,但是它的本質還是字元串常量,事實上對于可變字元串隻有為字元串常量時引用計數器才為-1,其他情況它的引用計數器跟一般對象完全一緻;

後記:注意上面這段代碼的運作結果是在xcode5中運作的結果,事實上針對最新的xcode6由于llvm的優化,隻有str2_1和str7的引用計數器為1(str7 retain一次後第二次為2),其他均為-1。

在今天的最後一節内容中讓我們看一下foundation中檔案操作,下面将以一個例子進行說明:

歸檔,在其他語言中又叫“序列化”,就是将對象儲存到硬碟;解檔,在其他語言又叫“反序列化”就是将硬碟檔案還原成對象。其實歸檔就是資料存儲的過程,在ios中資料的存儲有五種方式:

xml屬性清單(plist歸檔)

nsuserdefaults(偏好設定)

nskeyedarchiver歸檔(加密形式)

sqlite3(嵌入式資料庫)

core data(面向對象方式的嵌入式資料庫)

當然關于2、4、5點不是我們今天介紹的重點,這個在ios開發過程中我們會重點說到。

首先我們先來看一下xml屬性清單,xml屬性清單進行歸檔的方式是将對象存儲在一個plist檔案中,這個操作起來比較簡單,其實相當于xml序列化。但是同時它也有缺點:一是這種方式是明文儲存的;二是這種方式操作的對象有限,隻有nsarray、nsmutablearray、nsdictionary、nsmutabledictionary支援(歸檔時隻要調用對應的writetofile方法即可,解檔調用arraywithcontentsoffile或dictionarywithcontentsoffile,注意像nsstring、nsnumber、nsdata即使有這個方法它存儲的也不是xml格式)。

生成的檔案如下

iOS開發系列—Objective-C之Foundation架構概述Foundation概述常用結構體日期字元串數組字典裝箱和拆箱反射拷貝檔案操作歸檔
iOS開發系列—Objective-C之Foundation架構概述Foundation概述常用結構體日期字元串數組字典裝箱和拆箱反射拷貝檔案操作歸檔

如果要針對更多對象歸檔或者需要歸檔時能夠加密的話就需要使用nskeyedarchiver進行歸檔和解檔,使用這種方式歸檔的範圍更廣而且歸檔内容是密文存儲。從歸檔範圍來講nskeyedarchiver适合所有objc對象,但是對于自定義對象我們需要實作nscoding協定;從歸檔方式來講nskeyedarchiver分為簡單歸檔和複雜對象歸檔,簡單歸檔就是針對單個對象可以直接将對象作為根對象(不用設定key),複雜對象就是針對多個對象,存儲時不同對象需要設定不同的key。

首先看一下系統對象兩種歸檔方式(注意由于本章主要介紹foundation内容,下面的程式是os x指令行程式并沒有建立成ios應用,如果移植到到ios應用下運作将nsarchiver和nsunarchiver換成nskeyedarchiver和nskeyedunarchiver。雖然在foundation部分ios和os x在設計上盡可能通用但是還存在着細微差别。)

接下來看一下自定義的對象如何歸檔,上面說了如果要對自定義對象進行歸檔那麼這個對象必須實作nscoding協定,在這個協定中有兩個方法都必須實作:

-(void)encodewithcoder:(nscoder *)acoder;通過給定的archiver對消息接收者進行編碼;

-(id)initwithcoder:(nscoder *)adecoder;從一個給定的unarchiver的資料傳回一個初始化對象;

這兩個方法分别在歸檔和解檔時調用。下面通過一個例子進行示範(注意對于自定義類的多對象歸檔與系統類多對象歸檔完全一樣,代碼中不再示範):

今天的文章就到這裡了,内容确實不少,但是要用一篇文章把foundation的所有内容說完也是完全不現實的。這篇文章有一部分内容并沒有詳細的解釋代碼,這部分内容主要是個人認為比較簡單,通過注釋大家就可以了解。不過并不是說這部分内容不重要,而是這些内容多數是記憶性的東西,不需要過多解釋。

繼續閱讀