重點掌握
3 類對象和方法
- 對象就是一個物體
- 類的獨特存在就是一個實例,對實例進行操作叫做方法。方法可以應用于類或者實例本身
- @interface(接口) 部分用于描述類和類的方法。也可以為類申明實例變量。
- implementation 部分用于描述數據(類對象的實例變量存儲的數據),并實現在接口中申明方法的實際代碼。也可以添加實例變量。
- program 部分的程序代碼實現了程序的預期目的 (main函數)
- “-”表示實例方法,“+”表示類方法。實例方法可以對類進行操作
- alloc [Fraction alloc] alloc方法保證對象的所有實例變量都變成初始化狀態,但是卻并不意味著對該對象進行了適當的初始化,從而在創建對象之后還要進行初始化 [Fraction init]
- init 方法用于初始化類的實例變量。也就是說myFraction= [Fraction init] 表示要在這里初始化一個特殊的Fraction對象,因此,它沒有發送給類,而是是發送給了類的實例。init方法也可以放回一個值,即被初始化的對象。將返回的值存到Fraction的變量myFraction中
- Fraction *myFraction;“ * ”表明myFraction是Fraction對象的引用(指針)。變量myFraction實際上并不儲存Fraction數據,只是存儲了一個引用(內存地址),表明對象數據在內存中的位置。Fraction *myFraction=[ [Fraction alloc] init] alloc返回存儲數據的位置,并賦給變量myFraction
- 實例方法總可以直接訪問它的實例變量,而類方法不行。因為類方法只處理本身,并不處理任何實例
- setter 設置實例變量的方法通常稱為設置方法。設置方法不會返回任何值。主要目的是將方法參數設為對應的實例變量的值,在這種情況下不需要返回值
- getter 檢索實例變量的方法稱為取值方法。 目的是獲取存儲在對象中的實例變量的值,并通過程序返回發送出去。因此取值方法必須返回實例的值作為return的參數
- accessor 設置方法和取值方法通常稱為訪問器
atomic
- atomic 的意思就是setter/getter這個函數,是一個原語操作。如果有多個線程同時調用setter的話,不會出現某一個線程執行完setter全部語句之前,另一個線程開始執行setter情況,相當于函數頭尾加了鎖一樣,可以保證數據的完整性。
- atomic是Objc使用的一種線程保護技術,基本上來講,是防止在寫未完成的時候被另外一個線程讀取,造成數據錯誤。而這種機制是耗費系統資源的,所以在iPhone這種小型設備上,如果沒有使用多線程間的通訊編程,那么nonatomic是一個非常好的選擇。
- 如果你不指定 nonatomic ,在自己管理內存的環境中,解析的訪問器保留并自動釋放返回的值,如果指定了 nonatomic ,那么訪問器只是簡單地返回這個值。
nonatomic
- 關于nonatomic是線程不安全的,當有多個線程同時修改屬性name的值的時候,拿到的結果可能不是自己想要的,因為當屬性設置nonatomic的話是允許多個線程是可以同時修改name的值。
4 數據類型和表達式
- retain(strong), copy 把一個對象賦值給一個屬性變量,當這個對象變化了,如果希望屬性變量變化就使用strong屬性,如果希望屬性變量不跟著變化,就是用copy屬性。把一個對象a賦值給一個屬性變量b,如果這個變量b的屬性為strong時,a和b的地址相同,也就是淺拷貝,所以它們是同一個對象。如果把對象a賦值給一個屬性為copy的變量c,此時c的地址和a不同,copy是深復制,產生了一個新對象,所以它們地址不同。
- 深拷貝copy,淺拷貝strong。兩者對內存計數的影響都是一樣的,都會增加內存引用計數,都需要在最后的時候做處理。深拷貝是產生一個新的對象(地址不同),淺拷貝不產生新對象(地址是一樣的)
什么時候使用copy
- 用于指針變量,setter方法進行copy操作,與retain處理流程一樣,先舊值release,再copy出新的對象,retainCount為1。這是為了減少對上下文的依賴而引入的機制。copy是在你不希望a和b共享一塊內存時會使用到。a和b各自有自己的內存。
- 用 @property 聲明 NSString、NSArray、NSDictionary 經常使用 copy 關鍵字,是因為他們有對應的可變類型:NSMutableString、NSMutableArray、NSMutableDictionary,他們之間可能進行賦值操作,為確保對象中的字符串值不會無意間變動,應該在設置新屬性值時拷貝一份。
/** 適用于集合類型(collection type)*/
@property (copy, nonatomic) NSString *name;
-
strong
:強引用類型,被定義了該類型的屬性會被其所指對象持有,為這種屬性設置新值的時候,set方法先保留新值,并釋放掉舊值,然后再把新值賦值上去
/** 適用于對象類型,并且被持有*/
@property (strong, nonatomic) UIView *view;
-
weak
:愛恨交織的weak!弱引用類型,被定義了該類型的屬性不會被持有,在set的時候,既不保留新值也不會釋放舊值,和assign類似,不同的是在該屬性所指對象被銷毀之后,屬性值也會被置為nil
weak 和 assgin
- weak屬性的變量是不為其所屬對象持有的,并且在該變量被銷毀之后,此weak變量的值會自動被賦值為nil。而assign屬性一般是對C基本數據類型成員變量的聲明,當然也可以用在對象類型成員變量上,只是其代表的意義只是單純地拷貝所賦值變量的值。即如果對某assign成員變量B賦值某對象A的指針,則此B只是簡單地保存此指針的值,且并不持有對象A,也就意味著如果A被銷毀,則B就指向了一個已經被銷毀的對象,如果再對其發送消息會引發崩潰
/** 弱引用類型,對象釋放后置為nil*/
@property (weak, nonatomic) id<SSDelegate> delegate;
代理為什么用weak修飾?
- weak:指明該對象并不負責保持delegate這個對象,delegate這個對象的銷毀由外部控制
- 如果用strong:該對象強引用delegate,外界不能銷毀delegate對象,會導致循環引用
-
unsafe_unretained
:這個跟assign就更像了,但不同的是它適用于對象類型.同樣的不會被持有,但與weak不同是,所指對象被銷毀之后不會把屬性置為nil
/** 弱引用類型,對象釋放后不會置為nil*/
@property (unsafe_unretained, nonatomic) UIView *view;
-
assign
:適用于基本數據類型,其set方法只會進行簡單的賦值操作NSInteger,CGFloat...
/** 基本數據類型*/
@property (assign, nonatomic) NSInteger age;
- Int 類型的變量只能用于保存整型值,也就是不包含小數位數的值
- float 類型的變量可以存儲浮點數
- double 和 char 類型一樣,前者范圍大約是后者的兩倍。char 類型可以存儲單個字符
- 常量 它們的總成
- char charVar = ‘a’
- float 類型的變量可以存儲包含小數位的值
- 限定詞 long 、longlong、short、unsigned、signed
- long變量的具體范圍也可以由具體的計算機系統決定 %li 將用十進制顯示 long int 的值
- 如果把限定詞long放在int 聲明前,那聲明的整型變量在某些計算機上具有擴展的值域
- 如果把限定詞short放在int 聲明前,表示聲明特定的變量來存儲相當小的整數 (用short是對內存空間的考慮,可以節約空間)
- unsigned 放在 int 前,表示存儲的是正數的整數變量
- id 類型可以存儲任何類型的對象,是多態和動態綁定的基礎
- count += 10 等價于 count = count + 10 、 "-" 同理
strong / weak / unsafe_unretained 的區別?
- weak只能修飾OC對象,使用weak不會使計數器加1,對象銷毀時修飾的對象會指向nil
- strong等價與retain,能使計數器加1,且不能用來修飾數據類型
- unsafe_unretained等價與assign,可以用來修飾數據類型和OC對象,但是不會使計數器加1,且對象銷毀時也不會將對象指向nil,容易造成野指針錯誤
5 循環結構
- break 在執行循環過程中,有時候希望發生特定的條件就立刻退出循環。無論是for、 while、 do。循環內break之后的語句將被跳過,并且該循環的執行也將終止,從而轉去執行循環外的其他語句。如果在一組嵌套循環中執行break語句,僅會退出執行break語句的最內層循環。記住加“ ;”
- continue 它和break類似。但是它并不會使循環結束。執行continue時,會跳過該語句之后直到循環結尾處的所有語句。通常用來根據某個條件繞過循環中的一組語句。
6 選擇結構
- “ <>”引用系統文件 “ “” ”引用本地文件
- @synthesize:@synthesize age = _age。自動生成age的set和get方法的實現部分,并且會訪問_age這個成員變量
- @synthesize 合成存取法 設值方法和取值方法統稱為 存取方法 。它還有一個作用,可以指定與屬性對應的實例變量,例如@synthesize myButton = xxx;那么self.myButton其實是操作的實例變量xxx,而不是_myButton了。在實際的項目中,我們一般這么寫.m文件
@synthesize myButton;
這樣寫了之后,那么編譯器會自動生成myButton的實例變量,以及相應的getter和setter方法。
注意:_myButton這個實例變量是不存在的,因為自動生成的實例變量為myButton而不是_myButton。
所以現在@synthesize的作用就相當于指定實例變量,
如果.m文件中寫了@synthesize myButton;那么生成的實例變量就是myButton。
如果沒寫@synthesize myButton;那么生成的實例變量就是_myButton。
所以跟以前的用法還是有點細微的區別。
@synthesize 合成實例變量的規則,有以下幾點:
如果指定了成員變量的名稱,會生成一個指定的名稱的成員變量,
如果這個成員已經存在了就不再生成了.
如果是 @synthesize foo; 還會生成一個名稱為foo的成員變量,也就是說:
如果沒有指定成員變量的名稱會自動生成一個屬性同名的成員變量,
如果是 @synthesize foo = _foo; 就不會生成成員變量了.
在有了自動合成屬性實例變量之后,@synthesize還有哪些使用場景?
回答這個問題前,我們要搞清楚一個問題,什么情況下不會autosynthesis(自動合成)?
- 同時重寫了 setter 和 getter 時
- 重寫了只讀屬性的 getter 時
- 使用了 @dynamic 時
- 在 @protocol 中定義的所有屬性
- 在 category 中定義的所有屬性
- 重載的屬性 當你在子類中重載了父類中的屬性,你必須 使用 @synthesize 來手動合成ivar
@synthesize和@dynamic分別有什么作用?
@property有兩個對應的詞,一個是 @synthesize,一個是 @dynamic。如果 @synthesize和 @dynamic都沒寫,那么默認的就是@syntheszie var = _var;
@synthesize 的語義是如果你沒有手動實現 setter 方法和 getter 方法,那么編譯器會自動為你加上這兩個方法。
@dynamic 告訴編譯器:屬性的 setter 與 getter 方法由用戶自己實現,不自動生成。(當然對于 readonly 的屬性只需提供 getter 即可)。假如一個屬性被聲明為 @dynamic var,然后你沒有提供 @setter方法和 @getter 方法,編譯的時候沒問題,但是當程序運行到 instance.var = someVar,由于缺 setter 方法會導致程序崩潰;或者當運行到 someVar = var 時,由于缺 getter 方法同樣會導致崩潰。編譯時沒問題,運行時才執行相應的方法,這就是所謂的動態綁定。
使用@property 指令,就不需要在實現部分聲明相應的實例變量
局部變量 只有在局部變量所在的方法運行時才存在,并且只能在此方法中訪問
static 關鍵字可以使局部變量保留多次調用一個方法所得的值。static int hitCount = 0 ,聲明hitCount是一個靜態變量,和其他基本數據類型的局部變量不同,靜態變量的初始值為0,所以前面顯示的初始化時多余的。此外,它們只在程序開始執行初始化一次,并且在多次調用方法時保存這些數值。
-(int)showPage {
static int pageCount = 0;
++pageCount;
return pageCount;
}
在上面 定義了static變量,這個變量在編譯期會對這個變量進行初始化賦值,也就是說這個變量值要么為nil,要么在編譯期就可以確定其值,一般情況下,只能用NSString或者基本類型 ,并且這個變量只能在cellForRowAtIndexPath 訪問
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = @"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
return cell;
}
- static int pageCount = 0 將pageCount 設置為局部靜態變量和實例變量之間的區別。前一種,pageCount能記錄調用showPage方法的所有對象打印頁面的數目。后一種情況,pageCount變量可以計算每個對象打印的頁面數目,因為每個對象都有自己的pageCount副本
- 繼承通常用于擴展一個類。
- 分類:分類機制允許你以模塊的方式向現有類定義添加新方法。(不必經常給同一接口和實現文件增加新定義。當你希望對沒有源代碼訪問權限的類添加新定義時)
- 擴展類 (自己看到書上的方法總結:比如一個矩形類Rectangle,沒有中間坐標點的位置屬性,所以專門寫了一個坐標點的類XYPoint,定義坐標中點的屬性。然后在矩形類里面引入坐標點的類 XYPoint *origin)
Rectangle :NSObject;
XYPoint :NSObject;
@implementation Rectangle {
XYPoint *origin;
}
- @class 指令提高了效率(個人看視頻理解就是,在.h文件中引用@class。表示你這個類有可能用到@class的這個類。但是不知道會用到它具體的什么方法,所以在.m文件的時候要導入import 這個引用的類)
#import
與@class二者的區別在于:
import會鏈入該頭文件的全部信息,包括實體變量和方法等;而@class只是告訴編譯器,其后面聲明的名稱是類的名稱,至于這些類是如何定義的,暫時不用考慮。在頭文件中, 一般只需要知道被引用的類的名稱就可以了。 不需要知道其內部的實體變量和方法,所以在頭文件中一般使用@class來聲明這個名稱是類的名稱。 而在實現類里面,因為會用到這個引用類的內部的實體變量和方法,所以需要使用#import來包含這個被引用類的頭文件。
在編譯效率方面考慮,如果你有100個頭文件都#import了同一個頭文件,或者這些文件是依次引用的,如A–>B, B–>C, C–>D這樣的引用關系。當最開始的那個頭文件有變化的話,后面所有引用它的類都需要重新編譯,如果你的類有很多的話,這將耗費大量的時間。而是用@class則不會。
如果有循環依賴關系,如:A–>B, B–>A這樣的相互依賴關系,如果使用#import來相互包含,那么就會出現編譯錯誤,如果使用@class在兩個類的頭文件中相互聲明,則不會有編譯錯誤出現。所以,一般來說,@class是放在interface中的,只是為了在interface中引用這個類,把這個類作為一個類型來用的。 在實現這個接口的實現類中,如果需要引用這個類的實體變量或者方法之類的,還是需要import在@class中聲明的類進來.
7 類
- 子類中直接使用父類的實例變量,必須先在接口部分聲明,而不像書中其他例子在實現部分聲明。父類實現部分聲明和合成的實例變量是私有的,子類中并不能直接訪問,需要明確定義或合成取值方法,才能訪問實例變量的值
8 繼承
- 類的每個實例都擁有自己的實例變量,即使這些實例變量是繼承來的。 ClassA -> ClassB ->ClassC 。對象ClassC與對象ClassB具有完全不同的實例變量。
- 在默認情況下,合成的設值方法只是簡單的復制對象指針,而不是對象本身。
- 覆寫方法。就是在子類中定義一個和父類名稱一樣,返回類型一樣,傳入參數一樣的方法。
- 當定義一個子類時,不僅可以添加新的方法來有效的擴展類的定義,還可以添加新的實例變量。
- 抽象類:為了更容易創建子類。等價的稱抽象超類
9 多態、動態類型和動態綁定
- 多態 (相同的名稱,不同的類)能夠使來自不同類的對象定義相同名稱的方法。 可以讓你開發一組類,這組類中的每一個類都能響應相同的方法名。每個類的定義都封裝了響應特定方法所需要的代碼。多態允許添加新類,這些新類能響應相同的方法名。
- 動態類型 能使程序直到執行時才確定對象所屬的類。
- 動態綁定 則能使程序直到執行時才能確定實際要調用的對象方法
- id類型 先判定對象所屬的類,然后在運行時確定需要動態調用的方法,而不是在編譯的時候。
id dataValue = [ [Fraction alloc] init];
[dataValue setReal: 10.0 andImaginary: 2.5];
- 這些代碼在編譯時,不會產生警告信息,因為編譯器在處理源文件時并不知道存儲在dataValue中的對象類型(即編譯器并不知道Fraction對象早已存在于這個變量中)。直到運行包含這些代碼的程序時,會崩潰。
- [Fraction setReal: andImaginary] : unrecognized selector sent to instance 0x103f00
- 當程序運行時,系統首先檢查存儲在dataValue中的對象類型,因為在dataValue中存儲有Fraction 。所以運行時系統檢查并查找定義在Fraction類中的 setReal: andImaginary 方法。因為未找到這個方法,所以程序會崩潰
- 靜態類型 將一個變量定義為特定類的對象時,使用的是靜態類型。“靜態”指的是對存儲在變量中對象的類型進行顯示聲明。這樣存儲在這種形態中的對象的類型是預定義的,也就是靜態的。使用靜態類型時,編譯器盡可能確保變量的用法在程序中始終保持一致。編譯器能夠通過檢查來確定應用于對象的方法是由該類定義的還是由該類繼承的,否則它將顯示警告信息。
- 有一些方法可以調用變量指定的方法,在這種情況下,編譯器不會進行檢查
- 使用靜態類型的另一個原因是它能夠提高程序的可讀性。
- 使用動態類型來調用一個方法,需要注意以下規則:如果在多個類中實現名稱相同的方法,那么每個方法必須要符合各個參數的類型和返回值類型。這樣編譯器才能為消息表達式生成正確的代碼
- class-object是一個類對象(通常是由class方法產生)。selector是一個SEL類型的值(通常是由@selector指令產生的)
- respondsToSelector方法廣泛用于實現委托(delegation)的概念。比如系統需要你在類中實現一個或多個方法用來響應某些事件和為了提供一些信息。為了讓系統能夠檢查你確定實現了特定的方法,使用respondsToSelector 判斷是否可以將事件的處理委托給你的方法。如果你沒有實現這個方法,它會自己處理事件,按定義的默認行為來執行
- 使用@try處理異常:可以測試使程序異常終止的條件并處理這些情況,可能要記錄一條消息并完全終止程序,或者采取其他的正確措施
int main(int argc, const char * argv[]) {
@autoreleasepool {
Fraction *f = [[Fraction alloc] init];
@try {
[f noSuchMethod];
} @catch (NSException *exception) {
[NSLog(@"Caught %@%@"),[exception name], [exception reason]];
} @finally {
}
}
return 0;
}
- 上面代碼當出現異常時, @catch塊被執行。包含異常信息的NSException對象作為參數傳遞給這個塊。
- @finally塊包含是否執行拋出異常的@try塊中的語句代碼。
- @throw指令允許你拋出自己的異常。可以使用該指令拋出特定的異常或者在@catch塊內拋出帶你進入類似如下代碼塊的異常
@throw
10 變量和數據類型
- 枚舉數據類型可以為只存儲一些值得鏈表這種數據類型定義名稱。OC 語言的typedef 語句允許你為內置或派生的數據類型指定自己的名稱。
- 對象的初始化 即創建對象的新實例,然后對它初始化
- 常見的編程習慣是類中所有的初始化方法都以Init開頭。
- (id)init {
self = [super init];
if (self) {
//初始化代碼
}
return self;
}
- 上面是覆寫init方法的標準“模塊”。首先會調用父類初始化方法。執行父類的初始化方法,使得繼承的實例變量能夠正常初始化。
- 必須將父類init方法的執行結果賦值給self,因為初始化過程改變了對象在內存中的位置(意味著引用將要改變)
- 如果父類的初始化過程成功,返回的值將是非空的,通過if語句可以驗證。注釋說明可以在這個代碼塊的位置放入自定義的初始化代碼。通常可以在這個位置創建并初始化實例變量。
- 如果你的類中包含多個初始化方法,其中一個就應該是指定的(designated)初始化方法,并且其他所有的初始化方法都應該使用這個方法。通常,它是最復雜的初始化方法(一般是參數最多的初始化方法)。通過創建指定的初始化方法,可以把大部分初始化代碼集中到單個方法中,然后人要想從該類派生子類,都可以重載這個指定的初始化方法,以便保證正確的初始化新的實例
- 為了使用指定的初始化規則,你需要修改Fraction類的init方法。這一點尤其是作為子類特別重要
- 注意:init被定義為返回id類型。子類的對象有可能并不等同于父類,為保持一致
- 程序開始執行時,向所有類發送Initialize調用方法。如果存在一個類及相關的子類,則父類首先得到這條消息,該消息只向其發送初始化消息。這樣做的目的是程序開始時能夠執行到所有類的初始化工作。列如你可能希望在開始時初始化與類相關的靜態變量。
- @proterted 這個指令后面的實例變量可被該類以及該類的任何子類中定義的方法直接訪問。在接口部分定義的實例變量默認是這種作用域
- @private 只能被當前類的對象方法中直接訪問。在實現部分定義的實例變量默認是這種作用域。
但是要是@private在父類里面聲明了成員變量,子類可以通過set方法訪問(父類要有這個成員變量的set和get方法)。因為是繼承,所以相當于子類也擁有這個@private成員變量。 - @public 這個指令后面的實例變量可被該類中定義的方法直接訪問,也可被其他類或模塊中定義的方法直接訪問
- @package 對于64位映像,可以在實現該類的映像中任何地方訪問這個實例變量。。。。只要處在同一個框架中,就能直接訪問對象的成員變量
- 所謂的直接訪問,就是比如父類有個成員變量_age。直接訪問就是直接使用_age,如果_age是私有的private。要想在子類中訪問,就要調用set方法
- 將實例變量聲明為public并不是良好的編程習慣,因為違背了數據封裝的思想(一個類需要隱藏它的實例變量)
- 在實現部分顯示的聲明的實例變量(或者使用@synthesize指令隱性聲明的實例)是私有的,這就意味著并不能在子類中通過名字直接獲取到實例變量 。在子類中只能使用繼承的存取方法獲取到實例變量的值
- 外部變量是可被其他任何地方或函數訪問和更改其值得變量。在需要訪問外部變量的模塊中,變量聲明和普通方式一樣,只是需要在聲明前加上關鍵字 exten 。這就告知系統,要訪問其他文件中定義的全局變量。
extern int gMoveNumber
- 使用外部變量必須遵循下面的原則:1.變量必須定義在源文件的某個位置。即在所有的方法和函數之外聲明的變量,并且前面不加關鍵字 extern(可以選擇為這個變量賦值)
int gMoveNumber
- 確定外部變量的第二種方式是在所有函數之外聲明變量,在聲明前加 extern ,同時顯示的為變量指派初始值
extern int gMoveNumber = 0;
- 但是編譯器會出現警告,提示你已將變量聲明為extern,并同時為變量賦值。這是因為使用關鍵字 extern 表明這條語句是變量的聲明,而不是定義。(聲明不會引起分配變量的存儲空間,而定義會引起變量存儲空間的分配)。前面的例子強行將聲明當做定義處理。所以違背了這個原則
- extern聲明的變量,如果有很多方法都訪問,只在文件的開始進行一次 extern 聲明。如果只有幾個方法要訪問該這個變量,就在方法中進行extern聲明
- 如果變量定義在包含訪問該變量的文件中,那么不需要進行單獨extern聲明
- 用static定義的變量,放在方法外,就不是 外部變量。只是全局變量
static int gCount;
-------
+ (int) count {
extern int gCount;
return gCount;
}
- static作用 ------修飾局部變量:1.延長局部變量的生命周期,程序結束才會銷毀。2.局部變量只會生成一份內存,只會初始化一次。3.改變局部變量的作用域。------修飾全局變量:1.只能在本文件中訪問,修改全局變量的作用域,生命周期不會改。2.避免重復定義全局變量
- extern作用 ------只是用來獲取全局變量(包括全局靜態變量)的值,不能用于定義變量
- extern工作原理: ------先在當前文件查找有沒有全局變量,沒有找到,才會去其他文件查找。
- 聲明gCount為靜態。其實并不需要聲明 extern ,這樣只是為了閱讀該方法的人明白,他訪問的變量是定義在該方法之外。程序開始運行時gCount 的值會自動置為0(如果要將類作為整體進行任何特殊的初始化,例如,將其它靜態變量的值設置為一些非零值,可以重載繼承類 initialize方法)
- 枚舉 枚舉數據類型的定義以關鍵字 enum 開頭,之后是枚舉數據類型的名稱,然后是標識符序列(包含一對花括號內),它們定義了可以給該類型指派的所有允許值
enum flag { false, true }
enum direction { up, down, left = 10, right }
- 上述例子,up = 0; down = 1; left = 10; right = 11; 因為left = 10 ,所以后面的要遞增
- OC編譯器實際上是將枚舉標識符作為整型常量來處理
- 在代碼塊中定義的枚舉數據類型的作用域限于塊的內部。在程序開始及所有塊之外定義的枚舉數據類型對于該文件是全局的。
- 定義枚舉數據類型時,必須確保枚舉標識符與定義在相同作用域之內的變量名和其他標識符不同
- typedef 允許為數據類型另外指派一個名稱
typedef int Count
Count j, n;
- 上面第一句代碼就是相當于把 Count 看成 int 。第二句代碼就 相當于聲明了2個 int 類的變量。
11 分類和協議
- 分類
- 類擴展 創建一個未命名的分類 且在括號 “ ()”之間不能指定名字 。未命名分類中聲明的方法需要在主實現區域實現,而不是在分離的實現區域實現。
- 未命名分類非常有用,因為它們的方法都是私有的。如果只是想供類本身使用,用未命名分類比較合適
- 分類可以覆寫該類中的另一個方法,但是這樣不好。因為覆寫了以后,再也不能訪問原來的方法。如果真的要覆寫,最好是創建子類。如果要在子類中覆寫方法,仍然可以通過向super發送消息來引用父類的方法。
- 如果喜歡,可以擁有許多分類。
- 通過使用分類添加新方法來擴展類,不僅會影響這個類,還會影響它的所有子類。
- 協議是多個類共享的一個方法列表。協議提供一種方式,用指定的名稱定義一組多少有點相關的方法。
- 協議列出一組方法,有些可以選擇實現,有些必須實現。如果決定實現特定協議的所有方法,也就意味著要遵守( confirm to)或者采用 ( adopt )這項協議,可以定義協議中所有方法必須實現的,也可以都是選擇實現的
- 定義協議很簡單:只要使用 @protocol 指令,后面跟上你給出的協議名稱。然后和處理接口部分一樣,聲明一些方法。 @end 指令之前所有的方法都是協議的一部分。
Fraction.h
@protocol NSCopying
- (id)copyWithZone:(NSZone *)zone;
@end
- 如果你的類采用 NSCopying 協議,則必須實現名為 copyWithZone 的方法。通過 @interface 行的一對尖括號列出協議名稱。告訴編譯器,你采用一個協議
@interface Fraction : NSObject <NSCopying>
- 如果自己定義了自己的協議,可以不必有自己實現它,但是要告訴其他程序員。如果采用這項協議,則必須實現這項方法。這些方法可以從超類繼承。也就是說,一個類遵守 NSCopying 協議,則它的子類也遵守NSCopying協議
- @optional 指定 表示指令之后寫出的方法都是可選的。協議方法默認為 optional 修飾
- @requierd 指令來列出需要的方法。
- 協議不引用任何類,它是無類的(classless) 任何類都可以遵守 某個協議,并不僅僅是 XX的子類。
- 使用conformsToProtocol方法檢查一個對象是否遵循某項協議。使用@protocol指令獲取一個協議名稱,并產生Protocol對象。
id currentObject;
-----
if ([currentObject containsObject:@protocol(Drawing)] == YES) {
------
}
- 通過在類型名稱之后的尖括號中添加協議名稱,可以借助編譯器來檢查變量的一致性。告訴編譯器currentObject將包含遵守Drawing協議。如果向 currentObject 指派靜態類型的對象,這個對象不遵守Drawing協議,編譯器會警告 “waring: class 'XXX' does not implement the 'Drawing' protocol”。
id <Drawing> currentObject;
- 定義一項協議時,可以擴展現有協議的定義。
@protocol Drawing3D <Drawing>
- 說明協議Drawing3D也采用 Drawing 協議。因此任何采用 Drawing3D 協議的類都必須實現Drawing3D和 Drawing 協議的方法
- 分類也可以采用一項協議
@interface Fraction (Stuff) <NSCopying, NSCoding>
- 和類名一樣,協議名必須是唯一的
- 協議也是一種兩個類之間的接口定義。定義了協議的類可以看做是將協議定義的方法代理給了實現他們的分類。這樣,類的定義可以更為通用,因為具體動作由代理類來承擔,來響應某些事件或者定義某些參數。
- 非正式協議(抽象協議 abstract) 實際上是一個分類,列出了一組方法,但是沒有實現它們。每個人(或者幾乎每個人人)都繼承相同的跟對象,因此,非正式分類通常是為根類定義的。
- 非正式協議的類自己并不實現這些方法,并且選擇實現這些方法的子類需要在它的接口部分重新聲明這些方法,同時還要實現這些方法中的一個或多個。和正式協議不同,編譯器不提供有關非正式協議的幫助,這里沒有遵守協議或者由編譯器測試這樣的概念
- 如果一個對象采用正式協議,則它必須遵守協議中的所有信息。如果一個對象采用非正式協議,則它可能不需要采用此協議的所有方法,具體取決于這項協議。
- optional 指令可以取代非正式協議使用
- 合成對象 你已經學習了通過派生子類和分類等技術可以擴展類定義的幾種方式。還有一項技術可以定義一個類包含其他類的一個或多個對象。這個新類的對象就是所謂的合成對象,因為它是由其他對象組成的。(就是父類中有些方法子類不適合,或者不適用、還有可能改變父類以后。子類也改變,依賴父類,導致子類不能正常工作。所以就要創建子類的代替方式,定義一個新類,包含要擴展類的實例變量以及適合子類分方法)
12 預處理
- 預處理使用 “ # ” 標記。這個符號必須是一行的第一個非空字符
-
define 給符號名稱指定程序常量
- 預處理名稱不是變量。因此,不能為它賦值,除非替代指定值的結果實際上是一個變量。
- 預處理定義包含在程序的一行中。如果需要第二行,那么上一行最后一個字符必須是反斜線符號。表示還有后續。
-
undef 語句 使一些已經定義的名稱成為未定義。
13 基本的C語言特性
函數、結構、指針、聯合和數組。。。。
- 在函數中(方法)定義的變量稱為自動布局變量。因為每次調用函數都是自動創建的。而且是局部的。
- 塊 (blocks)是C語言的擴展 。也有返回值。定義在函數或者方法內部,并能夠訪問在函數或者方法范圍內塊之外的任何變量。一般來說,這些變量能夠訪問,但是不能夠改變這些變量的值。有一個特殊的塊修改器(由塊前面含有兩個下劃線的字符組成)能夠修改塊內變量的值。
- 塊的其中一個優勢在于能夠讓系統分配給其他處理器或應用的其他線程執行
- 聲明block 用copy。原因是在代碼塊里面我可能會使用一些本地變量。而block一開始是放在棧上的,只有copy后才會放到堆上。
- 默認情況下,block是存檔在棧中,可能被隨時回收,通過copy操作可以使其在堆中保留一份, 相當于一直強引用著, 因此如果block中用到self時, 需要將其弱化, 通過__weak或者__unsafe_unretained. 以下是示例代碼及其說明, 讀者可以試著打印出不同情況下block的內存情況
- 翻譯過來的。block使用weak的話,當對象被賦值以后,有可能被釋放掉。
void (^printMessage)(void) =
^(void) {
NSLog(@"------");
} ;
- 塊是以插入字符 “ ^ ”開頭為標識。后面跟的一個括號表示塊所需要的參數列表。
- 上面的方法是將塊賦給一個變量 printMessage (只要變量聲明正確)等號左邊表示printMessage指向一個沒有參數和返回值的塊指針。需要注意的是,賦值語句是以分號終止
- 執行一個變量引用的塊,和函數調是一樣的 printMessage();
- 塊能定義為全局或者局部的。
void (^printMessage)(int) =
^(int n) {
int i, triangularNumber = 0;
for (i = 1; i<= n; ++i) {
triangularNumber += i;
}
} ;
printMessage(10);
- 指向塊的指針變量printMessage 以int 作為參數,并沒有返回值
int (^gcd) (int, int) =
^(int u, int v) {
int temp;
while (v != 0) {
temp = u % v;
u = v;
v = temp;
}
return u;
};
- 塊可以訪問在其范圍內定義的變量,變量的值同時作為塊中定義的值。
int foo = 10;
void (^printFoo)(void) =
^(void) {
NSLog(@"----");
foo = 20;
};
foo = 15;
printFoo();
- 塊printFoo 可以訪問本地變量foo 。注意:打印的結果為10,而不是15。因為變量在定義塊的同時已具有值了,而不是在塊執行的時候 (不可以在塊的內部修改變量 ---但是我記得可以的,只需要在塊中加鎖還是什么來著)
- 如果在定義本地變量之前插入_ _block修改器 _ _ block int foo = 10;此時打印的結果有兩個,第一個為15,第二個為20.第一次顯示的是調用塊時foo的值,第二次驗證塊中能否將foo 值改變為20
結構
struct data {
int month;
int day;
int year;
};
struct data today;
today.month = 9;
today.day = 25;
today.year = 2011;
struct data today01 = {7,8,9};
- 結構的初始化和數組類似,例如上面最后一句。
typedef struct data today01;
- typedef提供了聲明變量更簡便的方式,不需要使用 struct 關鍵字
struct data {
int month;
int day;
int year;
} todayDate, purchaseDate;
- 定義了data結構,同時聲明了變量todayDate, purchaseDate的數據類型
struct data {
int month;
int day;
int year;
} todayDate = {7,8,9};
- 定義了data結構,同時也將變量todayDate, purchaseDate賦值
struct data {
int month;
int day;
int year;
} todayDate [100];
- 定義了100個元素的數組datas ,每個元都包含年月日三個整型成員結構。因為沒有為這個結構提供名稱,所以定義同類型變量唯一方式就是再次顯示地定義這個結構
指針
- 指針可以高效的表示復雜的數據結構,更改作為參數傳遞給函數和方法的值,并且更準確、高效的處理數組。
- & scanf 調用的時候使用&位運算符 在OC中是 一元預算符(又稱地址運算符)用來得到變量指針
int count = 10, x;
int *intPtr;
intPtr = &count;
x = *intPtr;
- 變量count和x以常規方式聲明為整型變量。變量intPtr聲明為“int 指針”類型。然后對變量count應用地址預算符。它的作用就是創建該變量的指針。接著程序將該指針賦值給變量intPtr。x = *intPtr 告知編譯器表達式 intPtr 是指向整型數據的值。而且前面的程序語句中已經將intPtr設為指向整型變量的count指針,所以count的值可以使用表達式intPtr間接訪問
指針和結構
struct data {
int month;
int day;
int year;
};
struct data todaysDate;
struct data *datePtr;
datePtr = &todaysDate;
(*datePtr).day = 21;
datePtr->dat = 21;
試圖生命周期
loadView -> viewDidLoad -> viewWillAppear -> viewDidAppear -> viewWillDisappear -> viewDidDisappear
當一個視圖控制器被創建,并在屏幕上顯示的時候。 代碼的執行順序
- alloc 創建對象,分配空間
- init (initWithNibName) 初始化對象,初始化數據
- loadView 從nib載入視圖 ,通常這一步不需要去干涉。除非你沒有使用xib文件創建視圖
- viewDidLoad 載入完成,可以進行自定義數據以及動態創建其他控件
- viewWillAppear 視圖將出現在屏幕之前,馬上這個視圖就會被展現在屏幕上了
- viewDidAppear 視圖已在屏幕上渲染完成
當一個視圖被移除屏幕并且銷毀的時候的執行順序,這個順序差不多和上面的相反
- viewWillDisappear 視圖將被從屏幕上移除之前執行
- iewDidDisappear 視圖已經被從屏幕上移除,用戶看不到這個視圖了
- dealloc 視圖被銷毀,此處需要對你在init和viewDidLoad中創建的對象進行釋放
A push B 的生命周期
A_viewDidLoad -> A_viewWillAppear -> A_viewDidAppear -> A_viewWillDisappear -> B_viewDidLoad ->
B_viewWillAppear -> A_viewDidDisappear -> B_viewDidAppear
B 回到 A (viewDidLoad 只加載一次)
B_viewWillDisappear -> A_viewWillAppear -> B_viewDidDisappear -> A_viewDidAppear
+load 和 +initialize 的區別是什么?
+load
- 當類對象被引入項目時, runtime 會向每一個類對象發送 load 消息
load 方法會在每一個類甚至分類被引入時僅調用一次,調用的順序:父類優先于子類, 子類優先于分類 - load 方法不會被類自動繼承
+initialize
也是在第一次使用這個類的時候會調用這個方法
load 在啟動程序的時候就會調用,而initialize是在類被使用的時候(初始化)調用
什么是 Protocol,Delegate 一般是怎么用的?
- 協議是一個方法簽名的列表,在其中可以定義若干個方法,遵守該協議的類可以實現協議里的方法,在協議中使用@property只會生成setter和getter方法的聲明
- delegate用法:成為一個類的代理,可以去實現協議里的方法
為什么 NotificationCenter 要 removeObserver? 如何實現自動 remove?
- 如果不移除的話,萬一注冊通知的類被銷毀以后又發了通知,程序會崩潰.因為向野指針發送了消息
- 實現自動remove:通過自釋放機制,通過動態屬性將remove轉移給第三者,解除耦合,達到自動實現remove
強引用和弱引用
在強引用中,有時會出現循環引用的情況,這時就需要弱引用來幫忙(__weak)。
強引用持有對象,弱引用不持有對象。
強引用可以釋放對象,但弱引用不可以,因為弱引用不持有對象,當弱引用指向一個強引用所持有的對象時,當強引用將對象釋放掉后,弱引用會自動的被賦值為nil,即弱引用會自動的指向nil。
----------------------------
用@property聲明的NSString(或NSArray,NSDictionary)經常使用copy關鍵字,為什么?如果改用strong關鍵字,可能造成什么問題?
因為父類指針可以指向子類對象,使用 copy 的目的是為了讓本對象的屬性不受外界影響,使用 copy 無論給我傳入是一個可變對象還是不可對象,我本身持有的就是一個不可變的副本.
-
如果我們使用是 strong ,那么這個屬性就有可能指向一個可變對象,如果這個可變對象在外部被修改了,那么會影響該屬性.
copy 此特質所表達的所屬關系與 strong 類似。然而設置方法并不保留新值,而是將其“拷貝” (copy)。 當屬性類型為 NSString 時,經常用此特質來保護其封裝性,因為傳遞給設置方法的新值有可能指向一個 NSMutableString 類的實例。這個類是 NSString 的子類,表示一種可修改其值的字符串,此時若是不拷貝字符串,那么設置完屬性之后,字符串的值就可能會在對象不知情的情況下遭人更改。所以,這時就要拷貝一份“不可變” (immutable)的字符串,確保對象中的字符串值不會無意間變動。只要實現屬性所用的對象是“可變的” (mutable),就應該在設置新屬性值時拷貝一份。
用 @property 聲明 NSString、NSArray、NSDictionary 經常使用 copy 關鍵字,是因為他們有對應的可變類型:NSMutableString、NSMutableArray、NSMutableDictionary,他們之間可能進行賦值操作,為確保對象中的字符串值不會無意間變動,應該在設置新屬性值時拷貝一份。
@implementation Son : Father
- (id)init
{
self = [super init];
if (self) {
NSLog(@"%@", NSStringFromClass([self class]));
NSLog(@"%@", NSStringFromClass([super class]));
}
return self;
}
@end
NSLog------NSStringFromClass([self class]) = Son
NSLog------NSStringFromClass([super class]) = Son
上面的例子不管調用[self class]還是[super class],接受消息的對象都是當前 Son *xxx 這個對象。
當使用 self 調用方法時,會從當前類的方法列表中開始找,如果沒有,就從父類中再找;而當使用 super 時,則從父類的方法列表中開始找。然后調用父類的這個方法。
這也就是為什么說“不推薦在 init 方法中使用點語法”,如果想訪問實例變量 iVar 應該使用下劃線( _iVar ),而非點語法( self.iVar )。
點語法( self.iVar )的壞處就是子類有可能覆寫 setter 。
面試題
- 在ios第一版中,我們為輸出口同時聲明了屬性和底層實例變量,那時,屬性是oc語言的一個新的機制,并且要求你必須聲明與之對應的實例變量
@interface MyViewController :UIViewController
{
UIButton *myButton;
}
@property (nonatomic, retain) UIButton *myButton;
@end
最近,蘋果將默認編譯器從GCC轉換為LLVM(low level virtual machine),從此不再需要為屬性聲明實例變量了。
如果LLVM發現一個沒有匹配實例變量的屬性,它將自動創建一個以下劃線開頭的實例變量。因此,在這個版本中,我們不再為輸出口聲明實例變量。
例如:
@interface MyViewController :UIViewController
@property (nonatomic, retain) UIButton *myButton;
@end
在MyViewController.m文件中編譯器也會自動的生成一個實例變量_myButton
那么在.m文件中可以直接的使用_myButton實例變量,也可以通過屬性self.myButton.都是一樣的。
注意這里的self.myButton其實是調用的myButton屬性的getter/setter方法
如果點表達式出現在等號 = 左邊,該屬性名稱的setter方法將被調用。如果點表達式出現在右邊,該屬性名稱的getter方法將被調用。"
所以在oc中點表達式其實就是調用對象的setter和getter方法的一種快捷方式, 例如:
dealie.blah = greeble 完全等價于 [dealie.blah setBlah:greeble];
簡單介紹以下幾個宏:
-
__VA_ARGS__
是一個可變參數的宏,這個可變參數的宏是新的C99規范中新增的,目前似乎只有gcc支持(VC6.0的編譯器不支持)。宏前面加上##的作用在于,當可變參數的個數為0時,這里的##起到把前面多余的","去掉,否則會編譯出錯。 -
__FILE__
宏在預編譯時會替換成當前的源文件名 -
__LINE__
宏在預編譯時會替換成當前的行號 -
__FUNCTION__
宏在預編譯時會替換成當前的函數名稱
重新定義系統的NSLog,OPTIMIZE 是release 默認會加的宏
#ifndef __OPTIMIZE__
#define NSLog(...) NSLog(__VA_ARGS__)
#else
#define NSLog(...){}
#endif
直接自己寫#define,當release版本的時候把#define 注釋掉即可
#define IOS_DEBUG
#ifdef IOS_DEBUG
#define NSLog(...) NSLog(__VA_ARGS__)
#endif
這種方式需要修改項目的配置,使得在debug編譯的時候,編譯DLog的宏,產生詳細的日志信息,而release的時候,不產生任何控制臺輸出相比而言,還是第一種比較方便
#ifdef DEBUG
# define DLog(format, ...) NSLog((@"[文件名:%s]" "[函數名:%s]" "[行號:%d]" format), __FILE__, __FUNCTION__, __LINE__, ##__VA_ARGS__);
#else
# define DLog(...);
#endif
這個DEBUG在哪設置呢,
在 "Target > Build Settings > Preprocessor Macros > Debug" 里有一個"DEBUG=1"。
設置為Debug模式下,Product-->Scheme-->SchemeEdit Scheme
設置Build Configuration成Debug時,就可以打印nslog了。
設置Release,發布app版本的時候就不會打印了,提高了性能
%@ 對象
%d,%i 整型 (%i的老寫法)
%hd 短整型
%ld , %lld 長整型
%u 無符整型
%f 浮點型和double型
%0.2f 精度浮點數,只保留兩位小數
%x: 為32位的無符號整型數(unsigned int),打印使用數字0-9的十六進制,小寫a-f;
%X: 為32位的無符號整型數(unsigned int),打印使用數字0-9的十六進制,大寫A-F;
%o 八進制
%zu size_t
%p 指針地址
%e float/double (科學計算)
%g float/double (科學技術法)
%s char * 字符串
%.*s Pascal字符串
%c char 字符
%C unichar
%Lf 64位double
%lu sizeof(i)內存中所占字節數
isMemberOfClass 和 isKindOfClass 聯系與區別
- 聯系 兩者都能查看一個對象是否是某個類的成員
- 區別 isKindOfClass可以查看一個對象是否是一個類的派生出來的類的成員
@interface CatA : NSObject
@end
@interface CatB : CatA
@end
@implementation CatC : NSObject
CatA *catA = [[CatA alloc] init];
CatB *catB = [[CatB alloc] init];
//第一組
[catA isKindOfClass:[A class]] = YES
[catA isMemberOfClass:[A class]] = YES
//第二組
[catB isKindOfClass:[CatB class]] = YES
[catB isMemberOfClass:[CatB class]] = YES
//第三組
[catB isKindOfClass:[CatA class] = YES
[catB isMemberOfClass:[CatA class] = NO
@end
上面代碼可以知道,CatB是CatA的子類,分析三組數據:
一、第一組
第一組數據catA,是類CatA的一個實例,根據isMemberOfClass和isKindOfClass的定義(一個對象是否是某個類的實例),毋庸置疑答案是YES。
一、第二組
第二組數據catB,是類CatB的一個實例,同第一組數據結果一樣都是YES。
一、第三組(重頭戲)
第三組我們已經知道CatB是CatA的子類,也就是說CatB是CatA的派生類,并且catB是類CatB的實例對象,符合isKindOfClass定義的第二條, 所以[catB isKindOfClass:[CatA class] = YES;不符合isMemberOfClass的定義所以NO。
結論:
- [類的實例 isMemberOfClass] = YES [類的實例 isMemberOfClass] = YES ;
- [子類實例 isMemberOfClass 父類 ] = NO [子類實例 isKindOfClass 父類 ] = YES
@protocol 和 category 中如何使用 @property
- 在 protocol 中使用 property 只會生成 setter 和 getter 方法聲明,我們使用屬性的目的,是希望遵守我協議的對象能實現該屬性
- category 使用 @property 也是只會生成 setter 和 getter 方法的聲明,如果我們真的需要給 category 增加屬性的實現,需要借助于運行時的兩個函數:
objc_setAssociatedObject
objc_getAssociatedObject
runtime 如何實現 weak 屬性
要實現 weak 屬性,首先要搞清楚 weak 屬性的特點:
- weak 此特質表明該屬性定義了一種“非擁有關系” (nonowning relationship)。為這種屬性設置新值時,設置方法既不保留新值,也不釋放舊值。此特質同 assign 類似, 然而在屬性所指的對象遭到摧毀時,屬性值也會清空(nil out)。
那么 runtime 如何實現 weak 變量的自動置nil?
- runtime 對注冊的類, 會進行布局,對于 weak 對象會放入一個 hash 表中。 用 weak 指向的對象內存地址作為 key,當此對象的引用計數為0的時候會 dealloc,假如 weak 指向的對象內存地址是a,那么就會以a為鍵, 在這個 weak 表中搜索,找到所有以a為鍵的 weak 對象,從而設置為 nil。
再命令編譯運行以后,在$ clang -rewrite-objc test.m 會把test.m 變成test.app C語言文件
使用static const 與 #define
-
使用static const修飾變量和宏定義的比較
相同點
- 都不能再被修改
- 一處修改,其它都改了
不同點
- static const修飾變量只有一份內存
- 宏定義,只是簡單的替換,每次使用都需要創建一份內存
-
結論
使用static const修飾更加高效,在同一個文件內可以使用static const取代
#define
// static const修飾變量只有一份內存
static const CGFloat ZMJRed = 0.4;
// 宏定義,只是用0.4替換ZMJRed,每次使用都需要創建一份內存
#define ZMJRed 0.4
unsigned int 0~4294967295
int -2147483648~2147483647
unsigned long 0~4294967295
long -2147483648~2147483647
long long的最大值:9223372036854775807
long long的最小值:-9223372036854775808
unsigned long long的最大值:1844674407370955161
__int64的最大值:9223372036854775807
__int64的最小值:-9223372036854775808
unsigned __int64的最大值:18446744073709551615
不用中間變量,用兩種方法交換A和B的值
A = A+B;
B = A - B;
A = A - B;
內存黃金法則:
- 誰alloc,誰release!(包括new);
- 誰retain,誰release!
- (retain) 引用計數+1
- (release) 引用計數-1
- 誰copy,誰release!
內存分區:
1)、棧區(stack)— 由編譯器自動分配釋放 ,存放函數的參數值,局部變量的值等。其操作方式類似于數據結構中的棧。
2)、堆區(heap) — 一般由程序員分配釋放, 若程序員不釋放,程序結束時可能由OS回收 。(ios中alloc都是存放在堆中)
3)、全局區(靜態區)(static)—,全局變量和靜態變量的存儲是放在一塊的,初始化的全局變量和靜態變量在一塊區域, 未初始化的全局變量和未初始化的靜態變量在相鄰的另一塊區域。 - 程序結束后有系統釋放。注意:全局區又可分為未初始化全局區:.bss段和初始化全局區:data段。
4)、常量區—常量字符串就是放在這里的。 程序結束后由系統釋放
5)、代碼區—存放函數體的二進制代碼。
集合類對象的copy與mutableCopy
- [immutableObject copy] // 淺復制
- [immutableObject mutableCopy] //單層深復制
- [mutableObject copy] //單層深復制
- [mutableObject mutableCopy] //單層深復制
對非集合類對象的copy操作:
- [immutableObject copy] // 淺復制
- [immutableObject mutableCopy] //深復制
- [mutableObject copy] //深復制
- [mutableObject mutableCopy] //深復制