前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >NSObject头文件解析 / 消息机制 / Runtime解读 (一)

NSObject头文件解析 / 消息机制 / Runtime解读 (一)

作者头像
周希
发布2019-10-15 01:27:54
1.1K0
发布2019-10-15 01:27:54
举报
文章被收录于专栏:APP自动化测试APP自动化测试

NSObject头文件解析

当我们需要自定义类都会创建一个NSObject子类, 比如:

代码语言:javascript
复制
#import <Foundation/Foundation.h>

@interface ClassA : NSObject

@end

那么NSObject里面具体有什么呢? 我们点到它的头文件里面去看看

代码语言:javascript
复制
@interface NSObject <NSObject> {
    Class isa  OBJC_ISA_AVAILABILITY;  //每个NSObject对象都拥有一个Class类作为成员变量, 稍后我们再具体看看Class的头文件
}

//load & initilize方法我们不常用到, 进一步的说明大家可以看下这个地址:http://www.cocoachina.com/ios/20150104/10826.html
+ (void)load;
+ (void)initialize;

//初始化方法, 返回一个关系型对象
- (instancetype)init
#if NS_ENFORCE_NSOBJECT_DESIGNATED_INITIALIZER
    NS_DESIGNATED_INITIALIZER
#endif
    ;

//初始化, 返回一个关系型对象
+ (instancetype)new OBJC_SWIFT_UNAVAILABLE("use object initializers instead");

//申请存储空间
+ (instancetype)allocWithZone:(struct _NSZone *)zone OBJC_SWIFT_UNAVAILABLE("use object initializers instead");

//申请存储空间
+ (instancetype)alloc OBJC_SWIFT_UNAVAILABLE("use object initializers instead");

//ARC下不再使用
- (void)dealloc OBJC_SWIFT_UNAVAILABLE("use 'deinit' to define a de-initializer");

//ARC下不再使用
- (void)finalize OBJC_DEPRECATED("Objective-C garbage collection is no longer supported");

//浅拷贝,需要实现NSCopying协议
- (id)copy;

//深拷贝, 需要实现NSMutableCopying协议
- (id)mutableCopy;

//浅拷贝时要实现的方法
+ (id)copyWithZone:(struct _NSZone *)zone OBJC_ARC_UNAVAILABLE;

//深拷贝时要实现的方法
+ (id)mutableCopyWithZone:(struct _NSZone *)zone OBJC_ARC_UNAVAILABLE;

//是否响应aSelector方法
+ (BOOL)instancesRespondToSelector:(SEL)aSelector;

//是否采用了protocol接口
+ (BOOL)conformsToProtocol:(Protocol *)protocol;

//返回aSelector的指针(方法的内存地址)
- (IMP)methodForSelector:(SEL)aSelector;

//类方法, 返回实例对象的aSelector指针
+ (IMP)instanceMethodForSelector:(SEL)aSelector;

//抛出异常, 一般发生无法识别selector时由系统调用, 也可以重写后定义一些动作, 还可以用来阻止某一个方法被继承,后面我们再单独演示下
- (void)doesNotRecognizeSelector:(SEL)aSelector;

//当消息经过动态解析后尚未被处理时, 重写这个方法后会调用这个方法进行重定向给其他类去实现
- (id)forwardingTargetForSelector:(SEL)aSelector OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0);

//当消息经过动态解析和重定向后仍未被处理, 重写下面两个方法完成消息转发
- (void)forwardInvocation:(NSInvocation *)anInvocation OBJC_SWIFT_UNAVAILABLE("");
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector OBJC_SWIFT_UNAVAILABLE("");
或
+ (NSMethodSignature *)instanceMethodSignatureForSelector:(SEL)aSelector OBJC_SWIFT_UNAVAILABLE("");

//是否允许弱引用, WeakReference为No的时候不能用weak字符修饰
- (BOOL)allowsWeakReference UNAVAILABLE_ATTRIBUTE;
- (BOOL)retainWeakReference UNAVAILABLE_ATTRIBUTE;

//检查是否为aClass的子类
+ (BOOL)isSubclassOfClass:(Class)aClass;

//动态解析方法
+ (BOOL)resolveClassMethod:(SEL)sel OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0);
+ (BOOL)resolveInstanceMethod:(SEL)sel OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0);

//hash值
+ (NSUInteger)hash;

//获取父类类名, 后面会找时间单独说下class/superClass/super的差别
+ (Class)superclass;

//获取类名
+ (Class)class OBJC_SWIFT_UNAVAILABLE("use 'aClass.self' instead");

//类信息:类名称和地址
+ (NSString *)description;
+ (NSString *)debugDescription;

上面是NSObject对象的头文件类部分, 可以看到还有一个NSObject protocol 我们也仔细看看都有什么协议方法@protocol NSObjec

代码语言:javascript
复制
//判断两个对象是否相同, 上面已经讲过
- (BOOL)isEqual:(id)object;

//hash编号
@property (readonly) NSUInteger hash;

//父类
@property (readonly) Class superclass;

//当前类
- (Class)class OBJC_SWIFT_UNAVAILABLE("use 'anObject.dynamicType' instead");

