NSObject头文件解析

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

#import <Foundation/Foundation.h>

@interface ClassA : NSObject

@end

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

@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

//判断两个对象是否相同, 上面已经讲过
- (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, 新增两个方法

#import <Foundation/Foundation.h>

@interface ClassA : NSObject

@property (nonatomic, strong) NSString *name;

- (instancetype)getACopy;

- (id)getAnotherCopy;

@end

下面是实现文件:

#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协议的例子

- (id)copyWithZone:(NSZone *)zone {

    ClassA *class = [[[self class] allocWithZone:zone] init];
class.name = self.name; return class;
}
 
- (BOOL)conformsToProtocol:(Protocol *)aProtocol;
判断调用者是否实现了某一接口, 多用在委托中 例子:
if ([self.delegate conformToProtocol:protocol]) {

    [self.delegagte protocolMethod];
}
isKindOfClass 跟 isMemberOfClass的区别:
上面讲过
isKindOfClass可以判断是否为该类的成员, 或者是否派生自该类的成员
isMemberOfClass则是能判断是否为当前类的成员
举个例子看看, 先创建一个NSObject子类ClassA, 然后再controller中加入以下指令
    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");

输出结果为:

-- ::57.782 RunTimeDemo[:] aClass isKindOfClass: ClassA: YES
-- ::57.783 RunTimeDemo[:] ClassA isKindOfClass: ClassA: NO
-- ::57.783 RunTimeDemo[:] aClass isKindOfClass: NSObject: YES
-- ::57.783 RunTimeDemo[:] ClassA isKindOfClass: NSObject: YES
-- ::57.783 RunTimeDemo[:] aClass isMemberOfClass: ClassA: YES
-- ::57.784 RunTimeDemo[:] ClassA isMemberOfClass: ClassA: NO
-- ::57.784 RunTimeDemo[:] aClass isMemberOfClass: NSObject: NO
-- ::57.784 RunTimeDemo[:] ClassA isMemberOfClass: NSObject: NO

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


-
(BOOL)respondsToSelector:(SEL)aSelector
判断调用者是否有某一个方法, 多用在方法的重定向中
例子:
if ([classA respondsToSelector: @selector(method)]) {

    [classA methodA];
}

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


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

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

当消息经过动态解析-重定向-转发后还是没有被处理时系统就会自己调用这个方法来抛出异常, 重写该方法可以在抛出异常时增加一些自定义的内容
例子:
在类的实现文件中重写doesNotRecognizeSelector方法, 增加打印一行字, 注意自定义的内容要写在调用父类方法前面, 否则调用父类方法就直接crash了, 不会继续执行后面的内容
- (void)doesNotRecognizeSelector:(SEL)aSelector {

    NSLog(@"调用了不存在的方法");
[super doesNotRecognizeSelector:aSelector];
}

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

也可以使用这个方法来让对象不能响应某一个方法, 多用来阻止子类继承某一方法
在子类重写父类的方法, 加入doesNotRecognizeSelector方法
- (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里面
@interface NSObject <NSObject> {
Class isa OBJC_ISA_AVAILABILITY;
}

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

typedef struct objc_class *Class;

原来Class是一个objc_class模型, 那么我们看看这个objc_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;
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属性

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, 并给它增加以下方法:

- (void)methodA {

    NSLog(@"self class: %@", [self class]);
NSLog(@"self superClass: %@", [self superclass]);
NSLog(@"super class: %@", [super class]);
}

调用输出结果为

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>, 我们挑出几个常用看看

获取对象的类:

Class object_getClass(id obj);  //Class是返回类型, 后面也一样

例子:(需要导入runtime库)

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

运行结果:

-- ::27.905 RunTimeDemo[:] printClassName Method: Class Name: ClassA

获取类的名称:

const char *class_getName(Class cls) 

例子:

NSLog(@"Class Name: %s", class_getName([ClassA class]));

运行结果:

-- ::08.057 RunTimeDemo[:] Class Name: ClassA

获取对象的类名称:

const char *object_getClassName(id obj)

例子:

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

运行结果:

-- ::56.671 RunTimeDemo[:] Class Name: ClassA

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

Class objc_getClass(const char *name)

例子:

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

运行结果:

-- ::47.582 RunTimeDemo[:] printClassName Method: Class Name: ClassA

获取元类:

Class objc_getMetaClass(const char *name)

判断是否为元类:

BOOL class_isMetaClass(Class cls)

判断是否为类:

BOOL object_isClass(id obj)

例子:

    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");

运行结果:

-- ::56.503 RunTimeDemo[:] YES
-- ::56.503 RunTimeDemo[:] YES
-- ::56.503 RunTimeDemo[:] NO
-- ::56.504 RunTimeDemo[:] YES

获取父类:

Class class_getSuperclass(Class cls) 

例子:

 NSLog(@"Class Name: %s", class_getName(class_getSuperclass([ClassA class])));

运行结果:

-- ::24.046 RunTimeDemo[:] Class Name: NSObject

获取版本信息:

int class_getVersion(Class cls)

例子:

NSLog(@"Class version: %d", class_getVersion([ClassA class]));

运行结果:

-- ::38.594 RunTimeDemo[:] Class version: 

设置类的版本信息:

void class_setVersion(Class cls, int version)

例子:

    //设置类的版本号
class_setVersion([ClassA class], ); //获取类的版本号
NSLog(@"Class version: %d", class_getVersion([ClassA class]));

运行结果:

-- ::12.647 RunTimeDemo[:] Class version: 

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

看看是怎么定义的

typedef struct objc_ivar *Ivar;

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

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个函数

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

Ivar class_getInstanceVariable(Class cls, const char *name)

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

void object_setIvar(id obj, Ivar ivar, id value) 

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

id object_getIvar(id obj, Ivar ivar) 

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

举例:

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

@interface ClassA : NSObject<NSCopying, NSMutableCopying> {

    NSString *privateName;
}

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

- (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));
}

