- 相关简单介绍
- 消息机制
消息传递机制
消息转发机制-动态添加方法
消息转发机制-快速转发
消息转发机制-慢速转发
消息转发机制-崩溃处理 - MethodSwizzeing方法交换
- 动态添加属性
- 获取类的成员变量,进行字典转模型
- 动态创建类,有兴趣自己研究
相关简单介绍
Runtime :运行时,所属框架 #import <objc/message.h>
- Runtime 是一种运行机制,OC就是运行时机制,运行时动态的根据函数名调用方法(只有声明,编译时未实现不报错),而c:编译时调用(方法未实现时报错
- 默认情况下,使用Runtime的方法是不会有参数提示的,需要更改一项设置(原因:苹果不推荐我们使用runtime)
方法的存储位置:
- 对象方法保存在类对象的方法列表,
- 类方法保存在元类中的方法列表
扩展:元类
自行百度
扩展:
- 内存分配区
栈 堆 静态区 常量区 方法区- isa指针
只要是对象(类是类对象,由元类对象创建),就有isa指针
类自身也有一个isa指针,指向一个元类isa指向
- Student实例对象->Student类对象->Student元类对象->根元类对象
- person实例对象->person类对象->person元类对象->根元类对象-
- nsobject实例对象->nsobject类对象->根元类对象
消息机制
- 其实就是方法的调用,告诉对象要干什么,给对象传递一个消息,采用动态绑定机制,运行时去确定要执行的代码
- 使用场景:调用私有方法的时候,即没有声明只有实现
消息传递机制(方法的调用流程):
1.通过isa指针找到对应的类,去类中查找
2.注册方法编号(把方法名转换成方法编号)
3.根据方法编号查找对应的方法
4.找到的是函数实现的地址,根据地址去方法区调用对应的函数
5.未找到(没有实现方法,只声明)继续从父类找,如果到了根类还未找到,就会触发消息转发机制
//viewdidload
[[YSHPerson new] aMouthd:@"hhh"];
// objc开头代表只有对象才能发送消息
// 参数1:消息调用者(对象) 参数2:发送的消息(方法名),参数3:发送的参数
// objc_msgSend(id _Nullable self, SEL _Nonnull op, ...)
//无参数
objc_msgSend([YSHPerson new], @selector(aMouthd:));
//有参数
objc_msgSend([YSHPerson new], @selector(aMouthd:),@"hhh");
// 后面为底层的写法,等效
// [YSHPerson new] 等价于 objc_getClass("YSHPerson")
// @selector(aMouthd:) 等价于 sel_registerName(aMouthd:)
消息转发机制-动态添加方法(未实现aMouthd:方法)
//YSHPerson.m
//时机:一个对象调用了一个未实现的方法,就会调用这个方法
//作用:动态添加方法,处理未实现
//根据需求动态创建的2个动态方法, 一般是实例方法
//+ (BOOL)resolveClassMethod:(SEL)sel;
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
/*匹配方法
if (sel == NSSelectorFromString(@"aMouthd:")) {
//等效于下面2句代码,按个人喜好来
}
*/
NSString *methodName = NSStringFromSelector(sel);
//注意带参数 加上 :冒号
if ([methodName isEqualToString:@"aMouthd:"]) {
//动态添加方法
//参数1:给谁添加 参数2:添加那个方法 参数3: 添加的函数的方法名, 参数4:方法类型,描述参数3这个函数
return class_addMethod(self, sel, (IMP)aMouthd, "v@:@");
//v@:@ 对应:方法返回参数(返回的void) 对象类型(id/yshperson) 方法类型(SEL) 对象类型(nsstring)
}
return [super resolveInstanceMethod:sel];;
}
//函数方法
//任何方法都有2个隐式参数 self, _cmd:当前方法的方法编号
void aMouthd(id self, SEL _cmd, NSString *str){
NSLog(@"传递的参数 = %@",str);//hhh
}
在方法内判断传过来的方法是不是当前调用的方法,是的话返回一个对应的方法,不是的话走快速转发
消息转发机制-快速转发
将消息发给另一个对象去处理,即备用对象,备用去对象中查看是否实现了改方法,实现则执行,未实现则向上从自身继承树寻找,若还没有则走慢速转发
//快速转发
//YSHPerson.m
- (id)forwardingTargetForSelector:(SEL)aSelector //Target:目标
{
NSString *methodName = NSStringFromSelector(aSelector);
if ([methodName isEqualToString:@"aMouthd:"]) {//注意带参数 加上 :
return [ChapterOne new];//备用对象
}
return [super forwardingTargetForSelector:aSelector];
}
//ChapterOne.m
-(void)aMouthd:(NSString *)str
{
//输出hhh
NSLog(@"快慢速转发备用对象ChapterOne——str = = %@",str);
}
@end
消息转发机制-慢速转发
手动生成方法,并转发给另一个对象(1.设置方法签名,2.找另一个对象进行消息转发)
//慢速转发
//1.方法签名 2.消息转发
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
NSString *methodName = NSStringFromSelector(aSelector);
if ([methodName isEqualToString:@"aMouthd:"]) {
return [NSMethodSignature signatureWithObjCTypes:"v@:@"];//签名
}
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation //forwardInvocation 向前声明
{
SEL sel = [anInvocation selector]; //获得对应的方法编号
ChapterOne *co = [ChapterOne new];
if ([co respondsToSelector:sel]) {
[anInvocation invokeWithTarget:co];
}else{
[super forwardInvocation:anInvocation];
}
}
消息转发机制-崩溃处理
如果都未找到,则会造成崩溃,补写doesNotRecognizeSelector方法并实现,可以避免崩溃
//防止调用未被实现的方法的时候,造成崩溃
-(void)doesNotRecognizeSelector:(SEL)aSelector
{
NSLog(@"找不到方法,但是你不能给我崩溃");
}
MethodSwizzeing方法交换
交换方法:使用场景:修改系统方法的时候(当然也可以写2个方法进行交换,但是毫无意义)
//添加一个uiimage的分类 -> UIImage+yshImage.m
//如下,判断每个图片是否加载成功
/*
方案1:修改imagenamed的实现,首先不可以用分类,因为会直接覆盖父类方法,且无法调用[super imagenamed]
方案2:使用自定义类,但是1.需要频繁导入,2.频繁修改类名,项目庞大时,工作量巨大
*/
UIImage * im = [UIImage imageNamed:@"1"];
UIImage+yshImage.m
//方法交换只需要执行一次,所以放在load中,把类加载到内存的时候调用
+ (void)load
{
//获取方法名(选择器)
SEL selInstMethod1 = @selector(imageNamed:);
SEL selInstMethod2 = @selector(ysh_imageNamed:);
//根据方法名获取方法对象(前两个是实例方法,后2个是类方法)
//Method method1 = class_getInstanceMethod(self, selInstMethod1);
//Method method2 = class_getInstanceMethod(self, selInstMethod2);
Method method1 = class_getClassMethod(self, selInstMethod1);
Method method2 = class_getClassMethod(self, selInstMethod2);
//交互2个实例方法的实现
if (!method1 || !method2) {
NSLog(@"方法交换失败");
return;
}
//交换2个方法
method_exchangeImplementations(method1, method2);
}
+ (UIImage *)ysh_imageNamed:(NSString *)str
{
UIImage * i = [UIImage ysh_imageNamed:str];//此处因为已经交换方法,所以不能使用imageName,使用了会死循环
if (i) {
NSLog(@"yes");
}else{
NSLog(@"no");
}
return i;
}
动态添加属性(场景:给系统类添加属性的时候)
本质是让某个属性与对象产生关联
//UIImage+yshImage.h
//定义一个属性,分类中无所谓属性修饰符,反正也不会生成set和get方法的实现,也不会有成员变量
@property NSString * name;
//UIImage+yshImage.m重写set和get方法
- (void)setName:(NSString *)name
{
//给那个对象添加属性 属性名 属性值 保存策略
objc_setAssociatedObject(self, @"name", name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)name
{
return objc_getAssociatedObject(self, @"name");
}
//调用
UIImage * im = [UIImage imageNamed:@"1"];
im.name = @"添加了name属性";
NSLog(@"%@",im.name);//添加了name属性
获取类的成员变量,进行字典转模型
//MJExtention的字典转模型策略
//YSHPerson中要有firstName,lastName,major属性
//Major中有heig age属性(Major用于2级转换)
NSDictionary * dic = @{
@"firstName":@"ysh",
@"lastName":@"hsy",
@"major":@{
@"heig":@"188",
@"age":@"18"
}
};
YSHPerson * y = [YSHPerson dicToModel:dic];
Major * m = y.major;
NSLog(@"%d",m.age); //18
添加NSObject的分类 NSObject+dicToModel
//MJExtention: 使用runtime,遍历模型中所有的属性(即根据属性从字段中找,KVC反之,是遍历字典找属性)
//kvc字典转模型,dic中的key在类中必须有,不然会崩溃,可以重写崩溃的方法
//- (void)setValue:(id)value forUndefinedKey:(NSString *)key
+(instancetype)dicToModel:(NSDictionary *)dic
{
id object = [[self alloc]init];
//方法列表
//class_copyMethodList(<#Class _Nullable __unsafe_unretained cls#>, <#unsigned int * _Nullable outCount#>)
//属性列表
//class_copyPropertyList(<#Class _Nullable __unsafe_unretained cls#>, <#unsigned int * _Nullable outCount#>)
//成员变量列表(通常使用这个,属性包含成员变量,定义的是属性,会自动生成_name 成员变量)
unsigned int count = 0;
Ivar * ivarList = class_copyIvarList(self, &count);
for (int i = 0; i < count; i++) {
//获取成员变量名, 字符char转换成nsstring
NSString * ivarName = [NSString stringWithUTF8String:ivar_getName(ivarList[i])];
//去掉下划线的 属性
NSString * key = [ivarName substringFromIndex:1];
NSLog(@"成员变量%d,成员变量%@,属性名或者说是字典的key%@",i,ivarName,key);
//取出了key,之后给模型中的属性赋值
id value = dic[key];
//获取class的类型,假设是Major类
NSString * ivarType = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivarList[i])];
//获取的是@\"Major\"
//字符串处理
ivarType = [ivarType stringByReplacingOccurrencesOfString:@"\"" withString:@""];
ivarType = [ivarType stringByReplacingOccurrencesOfString:@"@" withString:@""];
//二级转换:value是字典,并且是自定义的模型,都转换成模型
if ([value isKindOfClass:[NSDictionary class]] && ![ivarType hasPrefix:@"NS"]) {
//获取类Major
Class modelClass = NSClassFromString(ivarType);
//给modelClass赋值
value = [modelClass dicToModel:value];
}
//给类的属性赋值
if (value) {
[object setValue:value forKey:key];
}
}
return object;
}