//类或者实例自身
- (instancetype)self;

//调用selector
- (id)performSelector:(SEL)aSelector;
- (id)performSelector:(SEL)aSelector withObject:(id)object;
- (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;

//判断是否不是NSObject的子类, 返回NO表示是NSObject的子类
- (BOOL)isProxy;

//判断是否为该类的成员, 或者是否派生自该类的成员
- (BOOL)isKindOfClass:(Class)aClass;

//判断是否为当前类的成员
- (BOOL)isMemberOfClass:(Class)aClass;

//是否实现了某接口
- (BOOL)conformsToProtocol:(Protocol *)aProtocol;

//是否能响应某方法
- (BOOL)respondsToSelector:(SEL)aSelector;

//以下方法在ARC已经不再使用
- (instancetype)retain OBJC_ARC_UNAVAILABLE;
- (oneway void)release OBJC_ARC_UNAVAILABLE;
- (instancetype)autorelease OBJC_ARC_UNAVAILABLE;
- (NSUInteger)retainCount OBJC_ARC_UNAVAILABLE;
- (struct _NSZone *)zone OBJC_ARC_UNAVAILABLE;

//描述: 类名和地址
@property (readonly, copy) NSString *description;
@optional
@property (readonly, copy) NSString *debugDescription;

@end

对NSObject类和NSObject接口我们挑几个重点讲下:

instanceType 和id的区别

关于返回类型, 可以看到有instanceType & id两种, 那有什么区别呢?

instanceType上面有讲过是关系型或者说是关联对象, 具体有什么作用呢?

我们来看下, 定义一个NSobject子类ClassA, 新增两个方法

代码语言:javascript
复制
#import <Foundation/Foundation.h>

@interface ClassA : NSObject

@property (nonatomic, strong) NSString *name;

- (instancetype)getACopy;

- (id)getAnotherCopy;

@end

下面是实现文件:

代码语言:javascript
复制
#import "ClassA.h"

@implementation ClassA

- (instancetype)getACopy {
    
    ClassA *instance = [ClassA new];
    instance.name = self.name;
    
    return instance;
}

- (id)getAnotherCopy {
    
    ClassA *class = [ClassA new];
    class.name = self.name;
    
    return class;
}

@end

我们在Controller中分别调用这两个方法返回的对象属性看看

现在看到区别了吧, 以id类型返回的对象, 编译器无法识别出他的成员变量或者方法. 使用instanceType类型返回的对象编译器能找到他的属性方法

所以使用InstanceType是为了能更好的帮助编译器找到对象的属性和方法, 减少不必要的错误

Copy 和MutableCopy对象的复制

如果要让对象具有Copy或者MutableCopy功能, 需要实现NSCopying或者NSMutableCopy协议, 下面是实现NSCopying协议的例子

代码语言:javascript
复制
- (id)copyWithZone:(NSZone *)zone {
    
    ClassA *class = [[[self class] allocWithZone:zone] init];
    class.name    = self.name;
    
    return class;
}

- (BOOL)conformsToProtocol:(Protocol *)aProtocol;

判断调用者是否实现了某一接口, 多用在委托中 例子:

代码语言:javascript
复制
if ([self.delegate conformToProtocol:protocol]) {
    
    [self.delegagte protocolMethod];
}

isKindOfClass 跟 isMemberOfClass的区别:

上面讲过

isKindOfClass可以判断是否为该类的成员, 或者是否派生自该类的成员

isMemberOfClass则是能判断是否为当前类的成员

举个例子看看, 先创建一个NSObject子类ClassA, 然后再controller中加入以下指令

代码语言:javascript
复制
    ClassA *aClass = [[ClassA alloc] init];
    aClass.name = @"a";

    NSLog(@"aClass isKindOfClass: ClassA: %@", [aClass isKindOfClass:[ClassA class]]? @"YES": @"NO");
    NSLog(@"ClassA isKindOfClass: ClassA: %@", [ClassA isKindOfClass:[ClassA class]]? @"YES": @"NO");
    NSLog(@"aClass isKindOfClass: NSObject: %@", [aClass isKindOfClass:[NSObject class]]? @"YES": @"NO");
    NSLog(@"ClassA isKindOfClass: NSObject: %@", [ClassA isKindOfClass:[NSObject class]]? @"YES": @"NO");
    
    NSLog(@"aClass isMemberOfClass: ClassA: %@", [aClass isMemberOfClass:[ClassA class]] ? @"YES": @"NO");
    NSLog(@"ClassA isMemberOfClass: ClassA: %@", [ClassA isMemberOfClass:[ClassA class]] ? @"YES": @"NO");
    NSLog(@"aClass isMemberOfClass: NSObject: %@", [aClass isMemberOfClass:[NSObject class]] ? @"YES": @"NO");
    NSLog(@"ClassA isMemberOfClass: NSObject: %@", [ClassA isMemberOfClass:[NSObject class]] ? @"YES": @"NO");

输出结果为:

代码语言:javascript
复制
2017-01-22 17:33:57.782 RunTimeDemo[715:63499] aClass isKindOfClass: ClassA: YES
2017-01-22 17:33:57.783 RunTimeDemo[715:63499] ClassA isKindOfClass: ClassA: NO
2017-01-22 17:33:57.783 RunTimeDemo[715:63499] aClass isKindOfClass: NSObject: YES
2017-01-22 17:33:57.783 RunTimeDemo[715:63499] ClassA isKindOfClass: NSObject: YES
2017-01-22 17:33:57.783 RunTimeDemo[715:63499] aClass isMemberOfClass: ClassA: YES
2017-01-22 17:33:57.784 RunTimeDemo[715:63499] ClassA isMemberOfClass: ClassA: NO
2017-01-22 17:33:57.784 RunTimeDemo[715:63499] aClass isMemberOfClass: NSObject: NO
2017-01-22 17:33:57.784 RunTimeDemo[715:63499] ClassA isMemberOfClass: NSObject: NO

可以看出来isMemberOf只能看当前类是不是其类的子类, 另外同一个抽象类调用返回NO

- (BOOL)respondsToSelector:(SEL)aSelector

判断调用者是否有某一个方法, 多用在方法的重定向中

例子:

代码语言:javascript
复制
if ([classA respondsToSelector: @selector(method)]) {

    [classA methodA];
}

上面这两个方法的使用, 是Objective-C动态性的一种体现

- (IMP)methodForSelector:(SEL)aSelector;

返回aSelector的指针(方法的内存地址) 关于IMP 跟 SEL的解释, 如果有不清楚的可以看下这个http://www.jianshu.com/p/65cf7755d30e, 这里就不多讲了 返回的方法地址可以直接用, 但是要注意里面是否有self或者外部属性, 否则会报错

例子:

代码语言:javascript
复制
IMP imp = [aClass methodForSelector:@selector(methodA)];
imp();

- (void)doesNotRecognizeSelector:(SEL)aSelector;

抛出异常, 一般发生无法识别selector时由系统调用, 也可以重写后定义一些动作, 还可以用来阻止某一个方法被继承 当消息经过动态解析-重定向-转发后还是没有被处理时系统就会自己调用这个方法来抛出异常, 重写该方法可以在抛出异常时增加一些自定义的内容

例子: 在类的实现文件中重写doesNotRecognizeSelector方法, 增加打印一行字, 注意自定义的内容要写在调用父类方法前面, 否则调用父类方法就直接crash了, 不会继续执行后面的内容

代码语言:javascript
复制
- (void)doesNotRecognizeSelector:(SEL)aSelector {
    
    NSLog(@"调用了不存在的方法");
    [super doesNotRecognizeSelector:aSelector];
}

当我们调用该类实例不存在的方法时, 就会先打印"调用了不存在的方法", 然后再crash

也可以使用这个方法来让对象不能响应某一个方法, 多用来阻止子类继承某一方法 在子类重写父类的方法, 加入doesNotRecognizeSelector方法

代码语言:javascript
复制
- (void)methodA {
    
    [self doesNotRecognizeSelector: @selector(methodA)];
}

消息机制

接着讲剩下几个方法前, 先说下Objective-C的消息机制 在之前我们先看下C语言跟OC调用方法的差别:

1.C 语言,函数的调用在编译的时候就会决定调用哪个函数(C语言的函数调用),编译完成之后直接顺序执行,无任何二义特性.

2.OC函数的调用成为消息发送,属于动态调用的过程,在编译的时候并不能决定真正调用哪个函数(其实的过程是,在编译阶段,OC可以调用任何函数,即使这个函数并未实现呢,只要申明过就不会报错,而C语言在编译阶段就会报错),只有在真正运行的时候才会根据函数的名称找到对应的函数来调用

看了上面两点, 应该就清楚为什么说C语言是静态语言, 而OC是动态语言了, 因为OC是在运行时才去决定到底要调用哪个函数

那么什么是OC函数的调用成为消息发送呢?

OC的任何方法的调用, 都会被编译器转换成消息调用的模式

[obi makeText] -> 转换成objc_msgSend(obj,@selector(makeText));

如果要自己使用objc_msgsend方法则需要导入<objc/message.h>头文件

在理解这个之前, 我们看下这个, 每个NSObject对象都持有一个Class, 对象的属性和方法就存在这个Class里面

代码语言:javascript
复制
@interface NSObject <NSObject> {
    Class isa  OBJC_ISA_AVAILABILITY;
}

那么我们来看下Class到底是什么, 点进去看下

代码语言:javascript
复制
typedef struct objc_class *Class;

原来Class是一个objc_class模型, 那么我们看看这个objc_class模型都有什么

代码语言:javascript
复制
struct objc_class {
    Class isa  OBJC_ISA_AVAILABILITY;

#if !__OBJC2__
    Class super_class                                        OBJC2_UNAVAILABLE;
    const char *name                                         OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    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标记的属性是Ojective-C 2.0不支持的,但实际上可以用响应的函数获取这些属性,具体有哪些响应函数大家可以导入<objc/runtime.h>后输入class_get...看看

例如:如果想要获取Class的name属性

代码语言:javascript
复制
const char cname  = class_getName(classPerson); //需要先导入<objc/runtime.h>
printf("%s", cname); // 输出:Person

Class isa:

需要注意的是在Objective-C中,所有的类自身也是一个对象,这个对象的Class里面也有一个isa指针,它指向metaClass(元类)

元类对象(metaclass object)中存储的是关于类的信息(类的版本,名字,类方法等)

关于元类对象大家可以看下这个地址: http://www.cnblogs.com/zhangleixy/p/5125791.html 讲解得很透彻

super class:

指向该类的父类,如果该类已经是最顶层的根类(如NSObject或NSProxy),则super_class为NULL

当我们调用superClass时返回的就是这个Class的类名, 调用[super method];时就是子类去调用父类中的这个方法(注意是子类调用, 而不是父类, 只是从父类中取得方法地址而已)

这部分可能有些人会有一点疑惑, 我们讲下[self class] / [super class] / [self superClass]的差别

我们创建一个类ClassA, 并给它增加以下方法:

代码语言:javascript
复制
- (void)methodA {
    
    NSLog(@"self class: %@", [self class]);
    NSLog(@"self superClass: %@", [self superclass]);
    NSLog(@"super class: %@", [super class]);
}

调用输出结果为

代码语言:javascript
复制
2017-01-22 13:27:07.571 RunTimeDemo[1283:111444] self class: ClassA
2017-01-22 13:27:07.572 RunTimeDemo[1283:111444] self superClass: NSObject
2017-01-22 13:27:07.572 RunTimeDemo[1283:111444] super class: ClassA

可以看到[self class]跟[super class]都是当前类的名称, 只有superclass才是父类名称

[super method];方法的调用对象还是子类

name: 类的名称

可以用class_getName()来获得

version: 我们可以使用这个字段来提供类的版本信息

这对于对象的序列化非常有用,它可是让我们识别出不同类定义版本中实例变量布局的改变。

可以用class_getVersion()来获得

info: 类信息

供运行期使用的一些位标识

instanceSize: 该类的实例变量大小

可以用class_getInstanceSize()来获得

objc_ivar_list *ivars: 类的成员变量列表

可以用class_copyIvarList()来获取

objc_method_list **methodLists: 类的方法列表

可以用class_copyMethodList()来获取

objc_cache *cache:方法缓存

对象接到一个消息会根据isa指针查找消息对象,这时会在method Lists中遍历,如果cache了,常用的方法调用时就能够提高调用的效率。

objc_protocol_list *protocols: 协议列表

可以通过class_copyProtocolList获取

向object发送消息时,Runtime库会根据object的isa指针找到这个实例object所属于的类,然后在类的方法列表以及父类方法列表寻找对应的方法运行。id是一个objc_object结构类型的指针,这个类型的对象能够转换成任何一种对象。

再了解了objc的实质内容后, 我们再来说下消息机制

前面讲过, 所有方法的调用都会被编译器转化为objc_msgSend(obj,@selector(makeText));

我们来看下这句话:

objc_msgSend: 方法名, 发送消息

obj: 接受消息的对象

@selector(makeText): 传送的方法名

实际是给obj发送一个消息,

下面详细叙述下消息发送步骤:

1.检测这个 selector 是不是要忽略的。比如 Mac OS X 开发,有了垃圾回收就不理会 retain,release 这些函数了。 2.检测这个 target 是不是 nil 对象。ObjC 的特性是允许对一个 nil 对象执行任何一个方法不会 Crash,因为会被忽略掉。 3.如果上面两个都过了,那就开始查找这个类的 IMP,先从 cache 里面找,完了 找得到就跳到对应的函数去执行。 4.如果 cache 找不到就找一下方法表。 5.如果方法表找不到就到超类/父类的方法表去找,一直找,直到找到NSObject类为止。 6.如果还找不到就要开始进入动态方法解析了.

7.经过动态解析还是没有处理, 则会进入到方法的重定向.

8.重定向后还是没有找到则会开始消息转发.

9.如果消息转发机制仍未处理则抛出异常.

上面就是完整的消息机制, 动态解析/重定向/消息转发我们稍后会讲到, 大家先消化下objc_class模型的内容以及消息机制的流程再往下看

好了大概了解后我们回顾下整个过程, 方法调用会被转换为给调用对象发送一个带有方法名的消息(也可以还带有参数),

对象接收到消息后会先在cache中找之前的调用记录, 在调用记录中找到了该方法就直接运行, 找不到就去方法列表去找 还找不到就去父类去找

如果找到顶层还是没有, 一般情况下在我们没有进行任何操作的时候会crash

如果要对没有实现的方法调用做一些操作, 就可以在动态解析/重定向/消息转发中来做处理, 在其中任何一个阶段有处理该方法调用就不会crash

更详细的用法之前讲过, 请看: http://www.cnblogs.com/zhouxihi/p/6107467.html

接下来我们看看runtime的知识, 一样直接去看下<objc/runtime.h>, 我们挑出几个常用看看

获取对象的类:

代码语言:javascript
复制
Class object_getClass(id obj);  //Class是返回类型, 后面也一样

例子:(需要导入runtime库)

代码语言:javascript
复制
    //创建一个实例对象
    ClassA *aClass    = [[ClassA alloc] init];
    
    //获取实例对象的类
    Class GottenClass = object_getClass(aClass);
    
    //根据获取的类创建一个对象
    id class          = [[GottenClass alloc] init];
    
    //创建好的类执行实例方法
    [class printClassName];

运行结果:

代码语言:javascript
复制
2017-01-24 17:54:27.905 RunTimeDemo[980:87100] printClassName Method: Class Name: ClassA

获取类的名称:

代码语言:javascript
复制
const char *class_getName(Class cls) 

例子:

代码语言:javascript
复制
NSLog(@"Class Name: %s", class_getName([ClassA class]));

运行结果:

代码语言:javascript
复制
2017-01-24 18:24:08.057 RunTimeDemo[1253:108787] Class Name: ClassA

获取对象的类名称:

代码语言:javascript
复制
const char *object_getClassName(id obj)

例子:

代码语言:javascript
复制
    //创建一个实例对象
    ClassA *aClass    = [[ClassA alloc] init];
    
    //打印实例对象的类名称
    NSLog(@"Class Name: %s", object_getClassName(aClass));

运行结果:

代码语言:javascript
复制
2017-01-24 18:01:56.671 RunTimeDemo[1102:93786] Class Name: ClassA

根据类的字符串名称或者类:

代码语言:javascript
复制
Class objc_getClass(const char *name)

例子:

代码语言:javascript
复制
    //根据字符串的名称获取类
    Class GottenClass = objc_getClass("ClassA");
    
    //根据获取到的类创建对象
    id obj = [[GottenClass alloc] init];
    
    //执行实例方法
    [obj printClassName];

运行结果:

代码语言:javascript
复制
2017-01-24 18:06:47.582 RunTimeDemo[1123:96898] printClassName Method: Class Name: ClassA

获取元类:

代码语言:javascript
复制
Class objc_getMetaClass(const char *name)

判断是否为元类:

代码语言:javascript
复制
BOOL class_isMetaClass(Class cls)

判断是否为类:

代码语言:javascript
复制
BOOL object_isClass(id obj)

例子:

代码语言:javascript
复制
    NSLog(@"%@", object_isClass(objc_getClass("ClassA")) ? @"YES": @"NO");
    NSLog(@"%@", object_isClass(objc_getMetaClass("ClassA")) ? @"YES": @"NO");
    
    NSLog(@"%@", class_isMetaClass(objc_getClass("ClassA")) ? @"YES": @"NO");
    NSLog(@"%@", class_isMetaClass(objc_getMetaClass("ClassA")) ? @"YES": @"NO");

运行结果:

代码语言:javascript
复制
2017-01-24 18:11:56.503 RunTimeDemo[1196:101677] YES
2017-01-24 18:11:56.503 RunTimeDemo[1196:101677] YES
2017-01-24 18:11:56.503 RunTimeDemo[1196:101677] NO
2017-01-24 18:11:56.504 RunTimeDemo[1196:101677] YES

获取父类:

代码语言:javascript
复制
Class class_getSuperclass(Class cls) 

例子:

代码语言:javascript
复制
 NSLog(@"Class Name: %s", class_getName(class_getSuperclass([ClassA class])));

运行结果:

代码语言:javascript
复制
2017-01-24 18:26:24.046 RunTimeDemo[1273:110801] Class Name: NSObject

获取版本信息:

代码语言:javascript
复制
int class_getVersion(Class cls)

例子:

代码语言:javascript
复制
NSLog(@"Class version: %d", class_getVersion([ClassA class]));

运行结果:

代码语言:javascript
复制
2017-01-24 18:28:38.594 RunTimeDemo[1308:113101] Class version: 0

设置类的版本信息:

代码语言:javascript
复制
void class_setVersion(Class cls, int version)

例子:

代码语言:javascript
复制
    //设置类的版本号
    class_setVersion([ClassA class], 10);
    
    //获取类的版本号
    NSLog(@"Class version: %d", class_getVersion([ClassA class]));

运行结果:

代码语言:javascript
复制
2017-01-24 18:30:12.647 RunTimeDemo[1324:114369] Class version: 10

继续说之前前我们先看下什么是Ivar, Ivar是变量

看看是怎么定义的

代码语言:javascript
复制
typedef struct objc_ivar *Ivar;

objc_ivar的结构含有名称, 类型, 地址等信息

代码语言:javascript
复制
struct objc_ivar {
    char *ivar_name                                          OBJC2_UNAVAILABLE;
    char *ivar_type                                          OBJC2_UNAVAILABLE;
    int ivar_offset                                          OBJC2_UNAVAILABLE;
#ifdef __LP64__
    int space                                                OBJC2_UNAVAILABLE;
#endif
}  

那么怎么设置和获取Ivar呢? 现在看看下面3个函数

获取实例对象指定名称的成员变量: (注意只能是成员变量, 不能获取属性)

代码语言:javascript
复制
Ivar class_getInstanceVariable(Class cls, const char *name)

设置对象指定成员变量的值: (设置obj对象的ivar成员属性的值为value)

代码语言:javascript
复制
void object_setIvar(id obj, Ivar ivar, id value) 

或者对象指定成员变量的值:

代码语言:javascript
复制
id object_getIvar(id obj, Ivar ivar) 

这3个函数合起来可以读取和修改成员变量(不能获取@property修饰的属性变量),

举例:

我们有一个ClassA, 在类中添加一个私有成员属性

代码语言:javascript
复制
@interface ClassA : NSObject<NSCopying, NSMutableCopying> {
    
    NSString *privateName;
}

如果不额外增加setter/getter方法我们只能用KVC来读取值, 但是有了上面3个组合就也可以实现

代码语言:javascript
复制
- (void)viewDidLoad {
    
    [super viewDidLoad];
    
    ClassA *aClass = [ClassA new];
    
    //获取成员变量地址
    Ivar ivar = class_getInstanceVariable([aClass class], "privateName");

    
    //打印成员变量初始值
    NSLog(@"打印成员变量初始值: %@", object_getIvar(aClass, ivar));
    
    //设置成员变量的值
    object_setIvar(aClass, ivar, @"nihao");
    
    //打印修改后成员变量的值
    NSLog(@"打印修改后成员变量的值: %@", object_getIvar(aClass, ivar));
}

运行结果为:

代码语言:javascript
复制
2017-02-02 11:41:28.160 RunTimeDemo[1271:72477] 打印成员变量初始值: (null)
2017-02-02 11:41:28.161 RunTimeDemo[1271:72477] 打印修改后成员变量的值: nihao

获取实例方法:

代码语言:javascript
复制
Method class_getClassMethod(Class cls, SEL name)

交换两个方法的实现: (俗称Method Swizzling黑科技, 其实也没啥)

代码语言:javascript
复制
void method_exchangeImplementations(Method m1, Method m2) 

例子:

创建一个包含printA & printB方法的类

代码语言:javascript
复制
#import <Foundation/Foundation.h>

@interface ClassA : NSObject

- (void)printA;

- (void)printB;

@end
代码语言:javascript
复制
#import "ClassA.h"

@implementation ClassA

- (void)printA {
    
    NSLog(@"Print A");
}

- (void)printB {
    
    NSLog(@"Print B");
}

@end

Controller中使用

代码语言:javascript
复制
- (void)viewDidLoad {
    
    [super viewDidLoad];
    
    //创建一个实例对象
    ClassA *aClass = [ClassA new];
    
    //顺序调用printA / printB
    [aClass printA];
    [aClass printB];
    
    //获取printA方法
    Method methodA = class_getInstanceMethod([aClass class], @selector(printA));
    
    //获取printB方法
    Method methodB = class_getInstanceMethod([aClass class], @selector(printB));
    
    //方法替换
    method_exchangeImplementations(methodA, methodB);
    
    //再次顺序调用printA / printB
    [aClass printA];
    [aClass printB];
    
}

运行结果:

代码语言:javascript
复制
2017-02-02 16:21:19.006 RunTimeDemo[671:13108] Print A
2017-02-02 16:21:19.006 RunTimeDemo[671:13108] Print B
2017-02-02 16:21:19.010 RunTimeDemo[671:13108] Print B
2017-02-02 16:21:19.020 RunTimeDemo[671:13108] Print A

类似的还有获取类方法:

代码语言:javascript
复制
Method class_getClassMethod(Class cls, SEL name)

一样的效果就不举例了.

获取方法的IMP:

代码语言:javascript
复制
IMP class_getMethodImplementation(Class cls, SEL name)

接着上面的例子:

代码语言:javascript
复制
- (void)viewDidLoad {
    
    [super viewDidLoad];
    
    //创建一个实例对象
    ClassA *aClass = [ClassA new];
    
    //获取printA的方法指针
    IMP imp = class_getMethodImplementation([aClass class], @selector(printA));
    
    //调用
    imp();
}

运行结果:

代码语言:javascript
复制
2017-02-02 16:40:34.940 RunTimeDemo[711:20411] Print A

判断类是否相应某一方法:

代码语言:javascript
复制
BOOL class_respondsToSelector(Class cls, SEL sel)

例子:

代码语言:javascript
复制
NSLog(@"ClassA is response to Selector: %@", class_respondsToSelector([ClassA class], @selector(printA)) ? @"YES": @"NO");

运行结果:

代码语言:javascript
复制
2017-02-02 16:44:17.910 RunTimeDemo[731:22810] ClassA is response to Selector: YES

获取方法列表:

代码语言:javascript
复制
Method *class_copyMethodList(Class cls, unsigned int *outCount) 

获取方法的SEL描述:

代码语言:javascript
复制
SEL method_getName(Method m)

从SEL获取方法名:(这个方法不是runtime库中的, 而是objc.h中的, 可以直接调用)

代码语言:javascript
复制
const char *sel_getName(SEL sel)

例子:

代码语言:javascript
复制
- (void)viewDidLoad {
    
    [super viewDidLoad];
    
    //创建一个对象
    ClassA *aClass = [ClassA new];
    
    //创建一个无符号int变量, 用来保存方法个数
    unsigned int count = 0;
    
    //获取方法列表数组
    Method *methods = class_copyMethodList([aClass class], &count);
    
    //打印获取到的方法个数
    NSLog(@"方法个数: %d", count);
    
    //打印所有获取到的方法名
    for (int i = 0; i < count; i ++) {
        
        NSLog(@"第 %d 个方法名: %s", i, sel_getName(method_getName(methods[i])));
    }
}

运行结果:

代码语言:javascript
复制
2017-02-02 16:51:10.461 RunTimeDemo[754:26703] 方法个数: 2
2017-02-02 16:51:10.461 RunTimeDemo[754:26703] 第 0 个方法名: printA
2017-02-02 16:51:10.462 RunTimeDemo[754:26703] 第 1 个方法名: printB

判断是否有使用某一协议:

代码语言:javascript
复制
BOOL class_conformsToProtocol(Class cls, Protocol *protocol)

例子:

代码语言:javascript
复制
NSLog(@"ClassA 是否有使用NSCopying协议: %@", class_conformsToProtocol([ClassA class], @protocol(NSCopying)) ? @"YES": @"NO");

运行结果:

代码语言:javascript
复制
2017-02-02 17:11:07.376 RunTimeDemo[817:36512] ClassA 是否有使用NSCopying协议: NO

获取协议的名称:

代码语言:javascript
复制
const char *protocol_getName(Protocol *p)

获取类的协议列表:

代码语言:javascript
复制
Protocol * __unsafe_unretained *class_copyProtocolList(Class cls, unsigned int *outCount)

例子:

代码语言:javascript
复制
- (void)viewDidLoad {
    
    [super viewDidLoad];
    
    //创建一个对象
    ClassA *aClass = [ClassA new];
    
    //创建一个无符号变量存储获取到的接口个数
    unsigned int count = 0;
    
    //获取接口列表, 注意前面的修饰
     __unsafe_unretained Protocol **protocolList = class_copyProtocolList([ClassA class],&count);
    
    //打印获取到的接口个数
    NSLog(@"%d", count);
    
    //一次打印每一个接口的名称
    for (int i = 0; i < count; i ++) {
        
        Protocol *myProtocol     = protocolList[i];
        const char *protocolName = protocol_getName(myProtocol);
        NSLog(@"第 %d 个协议名: %s", i, protocol_getName(myProtocol));
    }
}

运行结果:

代码语言:javascript
复制
2017-02-02 17:35:32.433 RunTimeDemo[911:49142] 1
2017-02-02 17:35:32.434 RunTimeDemo[911:49142] 第 0 个协议名: NSCopying

获取对于名称的属性: (不能获取成员变量)

代码语言:javascript
复制
objc_property_t class_getProperty(Class cls, const char *name)

获取属性的名称:

代码语言:javascript
复制
const char *property_getName(objc_property_t property)

例子:

代码语言:javascript
复制
- (void)viewDidLoad {
    
    [super viewDidLoad];
    
    //创建一个对象
    ClassA *aClass = [ClassA new];
    
    //获取属性
    objc_property_t property = class_getProperty([aClass class], "name");
    
    //打印属性名称
    NSLog(@"属性名: %s", property_getName(property));
}

运行结果:

代码语言:javascript
复制
2017-02-02 18:03:18.820 RunTimeDemo[1048:65166] 属性名: name

获取属性列表:

代码语言:javascript
复制
objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount)

获取属性的attribute char格式描述:

代码语言:javascript
复制
const char *property_getAttributes(objc_property_t property) 

获取属性的attribute

代码语言:javascript
复制
objc_property_attribute_t *property_copyAttributeList(objc_property_t property, unsigned int *outCount)

我们顺便看看objc_property_attribute_t

代码语言:javascript
复制
typedef struct {
    const char *name;           /**< The name of the attribute */
    const char *value;          /**< The value of the attribute (usually empty) */
} objc_property_attribute_t;

可以看到这个结构体含有name & value, 可以直接用->name / ->value访问

例子:

代码语言:javascript
复制
- (void)viewDidLoad {
    
    [super viewDidLoad];
    
    //创建一个对象
    ClassA *aClass = [ClassA new];
    
    //创建你一个变量存储属性个数
    unsigned int count = 0;
    
    //获取属性列表
    objc_property_t *properties = class_copyPropertyList([aClass class], &count);
    
    NSLog(@"获取到的属性个数: %d", count);
    
    for (int i = 0; i < count; i ++) {
        
        objc_property_t property = properties[i];
        //获取成员名称
        NSString *name = [NSString stringWithUTF8String:property_getName(property)];
        
        //获取成员属性类型
        NSString *type = [NSString stringWithUTF8String:property_getAttributes(property)];
        
        unsigned int num = 0;
        //获取attributes
        objc_property_attribute_t *attributes = property_copyAttributeList(properties[i], &num);
        
        NSLog(@"attribute name: %@", [NSString stringWithUTF8String:attributes->name]);
        NSLog(@"attribute value: %@", [NSString stringWithUTF8String:attributes->value]);
        
        NSLog(@"第 %d 个属性的名称: %@ 类型: %@", i, name, type);
    }
    
}

运行结果:

代码语言:javascript
复制
2017-02-03 10:28:20.648 RunTimeDemo[1971:308225] 获取到的属性个数: 1
2017-02-03 10:28:20.649 RunTimeDemo[1971:308225] attribute name: T
2017-02-03 10:28:20.649 RunTimeDemo[1971:308225] attribute value: @"NSString"
2017-02-03 10:28:20.649 RunTimeDemo[1971:308225] 第 0 个属性的名称: name 类型: T@"NSString",&,N,V_name

给类或对象添加方法:

代码语言:javascript
复制
BOOL class_addMethod(Class cls, SEL name, IMP imp, 
                                 const char *types)

接下先来说一下types参数, 比如我们要添加一个这样的方法:-(int)say:(NSString *)str; 相应的实现函数就应该是这样:

代码语言:javascript
复制
int say(id self, SEL _cmd, NSString *str) 
{ 
    NSLog(@"%@", str); 
    return 100;//随便返回个值 
 } 

class_addMethod这句就应该这么写:

1

class_addMethod([EmptyClass class], @selector(say:), (IMP)say, "i@:@");

其中types参数为"i@:@“,按顺序分别表示:

i:返回值类型int,若是v则表示void

@:参数id(self)

::SEL(_cmd)

@:id(str)

这些表示方法都是定义好的(Type Encodings)

例子:

代码语言:javascript
复制
- (void)viewDidLoad {
    
    [super viewDidLoad];
    
    //创建一个对象
    ClassA *aClass = [ClassA new];
    
    //获取printA方法指针
    IMP imp = class_getMethodImplementation([aClass class], @selector(printA));
    
    //创建一个NSObject基类对象
    id classB = [NSObject new];
    
    //给基类对象添加printA方法
    class_addMethod([classB class], @selector(printA), imp, "v@:");
    
    //新对象调用printA方法
    [classB printA];
    
}

运行结果:

代码语言:javascript
复制
2017-02-03 11:48:18.739 RunTimeDemo[2209:348846] Print A

替换某一方法的方法指针:

代码语言:javascript
复制
IMP class_replaceMethod(Class cls, SEL name, IMP imp, 
                                    const char *types)

例子:

先创建一个包含printA方法的类, 在实现文件中添加一个静态方法printC

代码语言:javascript
复制
#import "ClassA.h"
#import <objc/runtime.h>

@implementation ClassA

void printC() {
    
    NSLog(@"print C");
}

- (void)printA {
    
    NSLog(@"Print A");
}

- (void)printB {
    
    NSLog(@"Print B");
}

- (void)modifyMethodPrintA {
    
    class_replaceMethod([self class], @selector(printA), (IMP)printC, "v@:");
}

@end

创建该类实例后直接调用printA后打印print A, 调用modify方法后再调用printA打印print C

代码语言:javascript
复制
- (void)viewDidLoad {
    
    [super viewDidLoad];
    
    //创建一个对象
    ClassA *aClass = [ClassA new];
    
    //对象运行printA
    [aClass printA];
    
    //交换方法
    [aClass modifyMethodPrintA];
    
    //再次执行printA
    [aClass printA];
    
}

运行结果:

代码语言:javascript
复制
2017-02-03 13:50:44.127 RunTimeDemo[2379:391408] Print A
2017-02-03 13:50:44.127 RunTimeDemo[2379:391408] print C

给类增加protocol协议

代码语言:javascript
复制
BOOL class_addProtocol(Class cls, Protocol *protocol)

例子:

代码语言:javascript
复制
- (void)viewDidLoad {
    
    [super viewDidLoad];
    
    //创建一个对象
    ClassA *aClass = [ClassA new];
    
    //检查是否有遵循NSCopying协议
    NSLog(@"ClassA confirm to protoco NSCopying: %@", class_conformsToProtocol([aClass class], @protocol(NSCopying))? @"YES": @"NO");
    
    //给对象类添加NSCopying协议
    class_addProtocol([aClass class], @protocol(NSCopying));
    
    //检查是否有遵循NSCopying协议
    NSLog(@"ClassA confirm to protoco NSCopying: %@", class_conformsToProtocol([aClass class], @protocol(NSCopying))? @"YES": @"NO");
    
}

运行结果:

代码语言:javascript
复制
2017-02-03 14:06:49.629 RunTimeDemo[2409:398582] ClassA confirm to protoco NSCopying: NO
2017-02-03 14:06:49.629 RunTimeDemo[2409:398582] ClassA confirm to protoco NSCopying: YES

篇幅上限了 请看下一篇

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2017-02-03 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • NSObject头文件解析
  • instanceType 和id的区别
  • Copy 和MutableCopy对象的复制
  • - (BOOL)conformsToProtocol:(Protocol *)aProtocol;
  • - (BOOL)respondsToSelector:(SEL)aSelector
  • - (IMP)methodForSelector:(SEL)aSelector;
  • - (void)doesNotRecognizeSelector:(SEL)aSelector;
  • 消息机制
    • Class isa:
      • super class:
        • name: 类的名称
          • version: 我们可以使用这个字段来提供类的版本信息
            • info: 类信息
              • instanceSize: 该类的实例变量大小
                • objc_ivar_list *ivars: 类的成员变量列表
                  • objc_method_list **methodLists: 类的方法列表
                    • objc_cache *cache:方法缓存
                      • objc_protocol_list *protocols: 协议列表
                      领券
                      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档