第1章:熟悉Objective-C
?? 第1條:瞭解 Objective-C 語言的起源
- Objective-C 為C語言新增了物件導向的特性,是其超級。Objective-C 說那個動態繫結的訊息結構,也就是說,在執行時才檢查物件型別。接收一條訊息之後,究竟應執行何種程式碼,由執行期環境而非編譯器來決定。
- 理解C語言的核心概念有助於寫好Objective-C程式。尤其要掌握記憶體模型和指標。
NSString *theString = @"Hello World";
NSString *theString2 = @"Hello World";
NSLog(@"theString:%p --- theString:2%p",theString,theString2);
複製程式碼
列印結果:
theString:0x11bb0d158 --- theString:20x11bb0d158
複製程式碼
兩個變數為指向同一塊記憶體的相同指標。此時將 theString2
賦值為 “Hello World !!!!”
theString2 = @"Hello World !!!!";
NSLog(@"theString:%p --- theString:2%p",theString,theString2);
複製程式碼
列印結果:
theString:0x12002e158 --- theString:20x12002e198
複製程式碼
此時,兩者變為不同的記憶體地址。所以,物件的本質是指向某一塊記憶體區域的指標,指標的儲存位置取決於物件宣告的區域和有無成員變數指向。若在方法內部宣告的物件,記憶體會分配到棧中,隨著棧幀彈出而被自動清理;若物件為成員變數,記憶體則分配在堆區,宣告週期需要程式設計師管理。
另外在探尋物件本質的過程中發現物件的本質為宣告為isa的指標,一枚指標在32位計算機佔4位元組,64位計算機佔8位元組,真正在iOS系統中,isa指標實際佔用了16位元組的記憶體區域,在此文中通過 clang
將OC程式碼轉化為 C++
程式碼探究了一個物件所佔的實際記憶體大小,詳細可參閱 iOS底層原理探究- NSObject 記憶體大小
?? 第2條:在類的標頭檔案中儘量少引入其他標頭檔案
- 除非確有必要,否則不要引入標頭檔案,一般來說,應該在某個類的標頭檔案中使用向前宣告來提及別的類,並在實現檔案中引入那些類的標頭檔案。這樣做可以儘量降低類之間的耦合。
- 有時無法使用向前宣告,比如要宣告某個類遵循一項協議,儘量把“該類遵循某協議” 的這條宣告移至“class-continuation 分類中”。如果不行的話,就把協議單獨放在某一個標頭檔案中,然後將其引入。
//Student.h
@class Book; //向前引用,避免在 .h 裡匯入其他檔案
@interface Student : NSObject
@property (nonatomic, strong) BOOK *book;
@end
//student.m
#import "Book.h"
@implementation Student
- (void)readBook {
NSLog(@"read the book name is %@",self.book);
}
@end
複製程式碼
?? 第3條:多用字面量語法,少用與之等價的方法
- 應該使用字面量語法來建立字串、數值、陣列、字典。與建立此類物件的常規方法相比,這麼做更加簡明扼要。
- 應該通過取下標操作來訪問陣列下標或字典中的鍵所對應的元素。
- 用字面量語法建立陣列或字典,若值中有 nil ,則會丟擲異常。因此,務必確保值裡不含 nil。
0️⃣ 字面數值
NSNumber *number = [NSNumber numberWithInteger:10086];
複製程式碼
改為
NSNumber *number = @10086;
複製程式碼
1️⃣ 字面量陣列
NSArray *books = [NSArray arrayWithObjects:@"演算法圖解",@"高效能iOS應用開發",@"Effective Objective-C 2.0", nil];
NSString *firstBook = [books objectAtIndex:0];
複製程式碼
改為
NSArray *books = @[@"演算法圖解",@"高效能iOS應用開發",@"Effective Objective-C 2.0"];
NSString *firstBook = books[0];
複製程式碼
2️⃣ 字面量字典
NSDictionary *info1 = [NSDictionary dictionaryWithObjectsAndKeys:@"極客學偉",@"name",[NSNumber numberWithInteger:18],@"age", nil];
NSString *name1 = [info1 objectForKey:@"name"];
複製程式碼
改為
NSDictionary *info2 = @{
@"name":@"極客學偉",
@"age":@18,
};
NSString *name2 = info2[@"name"];
複製程式碼
3️⃣ 可變陣列與字典
[arrayM replaceObjectAtIndex:0 withObject:@"new Object"];
[dictM setObject:@19 forKey:@"age"];
複製程式碼
改為
arrayM[0] = @"new Object";
dictM[@"age"] = @19;
複製程式碼
4️⃣ 侷限性
1、字面量語法所建立的物件必須屬於 Foundation 框架,自定義類無法使用字面量語法建立。
2、使用字面量語法建立的物件只能是不可變的。若希望其變為可變型別,可將其深複製一份
NSMutableArray *arrayM = @[@1,@"123",@"567"].mutableCopy;
複製程式碼
?️? 第4條:多用型別常量,少用 #define
預處理指令
- 不要用預處理指令定義常量。這樣定義的常量不含型別資訊,編譯器只是會在編譯前據此執行查詢與替換操作。即使有人重新定義了常量值,編譯器也不會產生警告資訊⚠️,這將導致應用程式中的常量值不一致。
- 在實現檔案中使用
static const
來定義“只在編譯單元內可見的常量”。由於此類常量不在全域性符號表中,所以無需為其名稱加字首。 - 在標頭檔案中使用
extern
來宣告全域性常量,並在相關實現檔案中定義其值。這種常量要出現在全域性符號表中,所以名稱應該加以區隔,通常用與之相關的類名做字首。
預處理指令是程式碼拷貝,在編譯時會將程式碼中所有預處理指令展開填充到程式碼中,減少預處理指令也會加快編譯速度。
私有常量
.m
static const NSTimeInterval kAnimationDuration = 0.3;
複製程式碼
全域性常量
.h
extern NSString * const XWTestViewNoticationName;
.m
NSString * const XWTestViewNoticationName = @"XWTestViewNoticationName";
複製程式碼
?? 第5條:用列舉表示狀態、選項、狀態碼
- 應該用列舉來表示狀態機的狀態、傳遞給方法的選項以及狀態碼等值,給這些值起個易懂的名字。
- 如果把傳遞給某個方法的選項表示為列舉型別,而多個選項又可以同時使用,那麼就將各選項定義為2的冪,以便通過按位或操作將其組合起來。
- 用
NS_ENUUM
與NS_OPTIONS
巨集來定義列舉型別,並指明其底層資料型別。這樣做可以確保列舉是用開發者所選的底層資料型別實現出來的,而不會採用編譯器所選型別。 - 在處理列舉型別的
switch
語句中不要實現default
分支。這樣的話,加入新列舉之後,編譯器就會提示開發者:switch
語句並未處理所以列舉。
/// 位移列舉
typedef NS_OPTIONS(NSUInteger, XWDirection) {
XWDirectionTop = 0,
XWDirectionBottom = 1 << 0,
XWDirectionLeft = 1 << 1,
XWDirectionRight = 1 << 2,
};
/// 常量列舉
typedef NS_ENUM(NSUInteger, SexType) {
SexTypeMale,
SexTypeFemale,
SexTypeUnknow,
};
複製程式碼
第2章:物件、訊息、執行時
?? 第6條:理解“屬性”這一概念
- 可以用
@property
語法來定義物件中所封裝的資料。 - 通過“特質”來指定儲存資料所需的正確語義。
- 在設定屬性所對應的例項變數時,一定要遵從該屬性所宣告的語義。
使用屬性編譯器會自動生成例項變數和改變數的get方法和set方法。
同時可以使用 @synthesize
指定例項變數的名稱,使用 @dynamic
使編譯器不自動生成get方法和set方法。
屬性可分為四類,分別:
1.原子性
atomic
原子性,系統預設。並不是執行緒安全,release
方法不受原子性約束.nonatomic
非原子性
2.讀寫許可權
readwrite
可讀可寫,同時擁有get方法和set方法。readonly
只讀,僅有 get 方法。
3.記憶體管理語義
assign
簡單賦值,用於基本成員型別strong
表示“擁有關係”,設定新值時會保留新值,釋放舊值,再把新值設定給當前屬性。weak
表示“非擁有關係”,設定新值時既不保留新值,也不釋放舊值。同assign
類似,所指物件銷燬時會置nilunsafe_unretained
表示一種非擁有關係,語義同assign
,僅適用於物件型別。當目標物件被銷魂時不會自動清空。copy
表達的關係和strong
類似。區別在於設定新值時不會保留新值,而是將其 拷貝 後賦值給當前屬性。
4.方法名
getter=<name>
指定獲取方法(getter)的方法名, 如:@property (nonatomic, getter=isOn) BOOL on;
setter=<name>
指定設定方法(setter)的方法名。
?? 第7條:在物件內部儘量直接訪問例項變數
- 在物件內部讀取資料時,應該直接通過例項變數來讀,而寫入資料時,則應通過屬性來寫。
- 在初始化方法及
dealloc
方法中,應該直接通過例項變數來讀寫資料。 - 有時會使用懶載入配置某資料,這種情況需要通過屬性來讀取資料。
在物件內部直接使用成員變數比使用點語法的優勢在於,前者不需要經過 Objective-C 的方法派發過程,執行速度會更快,這時編譯器會直接訪問儲存物件例項變數的那塊記憶體。不過直接訪問成員變數不會觸發 KVO
,所以使用點語法訪問屬性還是直接使用成員變數取決於具體行為。
?? 第8條:理解“物件等同性”這一概念
- 若想監測物件的等同性,請提供
isEqual:
與hash
方法。 - 相同物件必須具有相同的雜湊碼,但是兩個雜湊碼相同的物件未必相同。
- 不要盲目地逐個監測每條屬性,而是應該依照具體需求來制定檢測方案。
- 編寫
hash
方法時,應該使用計算速度快而且雜湊碼碰撞機率低的演算法。
常規比較相等的方式 ==
比較的是兩個物件指標是否相同。
在自定義物件重寫 isEqual
方法可使用此方式:
- (BOOL)isEqualToBook:(Book *)object {
if (self == object) return YES;
if (![_name isEqualToString:object.name]) return NO;
if (![_author isEqualToString:object.author]) return NO;
return YES;
}
複製程式碼
在自定義物件重寫 hash
方法可使用此方式:
@implementation Book
- (NSUInteger)hash {
NSUInteger nameHash = [_name hash];
NSUInteger authorHash = [_author hash];
return nameHash ^ authorHash;
}
@end
複製程式碼
?? 第9條:以“類族模式”隱藏實現細節
- 類族模式可以把實現細節隱藏在一套簡單的公共介面後面
- 系統框架中經常使用類族
- 從類族的公共抽象基類中繼承子類時要當心,若有開發文件,則應先閱讀
例如宣告一本書作為基類,通過“類族模式“建立相關的類,對應型別的在子類中實現相關方法。如下:
.h
typedef NS_ENUM(NSUInteger, BookType) {
BookTypeMath,
BookTypeChinese,
BookTypeEnglish,
};
@interface Book : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *author;
+ (instancetype)bookWithType:(BookType)type;
- (void)read;
@end
複製程式碼
.m
@interface BookMath : Book
- (void)read;
@end
@implementation BookMath
- (void)read {
NSLog(@"read The Math");
}
@end
@interface BookChinese : Book
- (void)read;
@end
@implementation BookChinese
- (void)read {
NSLog(@"read The Chinese");
}
@end
@interface BookEnglish : Book
- (void)read;
@end
@implementation BookEnglish
- (void)read {
NSLog(@"read The English");
}
@end
@implementation Book
+ (instancetype)bookWithType:(BookType)type {
switch (type) {
case BookTypeMath:
return [BookMath new];
break;
case BookTypeChinese:
return [BookChinese new];
break;
case BookTypeEnglish:
return [BookEnglish new];
break;
}
}
@end
複製程式碼
?? 第10條:在既有類中使用關聯物件存放自定義資料
- 可以通過“關聯物件”機制把兩個物件連起來
- 定義關聯物件時可指定記憶體管理語義,用以模仿定義屬性時所採用的“擁有關係” 與 “非擁有關係”
- 只有在其他做法不可行時才應選用關聯物件,因為這種做法通常會引入難於查詢的 bug
關聯物件的語法:
#import <objc/runtime.h>
// Setter 方法
void objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key, id _Nullable value, objc_AssociationPolicy policy)
// Getter 方法
id objc_getAssociatedObject(id _Nonnull object, const void * _Nonnull key)
// 移除指定物件的所有關聯物件值
void objc_removeAssociatedObjects(id _Nonnull object)
複製程式碼
例項一:使用關聯物件將宣告和執行進行 聚合 原寫法
- (void)testAlertAssociate {
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"提示" message:@"要培養哪種生活習慣?" delegate:self cancelButtonTitle:@"取消" otherButtonTitles:@"早起",@"早睡", nil];
[alertView show];
}
#pragma mark - UIAlertViewDelegate
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
if (buttonIndex == 1) {
NSLog(@"你要早起");
}else if (buttonIndex == 2) {
NSLog(@"你要晚睡");
}else{
NSLog(@"取消");
}
}
複製程式碼
使用 “關聯物件改寫” 改寫為:
static void *kAlertViewKey = "kAlertViewKey";
- (void)testAlertAssociate {
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"提示" message:@"要培養哪種生活習慣?" delegate:self cancelButtonTitle:@"取消" otherButtonTitles:@"早起",@"早睡", nil];
[alertView show];
void(^AlertBlock)(NSUInteger) = ^(NSUInteger buttonIndex){
if (buttonIndex == 1) {
NSLog(@"你要早起");
}else if (buttonIndex == 2) {
NSLog(@"你要早睡");
}else{
NSLog(@"取消");
}
};
objc_setAssociatedObject(alertView, kAlertViewKey, AlertBlock, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
#pragma mark - UIAlertViewDelegate
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
void(^AlertBlock)(NSUInteger) = objc_getAssociatedObject(alertView, kAlertViewKey);
AlertBlock(buttonIndex);
}
複製程式碼
如此可將實現和宣告在一起處理,在回撥處取出所關聯的程式碼塊執行。可使得程式碼更易讀。
例項二:為分類新增屬性 眾所周知,在 Objective-C 的分類中宣告屬性只能自動生成該屬性的 getter 方法和 setter 方法 的宣告,沒有具體實現。所以真正給分類新增屬性,使用關聯物件是比較好的一種方式。
//NSTimer+XW.h
@interface NSTimer (XW)
@property (nonatomic, assign) NSUInteger tag;
@end
//NSTimer+XW.m
#import "NSTimer+XW.h"
#import <objc/runtime.h>
@implementation NSTimer (XW)
static void *kXW_NSTimerTagKey = "kXW_NSTimerTagKey";
#pragma mark - tag / getter setter
/// setter
- (void)setTag:(NSUInteger)tag {
NSNumber *tagValue = [NSNumber numberWithUnsignedInteger:tag];
objc_setAssociatedObject(self, kXW_NSTimerTagKey, tagValue, OBJC_ASSOCIATION_ASSIGN);
}
/// getter
- (NSUInteger)tag {
NSNumber *tagValue = objc_getAssociatedObject(self, kXW_NSTimerTagKey);
return tagValue.unsignedIntegerValue;
}
@end
複製程式碼
?? 第11條:理解 objc_msgSend
的作用
- 訊息由接受者、選擇子及引數構成。給某物件“傳送訊息”也就是相當於在該物件上“呼叫方法”
- 發給某物件的全部訊息都要有“動態訊息派發系統”來處理,該系統會查出對應的方法,並執行其程式碼。
objc_msgSend
執行流程
眾所周知, OC 中方法呼叫的本質是傳送訊息 objc_msgSend
,其原型為:
/// self:訊息接受者,cmd:選擇子即執行方法,...:其他引數
void objc_msgSend(id self, SEL cmd, ...);
複製程式碼
舉個例子?:
// xx類
id returnValue = [self doSomething:@"param"];
實質為:
id returnValue = objc_msgSend(xx類, @selector(doSomething:),@"param");
複製程式碼
其中OC在實現此機制的同時設計了快取機制,每次呼叫一個方法會將此方法進行快取,再次執行相同方法會提高執行效率,使其和靜態繫結呼叫方法的速度相差不會那麼懸殊。
?? 第12條:理解訊息轉發機制
- 若物件無法響應某個選擇子(seletor),則進入訊息轉發流程
- 通過執行期的動態方法解析功能,我們可以在需要用到某個方法時再將其加入類中
- 物件可以把其無法解讀的某些選擇子轉交給其他物件來處理
- 經過上述兩步之後,如果還是沒辦法處理選擇子,那就啟動完整的訊息轉發機制
訊息轉發的全流程:
倘若呼叫一個沒有實現的方法,控制檯會丟擲如下經典錯誤資訊:
unrecognized selector sent to instance 0xxx
在方法呼叫和丟擲異常中間還經歷了一段鮮為人知的歷程,名曰:訊息轉發機制。上述錯誤提示便是呼叫沒實現的方法之後底層轉發給 NSObject
的 doedNotRecognizeSelector:
方法所丟擲的。
訊息轉發的具體過程,首先:
動態方法解析
/// 呼叫了未實現的類方法
+ (BOOL)resolveClassMethod:(SEL)sel {
return [super resolveClassMethod:sel];
}
/// 呼叫了未實現的例項方法
+ (BOOL)resolveInstanceMethod:(SEL)sel {
return [super resolveInstanceMethod:sel];
}
複製程式碼
表示是否可以新增一個例項方法用以處理此方法,前提此類需要在程式中提前寫好,可用Runtime 的 class_addMethod動態新增。
/// 呼叫了未實現的例項方法
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(test)) {
/// 呼叫了未實現的 test 方法,動態新增一個 trendsMethod 方法,使其轉發給新加的方法 trendsMethod
// 引數1:新增到的類, 引數2:新增新方法在類中的名稱, 引數3:新方法的具體實現
// 引數4:新方法的引數返回值說明,如 v@: - 無引數無返回值 i@: - 無引數返回Int i@:@ - 一個引數返回Int
class_addMethod(self, sel, (IMP)class_getMethodImplementation([self class], @selector(trendsMethod)), "v@:");
return YES; //此處返回 YES or NO 都可以
}
return [super resolveInstanceMethod:sel];
}
- (void)trendsMethod {
NSLog(@"這是動態新增的方法");
}
複製程式碼
備援接收者
/// 可將未實現的例項方法轉發給其他類處理
- (id)forwardingTargetForSelector:(SEL)aSelector {
if (aSelector == @selector(testInstanceMethod)) {
return [Chinese new]; // 訊息轉發給能夠處理該例項方法的類的物件
}
return [super forwardingTargetForSelector:aSelector];
}
/// 可將未實現的類方法轉發給其他類處理
+ (id)forwardingTargetForSelector:(SEL)aSelector {
if (aSelector == @selector(testClassMethod)) {
return [Chinese class]; // 訊息轉發給能夠處理該類方法的類
}
return [super forwardingTargetForSelector:aSelector];
}
複製程式碼
完整的訊息轉發
若上述過程都沒有處理,程式會有最後一次處理機會,便是:
動態轉發 例項 方法
/// 方法簽名,定義 返回值,引數
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
if (aSelector == @selector(testInstanceMethod:)) {
/// "v@:@"
return [NSMethodSignature signatureWithObjCTypes:"v@:@"];
}
return [super methodSignatureForSelector:aSelector];
}
/// NSInvocation 封裝了一個函式呼叫
//anInvocation.target - 方法呼叫者
//anInvocation.selector - 方法名
//anInvocation getArgument:<#(nonnull void *)#> atIndex:<#(NSInteger)#> - 獲取第 index 個引數
- (void)forwardInvocation:(NSInvocation *)anInvocation {
if (anInvocation.selector == @selector(testInstanceMethod:)) {
return [anInvocation invokeWithTarget:[Chinese new]];//將實現轉給另外一個實現了此方法的物件進行處理
}
return [super forwardInvocation:anInvocation];
}
複製程式碼
動態轉發 類 方法
+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
if (aSelector == @selector(testClassMethod:)) {
/// "v@:@"
return [NSMethodSignature signatureWithObjCTypes:"v@:@"];
}
return [super methodSignatureForSelector:aSelector];
}
+ (void)forwardInvocation:(NSInvocation *)anInvocation {
if (anInvocation.selector == @selector(testClassMethod:)) {
return [anInvocation invokeWithTarget:[Chinese class]];//將實現轉給另外一個實現了此方法的物件進行處理
}
return [super forwardInvocation:anInvocation];
}
複製程式碼
如上方法其實在實現 forwardingTargetForSelector
方法進行轉發就可以實現相同的功能,何必到最後這步處理呢。所以,他的功能不止於此。實際可以函式中直接對未處理方法進行實現,如下:
/// 方法簽名,定義 返回值,引數
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
if (aSelector == @selector(testInstanceMethod:)) {
/// "v@:@"
return [NSMethodSignature signatureWithObjCTypes:"v@:@"];
}
return [super methodSignatureForSelector:aSelector];
}
// 轉發方法最終實現
- (void)forwardInvocation:(NSInvocation *)anInvocation {
if (anInvocation.selector == @selector(testInstanceMethod:)) {
/// 可以在此處理, 未實現的方法
NSLog(@"這個方法 %s Student 沒有實現!!!",sel_getName(anInvocation.selector));
id param;
[anInvocation getArgument:¶m atIndex:2];
NSLog(@"傳進來的引數為: %@ - 可以使其搞事情",param);
return;
}
return [super forwardInvocation:anInvocation];
}
複製程式碼
訊息轉發的實際應用
我們可以使用訊息轉發的機制,使程式永遠不會出現
unrecognized selector sent to instance 0xxx
這種崩潰。並在控制檯輸出具體資訊,我們可以實現一個 NSObject
的分類 如下:
#import "NSObject+XWTool.h"
@implementation NSObject (XWTool)
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
if ([self respondsToSelector:aSelector]) {/// 已實現不做處理
return [self methodSignatureForSelector:aSelector];
}
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
NSLog(@"在 %@ 類中, 呼叫了沒有實現的例項方法: %@ ",NSStringFromClass([self class]),NSStringFromSelector(anInvocation.selector));
}
+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
if ([self respondsToSelector:aSelector]) {/// 已實現不做處理
return [self methodSignatureForSelector:aSelector];
}
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
+ (void)forwardInvocation:(NSInvocation *)anInvocation {
NSLog(@"在 %@ 類中, 呼叫了沒有實現的類方法: %@ ",NSStringFromClass([self class]),NSStringFromSelector(anInvocation.selector));
}
複製程式碼
?? 第13條:用“方法調配技術“除錯“黑盒方法“
- 在執行期,可以向類中新增或替換選擇子所對應的方法實現
- 使用另一份實現來替換原有的方法實現,這道工序叫做“方法調配”,開發者常用此技術向原有類中增加新功能
- 一般來說,只有除錯程式的時候才需要在執行時修改方法實現,這種做法不宜濫用
本質是使用 runtime
在執行時實現方法的替換:
/// 動態交換 m1 和 m2 兩個方法的實現
method_exchangeImplementations(Method _Nonnull m1, Method _Nonnull m2);
複製程式碼
方法的實現可通過如下方法獲取:
/// 獲取方法的實現 cls: 方法所在的物件, name: 方法名
Method class_getInstanceMethod(Class _Nullable __unsafe_unretained cls, SEL _Nonnull name)
複製程式碼
實際應用,在程式執行過程中控制檯列印當前所展示的控制器資訊,這在程式碼熟悉過程中十分有用:
//UIViewController+XWDebug.m
#import "UIViewController+XWDebug.h"
#import <objc/runtime.h>
@implementation UIViewController (XWDebug)
#ifdef DEBUG
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
/// 交換 class 的 viewDidLoad 方法
Method originViewDidLoad = class_getInstanceMethod(self, @selector(viewDidLoad));
Method xwViewDidLoad = class_getInstanceMethod(self, @selector(xw_viewDidLoad));
method_exchangeImplementations(originViewDidLoad, xwViewDidLoad);
/// 交換 class 的 viewDidAppear方法
Method originViewDidAppear = class_getInstanceMethod(self, @selector(viewDidAppear:));
Method xwViewDidAppear = class_getInstanceMethod(self, @selector(xw_viewDidAppear:));
method_exchangeImplementations(originViewDidAppear, xwViewDidAppear);
});
}
- (void)xw_viewDidLoad {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.01 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"********* %@ **** viewDidload ****",self);
});
[self xw_viewDidLoad];
}
- (void)xw_viewDidAppear:(BOOL)animated {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"********* %@ **** viewDidAppear ****",self);
});
[self xw_viewDidAppear:animated];
}
#else
#endif
@end
複製程式碼
?? 第14條:理解“類物件”的用意
- 每個例項都有一個指向Class物件的指標,用以表名其型別,而這些 Class 物件則構成類的繼承體系
- 如果物件型別無法在編譯期確定,那麼就應該使用型別資訊查詢方法來探知
- 儘量使用型別資訊查詢方式來確定物件型別,而不要直接比較類物件,因為某些物件可能實現了訊息轉發功能
判斷物件是否為某個類例項:
- (BOOL)isMemberOfClass:(Class)aClass;
複製程式碼
判斷物件是否為某類或其派生類的例項:
- (BOOL)isKindOfClass:(Class)aClass;
複製程式碼
例如判斷 一個 NSDictionary
的例項:
NSMutableDictionary *dict = @{@"key":@"value"}.mutableCopy;
BOOL example1 = [dict isMemberOfClass:[NSDictionary class]]; // NO
BOOL example2 = [dict isMemberOfClass:[NSMutableDictionary class]]; // NO
BOOL example3 = [dict isKindOfClass:[NSDictionary class]]; // YES
BOOL example4 = [dict isKindOfClass:[NSMutableDictionary class]]; // YES
BOOL example5 = [dict isKindOfClass:[NSArray class]]; // NO
// BOOL example6 = [dict isKindOfClass:[__NSDictionaryM class]]; // YES
複製程式碼
注意,在 [dict isMemberOfClass:[NSMutableDictionary class]]
的判斷中,實際上返回的 NO,雖然我們宣告 dict
為 NSMutableDictionary
的例項,但實際上 dict
為 __NSDictionaryM
類的一個例項,在控制檯可驗證:
(lldb) po [dict isMemberOfClass:[__NSDictionaryM class]]
YES
複製程式碼
《Effective Objective-C 2.0》書中所寫的例項是錯誤的!!
故 盡信書不如無書,相信實際所驗證的,這也啟發讀者在讀書過程中需要儘量將例項驗證一下,說不定作者在寫書時也是想當然的落筆。
前兩章完結,後續幾天會陸續發表其餘篇章的讀書/實戰筆記,筆者期待和眾大神一起學習,共同進步。
未完待續...