第六條:理解“屬性”這一概念
- 在物件介面的定義中,就可以使用屬性,這是一種標準的寫法,能夠訪問封裝在物件里的資料
- 屬性的優勢
- 如果使用了屬性的話,那么編譯器就會自動撰寫訪問這些屬性所需的方法,此程序叫做“自動合成”
- 編譯器還會自動向類中添加適當型別的實體變數(可以使用@synthesize語法來指定實體變數的名字)
- 可以使用@dynamic關鍵字,編譯器就不會為上面這個類自動合成存取方法或實體變數,
@interface EOCPerson : NSObject
@property (nonatomic, copy) NSString *firstName;
@property (nonatomic, copy) NSString *lastName;
//上面寫出來的類于下面這種寫法等效
- (NSString*)fullName;
-(void)setFullName:(NSString*)fullName;
- 訪問屬性——點語法/直接呼叫存取方法,這兩者等效
EOCPerson *person = [[EOCPerson alloc] init];
//兩種寫法是一樣的
person.firstName = @"Bob";
[person setFirstName:@"Bob"];
//兩種寫法是一樣的
NSString *lastName = person.lastName;
NSString *lastName1 = [person lastName];
- 屬性特質
-
原子性
- atomic:默認情況下,由編譯器所合成的方法會通過鎖定機制確保其原子(atomic)
- nonatomic: 如果屬性具備nonatomic特質,則不使用同步鎖,
- 一般都寫nonatomic
有效的屬性值,若是不加鎖的話(即nonatomic),那么其中一個執行緒正在改寫某屬性時,另一個執行緒突然闖入,就有可能讀到不準確的數值,如果開發過iOS程式,你就會發現,所有的屬性都宣告為 nonatomic,這樣做的歷史原因是:在iOS程式中使用同步鎖的開銷較大,這會帶來性能問題,一般情況下并不要求屬性必須是“原子的”,因為這并不能保證“執行緒安全”,若要實作“執行緒安全”的操作,還需采用更為深層的鎖定機制才行,例如:一個執行緒在連續多次讀取某屬性值的程序中有別的執行緒在改寫該值,那么即便屬性宣告為atomic,也還是會讀到不同的屬性值,因此,開發iOS程式時一般都會使用nonatomic屬性,但是在開發MacOS X程式時,使用atomic屬性通常時沒有問題的,
-
讀寫權限
- readwrite:擁有“獲取方法”(getter)和“設定方法”(setter)
- readonly:僅擁有“獲取方法”(getter)