运行结果为:

-- ::28.160 RunTimeDemo[:] 打印成员变量初始值: (null)
-- ::28.161 RunTimeDemo[:] 打印修改后成员变量的值: nihao

获取实例方法:

Method class_getClassMethod(Class cls, SEL name)

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

void method_exchangeImplementations(Method m1, Method m2) 

例子:

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

#import <Foundation/Foundation.h>

@interface ClassA : NSObject

- (void)printA;

- (void)printB;

@end
#import "ClassA.h"

@implementation ClassA

- (void)printA {

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

Controller中使用

- (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]; }

运行结果:

-- ::19.006 RunTimeDemo[:] Print A
-- ::19.006 RunTimeDemo[:] Print B
-- ::19.010 RunTimeDemo[:] Print B
-- ::19.020 RunTimeDemo[:] Print A

类似的还有获取类方法:

Method class_getClassMethod(Class cls, SEL name)

一样的效果就不举例了.

获取方法的IMP:

IMP class_getMethodImplementation(Class cls, SEL name)

接着上面的例子:

- (void)viewDidLoad {

    [super viewDidLoad];

    //创建一个实例对象
ClassA *aClass = [ClassA new]; //获取printA的方法指针
IMP imp = class_getMethodImplementation([aClass class], @selector(printA)); //调用
imp();
}

运行结果:

-- ::34.940 RunTimeDemo[:] Print A

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

BOOL class_respondsToSelector(Class cls, SEL sel)

例子:

NSLog(@"ClassA is response to Selector: %@", class_respondsToSelector([ClassA class], @selector(printA)) ? @"YES": @"NO");

运行结果:

-- ::17.910 RunTimeDemo[:] ClassA is response to Selector: YES

获取方法列表:

Method *class_copyMethodList(Class cls, unsigned int *outCount) 

获取方法的SEL描述:

SEL method_getName(Method m)

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

const char *sel_getName(SEL sel)

例子:

