@[TOC]

1. IOS面试考察(一):runtime相关问题

Runtime简介图

1.1 runtime相关问题

runtime是iOS开发最核心的知识了,如果下面的问题都解决了,那么对runtime的理解已经很深了。y 2 { runtime已经开源了,这有一份别人调试好可运行的源码objc-r F $untime,也可以去官网找objc4

官方的代码下载下来要让它运行起来,是需要花费一u h k = z &点时间去填坑的。我这里提供一下已经填好坑,可以直接编译运行的objc4_750代码:

  • objc4_75! f H0可编译代码:链接:pan.baidu.com/3 y U x b [ e b –s/1y0GgOOpF… 密码:gmlx
  • objc4_756.2可编译代码:链接:pan.baiduL j O h / 2 G [ r.com/s/15hzKJ12U… 密码:azet
objc_756代码结构

先思考一下下面的这些问题,N d N Z f看你能回答出多少:

  1. runtime怎么添加属性、方法等
  2. runtiy J O F 5me 如何实现 weak 属性
  3. runtime如何通过selector找到对应的IMP地址?(分别考虑类方法和实例方法)
  4. 使用runtime Associate方法关联的对象,需要在主l Q s g j Z对象dealloc的时u L 2候释放么?
  5. _objc_msgForward函数是做什么的?直接调E 1 z用它将会发生什么?
  6. 能否向编译后得到的类中增加实例变量?能否向运行时创建的类中添加实例变量?为什么?
  7. 简述下Objective-C中调用方法的过程(h e Bruntime)
  8. 什么是method swizzling(俗称黑魔法)

我们先来简单了解一下runtH i ] : ~ + $ 1 6ime的3个简单问题:

  1. 什么是runtime?
  2. 为啥要用runtime?
  3. runtime有什么作用?
    1. 什么是runtime?
  1. runtime本质上是~ % e h t x O一套比较底层的C语言,C++ ,汇编组成的API。我们有称为运行时,在runtime的P l D , f底层很多实现为了性能效率方面,都直接用汇编代码。
  2. 我们R o i y ] B f o 8平时编写的OC代码,需要runtime来创建类和对象,进行消息发送和转发( m { ^ ^ 0 ~ ] 3,其实最终会转换成Runtime的C语言代码。
  3. runtime是将数据类型的确定n 2 ` r由编译时推迟到了运行时。
    1. 为啥要用runtime?
  1. OC是一门动态语言,它会将一些工作放在代码的运行时才去处理而并非编译时,因此编译器是不够,我们还需要一个运行时系统来处理! ] M p N编译后的代码。
  2. runtime基本是用C语言和汇编语言写的,苹果和GNU各自维护一个开源的runtime版本,这两个版本之间都高度的保持一致v ^ ) F ~ 3
    1. runtime有什么作用?
  1. 消息传递、转发<消息机制>
  2. 访问私有变量 –eg:(UITextFiled 的修改)
  3. 交换系统a | 7 8 a h方法 –eg:(拦截–防止button连续点击)
  4. 动态增加方法
  5. 为分类增加属性
  6. 字典转模型 –{ O $ Q– eg:(YYModel, MJModel)

接下来我们来– f & F r 9 R给出runtime面试问题的解答,可能不完善,欢迎补充。

1.1.1 runtime怎么添加属性、方法等

ivar表示成员变量
class_addIvar
class_addMethod
class_addProperty
class_addProtocd S ) P ? ]ol
class_reb r 4 /placePr8 R t & p b Yoperty

1.1.1.11 k , S O 动态添加属性

  • 需求:给NSObject添加一个name属性,动态添加% _ ~ B @ C H [属性 -> runtime

思路:

  1. 给NSObject添加分类,在分类中添加属性。问题:@property在分类中作用:仅仅是生成get,set方法声明,不会生成get,set方法实现和下划线成员属性,所以要s r `在.m文件实现L m &setter/getter方法,用static保存下滑线属性,这样一来,当对象销毁时,属性无法销毁。x # / X D L g { g
  2. 用runtime动态添加属性:本质是让属性与某个对象产生一段关联
    使用场景:给系统的类添加t ; L p y属性时。

实现代码如下:

#import <objc/message.h>
@implementaC g N 8 g &tion NSObject (Property)
//static  NSString *_name;      //通过这样去保存属性u - + X p I ] .没法做到对象销毁,属性也销毁,static依然会让属性存在缓存池中,所以需要动态的添加成员属性
// 只要想调用runtime方法,思考:谁的事情
-(voidp v H)setName:(NSString *)name
{
// 保存; 1 + E 3  f A Gname
// 动态添加属性 = 本= P I m e ? W U质:让对象的某个属性与值产生关联
/*
object:保存到v ` #哪个对象中
key:用什么属性保存 属性名
value:保存值
policy:策略,strong,weak
objc_setAssociatedObject(<#a : + 6 _ & c Jid object#>, <#const void *key#>, <#id value#>, <#objc_As7 y # 9 ? QsociationPolicy policy#>)
*/
objc_setAssociatedObject(self, "name", name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
//    _name = name;
}
- (NSStringl 6 $ *)name
{
return objc_getAssociatedObject(self, "name");
//    return _name;
}
@end
1.1.1.1.1 自动生成属性

开发中,从网络数据中解析出字典数组,将数组转为模型时,如果有几百个key需要用,要写很多@property成员属性,下面提供一个万能的方法,可直接将字典数组转为全部@property成员属性,打印出来,这样直接复制在模型中就好了。

  • 自动& A 5 c B } u生成属性代码如下:
#import "NSDictionary+PropertyCode.h"
@implementation NSDictionary (PropertyCode)
//通过这个方法_ t L n {,自动将字典转成模型中需要用的! e I ; d $ Y #属性代码
// 私有API:真实存在,但是苹果没有暴露出来,不给你。如B3 } [ , @ L 7 ` BOOL值? e d ;,不知道类型,打印得知是__NSCFBool- = Y x n _ean,但是无法敲出来,只能用NSCl5 O W % i y Y j !assFromString(@"__NSCFBoolean")
// isKindOfClass:判断下是否是当前类或者子类,BOOL是NSNumber的子类,Y 8 v $ G !要先判断BOOL
- (void)createPropetyCode
{
// 模型中属性根据字典的ke} [ S y vy
// 有多少个key,生成多少个属性
NSMutableString *codes = [NSMutablz Z seString string];
// 遍历字典
[self enumerateKeysAndObjectsUsingBlock:^(id  _Nonnull key, id  _Nonnull value, BOOL * _Nonnull stop) {
NSSz t $ 8 K ) 5 m ftring *code = nil;R f E x
//        NSLog(@q M C = w k j d ;"%@",[value class]);
if ([value isKindOfClass:[NSString class]]) {
cd : Q # 1 M fode = [NSString stringWithFormat:@"@property (nonatomic, strong) NSString *%@;",key];
} else if ([value isKindOfClass:NSClassFromString(@"__NSCFBoolean")]){
code = [NSString stringWo F g a J g D * PithFormat:@"@property (nonatomic, assign) BOOL %@;",key];
} else if ([value isKindOfClaS 4 l (ss:[NSNumber cl% L 4 v s # 3 V Uass]]y ) * U t = a s) {
code = [NSString stringWithFormat:@"@property (nonatomic, assign) NSInteger %@;",key];
} e} X 5lse if ([value isKindOfClass:[NSArray class]]) {
code = [NSString stringWithF} 9 rormat:@"@property (non# g F t G j X 2 7atomic, strong) NSArray *%@;",key];
} else if ([value isKindOfClass:[NSL Q D Z p k R 3Dictionaj I C G ( _ h Y Bry cp ) ; 7 & W [ class]]) {
code = [NSString stringWithFD ; ? d 0 M Jormat:@"@property (nonatomic, strong) NSDictionary *%@;",keH 9 1 _y];
}
// 拼接字符串
[codes ap* H  N N S mpendFormat:@"%@n^ g = , 5 v 1 Q",code];
}];
NSLog(@"%@"- Q l = 6 F c,codes);
}
@end
1.1.1.1.2 KVC字典转模型
  • 需求:就是在开发中,通常后台会W @ f l给你很8 h d 0 M 2 # 3 H多数据,但是并不是每个数据都有用,这些没有用d { T 1 ( v h w的数据,需不需要保存到模型中。
    有很多第三方框架都是基于这些原理去实现的,如YYe r ;Model, MJModel.

实现代码如下:

@implementation Status
+ (instanR ^ Bcetype)statusWithDict:(NSDictionary *$ 8 * V u)dict{
// 创建模型
Status *s = [[self a0 3 S q 1 ^ U * &lloc] init];
// 字典value转模型属性保存
[s setValuesForKeysWithDictionary:d, 0 U * J SicI U pt];
//    s.reposts_count = dict[g j K (@"reposts_count"];
// 4️⃣MJExtension:可以字N 3 ; $典转模型,而且可以不用与字典中属性一一对应,runtime, : C %遍历模型中有多少个属性,直接去字典中取出对应va+ ~ 0 = s B ylue,给模型赋值
// 1️⃣setValuesForKeysWithDictionary:方法底层实现:遍历字典中所有key,去模型中查找! 0 W S  : = U对应的属性,把值给模型属性赋值,即调用下面方法:
/*
[dict enumerateKeysAndObjectsUsingBlock:^(id  _Nonnull key, id  _Nonnull obj, BOOL * _NonnullF e / stog @ x K up) {
// source
// 这行代码才是真正给模型的属性赋值
[s setValue:d p J _ @ a { adict[@"source"] forKey:@"source"];      //底层实现是:
2️⃣ [s setValue:c  i . q @dict[@"source"] forKey:@"source"];
1.首先会去模型中查找有没有setSource方法,直接调用setd ~ ] u - R % m B方法 [s seF 2 j 3 . ~ 8 ZtSource:dict[@"source"]];
2.去模型中查找有没有source属0 o E ; l性,source = dict[@"sourcep H a g d 7 y D"]
3.去查找有没有_source属性,_source = dict[@"source"]
4.调用对象的 setValuu * K 7 7e:forUndefinedKey:直接报错
[s setValue:obj forKey:key W M + %];
}];
*/
return s;
}
// 3️⃣用KN n N 7 R S % AVC,不想让系统报错,重写系统方法思想:& & M ( 7 d
// 1.想给系o d F D u 1 Q统方法添加功能
// 2.不想要系统实现
- (void)setValue:(id)value forUndefinedKey:(NSString *)key
{
}
@end
1.1.1.1.3 MJExtention的底层实现
#import "NSObject+Model.h"
#import <objc/v y K f / % A ( OmessagO 2 ` Q He.h>
//    class_copyPropertyList(<#__unsafe_unreq | x G ftained Class cls#>, <T c r s G P#unsigned int *outCount#&g ` Lgt;) 获取属性{ + V + 列表
@implementation NSObject[ 9 $ g u { ? (Model)
/**
字典转模型
@param dic1 p M yt 传入需要转模型的字典
@return 赋值好的模型
*/
+ (w l 6instancetype)modelWithDict:(NSDictionary *)Q G :dict
{
id objc = [[self alloG q ] rc] init];
//思路: runtime遍历模型中属性,去字典中取出对应@ 4 3 0 !value,在给模型中属! 1 z w 5 } p性赋值
// 1.获取模型中所有属性 -> 保存到类
// ivaM ) B G O f q fr:下划线成员变量 和 Property:属性
// 获取成员变量列表
// class:获取哪个类成员变量列表
// count:成员变量总数
//这个方法得到一个装有成员变量的数组
//class_copyIvarList(<#__unsafe_unretained Class cls#>, <#unsigned int *outCount#>)
int count = 0;
// 成员变量. 7 O +数组 指向数组第0个元素
Ivar *ivarList = cz Q e O b ; B s rlass_copyIvarList(self, &count);
// 遍历所有成员变量
for (int i = 0; i < count; i++) {
// 获取成员变量 user
Ivar ivar = ivarList[i];
// 获6 ~ h X Y :取成员变量名称,即将C语言的字符转为OC字符串
NSString *ivarName = [NSString sa ; % 1 X t P [ ktringWithUTF8String:ivar_getName(ivar)];
// 获取成员变量类型,j ;  C L s用于获取二级字典的模型名字
NSString *type = [NSString stringWi# E ; B Z AthUTF8String:ivar_getTypeEncodin7 L b T 2 P 7g(ivar)];
//  将% i X 6type这样的字符串@"@"User"" 转成 @"User"
type = [type stringByReplacingOccurrencesOfString:@"@"" withString:@""];
type = [type stringByReplacingOccurrencesOfString:@k Z x [""" withString:@"0 L 3 r c ~ y"];
// 成员变量名称转换key; k $ | G R s 5 n,即去掉成员变量前面的下划线
NSString *r ~ % mkey = [ivarName substringFromIndB : e & vex:1];
// 从字典中取出对应value dict[@"user"] -> 字典
id value = dict[key];
// 二级转换
// 并且是自定义类型,才` Q { N k j Q f u需要转换
if ([value isKin1 x * . t p Q 9 _dOfClass:[NSDictionary class]] &H  H& ![type containsStj O # V N ` % Yring:@"NS"]) { // 只有是字典才需w [ _要转换
Class className = NSClassFromString(type);
// 字典转模型
value = [className modelWithDict:value];
}
// 给模型中属性赋值 key:user value:字典 -> 模型
if (value) {
[objc setValue:value for1 { P wKey:key];
}
}
return objc;
}
@end
1.1.1.1.4 自动序列化
  • 用runtime提供的函数遍历Model自身所有属性,并对属性进行encode和decode操作。

在Model的基类中重写方法:

- (id)initWithCod$ h r 7 Y d @ er:(NSCoder *)aDecoder {
if (self =a z i x I 1 P  E [se g ? I L z g 4uper init]) {
unsigneb . 8 a Q  S T Id int outCouy k C y n W o n qnt;
Ivar * ivars = class_copyIvarList([s } -self class], &outCount);
for (int i = 0; i < outCount; i ++) {
Ivar ivar = ivars[i];
NSString * keyy ? y B = O N ] 8 = [NSString stringWi] S 4 - z e ] ;thUTT g . i . =F8String:ivar_getName(ivar) ` ^ A 7 O)6 7 J 8 8 $];7 U O a W ` a
[self setValue:[aDecoder decodeObjectForKey:3 U W ( Qkey] for= W k FKey:key];
}
}
return self;
}
- (voO h w iid)encodeWithCoder:(NSV E T 8Codep x e V Zr *! h = b & } U)aCoder {
unsigned int ouw { _ ~ d G YtCount;
Ivar * ivars =o | J class_copyIvarList([self class], &outCount);
for (int i = 0; i < outCount; i ++) {
Ivar ivn y J ,ar = ivars[i];
NSString * key = [NSString sm 0 % P ` !tringWithUTF8String:ivar_getv P ` Z l -Name(ivar)];
[aCoder encodeObject:[self valueForKe) $ 0 ! | l X v [y:key] forKey:key];
}
}

1.1.1.2 动态添加方法

  • 开发使用场景:如果一个类方法非常多,加载类到内存的时候` J D s m B H n [也比较耗费资源,需要给每个方法生成映射表,可以使用动态给某个类,添加方法解决。

可能的面试题:有6 ( % R Z x 8没有使用perfo0 C ) frmSelector,其1 m k 6 e M实主要想问你有没有动态添加过方法。

动态添加方法代码实现` i ( 2

@implementation ViewContro / y P v W l ( 0ller
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
Person *p( v / : = [[Person alloc] init];
// 默认pd o ) @erson,没有实现eat方法,可以通过performSelecG  & ) )tor调用,但是会报错。
// 动态添加方法就不会报错
[p performSelector:@selector(eat)]x P o g ,;
}
@end
@implementation Person
// void(*)()
// 默认方法都有两个隐式参数,
void eat(id self,SEL sel)
{
NSLog(@"%@ %@",self,NSStringFromSelector(sel));
}
// 当一个对象调用未实现的方法,会调用这个方法处理,并且会把对应的方法列表传过来.
// # v @ 6 b G 刚好可以用来判Z L M ( m E C 8断,未实现的方法是不是我们想要动! g t X态添加的方法
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
if (sel == @sel# [ s Fector(eat)) {
// 动态添加eat方B @ ; 
// 第一个参数:给哪个类添加方法
// 第二个参数:添加方法的方法编号
// 第三个参数:添加方法的函数实现(函数地址)
// 第四个参数:函数的类型,(返回值+参数类型) v:void @:对象->self :表示SEL->_cmd
class_addMethod(self, @selector(eat), eat, "v@:");
}
return [super resolveI- $ BnstanceMethod:sel];
}
@end

1.1.1.3 类,对象的关联对象

OC的分类允许给分类添加属性,但不会自动生成getter、setter方法
所以常规的仅仅添加之后,调用的话会crash。

关联对象不是为类对象A S _ s l ( J ;添加属性或者成员变量(因为在设置关联后也无法通过ivarList或者propertyList& q H 1 n取得) ,而是为类添加一个相关的对象,通常用于存储类信息,例如存储类的属性列表数组,为将来字典转模型的方便。

runtime关联对象的API:

//关联对象
void ob% ] T k q q O ! )jc_setAssocF + 8 &iatedObject(id object, conS ` @ G & I gst void *key, id value, objr  ! 6 z Jc_AssociationPolicy policy)
//获取关联的对象y } ) l J v -
id objc& ] w ) 2 E `_getAssociatedObject(id object, const void *kP n h ( b * vey)
//移除关联的对象
void objc_0 K [remT : G j , w toveAssociT O Y ~atedObjects(id. * [ ; = ; $ n objecT g z k 4 F Bt)
  • 给分类添加属性:
@implementation2 ] { [ W * E ViewController
- (void)v_ Z 8 n :iewDidLoad {
[super viewDidLoad];
// 给系统NSObR T 9ject类动态添加属性name
NSObject *objc = [y . y[NSObject alloc] init];
objc.name = @"孔雨露";
NSLog(@"%@",objc.name);
}
@end
// 定义关联的key
static const char *keyY f p = "name";
@implementation NSObject (Property)
- (NSString *)n[ ? h came
{
// 根据}   8 ] a - /关联的key,获取关联的值。
return objc_getAssociatedObject(self, key);
}
- (void)setName:(NSString *)name
{
// 第一个参数:给哪个对象添加关联` & [
// 第] 6 ^ R D E二个参数:关m 7 z ! P联的key,通过这个key获取
// 第三个参数:关联的value
// 第四个参数:关联的策略
objc_setAs? ~ / 7 / C G k CsociatedObject(self, key, name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end
  • 给对象添加关联对象:
    比如alertView,{ 0 l k p c 3一般传值,使用的是alertView的tag属性。我们想把更多的参数U j {传给alertViem b W C 2 e %w代理:
/**
*  删除点击
*  @param recId        购物车ID
*/
- (void)shopCartCell:(BSShopCart! u + l ~ _Cell *)shopCarT c . $ ^ c ] EtCell didDD | q H z E W z Jelete3 i ; 1 QClickedAtRecId:(NSString *)recId
{
UIAlertView *aT F 5 #lert = [[UIAlertView alloc] initWithTitle:@"" message:@"确认要删除这个宝贝" dele; 9 F T @gate:sg h 4 e 7 :elf cancelButtonTitle:@"取消" otherButtonTitles:@"确定", nil];
/*I G F Q | ^ ! Y *
objc_setAssociatedObject方法的参数解释:
第一个参数: id object, 当前对象
第二个参数: const void *key, 关联的key,是c字符串
第三个参数: id value, 被关联的对象的值
第四个参数: o2 J ( P Abjc_Association! Z T : c 5Policy policy关联引用的规则
*/
// 传递多参数
objc_setA# c l / 0 yssociatedObject(alert, "sup~ x } - [ n K D ipliers_id", @; n L ! K h u"1", OBJC_ASSO0 [ W P . 5 . 8 {CIATION_RETAIN_NONATOMIC);
objc_setAssociatedObje~ ; / = )ct(alert, "warehouse_id", @"2", OBJC_ASSOCIATION_RETAIN_NONATOMIC);
alert.tag = [recId intValue];
[alert show];
}
/**
*  确定删除操作
*/
- (void)g U 8 A r l # 5alertj h R RView:(UIAlertView *)a3  1lertView clicke9 _ ( I k A ~dButtonAtIndex:(NSIntege5 O e W ]r8 f B g)butX X j M c 7 M h 5tonIndex {
if (buttonIndex == 1) {
NSString *warehouse_id = objc_getAs- 8 O h ? ! ? Z =sociatedObject(alertView, "warehouse_id");
NSString *suppliers_id = objc_getAssociatedObject(alertView, "suppliers_id");
NO : d 6 d V i a 9SString *recId = [NSC + & f dString stringWithFormat:@"%ld",(long)alertView.t^ @ -  7 M 2 1 Fag];
}
}? F `

1.1.2 runtime 如何实现 weak 属性

  • 首先要搞清楚2 C k 8 3 # U y weak属性的特点:

weak策略表明该属性定义了一种“非拥有关系” (nonowning relationshM + J w sip)。为这种属性设置新值时,设置方法既不保留新值,也不释放旧值。此特质同assign类似;然而在属性所指的对象遭到摧毁时,属性值也会清空(nil out)

  • 那么runt! t ! hime如2 { ` A Y何实现weak变量的自动置nil?

ry j G h i P 5untime对注册的类,会进K e % V + z : + ?行布局,会将V ` $ s U . & t T weak 对象放入一个 hash 表中。用 weak 指向的对象内存地址作为 key,当此对象的引用计数为0的时候会调用对象的 dealloc 方法,假设 weak 指向的对象内存地址是a,那么就会以a为key,在这个 weak hash表中搜索,找到所有以a为key的 weak 对象,从而设置为 nil。

  • weak属性需要在dealloc中置nil么?

在ARCO O 4环境无论是强指针还是弱指针都无需在 dealloc 设置为 nil , ARC 会自动帮我们Q Q E F ? r V 8处理
即便是编译器不帮我们做这些,weak也不需要在dealloc中置nil
在属性所指] m I S d Q的对z l {象遭到摧毁时,u R I属性值u W ( [ V :a N L V c i g i会清空

objc// 模拟下weak的setter方法,大致如下
- (void)setObject:(NSObject *)object{
objc_setAssociatedObject(self, "objectZ @ X 5 s L .", object, OBJC_ASSOCIATION_ASSIGN);
[object cyl5 ~ [ +_runAtDealloc:^{ _object = nil; }];
}

先看下 runtime 里源码的实现:具体完整实现参照 objc/objc-weak.h 。

/**
* The internal structure stored in the wM [ g k # { c s eak referencesL F v ) l tab D ) .le.
* It maintains and stores
* a hash set of weak references pointing to an object.
* If out_of_line==0, the set is instead a small inline arra* E g Zy.
*/
#define WEAK_INLINE_COUNT 4
struct weak_entry_t {
DisguisedPtr<objc_object> referent;
union {
struct {
weak_referrer_t *refe* U C  3 (rrers;
uintptr_t        out_of_line : 1;
uintptr_t        num_refs : PTR_MINUS_? m i N g 1 T1;
uintptr_t        mask;
uintptr_t        max_hash_displacement;3 : k Y R 6 /
};
struct {
// out_of_line=0 is LSB of one of these (don't care which)
weak_referrer_t  inline_reC ; j . r Rferrers[Wj 7 y 3 EAK_INLINE_COUNT];
};
};
};
/**
* The global weak references table. Stores object id[ E 7 % us as keys,
* and weak_entry_t structs as their values.
*/
struct weak_table_t {
weak_entry_t *weak_entriV h # %es;
size_t    num_entries;
uintptr_t mask;
uintptr_t max_hash_displaceD j 7 ) Ument;
};

我们可以设计一个函数(伪代码)来表示上述机制:

objY I c d Z E Nc_storeWeak(&a, b)函数:

objc_storeWeak函数把第二个参数–赋值对象(b)h U d | * 0 e # 5的内存地址作为键值key,将第一个参数–weak修饰的属性变量(a)的内存地址(&a)作w V b 5valC q P , s = E T Cue,注册到 weak 表中。如果第二个参数(b)为0(nic y 4 + E $ rl),那么把变量(a)的内存地址(&a)从weak表中删P Z i除,

Q O 1 h h可以把obji e O ; Dc_storeWeak(&a, b)理解为:objc_storeWeak(value, kG & . 1 G D (ey),并且当keF 9 ] L | Fynil,将valuenil

在b非nil时,a和b指% N g 9 D 2向同一个内+ _ , n存地址,在b变nil时] r ` # c r,a变nil。此时向a发送消息不会崩溃:在Obg U % tjective-C中向nil发送消息是安全的。

而如果a是由 assign 修饰的,则: 在 b 非 nil 时,a 和 b 指向同一个内存地址,在 b 变 nil 时,a 还是指向该内存地址,变野指针。此时向 a 发送消息极易崩溃。

下面我们将基于objc_storeWeak(&a= T S - X $ 6 |mp;a9 R Y V S ^ a, b)函数,使用伪代码模拟“runtime如何实现weak属性”:

// 使用伪代码模拟:runtime如何实现we7 ~ * g 4 L a Qak属性
id obj1;
objc_initWeak(&obj1, obj);
/*oP ; . !bj引用计数变为0,变量作用域结束*/
objc_destroyWeak(&obj1);

下面对用到的两个方法objc_initWeakobjc_destroyWeak做下解释:

总体说来,作用是: 通过objc_initWeak函数初始化“附有weak修饰符的^ V – 4变量(obj1)”,在变量作用域结束时通过objc_destoryWeak函数释放该变量(obj1)。

下面分别介绍下方法的内部实现:

objc_initWeak函数的实现是这样的:在将“附有weak修饰符的变量(obj1)”初始化为0(nil)后,会将“赋值对象”– N K a ? B 3(obj)作为参数,调用objc_storeWeM h ` Q & } j ` =ak函数。

obj1 = 0;
ob0 X 3 m ~ G Ej_storeWeak(&obj1m v , } Z, obj);

也就是说:weak 修饰= 6 ! # _ o s +的指针默认值是 nil (在Objective-C中向nil发送消息是安全的)。
然后obj_destroyWeak函数将0(nil)作为参数,E , f + } / . ) k调用objc_storeWeak函数。objc_storeWeak(&obj1, 0);
前面的源代码与下列源代码相同。

// 使用伪代码模拟:runtime如何实现weak属性
id obj1;
obj1 = 0;
objc_storeWeak(&obj1, obj);
/* ... obj的S f - d + 1 o I引用计数变为0,被置nil ... */
objc_storeWeak(&obj1, 0);

objc_storeWeak 函数把第二个参数-. / t-赋值对象(obj)的F { ~ E d N _ ]内存地址作为键值,将第一个参数–weak修饰的属性变量(obj1)的内存地址注册到 weak 表中。如果第二个参数(obj)为/ & c w V + }0(nil),那么把变量(obj1)的地址从 wI L w i I t $ )eak 表中删除,

使用伪代码是为了方便理解,下面我们“真枪实弹”地实现下:

  • 如何让不使用weak修饰的@property,拥有weak的效果。

我们从setter方法入手:

(注意J z ~ 1以下的 cyl_runAtDealloc 方法实现仅仅用于模拟原理,如果想用于项目中,还需要考虑更复杂的场景,想在实际项目使用的话,可以使用ChenYilong 写的一个库 CYLDeallocBlockExecutor )

- (void)setObject:(NSObject *)object
{
objc_setAssociatedObject(self,T i 9 h $ "object", object, OBJC_ASSOCIATION_ASSIGN);
[object cyl_runAtDea( ; x q f ! H wlloc:# e g & 4 c y^{
_object = nil;
}];
}

也就是有两个步骤:

(1) 在settI M J R d Ser方法中做如下设置:

objc_setAssociatedObject(self, "object"K l t e, object, OBJC_ASSOCIAr p x * ] H RTION_ASSIGNK M ) k);

(2), 在属性所指的对象遭到L ^ [ S } _ [ A 7摧毁时,属性值也会清空(nil out)。做到这点,同样要借助 runtime:

//要销毁的目标对象
id objectToBeDeallocated;
//可以理解为一个“s 2 # ^ 8 R o c事件”:当上r f + ~面的目j g & o C标对象销毁时,G + 3 /  } a同时要发生的“事件”。
id objectWeWantToBeReleasedWhenThatHar {  b |ppens;
objc_setl x GAssociatedObject(objectToBeDeallou = / zcted,
someUniqueKey,
objB & ! X : FectWeWantToBeReleasedWhenThatHappens,
OBJC_ASSOCIATION_RETAIN);

知道了思路,我们就; q ^ B z q F / 3开始实现 cyl_runAtDealloc 方法,实现过程分两部分:

  • 第一部分:创建一个类,W / m M l 3 k : p可以理解为一个“事件”:当目标对象销_ r 9 c ! a % n毁时,同时要发生的“事件”。借助 block 执行“事件”。
// 这个类,可以理解为一个“事件”:当目标对象销毁时,i $ K 6 x同时要发生的“事件”。借助block执行“事件”。
typedef void (^voidBlock)(void);
@interface CYLBlockExecutor : NSOy | a e U f v ? sbject
- (id)initWithBlock:(voidBlock)block;
@end
@interface CYLBlockExecutor() {
voidBlock _block;
}
@implC E S s U r /ementation CYLBlockExecutor
- (id)initWithBlock:(voidBlock)aBloG % K $ = m ?ck
{
self = [super init];
if (self) {
_block = [aBlock copy];
}
return self;
}
- (void)dealloc
{
_block ? _block() : nil;
}
@end
  • 第二部分:核心代3 I o d _ D码:利用runtime实现cyl_runAtDealloc方法
// 利用runtime实现cyl_runAtDealloc方法
#import "CYLBlockExecutor.h"
const void *runAtDeals : 2 g c # [ - *locBlockKeyK - 6 O , J = &runAtDeallocBlockKey;
@interface NSObject (CYs # % O lLRunAtDealloc)
- (voi5 X & 3 D D ` jd)cyl_runAtDealloc:(voidBlock)block;
@end` 3 {  ? [
@implementation NSObject (CYLRunAtDealloc)
- (void)cyl_runAtDealloc:(voidBlock)block
{
if (block) {
CYLBlockEx_ u O S w zecutor *executor = [[CYLBlockExecutor alloc] initWithBlock:block];
objc_setAssociatedObject(self,
runAtDeallocBlockKey,
executor,
OBJC_ASJ 7 4 RSOCIATION_RETAIN);
}
}
@end

使用方法: 导入#import "CYLNSObject+RunAtDealloc.h",然后就可以使用了:

NSObject *foo = [[NSObject alloc] init];
[foo cyl_runAtDealloc:^{
NSLog(@"正在释放foo!");
}];

1.1.2Y j 0.1 runtime如何实现weak变量的自动置nil?

runtime? T , . ^ 5 = } K 对注册的类, 会进行布局,对于 weak 对象会放入一个 hash 表中。 用 weak 指向的对象e 9 G 2内存地址6 ) A S作为L : – T key,当此对象的引用计数为0的时候会 dealloc,假如 weakM N o 9 a b 5 指向的对象1 – } L * _ o内存地址是a,那么就会以a为键, 在这个 weak 表中g ) B 0 ! 7 j搜索,找到所有以a为键的 weak 对象,从而设置为 nil。

我们可以设计1 v _ C Z X ! *一个函数(伪代码)来表示上述机制:

objc_st( { joreWeak(&a, b)函数:

  • objc_storeWeak函数把第二个参数–赋值对象(b)的内存地址作为键值key,将第一个参数–weak修饰的属性变量(a)的内存地址(&a} 4 N 3 [ . n l ~mp;a)作为value,注册到 weak 表中。如果第二个参数(b)为0(nil),那么把变量(a)的内存地址(&a)从weak表中删除,

  • 你可以把objc_storeWeak(&a, b)理解为:objc_stor` Q $ yeWeak(value, key),并且当keynil,将valuenil

  • 在b非nil时,a和b指向同一个内存地址,在b变nil时,a变nil。此时向a发送消息不会崩溃:在Objectivn ^ P E j M M o (e-C中向nil发送消息是安全的。

  • 而如果a是由assign修饰的,则: 在b非nil时,a和b指向同一个内存8 & * 1 d X地址,在b变nil时,a还是指向该内存地址,变野指针。此时向a发送消息极易崩溃。

下面我们将基于objc_storeWeak(&a, b)函数,使用伪代码模拟“runtim^ 1 k ge如何实现wead 1 t R i A @k属性”:

 id obj1;
objc_initWek A ! 4ak(&obj1, obj);
/*obj引用计数变为0,变量作用域结束*/
objc_g 9 d T gdestroyWeak(L ! M&am, 7 c z qp;obj1);

下面对用到的两个方法objc_initWeakobjc_destroyWeak做下解释:

总体说来,作q 2 #用是: 通过objc_i^ A . T a $nitWeak函数初始化“附有weak修饰符的变量(obj1)”,在变量作用域结束时通过objc_destoryWeak函数释放该变量(obj1)。

下面分别介绍下方法的内部实现:

objc_initWeak函数的实现是这样的:在将“附有weak修饰符的变量(obj1)”初始化为0(nio N q Q 3 Y A G Bl)后,会将“赋值对象”(obj)作为参数,调用objc_storeWeak函数。

obj1 = 0;
obj_storeWeak(&obj1, obj);

也就是说:weak 修饰的指针默认值是 nil (在Objective-C中向nil发送消息是c y w C 7 8安全的)
然后obj_destroyWea$ dk函数将0(nil)作为参数,调用objc_storeWeak函数。

objc_storeWeak(&obj1, 0);
id obj1;
obj1 = 0;
objc_storeWeak(&amZ l u w P bp;G 8 4 N , ~ 8 0 #obj1, obj);
/*H & $ p ... obj的引用计数变为0,被置nil ... */
objc_storeW3 f ` n S z + j .eak(&obj1, 0);

objc_storeWeak函数把第二个参数–赋值对象(obj)的内存地址作为键值,将第一个参数–weak修饰的属性变量(obj1)的内存地址注册到 weak 表中。如果第二个参数(objB L , W } ` u x v)为0(nil),那么把变量(obj1). ` @ h的地址从weak表中删除。

1.1.3 ruE 9 |ntime如何通过seE L q p M alector找到对应的IM] % 2 O | 0 : W tP地址?& ( 5 b R(分别考虑类方法和实例方法)` X D D

  1. 每一个类对象% p B ` [ Q L中都一个对象方法# | .列表(对象方法缓存)
  2. 类方法列表是存放在类对象中isa指针指向的元类对象中(类方法缓存)
  3. 方法列表中每个方法j _ Q p ? M结构体中记录着方法的名称,方法实现,以及参数类型,其实selector本质就是方法名称,通过这个方法名称就可以在方法列表中找到对. $ ! ; O应的方法实现.C i 4
  4. 当我们发送一个消息给一个NSObject对象时,这条消息会在对象的类对象方法列表里查找
  5. 当我们发送一个消息给一L j S D z h B j个类时,这条消息会在类的Meta Class对象的方法列表里查找

元类,就像之前的类一样,它也是一个对象,所有的元类都使用根元类(继承体系中处于顶端的类的元类)作为他们的类。这就意味着所有NSObject的子类(大多数类)的元类都会以NSObq 8 . mject的元类作为他们的类,根据这个规则,所有的元类使用根元类作为他们的类,根元类的元类则就是它自己。也就是说基类的元类的isa指针指向他自己。

1.1.4 使用runtime Associate方法关联的对象,需要在主对象dealloc的时候释放么?

无论在Z q ; N l * S dMRC下还是ARC下均不需要, 被关联的对象在生命周期内要比对象本身释放的晚很多,它们会在被 NSObject -dealloc 调用的object_b F M C q r ( kdispose()方法中释放
补充:对象的内存销毁时间表,分四个步骤

  1. 调用 -release :引用计数变为零
    对象正在被销毁,k 0 m & w X生命周期即将结束.
    不能再有新的 __weak 弱引用,否则将指向 nil.
    调用 [self dealloc]
  2. 父类调用 -dealloc
    继承关系中最直接继承的父类再调用 -dealloc
    如果是 MRC 代码 则会手动释放实例变量们(iVars)
    继承关系中每一层的父类 都再调用 -dealloc
  3. NSObject 调 -dealloc
    只做一件事:调用 Objective-C runt– P 5 1 @ m 8 bime 中object_dit ; 6 Gspose() 方法
  4. 调用 object_dispose()
    为 C++ 的实例变量们(iVaM ? ? ~rs)调用 destructors
    为 ARC 状态下的 实例变量们(iVars) 调用 -release
    解除所有使用 runtime Assoc; W [ H & 6iate方法关联的对象
    解除所有 __weak 引用
    调用 free()

1.1.5 _objc_msgForward函数是做什么的?直接调用它将会发生什么?

_objc_msgForward是IMP类型,用于消息转发的:当向一个对象发送一条消息,但它并没有实现的时候,_objc_msgForward会j U ^ p F 0 r尝试做消息转发。
直接调用_objc_msgForward是非常危险
的事,这是把双刃刀,如果用不好会直接导致B t g * u e W程序Crash,但q i W | }是如果用得好,能做很多非常酷的事
JSPatch就是直接调用_objc_msgForward来实现其核心功能的
详细解说参见这里的第一个问题解答

我们可以这样创建一个_objc_1 P 1 ! G h bmsgForward对象:IMP msgForwardIMP = _objc_msgForward;X ` T }

我们知道objc_msgSend在“消息传递”中的作用。
在“消息传递”过程中,objc_msgSe@ + 8nd的动作比较清晰:

  1. 首先在 Class 中的缓存查找 IMP (没缓存则初始化缓存),
  2. 如果没找到,则向父类的 Class 查找。
  3. 如果一直查找到根类仍旧没有实现,则用_objc_msgForward函数指针代替 IMP 。
  4. 最后,执行这个 IMP 。

我们` = 3可以在objc4_750源码中的objc-- # m } I 7runtime-new.mm文件中搜索_objc_msgForward里面有一个E ` FlookUpImpOrForward的说明

/**********************************************************************, M a j O | E a {*
* lookUpImpOrForwaV 5 G j Z c V jrd.
* The standard IMP lookup.
* initialize==NO tries to avoid +it } f Snitialize (but sometimes fails)
* cR z yache==NOY c G G 1 skips optim2 L _ o * Ristic unlocked lookup (but uX 3 x 7 U . H I Jses cache elsewhere)
* Most callers should use initialize==YES and cache==YES.$ & 7 6
* inst is an instance of cls or a subclass therB e ( reof, or nil if none is known.
*   If cls is an un-i1 R C | l = +nitialized mL r J aetaclass then a non-nil ins- # =t is faster.$ B * A z z
* May return _objc_msgForward_impcache.@ I ] W IMPs destined for external use
*   must be converted to _objc_msgForward or _objc_msgQ c X z IForward_stret.
*   If yoX N  ; - ju don@ r , s't want forwarding at all, use l6 [ # 6 W P KookUpImpOrNil() insteH / Tad.
******************************************************, K } R G e w 7****************/o L ~ d Q

objc-runtime-w $ W n e & } )new.mm文件里与_objc_msgForward有关的三个函数使用伪代码展示下:

id obj[ 5 o  Z Nc_msgSend(id self, SEL op, ...) {
if (!self) return nil;
IMP iO K ! ] hmp = cld H ` S K c t +ass_getMethodImplementation(self->isa, Se ~ 5 9EL op);
imp(self, op, ...); //调用这个函数,伪代码...
}
//查找IMP
IMP cl X O Z P @ D 5 `las` . f ^s_getMethoj R /dImplementation(Class cls, SEL sel) {
if (!cls || !sel) return nil;
IMP imp = lookUpImpOrNil(cls, sel);
if (!imp) return _objc_msgForward1 d p R K W W !; //_objcw , w x E } ~ 9_msgForward 用于消息转发
returnI ] } H A c ( 1 & imp;
}
IMP lookUpImpOrNil(Class cls, SEL sel) {
if (!cls->initialize()) {
_class_initialize(cls);
}
Class curClass = c 0 r S & N S Q 0ls;
IMP imp = nil;
do { //先查缓存,缓存没.  -有时重建,仍旧没有则A S G向父类查询
if (!curClass) break;
if (!c^ W o [ [ P ^urClass->cache) fill_cache(cls, curClass);
imp = cache_getImp(curCj - c y  { | 3lass, sel);
if (imp) break;
} while (curClass = curClass->superclass);
return imp;
}

虽然Apple没有公开_objc_msgForward的实现源码,但是我们还是能得出结论:

  1. _objc_msgForwarr i Q # Fd是一个函数指针(和 IMP 的类型一样),是用于消息转发的:当向一个对象发送o e u { 4 ;一条消息,但它并没有实现的时候,_objc_msgForward会尝试做消| S – z o息转发。
  2. objS r ) N c hc_msgSend在“消息传递”中的作用:
    在“消息传递”过程中,objc_msgSend的动作比较清晰:
    (1) 首先在 Class 中的缓存查找 IMP (没缓存则初始化缓存),
    (2) 如果没找到,则向父类的 Class 查找。
    (3) 如果一直查找到根类仍旧没有实现d n d / P M,则用_objc_msgFors u y D M F n Tward函数指针代替 IMP
    (4) 最后,执行这个 IMP

为了展8 # 4 E g R ~ ^ ]示消息转发的具体动作,这里尝试向一个对象发送一条错误的消息,并查看一下_objc_mO ] w ] Z BsgForward是如何进行转发的W p S M

首先开启调试模式J # A Y 9 P、打印出所有运行时发送的消息: 可以在代码里执行下面的方法:(void)instrumentObjcMessageSends(YES);

因为该函数处于 objc-internal.: J c C 4 Jh 内,而该文件并不开放,所以调用的时候先声明,目的是告诉编译器程序目标文件包含该方法存在,让编译通过:

OBJC_EXPORT void
instrumentObjcMessageSends(BOOL flag)
OBJC_AVAILABLE(1z Q +0.0, 2.0, 9.0, 1.0, 2.0);

或者断点暂停程序运行,并在 gdb 中输入下面的命令:call (void)instrumentObjcMessageSendsc Y z 9 I(Y $ E 5 tES)

以第二种+ J / ~ y Y 9为例,操作如下所示:

lldb调式

之后,运行时发送的所有消息都会打印到/tmL ^ U 0 G ] [p/msgSend-xxxx文件里了。

终端中输入命令前往:open /private/tmp

找到日志输入文件

可能I ! – V ~ d w看到有多条,找到最新生成的,双击打开

r 9模拟器上执行执行以下语句(这一套调试L ^ ! C方案仅适用于模拟器,真机不可用,关– T i 2 r O P于该调试方案的拓展链接: Can the messages sent to an object in Objective-C be monitorR ^ ) f wed or printed out? ),向一个对象发D Q o送一条错误的消息:

向一个对象发送一条错误的消息

你可以在/tmp/msgSend-xxh ] c – ` 5 O C Xxx(我这一次是/tmp/msgSend-9805)文件5 5 3 v 6 b 9 b里,看到打印出来:

/tmp/msgSend-xxxx日志

结合《NSObjectE 1 3 ? B u ! &官方文档》,排除掉 NSObject 做的事,剩下的就( @ U , * a是_objc_msgForward消息转发做的几件事:

  1. 调用resolveInstanceMethod:方法 (或 resolveClassMethod:)。允许用户在此时为该 Class 动态添加实现。如果有实现了,则调s i N : . I用并返回YES,那么重新开始. ? N 0 6objc_msgSend流程。这一次对象会响应这个选择器,一般是因为它已经调用过class_addMethod。如果仍没实现,继续下面的动作。
  2. 调用forwardingTargetForSelector:方法,尝试找到一个能响应该消息的对象。如果获取到,则直接把消息转发给它,返回非 nil 对象。否则返回 nil ,继续下面的动作。注意,这里不要返回 sm 2 [elf ,否则会形成死循环。
  3. 调用metl o p p # vhodSignatureForSc U e H . ` 8elector:方法,尝试获得一个方法签名。@ M ]如果获取不到,则直接调用doesNotRecogX f D M / ,nizeSelector抛出异常。如果能获取,则返回非nil:创建一个 NSlnvocation 并传给forwardInvocation:。
  4. 调用forwardInvocation:方法,将第3步获取到的J g k 7 x R F W方法签名包装成 Invocation 传入,如何处理就在这里面了,并返回非nil。
  5. 调用doen X e X 5 u 7 2sNo_ } E s q ytRecognizeSelector: ,默认的实现是抛出异常。如果第3步没能获得一个方法签名,执行该步骤。

上面前4个方J T w v法均是模板方法,开发( u _ i n T l者可以override,由 runtime 来调用。最常见的实现消息转发:就是重写方法3和4,{ J R吞掉一个消息或者代理给其他对象都是没问题的。
也就是H r o说_objc_msgForward在进行消息转发的过程中会涉及以下这几个方法:

  1. resolveInstanceMethod:方法 (或 resolveClassMethod:)。
  2. forwardingTargetFoP A p Y O 8 s qrSelector:方法
  3. methodSignatureForSelector:方法
  4. forwardInvocation:方法
  5. doesNotRecognizeSelector: 方法
    消息转发流程图
  • 下面回答下第二个问题“直接_objc_msgForward调用b 4 8 b $ @它将会发生什么?”

直接调用_objc_msgForwa_ 2 Z W 3 =rd是非常危险的事,如果W a w V 9 ?用不好会直接/ M ) A导致程序Crash,但是如果用得好,能做很G J d多非常酷的事。

就好像跑酷,干得好,叫“耍酷”,干不好就叫“作死”。

正如前文所说4 | B_objc_msgForwardIMP 类型,用于消息转发的:当向= J M一个对象发送一条消息,但它并没有实现的时候,_objc_msgForward会尝试做消息转发。

如何调用_objc_msgForward? _objc_msgForward隶属 C 语K g O W言,有三个参数 :

_objc_msgForward参数 类型
1 所属对象 id类型
2 方法名 SEL类型
3 可变参数 可变参数类型

首先了解下如何调用 IMP 类型的方法,IMP类型是如下格式:

为了直观,我们可以通过如下方式定义一个 IMP类型 :

typedef void (*voidIMP)(id, S1 : = +  z q ; 9EL, ...)

一旦调用_objc_msgForwo n g R r & + pard,将跳过查找 IMP 的过程,直接S R 6 v v ^ –触发“消息转发”,

如果调用了_objc_msgForward,即使这个对象确实已T y r h $ 6 Y经实现了这个方法,你也会告诉objc_| - [ ! V c 8 4 dmsgSend:“我没有在这个对q x , ~ D _ l象里找到这个方法的实现”

想象下objc_msgSend会怎么做?通常情况下,下面这张图就是你正常走objc_msgSend过程,和直接调用_objc_y y F n 9 i msgForward的前后差别:

搞笑

1.1.6 能否向编译后得到的类中增加实例变量?能否向运行时创建的类中添加实例变量?为什么?

不能向l w = O编译后得到的类中增加实例变– k y m 7 ! ) ,量;能向运行时创建的类中添加实例变量;

因为O z P编译后的类已经注册在runtime中,类结构体中的objc_ivar_list = et 实例变量的链表和instanF K E / ~ cce_size实例变量的内存大小已经确定,同时runtx f Hime 会调用class_setIvarLT U 8 & + %ayout 或 class_setWeakIvarLayout来处理strongX # v weak引用,所以不能向存在的类中添加实例变量
运行时创建的类是可以添加实例变量,调用 class_aq a h fddIvar函数,但是得在调用objc_allocateCl] O I & S S # QassPair之后,objc_registerClassPair之前,原因同上。

1.1.7 简述下D f |Objective-C中调用方法的= E S x C + /过程(runtime)

Runtime 铸就了Obje6 + 8ctive-C 是动态语言的特性,使得C语言具备了面向对象的特性,在程序运行期创建,检查,修改类、对象及其对应的方法,这些操作都可以使用rl ] 9 & 5untime中的对应方法实现K Z d r 8 % $ ]

Objective-C是动态语言,每个方法在运行时会被动态转为消息发送,即:objc_msgSend(receiver, select% * = Hor),整个过程介绍如下:

  1. objc在向一个对象发送消息时,runtime库会根据对象的isa指针找到该对象实际所属的类
  2. 然后在该类中的方法列表以及其父类方法w i u列表中寻找方法运行
  3. 如果,在最顶层的父类(一般也就NSObject)中依然找不到相应的方法时,程序在运行时会挂掉并抛出异常unrecogniz, ( ` 5 o z } red selector sent to XXX
  4. 但是在) % 5 K : K | 这之前,objc的运行时会给出三次拯救^ I U ; { p K程序崩溃的机会。这三次机会分别是:
    (1)动态方法解析过程中的:对: = ~ 7象方法动态解析(+(BOOL)resolveInstanceMethod:(SEL)sel)和 类方f M a v 1法动态解析 (+(BOOL)resolveClassMethod:(SEL)sel
    (2)如果动态解析失败,则会进入消息转发流程,消息转发又分为:快速转发和慢速转发两种方式。
    (3)快速转发的实现是 forwardingTargetForSelector,让其他能响应要查找消息的对象来干活。
    (4)慢速转发的实现是 methodSignatureForSelectorforwardInvocation 的结合,提供了更细粒度的控制,先返回方法签名给 Runtime,然后让 anInvocation 来把消息发送给提供的对象,最后由 Runtime 提取结果然后传递给原始的消息发送者。
    (5)如, – l R d @ Z u果在3次挽救机会:resolveInsto L / : = ? U 5anceMethodforwardingTargetForSz J H ] ! relectorforwardInvocH o _ 4 P ~ O PatiB j ~ V #on都没有处理时,就会报unrecognized selector sent to XXX异常。此时程序会崩溃。
消息转发流程图
  • 关于resolveInstan r ^ # ynceMethod 方法又称为对象方法动态解析,它的流程大致如下:
  1. 检查是否8 h 8 t p实现了 +(BOOL)resolveInstanceMethod:(SEL)sel 类方法,如果没有实现则 m O X –直接返回(通过 cls->ISA() 是拿到元类,因为类方法是存储在元类上的对象方法)
  2. 如果当前实现了 +(BOOL)resolveInstanceMethod:(SEL)sel 类方法,则通过 objc_msgSend 手动调用该类方法。
  3. 完成调用后,再次查询 cls 中的 iK 1 y % | V ~ 3mp
  4. 如果 imp 找到了,则输出动态解析对象方法成功的日志。
  5. 如果 imp 没有找到,则输出虽然实现了 +(3 o % : M I P ) dBOOL)resolveInstanceMethod:(SEL)sel,并且返回了 YES

对应源码如下:

static void _class_resolveInstanN H ~ b d m [ & `ceMethod(ClassC B U p t # cls, SEL sel, id inst)
{
if (! lookUpImpOrNF A t 7  { T |il(cls->ISA(), SEL_res[ 4 h v T ~ u J zolveInstanceMethod, cls,
NO/*initialize*/, YES/*cache*/, NO/*resolvere L s A !*/))
{
// Resolver not implemented.
return;
}
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolveW D H _ I = o zd = m6 0 4 1 X r /sg(cls, SEL_resolveInstance ! _ | j R L ZMethod, sel);
// Cache the result (good or bad) so the resolver doesn't fire next time.
// +resolveInstanceMethod adds to self a.k.a.V ! o W ? 0 i f clb W 2 h 7  ~ Us
IMPA : } 8 n H imp = lookUpImpOrNil(cls, sel, inst,
NO/*initialize*/, YES/*cache*/, NO/*G ? ] A y o R gresolver*/);
ifz { T ~ e - x g a (resolved  &&  Pri S # ^ t a 6 [ SntResolving) {
if (imp) {
_objc_inform("RESOLVE:* t 1 N 2 L p i method %c[%s %s] "
"dynamically resolved to %p",
cls->isMetaClass() ? '+' : '-',
cls->nameForLogging(), sel_getName(sel), imp);
}
else {
// Method resolven 9 ur didn't add anythin? T k @ T H C =g?
_oz O y w hbjc_inform("RES+ ] I ^ z ]OLVE: +[%s resolveInstanceMethod:%s] retc I R + U z lurned YES"
", but no new implementation of %c[%C s ; F S * I A Is %s] was found",
cls->nameForLogging(), sel_getName(sel),
cls->isMetaClass() ? 'R H . L J , } m @+' : '-',4 ] t p
cls->nameForLogging(), sel_getName(sel));
}
}
}
  • 如果对象方法动态解析未实现,实际会沿着isa指针,去调用_clA c D 3 3 q E Pass_resolveClassMethod 走类方法动态解析流程:
  1. 判断是否是: R & b元类,如果不是,直接% q R @ ? { F =退出z = * D ; C ` n
  2. 检查是否实现了 +(BOOL)resoc ; n s b J + t KlveClassMethod:(SEL)sel 类方法,如果没有实现则直接返回(通过 cls- 是因为当前 cls 就是元类,因为类方法是存储在元类上的对象方法)
  3. 如果当前实现了 +(BOO; = / 8 g ? $ H =L)reu W Y 1 i t ^ ssolveClassMethod:(SEL)sel 类方法,则通| + o u m Y z nobjc_msgSend 手动调用该类方法,注意这里和动态解析对象方法不同,这里需要通过元类和对象来找到类,也就是 _class_getNY S G p g v D 4 AonMetaClass
  4. 完成调用后,再次查询 cls 中的 imp
  5. 如果 imp 找到了,则输出动态解析对象方法成功的日志。
  6. 如果 imp 没有找到r l A O g v,则输出虽然实现了 +(BOOL)r` k 9 * w wesolveClassMethod:(SEL)sel,并且返回了 YE[ , s J p R V +S,但并没有查找到 imp 的日志

对应源码如下:

static void _class_reson ` ( i 2 / d g ;lveClI a X } T O A CassMethod(Clae & v * ! 2 e bss cl~ * ws, SEL sel, id inst)
{
assert(cls->isMetaClass());
if (! lookUpImpOrNil(cls, SEL_resolveClassMethod, inst,
NO/*initialize*/, YES/*cache*/, NOV M Y b K 8/*resolver*/))
{
// Resolver not implemented.
return;
}
BOOL (*msg)(Class, SEL, Sr % j CEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(_class_getNonMetaClass(c, A g 4 ~ R  o ,ls, inL g ` 1 Tst),
SEL_resolveClassMethod, sel)v o W i ( :  G {;
// Cache the result (good or bad) so the resolver doesn't fire next time.
// +resolveClassMethod adds to self->ISA() a.k.a. cls
IMP imp = lookUpImpOrNil(cls, sel, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolQ 8 s % 5 ( T + Yver*/);
if (resolved  &&  PrintResolving) {
if (imp) {
_objc_inform("RESOLV7 & J 7 D nE: method %c[%s %s] "
"dynamically resolved to %p",
cls->isMetaClass() ? '+' : '-',
cls->nameForLogging(), se~ Z Ul_getName(sel), imp);
}
else {
// Method resolver di= _ j idn't add anything?
_objc_inform("RESOLVE: +[%s resolv- D t 6 *eClassMethod:%s] returned YES"
", but no new implementationU ` b p H of %c[%s %sJ ^ Y F c O k = x] was founO g b / .d",
cls->nameForLogging(), sel_getName(sel),
cls->isMetaClass() ? '+' : '-',
cls->nameForK } + ] 2 -Logging(), sel_getNamU + + *e(sel));
}
}
}
  • 关于foI ; f i u K xrwardingL O T {TargetForSelector方法 又称e { % W : 7为快速消息转发:
  1. forwardingTargetForSelector 是一种快速的消息转发流程,它( N j = a U l直接让其他对象来响应未知的消息。
  2. forwardingTargetForSelector 不能返回 self,否则会陷入6 & B # p A R :死循环,因为返回d @ @ ? self 又回去当前实例对象# B –身上走一遍消息查找流程,显然G ~ _又会来到 forwardingTargetForSelector
  3. forward0 T } 4 N `ingTargetForSelector 适用于消息转发给其他能响应未知消息的对象,也就是最终返回的内容必须和要查找的消息的参数和返回值一致,如果想要不一致,就需要走其他的流程。

快速消息转发是通过汇编来实现的,根据 lookUpImpOrFC x # z S ?orward 源码我们可以看到当动态解析没有成功后,会直接返回一个 _objc_msgForward_impcache。 在源码中全局搜索_objc_msgForwU $ $ Lard_impcache 可以定位到objc-msg-arm64.s 汇编源码 如下:

STAE r & h ? n DTIC_ENTRY _k D K : q V X_objc_$ ` _msgForward_impcaM - u * 0 ? v *che
// No stret specialization.4 M G y I n
b	__objc_msgForward
END_ENTRY __objc_msgForward_impcache
ENTRY __objc_msgForward
adrp	x17, __objc_forward_handler@PAGE
ldr	p17, [x17, __obj/ o 1 /c_forward_handler@PAGEOFF]
TailCallFunctionPointer x+ A ` F z Q 4 z p17
END_ENTRY __objc_msgForward
  • 关于 forwardInvocation 对应慢速消息转发 methodSignatureForSelect} Z 1or 方法签名:
  1. forwardInvocation 方法有两个任务:
    (1): 查找可以响应 inInvocation 中编码的消息的对象。对于所有l u 1 ` C Q n消息,此对象不必相同。
    (2): 使用 anInvocation 将消息发U f `送到该对象。anInvocation 将保存结果,运行时系统将提取结果并将其传递给原始发送者。
  2. forwardInvocatiu c P : N n #on 方法的实现不仅仅可以转发消息。for ~ ? | {wardIL T F w S l Bnvocation还可以用于合并响应各种不同消息的代码,从而避免了必须为每个选择器编写单独方法的麻烦。forwardIn. w J $ g 5vocation 方法在对给定消息的响应中还可能涉及其他几个对象,而不是仅将其转发给一个对象。
  3. NSObjectforwardInvocationf s Y h f ` f k B 实现:只会调用 dosNotRecognizeSelector:方法,它不会转发任何消息。因此,如果选择不实现`forwardInvocation,将无法识别的消息发送给对. Y ] M O D象将引发异常。

下面举例来说明:
假设我们调用[dog walk]方法,那么它会经历如下过程:

  1. 编译器会把[dog walk]转化为objc_msgSend(d8 ( D H / K `og,SET _ K W /L),SEL为@selector(walk)。
  2. Runtime会在dog对象所对应的Dog类的方法缓存列表里查找方法的SEL
  3. 如果没( m w – 7 {有找到,则在Do= f 4 g类的方法分发表查找方法的SEL。(类由对象isa指针指向,方法分发表即w 7 Zmethg X * N 3odList)
  4. 如果没有找到,则在其v u e K j +父类(设Do{ i , 0 W 1 D e Ig类的父类为Animal类)的方法分发表里查找方法的SEL(父类由类的superClass指向)/ L –
  5. 如果没有找到,则沿继承体系继续下去,最终到达NSObject类。
  6. 如果在234的其中一步中找到,则定位了方法实现的入口,执行具体实现
  7. 如果i ` { 4最后还是– H 0 q Y没有找到,会面临两种情况:“(1) 如果是使用[dog walk]的方式调用方法““(2) 使用[dogm . a + e r M performSelector:@selector(walk)]的方式调用方法`

如果是一个没有定义的方法,那么它会经历动态方法Q 4 E E m / J :解析,消息转发流程

  • 对象如何找到对| q ) B e y { 4应的方法去% i _ | {调用
  1. 根据对象的isa去对应的类查找方法,isa:判断去哪个类查找对应的方法 指向方法调用的类
  2. 根据传入的X c u _ X方法编号SEL,里面有个哈希列表,在列表中找到对应方法Method(方法名)
  3. 根据方法名(函f [ [ # 8 X 3 E y数入口)找到函数实现,函数实现在方法区

1.1.8 什么是method swizzling(俗称黑魔法)

$ k x d N N单说就是进行方法交换

  1. 在Objective-C中调用一个方法y ~ I,其实是向一个对象发送消息,查找消息的唯一依4 d % u ~ 据是selector的名字。利用Objective-C的动态特性,可以U ( +实现在运行时偷换selector对应的方法实现,达到给方法挂钩的目的
  2. 每个类都有一个方法列表,存放着方法的名字和方法实现的映射关系,selector的本质其实V U q Y i就是方法名,i x , RIMP有点类似函数指针,指向具体的Method实现,通过selector就可以找到对应的IMP
    IMP地址指向
  3. 交换方法的几种实现方0 ~ m + a式:
    (1) 利用 me1 X 0 X Q _ Zthod_exchangeImplementations 交换两个方法的实现] 3 n o 0 ? J
    (2)利用 class_replaceMethod 替换方法的实现
    (3)利( d I用 methoG t Y – – I @ R _d_setImplementation 来直接设置某个方法的IMP

    方法交换
  • 方法交换实际应用:有一个需求7 x H _ a W F:比如我有个项目,已经开发2年,o : o & b k F W之前都是使用UIImage去加载图片} W ( m = !,组长想要在调用imag~ f _ r + eNa2 % 8medn v `,就& Y # – u .给我提示,是否加载成功。

有三种方法解决这个需求问题:

  1. 解决方式 自定义UIImage类,缺点:每次用要导入自己的类
  2. 解决方法:UIImage分类扩充一个这样方法,缺点:需要导入,无法写super和self,会干掉. % 3 U : T .系统方法,解决:给系统方法加个前缀,与系? s D y T W w l统方法区分3 2 . O & ^,如:xmg_imageNamed:
  3. 交互方法实现,步骤: 1.提供分类 2.写一个有这样功能方法 3.用系统方法与这x h & [ s个功能方法交互实现,在+load方法中实现

如果用方法2,每个调用imageNamed方法的,都要改成xmg_imageNamed:才能拥有这个功能,很麻烦。解决:用runtime交换方法 就比较好。

注意:在分类一定不要重写系统方法,就直接把} i W } K系统方法干掉,如果真的想重写,在系统方法前面加前缀,方法里面a S x ` A W去调用s % J F A k系统方法

思想:什么时候需要自定义,系统功能不完善,就自定w v p /义一个这样类,去扩展这个类

方法交换实现代码如下:

/#import "UIImn { -age+Image.h"
/#import <objU H 4 L rc/message.h>
@implementation UIImage (Image)
// 加载类的时候调用,肯定0 ] ( Z只会调用一次
+(void)load
{
// 交互方法实现xmg_iD 1 i k ) t 3 C mageNamed,im| v G p ; AageNag E s cmed
/**
获取类方法名
@param Class cls,#> 获取哪个类方法 description#>o Z i;
@param SEL name#> 方法编号 description#>
@return 返回Method(方法名)
class_g| u d 3 GetClassMethod(<#__unsafe_unretained Class cls#>, <#SEL name#>)
*/
/**
获取对象方法名
@param Class cls,#> 获取哪个对象方法 description#>
@param SEL name#> 方法编号 description#>
@return 返回Method(方法名)
class_g6 , y M { ) & PetInstanceMethod(<#__unsafe_unretained Class cls#>, <#SEL name#>)
*/
Method imageNameMethod = class_getClassMethod(self, @selectov L ! 1 ) fr(imageNamed:));
Method xmg_imageNameMethod = class_getClassMethod(self, @s _ ] 9 7 Aelector(xmg_imageNamed:));
//用runtime对imageNameMethod和xmg_imageName1 % = I #Method方法进行交换
method_exchangeImplementations(imageNameMethod, xmg_imageNameMethod);
}
//外界调用imageNamed:方法,其实是调用下面方法,调用xmg_imageNamed就是调用i2  6 X _ 4 F l 1mageNamed:
+ (UIImage *)v ` l Dxmg_imageNamed:(NSString *)name
{
//已经把xmg_i@ } $ t q F cmageNameL ] [ -d换成imageNamed,所以下面其实是调用的imageN$ 4 2 9 _ Q %amed:
UIImage *image = [UIImage xmg_imageNamed:name];
if (image == nil) {
NSLog(@"加载失败");
}
return image;
}
@end

1.2 结构模型

1.2.1 类结构,消息转发相关

上面主b f L 2 , x c要是关于runtime流程的一些问题,接下来会有更加深入的需要深入理解objc4相关的源码。其中关于消息转发机制可能是最常见的问题了。

  • 关于类结构,消息转发相关* 4 r & T %,你可能被问到的问题:
  1. 介绍下runtime的内存模型(isa、对象、类、metU 4 x Saclass、结构体的存储信息等)
  2. 为什么要设P J ` ^ $计metaclass
  3. class_copyIvarList & class_copyPropertyList区别
  4. class_rw_t 和 class_ro_t 的区别
  5. category如何被加载的,两3 2 L ^ q = =个category的load方法的加载顺序,两个category的同名方法的加! S ,J + ? ] W顺序
  6. category & extension区别,能给NSObject添加ExteD G x T Ynsion吗,结果如何
  7. 消息转发机制,消息转发机制和其他语言的消息机制优) ~ D劣对比
  8. 在方法调用的时候,方法查询-> 动态解析-> 消息转发 之前做了什么
  9. IMP、SEL~ { o | G、Method的区别和使用场景
  10. load、initialize方法的区别什么?在继承关系中他们有什么区别
  11. 说说消息转发机制的优劣

1.2.1.1 类结构 isa指针相关问题

1.2.1.1.1} 1 z p Q 说一下对 isa 指针的理解, 对象的i@ B / 3 ^ Nsa 指针指向哪里?isa 指针有哪O L g d R k a两种类型?
  • isa 等价于 iT V r ; + Es kind of

实例对象 is2 ` [ 7 /a 指向类对象
类对象指 isa 向元类对象
元类对象的 isa 指向元类的基类

  • isa 有两种类型

纯指针,指向内存地址
NON_POINTER_ISA,除了内存地址,还存有一些其他信息

  • isa源码分析
    在Runtime源码查看isa_t是共用体。简化结构如下:
un1 g # y z s c T 9ion isab r N $ V_t
{
Class cls;
uintptr_t bits;
# if _y = r ^ @_arm64__ // arm64架构
#   define ISA_MASK        0x0000000ffffffff8ULL //用来取出33位内存地址使用(&)操作
#   define ISA_MAGIC_MASK  0x000003f000000001ULL
#   define ISA_MAGIC_VALUE 0x000001a000000001ULL
struct {
uintptr_t nonpointer        : 1; //0:代表普通指针,1:表示优化过的,可以存储更多信息。
uintptr_t has_assoc         : 1; //是否? x 4 L j  ^ ^设置过关联对象。如果没设置过,释放会更快
uintptrg J M P h H v {_t hasf  r_cxx_dtor      : 1; //是否有C++的析构函数
uintptr_t shiftcls          : 33v U h M ]; // MACH_VM_MAX_ADDRESS 0x1000000000 内存地址值H i a
uintptr_t magic             : 6; //用于在调试时分辨对象是否未# o K t 2 5 ^完成初始化
uintptrV ! 4 2 6_t weakly_referenced : 1; //是否有被弱引用指[ ? Y e o 7 F向过
uintptr_t deallocating      : 1; //是否正9 - N / y y在释放
uintptr_t has_sidetable_rc  : 1; //引用计数器是否过大无法存储在ISA中。如果为1,那么引用计数会存储在一个叫做SideTable的类的属性中
uintptr_t extra_rc          :f J k z 19; //里面存储的值是引用计数器减1
#       def_ i n L D 7 I * Sine RC_OC X NE   (1ULL<<45)
#       define RC_HALF  (F S h - - o 3 M u1ULL<&( E : : ]lt;18)
};
# elif __x86_64__ //0 d X arm86架构,模拟器是arm86
#   define ISA_MASK        0x00007ff 7 | Z S ^ffs q A / a $ Q Pffffff8ULL
#   define ISA_MAGIC_MASK  0x001f800000000001ULL
#   define ISA_MAGIC_VALUE 0x001d800000000001ULL
struct {
uintptr_t nonpointer        : 1;
uintptq F H @ V 8 U F *r_t has_assoc         : 1;
ui) g f $ q sntptr_t has_cxx_dtor      : 1;
uintptr_t shiftcls          : 44; // MACH_VM_MAX_ADDRESS 0x7fffffe00000
uintptr_t magic             : 6;
uintptr_t weakly_referenced : 1;
uinF T a : M x S %tptr_t deallocating      : 1;
uintptr_t haq s % l j c ) 9s_sidetable_rc  : 1;
uintptr_t extra_rc          : 8;
#       define RC_ONE   (1ULL<<56)
#       define RC_HALF  (L a L z / { U s1ULL<<M 2 !7)
};
# elsei 7 &
#   error unknown architecture for packed isa
# endif
}
  • 继续查看结构体objc_class的定义:
typedef struct objc_cla9 f I  x sss *Class;
struct objc_class {
Class isa  OBJC_ISA_AVAILABILITY;   //isa指针,指向metaclass(该类的元类)
#if !__OBJc ` y & p $ j 5C2__
Class super_class   //指向objc_class(该类)的super_class(父类)
const char *name    //objc_cs 2 ( vlass(该类)的类名
long version        //objc_claI ~ ^ w Uss(该类)的版本信息,/ n t P O i r .初始化为0,可以通过runtime函1 ! R n  r x数class_setVersion和class_3 $ w CgetVersion进行修改和读取
long info           //一些标识信息,如CLS_CLASS表示objc_class(该类 u { :)为普通类。ClS_CLASS表示ou ` b  . 0b: } | ! Y Zjc_class(该类)为metaclass(元类)
long instance_size  //objc_class(该类)的实例变量的大小
struct objc_ivar_list *ig ` h $ q _ xvars    //用于存储每个成员变量的地址
struct objc_methH 9 6 Rod_list **methodLists   //方W _ }法列表,与info标识关联
struct objc_cache *cache        //指向最近使用的方法的指针,用于提升效率
struct objc_protocol_list *prot4 ! J ^ cocols    //存储objc_class(该类)的一B S k T些协议
#endiI m M  H E b yf
} OBJC2_UNAVAILABLE;
typedef struct objc_object *id;
struct objc_object {
Class isa  OBJC_ISA_A* 5 G sVAILABILITY;
};

struct objc_classs结构体里存放的数据称为元数据(W h G B f T & &metadata),通过成员变量的名称我们可以猜测里面存放有指向父类的指针、类的名字、版本、实例大小、实例变量列表、方法列表、缓存、遵守的协议列表等,这些信息就足够创建一个实例了,该结{ & ! B r # K构体的第一个成员变量也是isa指针,这就说明了Class本身其实也是一个对象,我们称i C S ] # ) ]之为类对象,类对象在编译期产生用于创建实例对象,是单例。

objec_oba m J A T N t ) yject(对象)中isa指针指向的类结构称为objec_class(该对象的类),其中存放着普通成员变量与对象方法 (“-”开头的方法)。

objec_class(类)中isa指针指向的类结构称为metaclass(该类的元类),其中存放着static类型的成员变量与static类型的方法 (“+”开头的方法)。

元类(metaclass):N j e在oc中,每一个类实际上也是一个对象。也有一个isa指针X q ) y i F | [。因M G x U T ^ 7 [ h为类也是一个对象,所以也必须是另外一个类的实例,这个类就是元类(metaclass),r U E w ? s即元类的对象就是这个类,元类保存了类方法的列表

在oc语言中,每一个类实际上也是一个对象。每一个类也有一个isa指针。每一个类p I l B S v也可以接收消息` O v [。因为类也是一个& : f O 8 | j t K对象,所以也是另外一个类的实例,这个类就是[ = +元类(metaclass)。元, – j类也是一个对象,所有的元p M I ( q类的isa指针都会指向一个根元类。根元类的isa[ 4指针又会指向他自己,这样就形成了一个闭环。

如下图展示了类的继承和isa指向的关系:

isa指针走位图

isa指针的指向

  1. 一个objc对1 Y ] ~ U P e ?象的isa指针指向他的类对象,类对象的isa指针指向他的元类,元类的isa指针指向根元类,所有的元类isa都指向 & 0 W O同一个根元类,根元类的isa指针a t W , } F v x D指向根元类本身。v ; o e根元类super classp 3 .类指向NSObject类对象。根metaclass(元类)中的sup? ^ ; S ~ eerCm - N q klass指针指向根类,因为根metaclass(元类)是通过继承根类产` x $ 5 n 5 ^ s生的。
  2. 实例对象的isa指针, 指向他的类对象,类对象的isa 指针, 指向他的元类。系统判断一个对象r A m m P T x 7 (属于哪个类,也是通过这个对象的isaX j Y V G 2针的指向来判断。对象中的成员变量,存储在对象本身,b X _ W $ E k对象的实例方法,存储在他的isa 指针所指向的对象中。
  3. 对象在调用减号方法的时候,系统会在对象的isa指针所指向的类对象中找方法L r ] } k P Z d,这一段在kvo的实现原理中就能看到,kvo的实现原理就是系统动态的生成一个类对象,这个类是监听对象的类b ` ~ { c的子类,在生成的子类中重写了监听属性的set方法,之后将监听对象的isa指针指向系统动态生成的这个类,当监听对象调用set方法时,由于监听对象的isa指针指向的是刚刚动态生成的类,所以在执行的set方法也是子类中n i S x H , b重写的seP 6 ? [ t ct方法,这就是kvo的实现f ( 2 f { v原理。同理,我们也可以通过runtime中的方法设置某个对象isa指针指向的类对象,让对象调用一些原本不属于他的方法。
  • 类对象(class object):
  1. 类对象由编译器创建。任何直接或间接继承了NSObject的类,它的实例对象中都有一个isa指针,指向它的类对象。这个类对象中存储了关于这个实例对象所属的类的定义的一切:包括变量,方法,遵守的协议等等。因此,类对象能访问所3 v y有关于这个类的信息,利用这些信息可以产生一个新的实例,但是类对象不能访问任何实例对象的内容。类对象没有自己的实例变量。

例如:我们创建p对象4 I n u }P0 D X Kerson * p = [Persoa / Z sn new] ;

在创建一个对象p之前,在堆内存中就先存在了一9 T ; 9 k I 5 1 (个类(Person)(类对象),类对象在编绎时系统会为S I q l ; l我们自动创建。在类第一次加载进内存时创建。

创建一个对象之后,在堆内存中会创建了一个p对象,该对象包含了一个isa指针的成员变量(第一个属性),isa指针则指向在堆里面存在的类对象, 在栈内存里创建了一个该类的指针p,p指针指向的是isa地址,isa指向Person.

  • 对象方法的调用:

OC调用方法,运行的时候N N 4编译器会将代码转化为objc_msgSend(obj, @selecr + $ o / Ktor (selector)),在objc_msgSend函数中首先通过obj(对象)的isa指针找到obj(对象)对应的class(类)。在clt M Q L ) , Y % |ass(类)中, . ] O先去cache中通过SEL(方法的编号)查找对应metZ ; D L / ahod(方法),若cache中未找到,再去methodLists中查找,若methodists中未找到,则去superClass中查找V K @ F h,若能找到,则将method(方法)加入到cache中,以方便下次查找,并通& 7 h g过method(方法)中的函数指针跳转到对应的函数中去执行。如果仍然找不到_ H t &,则继续通过 super_class向上一级父类结构体中查找,直至根class

  • 类方法的调用:
  1. 当我们调用某个类方法时,它首先通过自己的isa指针指向的objc_clo 6 9 7 q L ass中的isa指针找到元类,并从其methodLists中查找该类方法,如果找不到则会通过元类{ ; s h t f $ l)的super_class指针E t ` )找到父类的元类结构体,然后从methodLists中查找该方法,如果仍然找不到,3 } ;则继续通过super_class向上一= @ G &级父类结构体中查 找,直至根元^ ~ N 7 1类;
  2. C语言函数编译a V 2 # = 5 = 3的时候就会决定调用哪个函数,OC是一种动态语言,他会尽可能把代码的从编译链– ^ y e , H接推迟到运行时,这就是oc运行时多态。 给一个对象发送消息,并不会立即执行,而是在运行的时候在去) Q h s D @ H寻找他对应的实现而OC的函数,属于动态调用过程,在编译期并不能决定6 ! r B ^ F . 7真正调用哪个函数,只有在真正运行时才. p j K 0 ,会根据函数的名称找到对应的函数来调用。

1.3 内存管理相关

参考:www.jianshu.com/p/( J X m 4 a Z8345a79fd…
juejin.im/post/5e0dbb…
g/ J A ( [ithub.com/ChenYilong/…
www.jianshu.com/p/0bf8787db…