1.概述
Objective-C使用的是small talk语法,在Objective-C中,调用方法有三种方式:
- 消息传递
- 方法编号perform selector
- 消息发送机制
//调用Person类的eat方法的三种方式:
Person * p = [[Person alloc] init];
//1. 直接调用
[p eat];
//2. 通过方法编号
[p performSelector:@selector(eat)];
//3. 通过运行时使用objc_msgSend
/*
* 要先加载objc/message.h 头文件
*/
#import
- (void)viewDidLoad {
//ios5.0之后不建议使用
/*
* objc_msgSend方法的两个参数:
* 1. 对象
* 2. 方法编号
* Person是类对象,使用class方法就可以获取对象
*/
//相当于 Person * p = [[Person alloc] init];
Person * p = objc_msgSend([Person class], @selector(alloc));
p = objc_msgSend(p, @selector(init));
//相当于[p eat];
objc_msgSend(p, @selector(eat));
}
oc方法由两个部分组成:
- SEL 方法编号
- IMP 方法实现(函数指针)
关于堆栈
栈:系统分配的(局部变量)
堆:程序员开辟的
alloc在堆内存开辟
内存释放 —— 当内存释放之后,这块区域还有数据吗?
不一定。
有数据,内存释放不一定把数据擦除。
栈的平衡:
函数调用都会遵循一个规则:函数调用前后,栈指针是指向一个地方的
2.Runtime
所有oc的代码,都转成Runtime的C语言代码
Runtime是C语言的API。
可以利用Runtime做什么?
- 底层操作(用oc无法实现的),引入头文件查看API
必须掌握的
先导入runtime头文件
#import
/// 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(函数指针)
//一一对应
SEL IMP
viewDidLoad -> viewDidLoad
案例一:URL为空无法判断
NSURL * url = [NSURL URLWithString:@"www.baidu.com/一一一"];
NSURLRequest * request = [NSURLRequest requestWithURL:url];
NSLog(@"%@",request);
优化:给NSURL添加判断是否为空的功能:
- 创建分类category,但是每个文件都要加载该头文件,整个项目都要改变
- HOOK思想:钩住一个方法,动态改变方法的实现
使用运行时进行方法实现的交换
1.在哪里进行方法交换?
在
+load()
里面进行交换。程序在手机硬盘里,是一堆二进制数据,叫指令。
CPU从硬盘装载指令到内存,此时调用
+load
函数,但是还没有执行。CPU从内存读指令的时候才执行代码。
load比main早加载。
因此在load进行方法交换。
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:));
//进行方法交换
method_exchangeImplementations(URLWithString, Cage_URLWithString);
}
+(instancetype)Cage_URLWithString:(NSString *)str{
NSURL * url = [NSURL URLWithString:str];
if (url == nil) {
NSLog(@"url为空!");
}
return url;
}
以上代码错误在于,由于两个方法的imp已经进行了交换,因此在Cage_URLWithString:
中调用的URLWithString:
= Cage_URLWithString:
,无限递归,导致错误,应该将URLWithString:
改成Cage_URLWithString:
正确
+(void)load{
//两个方法为类方法,因此getclassMethod来获取两个方法
Method URLWithString = class_getClassMethod([NSURL class], @selector(URLWithString:));
Method Cage_URLWithString = class_getClassMethod([NSURL class], @selector(Cage_URLWithString:));
//进行方法交换
method_exchangeImplementations(URLWithString, Cage_URLWithString);
}
+(instancetype)Cage_URLWithString:(NSString *)str{
NSURL * url = [NSURL Cage_URLWithString:str];
if (url == nil) {
NSLog(@"url为空!");
}
return url;
}
总结:
1.注意交换的时机
2.修改常用方法的时候,要做好注释
3.小知识点
子类继承父类,不用自父类的方法,在实现的时候,由于子类没有方法,就会向上寻找,找到父类有这个方法,于是实现,实际上子类并不拥有这个方法。
如果想要子类拥有父类的方法,就要override。
如果想要子类拥有父类的行为,就要super
//例如 - (void)viewDidLoad {
[super viewDidLoad];
//无法判断URL是否为空
NSURL * url = [NSURL URLWithString:@"www.baidu.com/一一一"];
NSURLRequest * request = [NSURLRequest requestWithURL:url];
NSLog(@"%@",request);
}
//子类敷写了父类的viewDidLoad,于是拥有了这个方法
//调用了[super viewDidLoad],拥有了父类的行为
2.动态添加方法
概述:
1.调用一个没有声明和实现的方法2.关于
resolveClassMethod
和resolveInstanceMethod
3.利用addMethod来动态添加方法
class_addMethod(Class _Nullable __unsafe_unretained cls, SEL _Nonnull name, IMP _Nonnull imp, const char * _Nullable types)
四个参数:
-
cls
:方法的类 -
SEL
:方法编号 -
IMP
:方法实现(函数指针)(函数名) -
types
:返回值和参数类型
代码示例
//当类被调用了一个没有实现的类方法
//+(BOOL)resolveClassMethod:(SEL)sel{
//
//}
//当类被调用了一个没有实现的实例方法
+(BOOL)resolveInstanceMethod:(SEL)sel{
class_addMethod([Person class], sel, eat, "");
return [super resolveInstanceMethod:sel];
}
void eat(){
NSLog(@"eat or not?");
}
3.KVO源码分析
日后补上。