- (void)viewDidLoad {

    [super viewDidLoad];

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

运行结果:

-- ::10.461 RunTimeDemo[:] 方法个数:
-- ::10.461 RunTimeDemo[:] 第 个方法名: printA
-- ::10.462 RunTimeDemo[:] 第 个方法名: printB

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

BOOL class_conformsToProtocol(Class cls, Protocol *protocol)

例子:

NSLog(@"ClassA 是否有使用NSCopying协议: %@", class_conformsToProtocol([ClassA class], @protocol(NSCopying)) ? @"YES": @"NO");

运行结果:

-- ::07.376 RunTimeDemo[:] ClassA 是否有使用NSCopying协议: NO

获取协议的名称:

const char *protocol_getName(Protocol *p)

获取类的协议列表:

Protocol * __unsafe_unretained *class_copyProtocolList(Class cls, unsigned int *outCount)

例子:

- (void)viewDidLoad {

    [super viewDidLoad];

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

运行结果:

-- ::32.433 RunTimeDemo[:]
-- ::32.434 RunTimeDemo[:] 第 个协议名: NSCopying

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

objc_property_t class_getProperty(Class cls, const char *name)

获取属性的名称:

const char *property_getName(objc_property_t property)

例子:

- (void)viewDidLoad {

    [super viewDidLoad];

    //创建一个对象
ClassA *aClass = [ClassA new]; //获取属性
objc_property_t property = class_getProperty([aClass class], "name"); //打印属性名称
NSLog(@"属性名: %s", property_getName(property));
}

运行结果:

-- ::18.820 RunTimeDemo[:] 属性名: name

获取属性列表:

objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount)

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

const char *property_getAttributes(objc_property_t property) 

获取属性的attribute

objc_property_attribute_t *property_copyAttributeList(objc_property_t property, unsigned int *outCount)

我们顺便看看objc_property_attribute_t

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访问

例子:

- (void)viewDidLoad {

    [super viewDidLoad];

    //创建一个对象
ClassA *aClass = [ClassA new]; //创建你一个变量存储属性个数
unsigned int count = ; //获取属性列表
objc_property_t *properties = class_copyPropertyList([aClass class], &count); NSLog(@"获取到的属性个数: %d", count); for (int i = ; 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 = ;
//获取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);
} }

运行结果:

-- ::20.648 RunTimeDemo[:] 获取到的属性个数:
-- ::20.649 RunTimeDemo[:] attribute name: T
-- ::20.649 RunTimeDemo[:] attribute value: @"NSString"
-- ::20.649 RunTimeDemo[:] 第 个属性的名称: name 类型: T@"NSString",&,N,V_name

给类或对象添加方法:

BOOL class_addMethod(Class cls, SEL name, IMP imp,
const char *types)

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

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)

例子:

- (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]; }

运行结果:

-- ::18.739 RunTimeDemo[:] Print A

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

IMP class_replaceMethod(Class cls, SEL name, IMP imp,
const char *types)

例子:

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

#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

- (void)viewDidLoad {

    [super viewDidLoad];

    //创建一个对象
ClassA *aClass = [ClassA new]; //对象运行printA
[aClass printA]; //交换方法
[aClass modifyMethodPrintA]; //再次执行printA
[aClass printA]; }

运行结果:

-- ::44.127 RunTimeDemo[:] Print A
-- ::44.127 RunTimeDemo[:] print C

给类增加protocol协议

BOOL class_addProtocol(Class cls, Protocol *protocol)

例子:

