iOS开发--1.对runtime的理解和整理

在我以往的面试中常常会被问到runtime,开始本人其实对runtime只是个简单的了解,用过一些常用的功能,并未仔细研究。不过看来现在iOS开发越来越关注runtime了,所有工作之余仔细查看了一些大牛的博客。本篇文章只是对一些大牛博客的整理,还有一些自己对runtime的不成熟的理解,只是为了方便学习。希望大家多多指点批评

附带上大牛博客:onevcat的博客:runtime的博客

一:基本概念

Runtime简称运行时,基本是用汇编和C语言编写的,只是苹果为了动态系统的高效而作出的努力。点击这里下 到苹果维护的开源代码。苹果和GNU各自维护一个开源的runtime版本,这两个版本之间都在努力的保持一致。Objective-C 从三种不同的层级上与 Runtime 系统进行交互:

1.通过 Objective-C 源代码

2.通过 Foundation 框架的NSObject类定义的方法

3.通过对 runtime 函数的直接调用。大部分情况下你就只管写你的Objc代码就行,runtime 系统自动在后台做一些操作。

( 这里简单介绍下GNU:是“GNU is Not Unix”的递归缩写GNU计划,又称革奴计划是由Richard Stallman在1983年9月27日公开发起的。它的目标是创建一套完全自由的操作系统。)

  • OC就是运行时机制,也就是在运行时候的一些机制,其中最主要的是消息机制。
  • 对于C语言,函数的调用在编译的时候会决定调用哪个函数,编译完成之后直接顺序执行,无任何二义性。
  • OC的函数调用成为消息发送。属于动态调用过程。在编译的时候并不能决定真正调用哪个函数只有在真正运行的时候才会根据函数的名称找 到对应的函数来调用。
  • 事实证明
  • 在编 译阶段,OC可以调用任何函数,即使这个函数并未实现,只要申明过就不会报错。而C语言在编译阶段就会报错。

二:runtime的具体实现(以下是copy大牛的文章原版地址

我们写的oc代码,它在运行的时候也是转换成了runtime方式运行的,更好的理解runtime,也能帮我们更深的掌握oc语言。
每一个oc的方法,底层必然有一个与之对应的runtime方法。

  • 当我们用OC写下这样一段代码
    [tableView cellForRowAtIndexPath:indexPath];
  • 在编译时RunTime会将上述代码转化成[发送消息]
    objc_msgSend(tableView, @selector(cellForRowAtIndexPath:),indexPath);

三:常见方法

unsigned int count;

获取属性列表objc_property_t *propertyList = class_copyPropertyList([self class], &count);for (unsigned int i=0; i%@", [NSString stringWithUTF8String:propertyName]);

1

2

3

objc_property_t *propertyList = class_copyPropertyList([self class], &count);

for (unsigned int i=0; i%@", [NSString stringWithUTF8String:propertyName]);

}

获取方法列表Method *methodList = class_copyMethodList([self class], &count);for (unsigned int i; i%@", NSStringFromSelector(method_getName(method)));
}

1

2

3

Method *methodList = class_copyMethodList([self class], &count);

for (unsigned int i; i%@", NSStringFromSelector(method_getName(method)));

}

获取成员变量列表Ivar *ivarList = class_copyIvarList([self class], &count);for (unsigned int i; i%@", [NSString stringWithUTF8String:ivarName]);
}

1

2

3

Ivar *ivarList = class_copyIvarList([self class], &count);

for (unsigned int i; i%@", [NSString stringWithUTF8String:ivarName]);

}

获取协议列表__unsafe_unretained Protocol **protocolList = class_copyProtocolList([self class], &count);for (unsigned int i; i%@", [NSString stringWithUTF8String:protocolName]);
}

1

2

3

__unsafe_unretained Protocol **protocolList = class_copyProtocolList([self class], &count);

for (unsigned int i; i%@", [NSString stringWithUTF8String:protocolName]);

}

现在有一个Person类,和person创建的xiaoming对象,有test1和test2两个方法

  • 获得类方法Class PersonClass = object_getClass([Person class]);SEL oriSEL = @selector(test1);Method oriMethod = class_getInstanceMethod(xiaomingClass, oriSEL);

1

  • 2

3

Class PersonClass = object_getClass([Person class]);

SEL oriSEL = @selector(test1);

Method oriMethod = class_getInstanceMethod(xiaomingClass, oriSEL);

  • 获得实例方法Class PersonClass = object_getClass([xiaoming class]);SEL oriSEL = @selector(test2);Method cusMethod = class_getInstanceMethod(xiaomingClass, oriSEL);

1

2

3

Class PersonClass = object_getClass([xiaoming class]);

SEL oriSEL = @selector(test2);

Method cusMethod = class_getInstanceMethod(xiaomingClass, oriSEL);

  • 添加方法BOOL addSucc = class_addMethod(xiaomingClass, oriSEL, method_getImplementation(cusMethod), method_getTypeEncoding(cusMethod));
  • 1

BOOL addSucc = class_addMethod(xiaomingClass, oriSEL, method_getImplementation(cusMethod), method_getTypeEncoding(cusMethod));

  • 替换原方法实现class_replaceMethod(toolClass, cusSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));
  • 1

class_replaceMethod(toolClass, cusSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));

  • 交换两个方法method_exchangeImplementations(oriMethod, cusMethod);
  • 1

