在我们刚刚开始学习oc这门语言时,总有一些词我们在各种基础入门教程上反复提到,例如,Objective-C语言是一门动态语言,且面向对象,why?
Objective-C语言是一门动态语言,它将很多静态语言在编译和链接时期做的事放到了运行时来处理。这种动态语言的优势在于:我们写代码时更具灵活性,如我们可以把消息转发给我们想要的对象,或者随意交换一个方法的实现等。
这种特性意味着Objective-C不仅需要一个编译器,还需要一个运行时系统来执行编译的代码。对于Objective-C来说,这个运行时系统就像一个操作系统一样:它让所有的工作可以正常的运行。这个运行时系统即Objc Runtime。Objc Runtime其实是一个Runtime库,它基本上是用C和汇编写的,这个库使得C语言有了面向对象的能力。
一、什么是类与对象?
一个对象通过一个类来描述它的特性,就如car类,有价格price,载客量peopleNumber等成员变量,或者属性来描述车,还有一系列方法,包括实例方法和类方法来实现车的特性,比如-(void)begin;-(void)stop;
Objective-C类是由Class类型来表示的,它实际上是一个指向objc_class结构体的指针。它的定义如下:
typedef struct objc_class *Class;
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE; // 父类
const char *name OBJC2_UNAVAILABLE; // 类名
long version OBJC2_UNAVAILABLE; // 类的版本信息,默认为0
long info OBJC2_UNAVAILABLE; // 类信息,供运行期使用的一些位标识
long instance_size OBJC2_UNAVAILABLE; // 该类的实例变量大小
struct objc_ivar_list *ivars OBJC2_UNAVAILABLE; // 该类的成员变量链表
struct objc_method_list **methodLists OBJC2_UNAVAILABLE; // 方法定义的链表
struct objc_cache *cache OBJC2_UNAVAILABLE; // 方法缓存
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; // 协议链表
#endif
} OBJC2_UNAVAILABLE;
其实一个类oc中其实就是一个结构体,这个结构体又是一些什么属性能够描述一个类呢?
-
Class isa OBJC_ISA_AVAILABILITY isa指针指向它的metaClass(元类),可以看成储存这个类的类方法的一个类,而这个元类其中的结构体结构也有一个isa指针,是指向Nsobject的metaClass的,这样就形成完美闭环
-
metaClass 存储着这个类所有的类方法,当向一个类发送消息时,则会通过isa指针找到相应的方法,当向一个类实例对象发送消息,则通过struct objc_method_list **methodLists找到相应的方法
runtime提供了大量的函数来操作类与对象。类的操作方法大部分是以class为前缀的,而对象的操作方法大部分是以objc或object_为前缀。下面我们将根据这些方法的用途来分类讨论这些方法的使用。
-动态交换系统方法
#import <Foundation/Foundation.h>
@interface runtimeManage : NSObject
//自定义两个对象方法
-(void)run;
-(void)sleep;
@end
#import "runtimeManage.h"
@implementation runtimeManage
//实现两个对象方法
-(void)run
{
NSLog(@"run");
}
-(void)sleep
{
NSLog(@"sleep");
}
@end
接下来就是关键代码,怎么交换
//通过SEL映射 取到对应的方法,分别是run sleep
Method m1=class_getInstanceMethod([runtimeManage class], @selector(run));
Method m2=class_getInstanceMethod([runtimeManage class], @selector(sleep));
//然后就交换
method_exchangeImplementations(m1, m2);
runtimeManage *manage=[[runtimeManage alloc]init];
//调用方法检验是否交换成功
[manage run];
[manage sleep];
方法总结
class_getInstanceMethod
;method_exchangeImplementations
-拦截系统方法并添加功能或者替换
当整个项目临时需要更换另外一种字体,不紧急的情况是可以搜索出来一个个改,任务量和修改的完整度视项目大小而定,可以使用runtime来实现整体修改功能
#import <objc/runtime.h>
#import "UIFont+user.h"
@implementation UIFont (user)
+(UIFont *)UserSystemFontOfSize:(CGFloat)fontSize
{
//这里可以增加判断,什么情况下要交换,在7以上使用带后缀的字体
double version = [[UIDevice currentDevice].systemVersion doubleValue];
if (version >= 7.0) {
// 如果系统版本是7.0以上,使用另外一套文件名结尾是‘_os7’的扁平化图片
fontSize=fontSize+10;
}
return [UIFont UserSystemFontOfSize:fontSize];
}
+(void)load
{
//此方法仅调用一次
Method m1=class_getClassMethod([UIFont class], @selector(systemFontOfSize:
));
Method m2=class_getClassMethod([UIFont class], @selector(UserSystemFontOfSize:));
method_exchangeImplementations(m1, m2);
}
@end
-动态添加方法与消息转发
当方法太多,在编译期间会把所有方法添加到相应的地方,这是会消耗内存的,动态添加会减少内存消耗,(具体原因做个标记,后期调研)-------------------按照正常流程,当一个消息发送后,会在消息接收的对象的缓存方法列表Cache里查找方法,找不到则会调用
resolveInstanceMethod:
或者resolveClassMethod:
,在这两个方法里我们有一次机会来添加方法
//方法是c语言格式的方法,其中id,sel是两个隐藏参数,是必须要有的
void dynamicMethodIMP(id self, SEL _cmd) {
// implementation ....
}
@implementation MyClass
//如果是-开头的实例方法,则在这个函数,类方法resolveClassMethod:中
+ (BOOL)resolveInstanceMethod:(SEL)aSEL
{
//如果是调用的没有实现的方法,也就是我们要动态添加实现的这个方法名
if (aSEL == @selector(resolveThisMethodDynamically)) {
//第一个参数 :为哪个类添加方法,第三个参数:IMP理解为这个方法的具体实现的地址 第四个参数:是一种编码格式,需要去学习一下Type Encodings
class_addMethod([self class], aSEL, (IMP) dynamicMethodIMP, "v@:");
//返回yes代表此次消息处理到此为止,不在往下进行,返回no则进行消息转发步骤
return YES;
}
return [super resolveInstanceMethod:aSEL];
}
@end
消息转发.png消息转发
转发机制开始前,我们可以还有一次机会可以更换消息的接受者,在方法
(id)forwardingTargetForSelector:(SEL)aSelector
里
- (id)forwardingTargetForSelector:(SEL)aSelector
{
if(aSelector == @selector(mysteriousMethod:)){
//此处返回一个另外的类,则会在另外的类里去查找是否有此方法的实现,返回nil或者self,则正式进入消息转发流程forwardInvocation:
return alternateObject;
}
return [super forwardingTargetForSelector:aSelector];
}
最后一步,也是我们对这个没人要的消息作处理的最后一次机会
forwardInvocation:
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
if ([someOtherObject respondsToSelector:
[anInvocation selector]])
[anInvocation invokeWithTarget:someOtherObject];
else
[super forwardInvocation:anInvocation];
}
//重点:注意:参数 anInvocation 是从哪来的?
在 forwardInvocation: 消息发送前,Runtime 系统会向对象发送methodSignatureForSelector: 消息,并取到返回的方法签名用于生成 NSInvocation 对象。所以重写 forwardInvocation: 的同时也要重写 methodSignatureForSelector: 方法,否则会抛异常。
这一步我们把没人接纳的这条消息都发给这个垃圾桶类来处理,这样不会崩溃,留了一个坑,后续加深学习在整理,这样通过消息转发我们可以实现类似多继承的效果,实际用到的地方只能实际问题实际分析,熟悉远离,遇到问题才能灵活的去利用这个知识点解决问题,直接复制来心里总是不踏实的。。。。
-分类增加属性
给一个类声明属性,其实本质就是给这个类添加关联,并不是直接把这个值的内存空间添加到类存空间。
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
// 给系统NSObject类动态添加属性name
NSObject *objc = [[NSObject alloc] init];
objc.name = @"小码哥";
NSLog(@"%@",objc.name);
}
@end
// 定义关联的key
static const char *key = "name";
@implementation NSObject (Property)
- (NSString *)name
{
// 根据关联的key,获取关联的值。
return objc_getAssociatedObject(self, key);
}
- (void)setName:(NSString *)name
{
// 第一个参数:给哪个对象添加关联
// 第二个参数:关联的key,通过这个key获取
// 第三个参数:关联的value
// 第四个参数:关联的策略
objc_setAssociatedObject(self, key, name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end
-获取类的属性和方法以及运用
获取属性主要就是熟悉下面的方法,直接摘录一段其他文章的方法列表,作为纪录和查找之用
获得某个类的类方法Method class_getClassMethod(Class cls , SEL name)
获得某个类的实例对象方法Methodclass_getInstanceMethod(Class cls , SEL name)
交换两个方法的实现void method_exchangeImplementations(Method m1 , Method m2)
set方法,将值value 跟对象object 关联起来(将值value 存储到对象object 中) 参数 object:给哪个对象设置属性 参数 key:一个属性对应一个Key,将来可以通过key取出这个存储的值,key 可以是任何类型:double、int 等,建议用char 可以节省字节 参数 value:给属性设置的值 参数policy:存储策略 (assign 、copy 、 retain就是strong)void objc_setAssociatedObject(id object , const void *key ,id value ,objc_AssociationPolicy policy)
利用参数key 将对象object中存储的对应值取出来id objc_getAssociatedObject(id object , const void *key)
获得某个类的所有成员变量(outCount 会返回成员变量的总数) 参数: 1、哪个类 2、放一个接收值的地址,用来存放属性的个数 3、返回值:存放所有获取到的属性,通过下面两个方法可以调出名字和类型Ivar *class_copyIvarList(Class cls , unsigned int *outCount)
获得成员变量的名字const char *ivar_getName(Ivar v)
获得成员变量的类型const char *ivar_getTypeEndcoding(Ivar v)
获取协议列表protocol_getName
获取属性列表property_getName
网友评论