- (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"); }

运行结果:

-- ::49.629 RunTimeDemo[:] ClassA confirm to protoco NSCopying: NO
-- ::49.629 RunTimeDemo[:] ClassA confirm to protoco NSCopying: YES

篇幅上限了 请看下一篇

NSObject头文件解析 / 消息机制 / Runtime解读 (一)的更多相关文章

  1. NSObject头文件解析 / 消息机制 / Runtime解读 (二)

    本章接着NSObject头文件解析 / 消息机制 / Runtime解读(一)写 给类添加属性: BOOL class_addProperty(Class cls, const char *name, ...

  2. Spring的配置文件ApplicationContext.xml配置头文件解析

    Spring的配置文件ApplicationContext.xml配置头文件解析 原创 2016年12月16日 14:22:43 标签: spring配置文件 5446 spring中的applica ...

  3. 探索C++头文件解析方法

    最近一直在搞基于SWIG的C++接口翻译Java代码的工作.SWIG内部基于Bison(Yacc)的C/C++解析器,最近纠结于SWIG不能解析C++构造函数中的默认初始化赋值操作,想找一个能够补充此 ...

  4. MSP430G2553头文件解析

    MSP430寄存器中文注释---P1/2口(带中断功能)       /************************************************************     ...

  5. Unix网络编程 — 头文件解析

    1.1. < sys/types.h > primitive system data types(包含很多类型重定义,如pid_t.int8_t等) 1.2. < sys/socke ...

  6. TC297B - 外设头文件解析(以IO为例)

    打开例程,目录树下的Includes中包含了各个片上资源对应的头文件,这些头文件定义了相应外设的寄存器地址(寄存器是内置于各个 IP 外设中,是一种用于配置外设功能的存储器,就是一种内存,并且有相对应 ...

  7. 带BOM头文件解析

    在java中apache提供了一个工具类BOMStream,在获取文件流时,将获取到的文件流转化成为BOM流: InputStreamReader is = new InputStreamReader ...

  8. iOS开发runtime学习:一:runtime简介与runtime的消息机制

    一:runtime简介:也是面试必须会回答的部分 二:runtime的消息机制 #import "ViewController.h" #import <objc/messag ...

  9. C++解析头文件-Qt自动生成信号声明

    目录 一.瞎白话 二.背景 三.思路分析 四.代码讲解 1.类图 2.内存结构声明 3.QtHeaderDescription 4.私有函数讲解 五.分析结果 六.下载 一.瞎白话 时间过的ZTMK, ...

随机推荐

  1. (简单) POJ 3126 Prime Path,BFS。

    Description The ministers of the cabinet were quite upset by the message from the Chief of Security ...

  2. 改变导航栏title字体的大小和颜色

    方法一:自定义视图的方法 就是在导航向上添加一个titleView,可以使用一个label,再设置label的背景颜色透明,字体什么的设置就很简单了. //自定义标题视图 UILabel *title ...

  3. 【转】国外程序员收集整理的PHP资源大全

    ziadoz在 Github发起维护的一个PHP资源列表,内容包括:库.框架.模板.安全.代码分析.日志.第三方库.配置工具.Web 工具.书籍.电子书.经典博文等等.伯乐在线对该资源列表进行了翻译, ...

  4. JRPC 轻量级RPC框架

    JRPC是一个轻量级的java RPC框架.它支持服务注册和发现. 目前它开源了,地址为:https://github.com/dinstone/jrpc. Quick Start step 1: g ...

  5. AppBarLayout学习笔记

    LinearLayout的子类 AppBarLayout要点: 功能:让子View(AppBar)可以选择他们自己的滚动行为. 注意:需要依赖CoordinatorLayout作为父容器,同时也要求一 ...

  6. <!DOCTYPE> 声明 引发的错误

    <!DOCTYPE> 声明必须是 HTML 文档的第一行,位于 <html> 标签之前. 在写模板的时候,因为最近开始给每个文件添加注释,无意中将注释写在文件的第一行.导致页面 ...

  7. Beautiful Soup 定位指南

    Reference: http://blog.csdn.net/abclixu123/article/details/38502993 网页中有用的信息通常存在于网页中的文本或各种不同标签的属性值,为 ...

  8. MySQL数据库面试

    1. MySql的存储引擎的不同 特点 Myisam BDB Memory InnoDB Archive 存储限制 没有 没有 有 64TB 没有 事务安全   支持   支持   锁机制 表锁 页锁 ...

  9. localStorage eval script

    var globalEval =function(data) { (window.execScript || function(data){ window.eval.call(window,data) ...

  10. iOS 专题 之 界面开发 之 控件

    iOS 之 UIViewController iOS 之 Navagation Button iOS 之 UIButton iOS 之 UITextField iOS 之 UIStackView iO ...