1.概述
Objective-C使用的是small talk語法,在Objective-C中,調(diào)用方法有三種方式:
- 消息傳遞
- 方法編號perform selector
- 消息發(fā)送機(jī)制
//調(diào)用Person類的eat方法的三種方式:
Person * p = [[Person alloc] init];
//1. 直接調(diào)用
[p eat];
//2. 通過方法編號
[p performSelector:@selector(eat)];
//3. 通過運(yùn)行時使用objc_msgSend
/*
* 要先加載objc/message.h 頭文件
*/
#import <objc/message.h>
- (void)viewDidLoad {
//ios5.0之后不建議使用
/*
* objc_msgSend方法的兩個參數(shù):
* 1. 對象
* 2. 方法編號
* Person是類對象,使用class方法就可以獲取對象
*/
//相當(dāng)于 Person * p = [[Person alloc] init];
Person * p = objc_msgSend([Person class], @selector(alloc));
p = objc_msgSend(p, @selector(init));
//相當(dāng)于[p eat];
objc_msgSend(p, @selector(eat));
}
oc方法由兩個部分組成:
- SEL 方法編號
- IMP 方法實(shí)現(xiàn)(函數(shù)指針)
關(guān)于堆棧
棧:系統(tǒng)分配的(局部變量)
堆:程序員開辟的
alloc在堆內(nèi)存開辟
內(nèi)存釋放 —— 當(dāng)內(nèi)存釋放之后,這塊區(qū)域還有數(shù)據(jù)嗎?
不一定。
有數(shù)據(jù),內(nèi)存釋放不一定把數(shù)據(jù)擦除。
棧的平衡:
函數(shù)調(diào)用都會遵循一個規(guī)則:函數(shù)調(diào)用前后,棧指針是指向一個地方的
2.Runtime
所有oc的代碼,都轉(zhuǎn)成Runtime的C語言代碼
Runtime是C語言的API。
可以利用Runtime做什么?
- 底層操作(用oc無法實(shí)現(xiàn)的),引入頭文件查看API
必須掌握的
先導(dǎo)入runtime頭文件
#import <objc/runtime.h>
/// An opaque type that represents a method in a class definition.
//成員方法
typedef struct objc_method *Method;
/// An opaque type that represents an instance variable.
//成員變量
typedef struct objc_ivar *Ivar;
面向切面編程:HOOK思想,核心就是Runtime。
方法欺騙:SEL——IMP(函數(shù)指針)
//一一對應(yīng)
SEL IMP
viewDidLoad -> viewDidLoad
案例一:URL為空無法判斷
NSURL * url = [NSURL URLWithString:@"www.baidu.com/一一一"];
NSURLRequest * request = [NSURLRequest requestWithURL:url];
NSLog(@"%@",request);
優(yōu)化:給NSURL添加判斷是否為空的功能:
- 創(chuàng)建分類category,但是每個文件都要加載該頭文件,整個項(xiàng)目都要改變
- HOOK思想:鉤住一個方法,動態(tài)改變方法的實(shí)現(xiàn)
使用運(yùn)行時進(jìn)行方法實(shí)現(xiàn)的交換
1.在哪里進(jìn)行方法交換?
在
+load()
里面進(jìn)行交換。程序在手機(jī)硬盤里,是一堆二進(jìn)制數(shù)據(jù),叫指令。
CPU從硬盤裝載指令到內(nèi)存,此時調(diào)用
+load
函數(shù),但是還沒有執(zhí)行。CPU從內(nèi)存讀指令的時候才執(zhí)行代碼。
load比main早加載。
因此在load進(jìn)行方法交換。
2.交換的圖示
[圖片上傳失敗...(image-78f03d-1509985892800)]
3.交換的過程
錯誤過程
//NSURL+url.m file
+(void)load{
//兩個方法為類方法,因此getclassMethod來獲取兩個方法
Method URLWithString = class_getClassMethod([NSURL class], @selector(URLWithString:));
Method Cage_URLWithString = class_getClassMethod([NSURL class], @selector(Cage_URLWithString:));
//進(jìn)行方法交換
method_exchangeImplementations(URLWithString, Cage_URLWithString);
}
+(instancetype)Cage_URLWithString:(NSString *)str{
NSURL * url = [NSURL URLWithString:str];
if (url == nil) {
NSLog(@"url為空!");
}
return url;
}
以上代碼錯誤在于,由于兩個方法的imp已經(jīng)進(jìn)行了交換,因此在Cage_URLWithString:
中調(diào)用的URLWithString:
= Cage_URLWithString:
,無限遞歸,導(dǎo)致錯誤,應(yīng)該將URLWithString:
改成Cage_URLWithString:
正確
+(void)load{
//兩個方法為類方法,因此getclassMethod來獲取兩個方法
Method URLWithString = class_getClassMethod([NSURL class], @selector(URLWithString:));
Method Cage_URLWithString = class_getClassMethod([NSURL class], @selector(Cage_URLWithString:));
//進(jìn)行方法交換
method_exchangeImplementations(URLWithString, Cage_URLWithString);
}
+(instancetype)Cage_URLWithString:(NSString *)str{
NSURL * url = [NSURL Cage_URLWithString:str];
if (url == nil) {
NSLog(@"url為空!");
}
return url;
}
總結(jié):
1.注意交換的時機(jī)
2.修改常用方法的時候,要做好注釋
3.小知識點(diǎn)
子類繼承父類,不用自父類的方法,在實(shí)現(xiàn)的時候,由于子類沒有方法,就會向上尋找,找到父類有這個方法,于是實(shí)現(xiàn),實(shí)際上子類并不擁有這個方法。
如果想要子類擁有父類的方法,就要override。
如果想要子類擁有父類的行為,就要super
//例如 - (void)viewDidLoad {
[super viewDidLoad];
//無法判斷URL是否為空
NSURL * url = [NSURL URLWithString:@"www.baidu.com/一一一"];
NSURLRequest * request = [NSURLRequest requestWithURL:url];
NSLog(@"%@",request);
}
//子類敷寫了父類的viewDidLoad,于是擁有了這個方法
//調(diào)用了[super viewDidLoad],擁有了父類的行為
2.動態(tài)添加方法
概述:
1.調(diào)用一個沒有聲明和實(shí)現(xiàn)的方法2.關(guān)于
resolveClassMethod
和resolveInstanceMethod
3.利用addMethod來動態(tài)添加方法
class_addMethod(Class _Nullable __unsafe_unretained cls, SEL _Nonnull name, IMP _Nonnull imp, const char * _Nullable types)
四個參數(shù):
-
cls
:方法的類 -
SEL
:方法編號 -
IMP
:方法實(shí)現(xiàn)(函數(shù)指針)(函數(shù)名) -
types
:返回值和參數(shù)類型
代碼示例
//當(dāng)類被調(diào)用了一個沒有實(shí)現(xiàn)的類方法
//+(BOOL)resolveClassMethod:(SEL)sel{
//
//}
//當(dāng)類被調(diào)用了一個沒有實(shí)現(xiàn)的實(shí)例方法
+(BOOL)resolveInstanceMethod:(SEL)sel{
class_addMethod([Person class], sel, eat, "");
return [super resolveInstanceMethod:sel];
}
void eat(){
NSLog(@"eat or not?");
}
3.KVO源碼分析
日后補(bǔ)上。