將引入頭文件的時機儘可能延後。
@class SomeClass;
- (void)viewDidLoad {
[super viewDidLoad];
NSNumber *someNumber1 = [NSNumber numberWithInt:1];
NSNumber *someNumber2 = @1;
NSArray *animals1 = [NSArray arrayWithObjects:@"cat", @"dog", @"mouse", nil];
NSArray *animals2 = @[@"cat", @"dog", @"mouse"];
/* 用字面兩語法建立數組時,若數組元素對象中有nil,則會拋出異常。 「arrayWithObjects:」方法會一次處理各個參數,直到發現nil爲止。 使用字面量語法更安全。拋出異常比建立數組以後發現元素個數少了要好。 */
NSDictionary *dictA = [NSDictionary dictionaryWithObjectsAndKeys:
@"value1", @"key1",
@"value2", @"key2",
@"value3", @"key3", nil];
NSDictionary *dictB = @{@"key1" : @"value1",
@"key2" : @"value2",
@"key3" : @"value3"};
/* 字典中的 Key 跟 Value 都必須是OC對象 字典跟數組同樣,存在nil的問題 */
}
複製代碼
總之,勿使用預處理指令定義常量,應藉助編譯器來確保常量正確
#define ANIMATION_DURATION 0.3
static const NSTimeInterval kAnimationDuration = 0.3;
//In the .h
extern NSString *const EOCStringConstant;
//In the .m
NSString *const EOCStringConstant = @"VALUE";
複製代碼
在對象之間傳遞數據並執行任務的過程叫作「消息傳遞」
當應用程序運行起來之後,爲其提供相關支持的代碼叫作「Objective-C 運行時環境」
「屬性」(property)是OC的一項特性,用於封裝對象中的數據。OC對象一般會把其所須要的數據保存爲各類實例變量。實例變量通常經過「存取方法」來訪問。其中 getter方法
用於讀取變量值,而 setter方法
用於寫入變量值。
php
property = ivar(實例變量) + getter + setter (存取方法)
在正規的OC編碼風格中,存取方法有着嚴格的命名規範。所以,OC這門語言才能根據名稱自動建立出存取方法。數組
在對象接口的定義中,可使用屬性,這是一種標準的寫法,可以訪問封裝在對象裏的數據。所以,也能夠把屬性理解爲:編譯器會自動寫出一套存取方法,用以訪問給定類型中具備給定名稱的變量。以下的這個類:緩存
@interface Person : NSObject
@property NSString *firstName;
@property NSString *lastName;
@end複製代碼
上述代碼寫出來的類與下面這種寫法等效:安全
@interface Person : NSObject
- (NSString *)firstName;
- (void)setFirstName:(NSString *)firstName;
- (NSString *)lastName;
- (void)setLastName:(NSString *)lastName;
@end複製代碼
要訪問屬性,可使用「點語法「。編譯器會把」點語法「轉換爲對存取方法的調用
若是使用了屬性,那麼編譯器會自動編寫訪問這些屬性所需的方法,此過程叫作「自動合成」。除了生成方法代碼以外,編譯器還要自動向類中添加適當類型的實例變量,而且在屬性名前面加下劃線,以此做爲實例變量的名字。也能夠在類的實現代碼裏經過 @synthesize
語法來指定實例變量的名字:bash
@implementation Person
@synthesize firstName = _myFirstName;
@synthesize lastName = _myLastName;
@end複製代碼
若不想令編譯器自動合成存取方法,則能夠本身實現。但你不能同時本身實現 getter 和 setter。還有一種辦法能阻止編譯器自動合成存取方法,就是使用 @dynamic
關鍵字,它會告訴編譯器:不要自動建立實現屬性所用的實例變量,也不要爲其建立存取方法。框架
//In the .h
@interface ViewController : UIViewController
@property NSString *firstName;
@property NSString *lastName;
@end
//In the .m
@implementation ViewController
@dynamic firstName, lastName;
@end
/* 假如一個屬性被聲明爲 @dynamic var,而後你沒有提供 @setter方法和 @getter 方法,編譯的時候 沒問題,但程序運行到instance.var = someVar, 或者 somevar = var時, 因爲缺乏 setter 和 getter,程序會崩潰。 編譯時沒問題,運行時才執行相應的方法,這就是所謂的動態綁定。 */複製代碼
使用屬性時需注意:各類特質設定也會影響編譯器所生成的存取方法。
屬性能夠擁有的特質分爲四類:函數
6.3.一、原子性ui
在默認狀況下,由編譯器所合成的方法會經過鎖定機制確保其原子性(atomicity)。若是屬性具有 nonatomic 特質,則不使用同步鎖。編碼
6.3.二、讀 / 寫權限atom
具有 readwrite(讀寫)特質的屬性擁有 getter 和 setter。
具有 readonly(只讀)特質的屬性僅有獲取方法。
6.3.三、內存管理語義
屬性用於封裝數據,而數據則要有「具體的全部權語義」。
下面這一組特質僅會影響 setter方法:
6.3.五、方法名
一、getter=<name> 指定「獲取方法」的方法名。
@property (nonatomic, getter=isOn) BOOL on;複製代碼
二、setter=<name> 指定「設置方法」的方法名,這種用法不太常見
直接訪問實例變量 跟 經過屬性來訪問 的區別:
要點:
使用 NSObject 協議中聲明的 「isEqual:」 方法來判斷兩個對象的等同性
「類族」能夠隱藏「抽象基類」背後的實現細節。OC的系統框架中廣泛使用此模式,如 UIKit 中的UIButton類,想建立按鈕,只需調用下面這個「類方法」。使用者無需關心建立的按鈕屬於哪一個類,也不用考慮按鈕的實現細節。
+ (UIButton *)buttonWithType:(UIButtonType)type;
有時候類的實例多是由某種機制所建立的,而開發者沒法令這種機制建立出本身所寫的自類實例。OC經過 關聯對象 這一特性解決此問題。
能夠給某對象關聯許多其餘對象,這些對象經過「鍵」來區分。存儲對象值的時候,能夠指明「存儲策略」,用以維護相應的「內存管理語義」。
10.一、存儲策略由名爲 objc_AssociationPolicy 的枚舉所定義
10.二、管理關聯對象的方法
//此方法以給定的鍵和策略爲某對象設置關聯對象值
objc_setAssociatedObject(id object, void *key, id value, objc_AssociationPolicy policy)
//此方法根據給定的鍵從某對象中獲取相應的關聯對象值
objc_getAssociatedObject(id object, void *key)
//此方法移除指定對象的所有關聯對象
objc_removeAssociatedObjects(id object)複製代碼
關聯對象保存在一個全局的AssociationsManager裏邊,原理在此。
在對象上調用方法是OC常用的功能,用哪一個OC的術語來講,這叫作「傳遞消息」。消息有 「名稱」 和 「選擇子」,能夠接受參數,並且可能還有返回值。
C語言的函數調用方式:C語言使用「靜態綁定」,就是在編譯期就能決定運行時所應調用的函數。
給對象發送消息能夠這樣寫:
id returnValue = [someObject messageName:parameter];
本例中,someObject 叫作「接收者」,messageName 叫作「選擇子」。選擇子與參數合起來稱爲「消息」。編譯器看到此消息後,將其轉換爲標準的C語言函數調用,所調用的函數乃是消息傳遞機制中的核心函數,叫作 objc_msgSend
,其「原型」以下:
//這是個「參數個數可變的函數」,能接受兩個或兩個以上的參數
void objc_msgSend(id self, SEL cmd, ...)複製代碼
編譯器會把本例中的消息轉換爲以下函數:
id returnValue = objc_msgSend(someObject, @selector(messageName:), parameter);
對象在收到沒法解讀的消息以後會發生什麼狀況?-- 會啓動「消息轉發」機制
消息轉發過程,分爲兩大階段:
第一階段:先徵詢接收者所屬的類,看其是否能動態添加方法,以處理當前這個「未知的選擇子」,這叫作「動態方法解析」。
若是第一階段執行完,那麼接收者本身就沒法再以動態新增方法的手段來響應包含該選擇子的消息。此時來到第二階段,這裏涉及「完整的消息轉發機制」。
第二階段:會先讓接收者看看有沒有其餘對象能處理這條消息,如有,則運行時系統會把消息轉發給那個對象,因而消息轉發過程結束,一切正常。
第三階段:若沒有「被援的接收者」,則啓動完整的消息轉發機制,運行時系統會把與消息有關的所有細節都封裝到 NSInvocation 對象中,再給接收者最後一次機會。
對象在收到沒法解讀的消息後,首先將調用其所屬類的下列類方法:
+ (BOOL)resolveInstanceMethod:(SEL)selector
該方法的參數就是那個未知的選擇子,其返回值是布爾類型,表示這個類是否能新增一個實例方法用一處理此選擇子。
在繼續往下執行轉發機制以前,奔雷有機會新增一個處理此選擇子的方法。
假設還沒有實現的方法不是實例方法而是類方法,那麼運行時系統會調用另一個方法,該方法是:resovleClassMethod:
調用 class_addMethod:
來新增一個實例方法
當前接收者還有第二次機會能處理未知的選擇子。在此,運行時系統會問它:能不能把這條消息轉給其餘接收者來處理。會調用下列方法:
- (id)forwardingTargetForSelector:(SEL)selector
該方法的參數就是那個未知的選擇子,若當前接收者能找到被援對象,則將其返回。若找不到,就返回 nil。
咱們沒法操做經由這一步所轉發的消息。若想在發送給被援接收者以前先修改消息內容,那就的經過完整的消息轉發機制來作了。
首先建立 NSInvocation 對象,把與還沒有處理的那條消息有關的所有細節都封於其中。在觸發 NSInvocation 對象時,「消息派發系統」將親自出馬,把消息指派給目標對象。會調用下列方法:
- (void)forwardInvocation:(NSInvocation *)invocation
實現此方法時,若發現某調用操做不該由本類處理,則需調用超類的同名方法。這樣的話,繼承體系中的每一個類都有機會處理此調用請求,直至 NSObject。
若是最後調用了 NSObject 類的方法,那麼該方法會調用 「doesNotRecognizeSelector:
」 以拋出異常,此異常代表選擇子最終未能獲得處理。
消息轉發全流程:
給定的選擇子名稱相對應的方法能夠在運行期改變,此方案成爲「方法調配」。
類的方法列表會把選擇的名稱映射到相關的方法實現之上,使得「動態消息派發系統」可以據此找到應該調用的方法。這些方法均爲函數指針的形式來表示,這種指針叫作IMP,原型:
id (*IMP) (id, SEL, ...)
想交換方法的實現,可用下列函數:
void method_exchangeImplementaions(Method m1, Method m2)
此函數的兩個參數表示待交換的兩個方法實現,而方法實現則可經過下列函數得到:
Method class_getInstanceMethod(Class aClass, SEL aSelector)
舉個例子:
假設在調用 lowercaseString
時記錄某些信息,這時就能夠經過交換方法實現來達成此目標。咱們新編寫一個方法,在此方法中實現所需的附加功能,並調用原有實現。
新增的方法添加至NSString的一個分類中:
//In the .h
@interface NSString (Category)
- (NSString *)eoc_myLowercaseString;
@end
//In the .m
@implementcation NSString (Category)
- (NSString *)eoc_myLowercaseString {
NSString *lowercase = [self eoc_myLowercaseString];
NSLog(@"%@ => %@", self, lowercase);
return lowercase;
}
/* 這段代碼看上去好像會陷入遞歸調用的死循環,不過你們要記住, 此方法是準備和 lowercaseString 方法互換的,因此不會有問題 */
@end複製代碼
最後經過下列代碼交換這兩個方法實現:
Method oMethod = class_getClassMethod([NSString class], @selector(lowercaseString));
Method sMethod = class_getClassMethod([NSString class], @selector(eoc_myLowercaseString));
method_exchangeImplementations(oMethod, sMethod);複製代碼
經過此方案,開發者能夠爲那些黑盒方法增長日誌記錄功能。
id
,它能指代任意的 Objective-C
對象類型。typedef struct objc_object {
Class isa;
} *id;複製代碼
NSString *pointerVariable = @"Some string";
這個例子中,所用的對象「是一個」 (isa)NSString
,因此其 「isa」
指針就指向 NSString
。typedef struct objc_class *Class;
struct objc_class {
Class isa;
Class super_class;
const char *name;
long version;
long info;
long instance_size;
struct obje_ivar_list *ivars;
struct objc_method_list **methodLists;
struct objc_cache *cache;
struct objc_protocol_list *protocols;
};複製代碼
isa指針
,這說明 Class 自己亦爲 OC 對象。isa指針
所指向的類型),叫作「元類」(metaclass),用來表述類對象自己所具有的元數據。「類方法」就定義於此處,這些方法能夠理解爲對象的實例方法。每一個類僅有一個「類對象」,僅有一個與之相關的「元類」。super_class
定義了本類的超類,它確立了繼承關係。而 isa指針
描述了實例所屬的類。「isMemberOfClass:」
可以判斷出對象是否爲某個特定類的實例。"isKindOfClass:"
可以判斷出對象是否爲某類或其派生類的實例。