3.記憶體管理語意
- assign 只會針對“純量型別”例如CGFloat或NSInteger
- strong 設定方法回先保留新值,并釋放舊值,然后再將新值設定上去
- weak 既不保留新值,也不釋放舊值,同assign類似,指向被賦值的物件,該物件也可能被回收
- unsafe_unretained 類似于week,不同是指標所參考的物件回收之后,該指標不會被賦值為nil,該指示符極少用,
- copy 此特質所表達的所屬關系與strong類似,然而設定方法并不保留新值,而是將其“拷貝”(copy),NSString一般使用copy,因為有可能會是NSMutableString類的實體,字串的值可能會在非自愿情況下被修改,只要實作屬性所用的物件是“可變的”,就應該設定新屬性值時拷貝一份
ARC是iOS 5推出的新功能,全稱叫 ARC(Automatic Reference Counting),簡單地說,就是代碼中自動加入了retain/release,原先需要手動添加的用來處理記憶體管理的參考計數的代碼可以自動地由編譯器完成了,該機制在iOS 5/ Mac OS X 10.7 開始匯入,利用 Xcode4.2 可以使用該機制,簡單地理解ARC,就是通過指定的語法,讓編譯器(LLVM 3.0)在編譯代碼時,自動生成實體的參考計數管理部分代碼,
iOS 屬性關鍵字
Objective-C高級編程之參考計數,看我就夠了
被無數人寫過的assign,retain,strong,weak,unsafe_unretained,還有copy
- 方法名
可以指定存取方法的方法名
第七條:在物件內部盡量直接訪問實體變數
-
點語法和直接訪問實體變數
在物件內部讀取資料時,應該直接通過實體變數來讀,而寫入資料時,則應通過屬性來寫,
//點語法 self.firstName //直接訪問 _firstName- 直接訪問比較快
- 直接訪問直接跳過記憶體管理語意(如copy),不會呼叫其“設定方法”
- 直接訪問不會觸發KVO
- 點語法訪問可以在setter/getter處設定斷點檢驗錯誤
-
在初始化方法及dealloc方法中,總是應該直接通過實體變數來讀寫資料,因為子類可能會覆寫,
-
有時會使用惰性初始化技術配置某份資料,這種情況下,需要通過屬性來讀取屬性來讀取資料,
如果使用來“惰性初始化”技術,那么必須通過存取方法來訪問brain屬性(在重寫getter方法時,不能通過self.label對屬性label進行訪問,因為用點語法就相當于呼叫set或get方法,導致回圈呼叫,在外部呼叫點語法時就形成無限回圈,)
iOS 懶加載_Echo &的博客-CSDN博客
第八條:理解“物件等同性”這一概念
iOS - 判斷物件相等,重寫isEqual,hash_愛爾蘭堤壩的博客-CSDN博客
- ==比較的是兩個指標本身,而不是其指向的物件
- isEqual來判斷兩個物件的等同性:一般兩個型別不同的物件總是不相等的
- 獨有的等同性判斷方法:比如isEqualToString,傳遞給該方法的物件必須是NSString
NSObject協議中有兩個用于判斷等同性的關鍵方法:
-(BOOL)isEqual:(id)object;
//1. 首先判斷兩個指標是否相等
//2. 比較兩個物件所屬的類
//3. 檢驗每個屬性是否相等
//4.實作hash方法
- (NSUInteger)hash;
-(BOOL)isEqual:(id)object
{
//1. 首先判斷兩個指標是否相等
//2. 比較兩個物件所屬的類
//3. 檢驗每個屬性是否相等
//4.實作hash方法
if (self == object) return YES;
if ([self class] != [object class]) return NO;
Person *otherPerson = (Person*)object;
if (![_firstName isEqualToString:otherPerson.firstName]) {
return NO;
}
if (![_lastName isEqualToString:otherPerson.lastName]) {
return NO;
}
if (_age != otherPerson.age) {
return NO;
}
return YES;
}
//這種做法能提高效率,又能使生成的哈希碼至少位于一定范圍之內
-(NSUInteger)hash
{
NSUInteger firstNameHash = [_firstName hash];
NSUInteger lastNameHash = [_lastName hash];
NSUInteger ageHash = _age;
return firstNameHash^lastNameHash^ageHash;
}
如果“isEqual:”方法判定兩個物件相等,那么其hash方法也必須回傳同一個值,但是,如果兩個物件的hash方法回傳同一個值,那么“isEqual:”方法未必會認為兩者相等,
第九條:以“類組模式”隱藏實作細節
- 類族模式:該模式可以靈活對應多個類,將他們的實作細節隱藏在抽象基類后面,以保持介面簡潔,系統框架中經常用到類族
- 創建類族
- 經常要像類族中新增物體子類,從公共抽象類中繼承子類時要當心,
- 子類應該繼承自類族中的抽象基類
- 子類應該定義自己的資料存盤方式
- 子類應該覆寫超類檔案中指明需要覆寫的方法
第十條:在既有類中使用關聯物件存放自定義資料
#import "ViewController.h"
#import <objc/runtime.h>
static void *MyAlertViewKey = "MyAlertViewKey";
@interface ViewController ()<UIAlertViewDelegate>
@end
@implementation ViewController
//在創建警告框視圖的時候直接把處理每個按鈕的邏輯都寫好,可以通過關聯物件來做
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"提示" message:@"資訊" delegate:self cancelButtonTitle:@"NO" otherButtonTitles:@"YES", nil];
void (^block)(NSInteger) = ^(NSInteger buttonIndex){
if (buttonIndex == 0) {
NSLog(@"NO");
} else {
NSLog(@"YES");
}
};
objc_setAssociatedObject(alert, MyAlertViewKey, block, OBJC_ASSOCIATION_COPY);
[alert show];
}
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
void(^block)(NSInteger) = objc_getAssociatedObject(alertView, MyAlertViewKey);
block(buttonIndex);
}
-
此方法根據給定的鍵從某物件中獲取相應的關聯物件值
objc_getAssociatedObject(<#id _Nonnull object#>, <#const void * _Nonnull key#>);
-
此方法以給定的鍵和策略為弄物件設定關聯物件值
objc_setAssociatedObject(<#id_Nonnull object#>, <#const void * _Nonnull key#>, <#id
_Nullable value#>, <#objc_AssociationPolicy policy#>); -
此方法移除指定物件的全部關聯物件
objc_removeAssociatedObjects(<#id _Nonnull object#>)
關聯物件
runtime - 關聯物件_CatStarXcode的博客-CSDN博客
可以通過“關聯物件”機制來把兩個物件連起來;
定義關聯物件時可指定記憶體管理語意,用以模仿定義屬性時所采用的“擁有關系(保留)”與“非擁有關系(不保留)”
只有在其他做法不可行時才應選用關聯物件,因為這種做法通常會引入難于查找的bug;
第十一條:理解objc_msgSend的作用
在OC中,如果向某物件傳遞訊息,那就會使用動態系結機制來決定需要的方法
首先給物件發送資訊,
void returnValue = [someObject messageName:parameter];
在本例中,someObject叫做“接受者”,messageName叫做“選擇子”,選擇子與引數合起來稱為“訊息”,編譯器看到此訊息后,將其轉換為一條標準的C語言函式呼叫
所呼叫的函式乃是訊息傳遞機制中的核心函式,叫做objc_msgSend,原型如下:
void objc_msgSend(id self, SEL cmd,...)
//編譯器會將剛剛那個例子中的訊息轉換為如下函式
id returnValue = objc_msgSend(SomeObject, @selector(messageName:),parameter);
objc_msgSend函式會依據接受者與選擇子的型別來呼叫適當的方法
objc_msgSend 函式會依據接收者與選擇子的型別來呼叫適當的方法,為了完成此操作,該方法需要接收者所屬的類中搜尋其“方法串列”(list of methods),如果能找到與選擇子名稱相符的方法,就跳至其實作代碼,若是找不到,那就沿著繼承體系繼續向上查找,等找到合適的方法之后再跳轉,如果最侄訓是找不到相符的方法,那就執行“訊息轉發”(message forwarding)操作;這么說來,想呼叫一個方法似乎需要很多步驟,
所幸objc_msgSend會將匹配結果快取在“快速映射表”(fast map)里面,每個類都有這樣一塊快取,若是稍后還向該類發送與選擇子相同的訊息,那么執行起來就很快了,
訊息由接收者,選擇子及引數構成,給某物件“發送訊息”(invoke a message)也就相當于在該物件上“呼叫方法”(call a method);然后通過“動態訊息派發系統“即objc_msgSend函式,查出對應的方法,并執行代碼
第十二條:理解訊息轉發機制
當我們在.h檔案中寫了一個方法,但是卻沒寫實作,你會發現你可以呼叫這個方法,編譯器在編譯器不會報錯,它相信在運行期可以找到方法實作,因為并沒有,所以在運行的時候會崩,
當物件收到無法解讀的訊息后,將啟動訊息轉發機制,程式員可經由此程序告訴物件應該如何處理未知訊息,
首先將呼叫其所屬類的一個方法,這個方法表示這個類能否新增一個實體方法用來處理這個選擇子,在繼續向下執行轉發機制之前,本類有機會新增一個處理此選擇子的方法,
+ (BooL)resolveInstanceMethod:(SEL)sel
+ (BOOL)resolveInstanceMethod:(SEL)sel {
NSString *selectorString = NSStringFromSelector(sel);
Method method = class_getInstanceMethod([self class], @selector(rush));
if ([selectorString isEqualToString:@"rushB"]) {
class_addMethod(self, sel, method_getImplementation(class_getInstanceMethod([self class], @selector(rush))), method_getTypeEncoding(method));
return YES;
}
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types);

這個方法主要接受四個引數
- Class cls 要添加方法的類
- SEL name 被添加方法的名字
- IMP imp 添加的方法的實作
- const char *types 描述方法引數型別的字符陣列,
動態方法決議
- (BOOL)resolveInstanceMethod:(SEL)selector;
備援接收者
- (id)forwordingTargetForSelector:(SEL)selector;
完整的訊息轉發
- (void)forwordInvocation:(NSInvocation *)invocation;
若物件 無法回應某個選擇子,則進入訊息轉發流程;
通過運行期的動態方法決議功能,我們可以在需要用到某個方法時再將其加入類中
物件可以把其無法解讀的某些選擇子轉交給其他物件來處理
經過上述兩步之后,如果還是沒有處理選擇子,那就啟動完整的訊息轉發機制;

第十三條:用“方法調配技術” 除錯“黑盒方法”
-
方法調配:類的方法串列會把選擇子的名稱映射到相關的方法實作上,這種方法均以函式指標的形式來表示,這種指標叫做IMP
原型
id (*IMP) (id, SEL,...)NSString類可以回應lowercaseString,uppercaseString,capitalizedString等選擇子

可以新增選擇子,可以交換選擇子

可以用于除錯,但很少有人在除錯程式之外的場合用上述“方法調配”來永久改動某個類的功能,
- 在 Objective-C 中,selector,Method 和 implementation(IMP) 都是 Runtime 的組成部分,在實際開發中它們常常是可以相互轉換來處理訊息的發送的,選擇子代表方法在 Runtime 期間的識別符號,為 SEL 型別,雖然 SEL 是 objc_selector 結構體指標,但實際上它只是一個 C 字串,在類加載的時候,編譯器會生成與方法相對應的選擇子,并注冊到 Objective-C 的 Runtime 運行系統,
選擇子其實是方法的名稱,不同類中方法名相同引數不同的倆個方法,他們的選擇子是相同的,
// Method
struct objc_method {
SEL method_name;
char *method_types;
IMP method_imp;
};
方法名 method_name 型別為 SEL,前面提到過相同名字的方法即使在不同類中定義,它們的方法選擇器也相同,
方法型別 method_types 是個 char 指標,其實存盤著方法的引數型別和回傳值型別,即是 Type Encoding 編碼,(即型別編碼)
method_imp 指向方法的實作,本質上是一個函式的指標
第十四條:理解“類物件”的用意
- 每個實體都有一個指向Class物件的指標,用以表明其型別,而這些Class物件則構成了類的繼承體系

typedef struct objc_object {
Class isa;
} id;
每個物件結構體的首個成員是Class類的變數,該變數定義了物件所屬的類,通常稱為*“isa”指標**,
- 如果物件型別無法在編譯期確定,那么就應該使用型別資訊查詢方法來探知
- -(BOOL) isKindOfClass: class-object
判斷是否是這個類或者這個類的子類的實體
- -(BOOL) isMemberOfClass: class-object
判斷是否是這個類的實體
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/255237.html
標籤:其他
