NSString *theString = @"Hello World";
NSString *theString2 = @"Hello World";
NSLog(@"theString:%p --- theString:2%p",theString,theString2);
複製代碼
打印結果:程序員
theString:0x11bb0d158 --- theString:20x11bb0d158
複製代碼
兩個變量爲指向同一塊內存的相同指針。此時將 theString2
賦值爲 「Hello World !!!!」objective-c
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 內存大小數組
//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
複製代碼
NSNumber *number = [NSNumber numberWithInteger:10086];
複製代碼
改成緩存
NSNumber *number = @10086;
複製代碼
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];
複製代碼
NSDictionary *info1 = [NSDictionary dictionaryWithObjectsAndKeys:@"極客學偉",@"name",[NSNumber numberWithInteger:18],@"age", nil];
NSString *name1 = [info1 objectForKey:@"name"];
複製代碼
改成bash
NSDictionary *info2 = @{
@"name":@"極客學偉",
@"age":@18,
};
NSString *name2 = info2[@"name"];
複製代碼
[arrayM replaceObjectAtIndex:0 withObject:@"new Object"];
[dictM setObject:@19 forKey:@"age"];
複製代碼
改成框架
arrayM[0] = @"new Object";
dictM[@"age"] = @19;
複製代碼
NSMutableArray *arrayM = @[@1,@"123",@"567"].mutableCopy;
複製代碼
#define
預處理指令static const
來定義「只在編譯單元內可見的常量」。因爲此類常量不在全局符號表中,因此無需爲其名稱加前綴。extern
來聲明全局常量,並在相關實現文件中定義其值。這種常量要出如今全局符號表中,因此名稱應該加以區隔,一般用與之相關的類名作前綴。預處理指令是代碼拷貝,在編譯時會將代碼中全部預處理指令展開填充到代碼中,減小預處理指令也會加快編譯速度。函數
.m
static const NSTimeInterval kAnimationDuration = 0.3;
複製代碼
.h
extern NSString * const XWTestViewNoticationName;
.m
NSString * const XWTestViewNoticationName = @"XWTestViewNoticationName";
複製代碼
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,
};
複製代碼
@property
語法來定義對象中所封裝的數據。使用屬性編譯器會自動生成實例變量和改變量的get方法和set方法。 同時可使用 @synthesize
指定實例變量的名稱,使用 @dynamic
使編譯器不自動生成get方法和set方法。 屬性可分爲四類,分別:性能
atomic
原子性,系統默認。並非線程安全,release
方法不受原子性約束.nonatomic
非原子性readwrite
可讀可寫,同時擁有get方法和set方法。readonly
只讀,僅有 get 方法。assign
簡單賦值,用於基本成員類型strong
表示「擁有關係」,設置新值時會保留新值,釋放舊值,再把新值設置給當前屬性。weak
表示「非擁有關係」,設置新值時既不保留新值,也不釋放舊值。同 assign
相似,所指對象銷燬時會置nilunsafe_unretained
表示一種非擁有關係,語義同 assign
,僅適用於對象類型。當目標對象被銷魂時不會自動清空。copy
表達的關係和 strong
相似。區別在於設置新值時不會保留新值,而是將其 拷貝 後賦值給當前屬性。getter=<name>
指定獲取方法(getter)的方法名, 如: @property (nonatomic, getter=isOn) BOOL on;
setter=<name>
指定設置方法(setter)的方法名。dealloc
方法中,應該直接經過實例變量來讀寫數據。在對象內部直接使用成員變量比使用點語法的優點在於,前者不須要通過 Objective-C 的方法派發過程,執行速度會更快,這時編譯器會直接訪問保存對象實例變量的那塊內存。不過直接訪問成員變量不會觸發 KVO
,因此使用點語法訪問屬性仍是直接使用成員變量取決於具體行爲。
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
複製代碼
例如聲明一本書做爲基類,經過「類族模式「建立相關的類,對應類型的在子類中實現相關方法。以下:
.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
複製代碼
關聯對象的語法:
#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
複製代碼
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在實現此機制的同時設計了緩存機制,每次調用一個方法會將此方法進行緩存,再次執行相同方法會提升執行效率,使其和靜態綁定調用方法的速度相差不會那麼懸殊。
消息轉發的全流程:
假若調用一個沒有實現的方法,控制檯會拋出以下經典錯誤信息: 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));
}
複製代碼
本質是使用 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
複製代碼
判斷對象是否爲某個類實例:
- (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》書中所寫的實例是錯誤的!!
故 盡信書不如無書,相信實際所驗證的,這也啓發讀者在讀書過程當中須要儘可能將實例驗證一下,說不定做者在寫書時也是想固然的落筆。
前兩章完結,後續幾天會陸續發表其他篇章的讀書/實戰筆記,筆者期待和衆大神一塊兒學習,共同進步。
未完待續...