@synthesize testString = _testString;
@dynamic testString; _testString //Use of undeclared identifier '_testString'
@interface TestObject () //雖然這個屬性已是隻讀性質,也要寫上具體的語義,以此代表初始化方法在設置這些值時所用方法 @property(copy,readonly) NSString *testString; @end @implementation TestObject - initWithString:(NSString *)string { self = [super init]; if (self) { //用初始化方法設置好屬性值以後,就不要再改變了,此時屬性應設爲「只讀」 _testString = [string copy]; } return self; } @end
NSString *textA = @"textA"; NSString *textAnother = [NSString stringWithFormat:@"textA"]; NSLog(@"%d",textA == textAnother);// 0 NSLog(@"%d",[textA isEqual:textAnother]);// 1 NSLog(@"%d",[textA isEqualToString:textAnother]);// 1
@interface TestObject : NSObject @property NSString *testString; @end @implementation TestObject - (BOOL)isEqual:(id)object { if (self == object) return YES; if ([self class] != [object class]) return NO; TestObject *otherObject = (TestObject *)object; if (![self.testString isEqualToString:otherObject.testString]) { return NO; } return YES; } -(NSUInteger)hash { //在沒有性能問題下,hash 方法能夠直接返回一個數 return 1227; } @end
在繼承體系中判斷等同性,還需判斷是不是其子類
相同的對象必須具備相同的哈希碼,可是相同哈希碼的對象卻未必相同
程序員
- (BOOL)isEqualToTestObject:(TestObject *)testobject { if (self == testobject) { return YES; } if (![self.testString isEqualToString:testobject.testString]) { return NO; } return YES; } - (BOOL)isEqual:(id)object { if ([self class] == [object class]) { return [self isEqualToTestObject:(TestObject *)object]; }else { return [super isEqual:object]; } }
有時候無需將全部數據逐個比較,只根據其中部分數據便可判明兩者是否相等。objective-c
比方說一個模型類的實例是根據數據庫的數據建立而來,那麼其中可能會含有一個惟一標識符(unique identifier),在數據庫中用做主鍵。這時候,咱們就能夠根據標識符來斷定等同性,尤爲是此屬性聲明爲 readonly 時更應該如此。只要標識符相等,就能夠說明這兩個對象是由相同數據源建立,據此判定,其餘數據也相等。
固然,只有類的編寫者才知道那個關鍵屬性是什麼。數據庫
要點:不要盲目的逐個檢測每條屬性,而是應該按照具體需求制定檢測方案編程
在 OC 中,對象收到消息以後,究竟該調用哪一個方法徹底於運行期決定,甚至能夠在運行時改變,這些特性使 OC 成爲一門真正的動態語言。
給對象發送消息能夠這樣寫:緩存
id value = [obj messageName:parameter]安全
obj 叫作接收者,messageName 叫作 selector,selector 和參數合起來稱爲消息
編譯器看到此消息後,將其轉換爲一條標準的 C 語言函數調用網絡
void objc_msgSend(id _Nullable self, SEL _Nonnull op, ...)session
第一個參數表明接收者,第二個表明 selector(SEL是selector類型)
這是個「參數個數可變的函數」,」…「 表明後續參數,就是消息中的參數
數據結構
//Sends a message with a simple return value to the superclass of an instance of a class. objc_msgSendSuper(struct objc_super * _Nonnull super, SEL _Nonnull op, ...) //Sends a message with a data-structure return value to an instance of a class. objc_msgSend_stret(id _Nullable self, SEL _Nonnull op, ...) //Sends a message with a data-structure return value to the superclass of an instance of a class. objc_msgSendSuper_stret(struct objc_super * _Nonnull super, SEL _Nonnull op, ...)
<return_type> Class_selector(id self, SEL _cmd, ...)
每一個類都有一張表格,selector 的名稱就是查表時所用的 key
多線程
原型的樣子和 objc_msgSend 很像,並且函數的最後一項操做是調用另外一個函數並且不會將其返回值另做他用,就能夠利用」尾調用優化「技術,令」跳至方法實現「變得簡單。
尾調用技術:編譯器會生成跳轉至另外一函數所需的指令碼,並且不會向調用堆棧中推入新的」棧幀「
要點:發給某對象的所有消息都要由「動態消息派發系統」來處理,該系統會查出對應的方法,並執行其代碼
- 由於在運行期能夠繼續向類中添加方法,因此編譯器在編譯期還沒法確知類中是否有某個方法的具體實現。 - 當對象接收到沒法解讀的消息,就會觸發「消息轉發機制」,程序員能夠經由此過程告訴對象如何處理未知消息
消息轉發分爲兩大階段
//當未知的 selector 是實例方法時的調用 + (BOOL)resolveInstanceMethod:(SEL)sel ; //當未知的 selector 是類方法時的調用 + (BOOL)resolveClassMethod:(SEL)sel;
使用這種辦法的前提是:相關方法的實現代碼已經寫好,只等着運行時加入類裏面
//第一步:詢問能不能把未知的消息轉給其餘接收者處理 - (id)forwardingTargetForSelector:(SEL)aSelector ;
若當前接收者能找到備援接收者,則將其返回,若找不到,則返回 nil
-若是返回一個對象,則運行期系統把消息轉給那個對象,因而消息轉發結束
若是返回 nil,執行第二步👇
//第二步:把消息相關的細節封裝在 NSInvocation對象中,再給接收者最後一次機會 - (void)forwardInvocation:(NSInvocation *)anInvocation
能夠在觸發消息前,先以某種方式改變消息內容,好比追加一個參數,或改換 selector,等等
若是實現此方法時,發現某調用操做不該由本類處理,則調用超類的調用方法。若是繼承體系的類都不處理此調用請求,那就最後調用 NSObject 類的方法,那麼該方法會執行👇方法
//拋出異常,代表 selector 未能處理 - (void)doesNotRecognizeSelector:(SEL)aSelector;
+ (BOOL)resolveInstanceMethod:(SEL)sel { NSString *selString = NSStringFromSelector(sel); if ([selString hasPrefix:@"set"]) { //最後一個參數表示待添加方法的類型編碼(type encoding) class_addMethod([self class], sel, (IMP)autoDictionarySetter, "v@:@"); }else { class_addMethod([self class], sel, (IMP)autoDictionaryGetter, "@@:"); } return [super resolveInstanceMethod:sel]; }
注
SEL 是方法編號,SEL類型經過 @selector() 提取
IMP 是一個函數指針,保存了方法的地址
type encoding:
v 表明 void
: 表明 (method selector)SEL
@ 表明 object(whether statically typed or typed id);
在 setter 實現的時候給一個字典添加鍵值對,getter 從字典獲取值,這樣的實現就在 iOS 的 CoreAnimation 框架中的 CALayer 類裏面。CALayer 是一種「兼容於鍵值編碼的」容器類,能向其中隨意添加屬性,而後以鍵值對的方式訪問。
NSString *pointerVariable = @"Some string";
pointerVariable 是存放內存地址的變量,而 NSString 自身的數據就存於那個地址中
//對於通用的對象類型 id ,其自己已是指針了,能夠這樣寫 id genericTypeString = "id String"
上面兩種不一樣的定義方式的區別在於,若是聲明時指定了類型,在實例上調用沒有的方法時,編譯器會發出警告。而 id 類型,編譯器默認它能響應全部本項目中存在的方法。
自此,咱們知道的了對象就是一個含有指向 Class 類的 isa 指針的結構體,Class 也是一個對象,它其中也有一個 isa 指針。那麼類對象的 isa 指針指向哪裏,類對象的父類又是什麼呢?看下圖
這樣 OC 中類和對象的關係就清楚了。對象的 isa 指針指向類對象,類對象的 isa 指針指向 metaClass,譯爲元類。元類的 isa 指針指向根元類
由此能夠看出來 Apple 設計類對象的用意就是爲了存儲對象的信息,好比對象方法,對象屬性,遵照的協議,對象的類的父類,等等,而類對象的相關信息被存儲在元類中。
- initWithString:(NSString *)string { if (self = [super init]) { if (string == nil) { self = nil; }else { //Initialize instance } } return self; }
NSError 的用法更加靈活,由於經過此對象,咱們能夠獲知錯誤的具體信息。NSError 對象裏封裝了三條消息:
Error code(錯誤碼,其類型爲整數)
獨有的錯誤代碼,用來指明在某個範圍內具體發生了何種錯誤。某個特定範圍內可能會發生一系列相關錯誤,這些錯誤狀況一般採用 enum 來定義。例如:當 http 請求出錯時,把 HTTP 狀態碼設爲錯誤碼.
@{NSLocalizedDescriptionKey : @"Image data is nil"}
/* Sent as the last message related to a specific task. Error may be * nil, which implies that no error occurred and this task is complete. */ - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(nullable NSError *)error;
這個委託方法未必非得實現不可,是否是必須處理此錯誤,可交由用戶來處理。
NSError 的另外一種常見用法:經由方法的「輸出參數」返回給調用者。好比在 MJExtension 中:
/** * 經過字典來建立一個模型 * @param keyValues 字典 * @return 新建的對象 */ + (instancetype)objectWithKeyValues:(id)keyValues; + (instancetype)objectWithKeyValues:(id)keyValues error:(NSError **)error;
入參是一個指針,指向另外一個指針,那個指針指向 NSError 對象。使用以下方式獲取錯誤信息。
NSError *error = nil; MyModel *model = [MyModel objectWithKeyValues:responseObject error:&error]; if (error) { //獲取 error 信息 }
實際上,在使用 ARC 時,編譯器會把方法簽名中的 NSError ** 轉換成 NSError *__autoreleasing *,也就是說,指針所指的對象會在方法執行完畢後自動釋放。
MJ 經過如下代碼把 NSError 對象傳遞到「輸出參數」中:
// 構建錯誤 #define MJBuildError(error, msg) \ if (error) *error = [NSError errorWithDomain:msg code:250 userInfo:nil];
-這段代碼以 *error 語法爲 error 參數」解引用「,也就是說,error 所指的那個指針如今要指向一個新的 NSError 對象了。在解引用前,必須確保 error 不是 nil,由於空指針解引用會致使「段錯誤」並使程序奔潰。
Objective-C 語言使用引用計數來管理內存,也就是說,每一個對象都有一個能夠遞增遞減的計數器,若是想要保留對象,就遞增,用完了以後就遞減,當計數爲 0 時,對象就銷燬。要寫出優秀的 OC 代碼,除了知道這些,還必須知道其中的原理。
- (void)retain;//使遞增 - (void)release;//使遞減 - (void)autorelease;//稍後清理「自動釋放池」時再進行遞減
查看引用計數的方法是 retainCount,不過這個方法不是很準確,蘋果官方也不推薦
應用程序在生命期內會建立不少對象,這些對象相互聯繫着。例如表示我的信息的對象會引用表示名字的字符串對象。對象若是持有其餘對象的強引用,那麼前者就擁有後者。也就是說,對象想讓它所引用的對象繼續存活,可將其「保留」。等用完了以後再釋放。
下圖表示了一個對象從保留到釋放的過程
按圖能夠想象,有一些其餘對象想要保持 B 或 C 對象存活,而引用程序中又會有另一些對象想讓這些對象存活。若是按「引用樹」回溯,那麼最終會發現有一個「根對象」。在 iOS 中,就是 UIApplication 對象。是在應用程序啓動時建立的對象。
當對象的引用計數爲 0 ,對象所佔內存」解除分配「以後,就被放回」可用內存池「。此時再去調用該對象,可能會有不一樣狀況發生:若是此時內存對象已經作了他用,就會引發程序奔潰;若是此時對象內存未被複寫,就可能正常運行。因而可知,由過早釋放對象而致使的 bug 很難調試。
- (void)setFoo:(id)foo { [foo retain]; [_foo release]; _foo = foo; }
此方法保留新值,釋放舊值,而後更新變量指向新值。順序很重要。若是先釋放舊值,那此對象就被回收。後續操做就都沒有意義了。
塊是一種可在 C 、C++、OC 代碼中使用的「詞法閉包」,它很是有用,能夠把一段代碼像對象同樣傳遞。在定義「塊」的範圍內,它能夠訪問到其中全部的變量。
說「塊」必須離不開說多線程。蘋果公司設計的多線程編程的核心就是「塊」(block)和「大中樞派發」(GCD),這雖然是兩種不一樣的技術,但他們是一併引入的。
GCD 提供了對線程的抽象,而這種抽象基於「派發隊列」。開發者可將塊排入隊列中,由 GCD 處理全部調度事宜。
int adder = 8; //定義一個變量名爲 myBlock 的塊 //語法結構:return_type (^blockName) (parameters) int (^addBlock)(int num) = ^(int num){ //在塊內部可使用外部變量 return num+ adder; }; int addEight = addBlock(5);
這裏有個疑問,塊裏面的實例變量在沒有 __block 修飾的狀況下卻也是能夠改變值的,爲何?
塊自己是對象,因此他也有內存空間。
void (^myBlock)(); if (/* some condition*/) { myBlock = ^{}; }else { myBlock = ^{}; } myBlock();
定義在 if 和 else 裏面的兩個塊都分配在棧內存中。編譯器會給每一個塊分配好棧內存,可是等離開了相應的範圍以後,編譯器就可能把分配給塊的內存覆寫掉。這樣寫出來的代碼時而正確時而錯誤。
爲解決此問題,可給塊對象發送 copy 消息以拷貝之。這樣就把塊對象從棧內存移到了堆內存中,拷貝後的塊,就能夠在定義它的範圍以外使用了。並且,一旦複製到堆上,塊對象就成了具備引用計數的對象了。後續的複製操做就只是遞增引用計數了。
明白了這一點,咱們只要給代碼加上兩個 copy 調用就安全了。
void (^myBlock)(); if (/* some condition*/) { myBlock = [^{} copy]; }else { myBlock = [^{} copy]; } myBlock();
void (^myBlock)() = ^{ NSLog(@"This is a block"); };
因爲運行該塊的全部信息都能在編譯的時候肯定,因此可把他作成全局塊。
這種塊不會捕捉任何狀態(好比外圍的變量等),能夠聲明在全局內存中,不須要在每次用到的時候於棧中建立。這種塊實際上至關於單例。