method_exchangeImplementations(oriMethod, cusMethod);

四:常见作用

  • 动态的添加对象的成员变量和方法
  • 动态交换两个方法的实现
  • 拦截并替换方法
  • 在方法上增加额外功能
  • 实现NSCoding的自动归档和解档

实现字典转模型的自动转换

五:代码实现

要使用runtime,要先引入头文件#import
这些代码的实例有浅入深逐步讲解,最后附上一个我在公司项目中遇到的一个实际问题。

1. 动态变量控制

在程序中,xiaoming的age是10,后来被runtime变成了20,来看看runtime是怎么做到的。

1.动态获取XiaoMing类中的所有属性[当然包括私有]

Ivar *ivar = class_copyIvarList([self.xiaoming class], &count);

2.遍历属性找到对应name字段

const char *varName = ivar_getName(var);

3.修改对应的字段值成20

object_setIvar(self.xiaoMing, var, @"20");

4.代码参考-(void)answer{unsigned int count = 0;Ivar *ivar = class_copyIvarList([self.xiaoMing class], &count);for (int i = 0; i

1

2

3

  • 4

-(void)answer{

unsigned int count = 0;

Ivar *ivar = class_copyIvarList([self.xiaoMing class], &count);

for (int i = 0; i

2.动态添加方法

在程序当中,假设一个Person类中没有eat这个方法,后来被Runtime添加一个名字叫eat的方法。

1.动态给Person类中添加guess方法:class_addMethod([self.xiaoMing class], @selector(guess), (IMP)guessAnswer, "v@:");

  • 1

class_addMethod(self, @selector(eat), (IMP)eat, "[email protected]:");

这里参数地方说明一下:

(IMP)eat 意思是eat的地址指针;
“[email protected]:” 意思是,v代表无返回值void,如果是i则代表int;@代表 id sel; : 代表 SEL _cmd;
“[email protected]:@@” 意思是,两个参数的没有返回值。

2.调用guess方法响应事件:

[[[Penson alloc] init] performSelector:@selector(eat)];

当一个对象调用未实现的方法,会调用这个方法处理,并且会把对应的方法列表传过来,+(BOOL)resolveInstanceMethod:(SEL)sel;这个方法刚好可以用来判断,未实现的方法是不是我们想要添加的方法

这个有两个地方留意一下:

  • void的前面没有+、-号,因为只是C的代码。
  • 必须有两个指定参数(id self,SEL _cmd)

4.代码参考-(void)answer{class_addMethod([self.xiaoMing class], @selector(guess), (IMP)guessAnswer, "v@:");if ([self.xiaoMing respondsToSelector:@selector(guess)]) {[self.xiaoMing performSelector:@selector(guess)];} else{NSLog(@"Sorry,I don‘t know");}}void guessAnswer(id self,SEL _cmd){NSLog(@"i am from


+(BOOL)resolveInstanceMethod:(SEL)sel

{

if (sel == @selector(eat)){

class_addMethod(self, @selector(eat), (IMP)eat, "[email protected]:");

}

return [super resolveInstanceMethod:sel];

}

3:动态交换两个方法的实现

在程序当中,系统imageNamed无法输出打印信息,可以自定义一个imageWithNamed的方法,在这个方法中进行打印信息,然后用Runtime交换方法,这样就可以在调用系统imageNamed就能够输出打印信息

  1. 获取这个类中的两个方法并交换Method m1 = class_getInstanceMethod([self.xiaoMing class], @selector(test1));Method m2 = class_getInstanceMethod([self.xiaoMing class], @selector(test2));method_exchangeImplementations(m1, m2);

1

2

3

Method m1 = class_getInstanceMethod(self, @selector(imageNamed));

Method m2 = class_getInstanceMethod(self, @selector(imageWithNamed));

method_exchangeImplementations(m1, m2);

交换方法之后,以后每次调用这两个方法都会交换方法的实现

4:拦截并替换方法

在程序当中,假设XiaoMing的中有test1这个方法,但是由于某种原因,我们要改变这个方法的实现,但是又不能去动它的源代码(正如一些开源库出现问题的时候),这个时候runtime就派上用场了。

我们先增加一个tool类,然后写一个我们自己实现的方法-change,
通过runtime把test1替换成change。Class PersionClass = object_getClass([Person class]);Class toolClass = object_getClass([tool class]);////源方法的SEL和MethodSEL oriSEL = @selector(test1);Method oriMethod = class_getInstanceMethod(PersionClass, oriSEL);////交换方法的SEL和MethodSEL cusSEL = @selector(change);Method cusMethod = class_getInstanceMethod(toolClass, cusSEL);////先尝试給源方法添加实现,这里是为了避免源方法没有实现的情况BOOL addSucc = class_addMethod(PersionClass, oriSEL, method_getImplementation(cusMethod), method_getTypeEncoding(cusMethod));if (addSucc// 添加成功:将源方法的实现替换到交换方法的实现class_replaceMethod(toolClass, cusSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));//添加失败:说明源方法已经有实现,直接将两个方法的实现交换即method_exchangeImplementations(oriMethod, cusMethod);}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

Class PersionClass = object_getClass([Person class]);

Class toolClass = object_getClass([tool class]);

////源方法的SEL和Method

SEL oriSEL = @selector(test1);

Method oriMethod = class_getInstanceMethod(PersionClass, oriSEL);

////交换方法的SEL和Method

SEL cusSEL = @selector(change);

Method cusMethod = class_getInstanceMethod(toolClass, cusSEL);

////先尝试給源方法添加实现,这里是为了避免源方法没有实现的情况

BOOL addSucc = class_addMethod(PersionClass, oriSEL, method_getImplementation(cusMethod), method_getTypeEncoding(cusMethod));

if (addSucc) {

// 添加成功:将源方法的实现替换到交换方法的实现

class_replaceMethod(toolClass, cusSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));

}else {

//添加失败:说明源方法已经有实现,直接将两个方法的实现交换即

method_exchangeImplementations(oriMethod, cusMethod);

}

5:在方法上增加额外功能

有这样一个场景,出于某些需求,我们需要跟踪记录APP中按钮的点击次数和频率等数据,怎么解决?当然通过继承按钮类或者通过类别实现是一个办法, 但是带来其他问题比如别人不一定会去实例化你写的子类,或者其他类别也实现了点击方法导致不确定会调用哪一个,runtime可以这样解决:@implementation UIButton (Hook)+ (void)load {

static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{

Class selfClass = [self class];

SEL oriSEL = @selector(sendAction:to:forEvent:);
Method oriMethod = class_getInstanceMethod(selfClass, oriSEL);

SEL cusSEL = @selector(mySendAction:to:forEvent:);
Method cusMethod = class_getInstanceMethod(selfClass, cusSEL);

BOOL addSucc = class_addMethod(selfClass, oriSEL, method_getImplementation(cusMethod), method_getTypeEncoding(cusMethod));
if (addSucc) {
class_replaceMethod(selfClass, cusSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));
}else {
method_exchangeImplementations(oriMethod, cusMethod);
}

});
}

- (void)mySendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event {
[CountTool addClickCount];
[self mySendAction:action to:target forEvent:event];
}

@end

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

@implementation UIButton (Hook)

+ (void)load {

static dispatch_once_t onceToken;

dispatch_once(&onceToken, ^{

Class selfClass = [self class];

SEL oriSEL = @selector(sendAction:to:forEvent:);

Method oriMethod = class_getInstanceMethod(selfClass, oriSEL);

SEL cusSEL = @selector(mySendAction:to:forEvent:);

Method cusMethod = class_getInstanceMethod(selfClass, cusSEL);

BOOL addSucc = class_addMethod(selfClass, oriSEL, method_getImplementation(cusMethod), method_getTypeEncoding(cusMethod));

if (addSucc) {

class_replaceMethod(selfClass, cusSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));

}else {

method_exchangeImplementations(oriMethod, cusMethod);

}

});

}

- (void)mySendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event {

[CountTool addClickCount];

[self mySendAction:action to:target forEvent:event];

}

@end

load方法会在类第一次加载的时候被调用,调用的时间比较靠前,适合在这个方法里做方法交换,方法交换应该被保证,在程序中只会执行一次。

6.实现NSCoding的自动归档和解档

如果你实现过自定义模型数据持久化的过程,那么你也肯定明白,如果一个模型有许多个属性,那么我们需要对每个属性都实现一遍encodeObjectdecodeObjectForKey方法,如果这样的模型又有很多个,这还真的是一个十分麻烦的事情。下面来看看简单的实现方式。
假设现在有一个Movie类,有3个属性,它的h文件这这样的#import//1. 如果想要当前类可以实现归档与反归档,需要遵守一个协议NSCoding@interface Movie : NSObject

@property (nonatomic, copy) NSString *movieId;
@property (nonatomic, copy) NSString *movieName;
@property (nonatomic, copy) NSString *pic_url;

@end

1

2

3

4

5

6

7

8

9

10

#import

//1. 如果想要当前类可以实现归档与反归档,需要遵守一个协议NSCoding

@interface Movie : NSObject

@property (nonatomic, copy) NSString *movieId;

@property (nonatomic, copy) NSString *movieName;

@property (nonatomic, copy) NSString *pic_url;

@end

如果是正常写法, m文件应该是这样的:#import "Movie.h"

@implementation Movie

- (void)encodeWithCoder:(NSCoder *)aCoder
{
[aCoder encodeObject:_movieId forKey:@"id"];
[aCoder encodeObject:_movieName forKey:@"name"];
[aCoder encodeObject:_pic_url forKey:@"url"];

}

- (id)initWithCoder:(NSCoder *)aDecoder
{
if (self = [super init]) {
self.movieId = [aDecoder decodeObjectForKey:@"id"];
self.movieName = [aDecoder decodeObjectForKey:@"name"];
self.pic_url = [aDecoder decodeObjectForKey:@"url"];
}
return self;
}
@end

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

#import "Movie.h"

@implementation Movie

- (void)encodeWithCoder:(NSCoder *)aCoder

{

[aCoder encodeObject:_movieId forKey:@"id"];

[aCoder encodeObject:_movieName forKey:@"name"];

[aCoder encodeObject:_pic_url forKey:@"url"];

}

- (id)initWithCoder:(NSCoder *)aDecoder

{

if (self = [super init]) {

self.movieId = [aDecoder decodeObjectForKey:@"id"];

self.movieName = [aDecoder decodeObjectForKey:@"name"];

self.pic_url = [aDecoder decodeObjectForKey:@"url"];

}

return self;

}

@end

如果这里有100个属性,那么我们也只能把100个属性都给写一遍。
不过你会使用runtime后,这里就有更简便的方法。
下面看看runtime的实现方式:#import "Movie.h"

#import
@implementation Movie

- (void)encodeWithCoder:(NSCoder *)encoder

{
unsigned int count = 0;
Ivar *ivars = class_copyIvarList([Movie class], &count);

for (int i = 0; i

1

2

3

4

5

6

7

8

9

10

11

#import "Movie.h"

#import

@implementation Movie

- (void)encodeWithCoder:(NSCoder *)encoder

{

unsigned int count = 0;

Ivar *ivars = class_copyIvarList([Movie class], &count);

for (int i = 0; i

这样的方式实现,不管有多少个属性,写这几行代码就搞定了。怎么,还嫌麻烦,下面看看更加简便的方法:两句代码搞定。
我们把encodeWithCoderinitWithCoder这两个方法抽成宏#import "Movie.h"

#import

#define encodeRuntime(A)

unsigned int count = 0;
Ivar *ivars = class_copyIvarList([A class], &count);
for (int i = 0; i

1

2

3

4

5

6

7

8

#import "Movie.h"

#import

#define encodeRuntime(A)

unsigned int count = 0;

Ivar *ivars = class_copyIvarList([A class], &count);

for (int i = 0; i

我们可以把这两个宏单独放到一个文件里面,这里以后需要进行数据持久化的模型都可以直接使用这两个宏。

7.实现字典转模型的自动转换

字典转模型的应用可以说是每个app必然会使用的场景,虽然实现的方式略有不同,但是原理都是一致的:遍历模型中所有属性,根据模型的属性名,去字典中查找key,取出对应的值,给模型的属性赋值。
像几个出名的开源库:JSONModel,MJExtension等都是通过这种方式实现的。

  • 先实现最外层的属性转换

unsigned int count = 0;

// 1.获取成员属性数组
Ivar *ivarList = class_copyIvarList(self, &count);

// 2.遍历所有的成员属性名,一个一个去字典中取出对应的value给模型属性赋值
for (int i = 0; i OC 字符串
NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)];

// 2.3 _成员属性名 => 字典key
NSString *key = [ivarName substringFromIndex:1];

// 2.4 去字典中取出对应value给模型属性赋值
id value = dict[key];

// 获取成员属性类型
NSString *ivarType = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];
}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

// 创建对应模型对象

id objc = [[self alloc] init];

unsigned int count = 0;

// 1.获取成员属性数组

Ivar *ivarList = class_copyIvarList(self, &count);

// 2.遍历所有的成员属性名,一个一个去字典中取出对应的value给模型属性赋值

for (int i = 0; i  OC 字符串

NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)];

// 2.3 _成员属性名 => 字典key

NSString *key = [ivarName substringFromIndex:1];

// 2.4 去字典中取出对应value给模型属性赋值

id value = dict[key];

// 获取成员属性类型

NSString *ivarType = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];

}

如果模型比较简单,只有NSString,NSNumber等,这样就可以搞定了。但是如果模型含有NSArray,或者NSDictionary等,那么我们还需要进行第二步转换。

  • 内层数组,字典的转换if ([value isKindOfClass:[NSDictionary class]] & ![ivarType containsString:@"NS"]) {

// 是字典对象,并且属性名对应类型是自定义类型
// 处理类型字符串 @"User" -> User
ivarType = [ivarType stringByReplacingOccurrencesOfString:@"@" withString:@""];
ivarType = [ivarType stringByReplacingOccurrencesOfString:@""" withString:@""];
// 自定义对象,并且值是字典
// value:user字典 -> User模型
// 获取模型(user)类对象
Class modalClass = NSClassFromString(ivarType);

// 字典转模型
if (modalClass) {
// 字典转模型 user
value = [modalClass objectWithDict:value];
}

}

if ([value isKindOfClass:[NSArray class]]) {
// 判断对应类有没有实现字典数组转模型数组的协议
if ([self respondsToSelector:@selector(arrayContainModelClass)]) {

// 转换成id类型,就能调用任何对象的方法
id idSelf = self;

// 获取数组中字典对应的模型
NSString *type = [idSelf arrayContainModelClass][key];

// 生成模型
Class classModel = NSClassFromString(type);
NSMutableArray *arrM = [NSMutableArray array];
// 遍历字典数组,生成模型数组
for (NSDictionary *dict in value) {
// 字典转模型
id model = [classModel objectWithDict:dict];
[arrM addObject:model];
}

// 把模型数组赋值给value
value = arrM;

}
}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

if ([value isKindOfClass:[NSDictionary class]] & ![ivarType containsString:@"NS"]) {

//  是字典对象,并且属性名对应类型是自定义类型

// 处理类型字符串 @"User" -> User

ivarType = [ivarType stringByReplacingOccurrencesOfString:@"@" withString:@""];

ivarType = [ivarType stringByReplacingOccurrencesOfString:@""" withString:@""];

// 自定义对象,并且值是字典

// value:user字典 -> User模型

// 获取模型(user)类对象

Class modalClass = NSClassFromString(ivarType);

// 字典转模型

if (modalClass) {

// 字典转模型 user

value = [modalClass objectWithDict:value];

}

}

if ([value isKindOfClass:[NSArray class]]) {

// 判断对应类有没有实现字典数组转模型数组的协议

if ([self respondsToSelector:@selector(arrayContainModelClass)]) {

// 转换成id类型,就能调用任何对象的方法

id idSelf = self;

// 获取数组中字典对应的模型

NSString *type =  [idSelf arrayContainModelClass][key];

// 生成模型

Class classModel = NSClassFromString(type);

NSMutableArray *arrM = [NSMutableArray array];

// 遍历字典数组,生成模型数组

for (NSDictionary *dict in value) {

// 字典转模型

id model =  [classModel objectWithDict:dict];

[arrM addObject:model];

}

// 把模型数组赋值给value

value = arrM;

}

}

我自己觉得系统自带的KVC模式字典转模型就挺好的,假设movie是一个模型对象,dict 是一个需要转化的 [movie setValuesForKeysWithDictionary:dict]; 这个是系统自带的字典转模型方法,个人感觉也还是挺好用的,不过使用这个方法的时候需要在模型里面再实现一个方法才行,
- (void)setValue:(id)value forUndefinedKey:(NSString *)key 重写这个方法为了实现两个目的:1. 模型中的属性和字典中的key不一致的情况,比如字典中有个id,我们需要把它赋值给uid属性;2. 字典中属性比模型的属性还多的情况。
如果出现以上两种情况而没有实现这个方法的话,程序就会崩溃。
这个方法的实现:

- (void)setValue:(id)value forUndefinedKey:(NSString *)key
{
if ([key isEqualToString:@"id"]) {
self.uid = value;
}
}

1

2

3

4

5

6

- (void)setValue:(id)value forUndefinedKey:(NSString *)key

{

if ([key isEqualToString:@"id"]) {

self.uid = value;

}

}

六.几个参数概念

以上的几种方法应该算是runtime在实际场景中所应用的大部分的情况了,平常的编码中差不多足够用了。
如果从头仔细看到尾,相信你基本的用法应该会了,虽然会用是主要的目的,有几个基本的参数概念还是要了解一下的。

1.objc_msgSend

/* Basic Messaging Primitives
*
* On some architectures, use objc_msgSend_stret for some struct return types.
* On some architectures, use objc_msgSend_fpret for some float return types.
* On some architectures, use objc_msgSend_fp2ret for some float return types.
*
* These functions must be cast to an appropriate function pointer type
* before being called.
*/

1

2

3

4

5

6

7

8

9

/* Basic Messaging Primitives

*

* On some architectures, use objc_msgSend_stret for some struct return types.

* On some architectures, use objc_msgSend_fpret for some float return types.

* On some architectures, use objc_msgSend_fp2ret for some float return types.

*

* These functions must be cast to an appropriate function pointer type

* before being called.

*/

这是官方的声明,从这个函数的注释可以看出来了,这是个最基本的用于发送消息的函数。另外,这个函数并不能发送所有类型的消息,只能发送基本的消息。比如,在一些处理器上,我们必须使用objc_msgSend_stret来发送返回值类型为结构体的消息,使用objc_msgSend_fpret来发送返回值类型为浮点类型的消息,而又在一些处理器上,还得使用objc_msgSend_fp2ret来发送返回值类型为浮点类型的消息。
最关键的一点:无论何时,要调用objc_msgSend函数,必须要将函数强制转换成合适的函数指针类型才能调用。
objc_msgSend函数的声明来看,它应该是不带返回值的,但是我们在使用中却可以强制转换类型,以便接收返回值。另外,它的参数列表是可以任意多个的,前提也是要强制函数指针类型。
其实编译器会根据情况在objc_msgSend, objc_msgSend_stret, objc_msgSendSuper, 或 objc_msgSendSuper_stret四个方法中选择一个来调用。如果消息是传递给超类,那么会调用名字带有”Super”的函数;如果消息返回值是数据结构而不是简单值时,那么会调用名字带有”stret”的函数。

2.SEL

objc_msgSend函数第二个参数类型为SEL,它是selector在Objc中的表示类型(Swift中是Selector类)。selector是方法选择器,可以理解为区分方法的 ID,而这个 ID 的数据结构是SEL:
typedef struct objc_selector *SEL;
其实它就是个映射到方法的C字符串,你可以用 Objc 编译器命令@selector()或者 Runtime 系统的sel_registerName函数来获得一个SEL类型的方法选择器。
不同类中相同名字的方法所对应的方法选择器是相同的,即使方法名字相同而变量类型不同也会导致它们具有相同的方法选择器,于是 Objc 中方法命名有时会带上参数类型(NSNumber一堆抽象工厂方法),Cocoa 中有好多长长的方法哦。

3.id

objc_msgSend第一个参数类型为id,大家对它都不陌生,它是一个指向类实例的指针:
typedef struct objc_object *id;
那objc_object又是啥呢:
struct objc_object { Class isa; };
objc_object结构体包含一个isa指针,根据isa指针就可以顺藤摸瓜找到对象所属的类。
PS:isa指针不总是指向实例对象所属的类,不能依靠它来确定类型,而是应该用class方法来确定实例对象的类。因为KVO的实现机理就是将被观察对象的isa指针指向一个中间类而不是真实的类,这是一种叫做 isa-swizzling 的技术,详见官方文档.

4.Class

之所以说isa是指针是因为Class其实是一个指向objc_class结构体的指针:
typedef struct objc_class *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;

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

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;

可以看到运行时一个类还关联了它的超类指针,类名,成员变量,方法,缓存,还有附属的协议。
objc_class结构体中:ivars是objc_ivar_list指针;methodLists是指向objc_method_list指针的指针。也就是说可以动态修改 *methodLists 的值来添加成员方法,这也是Category实现的原理.

时间: 2024-10-10 05:06:22

iOS开发--1.对runtime的理解和整理的相关文章

iOS开发之使用Runtime给Model类赋值

本篇博客算是给网络缓存打个基础吧,本篇博客先给出简单也是最容易使用的把字典转成实体类的方法,然后在给出如何使用Runtime来给Model实体类赋值.本篇博客会介绍一部分,主要是字典的key与Model的属性名相同时,使用Runtime来进行赋值,下篇博客会给出字典key的值和Model的名字不同时的解决方案,并给出使用Runtime打印实体类属性值的方式. 当然你可以使用KVC的setValuesForKeysWithDictionary:方法,下面的方法也是一种解决方案.如果使用setVal

iOS开发——高级特性&Runtime运行时特性详解

Runtime运行时特性详解 本文详细整理了 Cocoa 的 Runtime 系统的知识,它使得 Objective-C 如虎添翼,具备了灵活的动态特性,使这门古老的语言焕发生机.主要内容如下: 引言 简介 与Runtime交互 Runtime术语 消息 动态方法解析 消息转发 健壮的实例变量(Non Fragile ivars) Objective-C Associated Objects Method Swizzling 总结 引言 曾经觉得Objc特别方便上手,面对着 Cocoa 中大量

iOS开发-KVC和KVO的理解

KVC和KVO看起来很专业,其实用起来还是比较简单的,KVC(Key-value coding)可以理解为键值对编码,如果对象的基本类型,那么键值对编码实际上和get,set方法没有区别,如果是属性是另外一个对象,那么发现KVC用起来还是非常顺手,KVO(key-value observing)是键值对的观察者模式,如果对象的属性发生变更,那么会触发observeValueForKeyPath事件,KVO的这种通知特性让我们在开发的时候节省了不必要的代码,提高了开发效率. KVC键值对编码 KV

ios 开发日记 20 - runTime

前言 本篇主要介绍Runtime在开发中的一些使用场景,顺便讲解了下MJExtension的底层实现. 一.runtime简介 RunTime简称运行时.OC就是运行时机制,也就是在运行时候的一些机制,其中最主要的是消息机制. 对于C语言,函数的调用在编译的时候会决定调用哪个函数. 对于OC的函数,属于动态调用过程,在编译的时候并不能决定真正调用哪个函数,只有在真正运行的时候才会根据函数的名称找到对应的函数来调用. 事实证明: 在编译阶段,OC可以调用任何函数,即使这个函数并未实现,只要声明过就

iOS开发笔记之Runtime实用总结

前言 runtime的资料网上有很多了,部分有些晦涩难懂,我通过自己的学习方法总结一遍,主要讲一些常用的方法功能,以实用为主,我觉得用到印象才是最深刻的.另外runtime的知识还有很多,想要了解更多可以一些翻译的官方文档(有点枯燥) 什么是runtime? runtime 是 OC底层的一套C语言的API(引入 <objc/runtime.h> 或<objc/message.h>),编译器最终都会将OC代码转化为运行时代码,通过终端命令编译.m 文件:clang -rewrite

IOS开发—UITableView重用机制的理解

引言 对于一个UITableView而言,可能需要显示成百上千个Cell,如果每个cell都单独创建的话,会消耗很大的内存.为了避免这种情况,重用机制就诞生了. 假设某个UITableView有100个数据需要显示,即需要100个Cell,然而屏幕中最多只能一次性显示10个Cell,那么有一个办法可以不用创建100cell,而只需要创建11(10+1)个. 理解 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowA

iOS开发笔试面试- runtime的原理(一)

首先先说一个简书作者的博客,把runtime入门讲得够清晰. http://www.jianshu.com/p/25a319aee33d Objective-C语言使用了面向对象特性和消息转发机制. 一:Runtime的数据结构: 解析:Root class其实就是NSObject,NSObject是没有超类的,所以Root class 的superclass指向nil: 每一个Class都有一个isa指针指向唯一的Meta class: Root class(meta)的superclass指

ios开发中MVC模式的理解

MVC是80年代出现的一种软件设计模式,是模型(model),视图(view)和控制(Controller)的缩写. 其中Model的主要功能包括业务逻辑的处理以及数据的访问,这是应用程序的主体部分. View的主要功能是用来跟用户进行交互,实现数据的收集和展示,视图是用户看到和直接操作的的界面,它只接受用户的操作. Controller的主要功能用来在视图和模型之间建立联系并控制数据的走向,控制器本身不输出任何内容和对数据做任何处理. 用个简单的例子来说明三者的关系 一个简单的计算器,它除了我

iOS开发&gt;学无止境 - 遍历Model类的属性并完善使用Runtime给Model类赋值

在前几天的一篇博客<iOS开发之使用Runtime给Model类赋值>中介绍了如何使用运行时在实体类的基类中添加给实体类的属性赋值的方法,这个方法的前提是字典的Key必须和实体类的Property Name相同,然后通过运行时来生成和执行Setter方法给Model类的属性赋值. 通 过Runtime来给Model类属性赋值的好处是多多的,它便于代码的后期维护,并且提高了开发效率.当你拿到解析后的字典时你不用一个一个的通过 key去把字典的值赋值给相应的Model类的属性,本篇博客中会给出如何