@[TOC]
1. IOS面试考察(一):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
先思考一下下面的这些问题,N d N Z f看你能回答出多少:
- runtime怎么添加属性、方法等
- runtiy J O F 5me 如何实现 weak 属性
- runtime如何通过selector找到对应的IMP地址?(分别考虑类方法和实例方法)
- 使用runtime Associate方法关联的对象,需要在主l Q s g j Z对象dealloc的时u L 2候释放么?
- _objc_msgForward函数是做什么的?直接调E 1 z用它将会发生什么?
- 能否向编译后得到的类中增加实例变量?能否向运行时创建的类中添加实例变量?为什么?
- 简述下Objective-C中调用方法的过程(h e Bruntime)
- 什么是method swizzling(俗称黑魔法)
我们先来简单了解一下runtH i ] : ~ + $ 1 6ime的3个简单问题:
- 什么是runtime?
- 为啥要用runtime?
- runtime有什么作用?
-
- 什么是runtime?
- runtime本质上是~ % e h t x O一套比较底层的C语言,C++ ,汇编组成的API。我们有称为运行时,在runtime的P l D , f底层很多实现为了性能效率方面,都直接用汇编代码。
- 我们R o i y ] B f o 8平时编写的OC代码,需要runtime来创建类和对象,进行消息发送和转发( m { ^ ^ 0 ~ ] 3,其实最终会转换成Runtime的C语言代码。
- runtime是将数据类型的确定n 2 ` r由编译时推迟到了运行时。
-
- 为啥要用runtime?
- OC是一门动态语言,它会将一些工作放在代码的运行时才去处理而并非编译时,因此编译器是不够,我们还需要一个运行时系统来处理! ] M p N编译后的代码。
- runtime基本是用C语言和汇编语言写的,苹果和GNU各自维护一个开源的runtime版本,这两个版本之间都高度的保持一致v ^ ) F ~ 3。
-
- runtime有什么作用?
- 消息传递、转发<消息机制>
- 访问私有变量 –eg:(UITextFiled 的修改)
- 交换系统a | 7 8 a h方法 –eg:(拦截–防止button连续点击)
- 动态增加方法
- 为分类增加属性
- 字典转模型 –{ 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
思路:
- 给NSObject添加分类,在分类中添加属性。问题:@property在分类中作用:仅仅是生成get,set方法声明,不会生成get,set方法实现和下划线成员属性,所以要s r `在.m文件实现L m &setter/getter方法,用static保存下滑线属性,这样一来,当对象销毁时,属性无法销毁。x # / X D L g { g
- 用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 5为valC 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 | Fy
变nil
,将value
置nil
。
在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_initWeak
和objc_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 : – Tkey
,当此对象的引用计数为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)
,并且当key
变nil
,将value
置nil
。 -
在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_initWeak
和objc_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
- 每一个类对象% p B ` [ Q L中都一个对象方法# | .列表(对象方法缓存)
- 类方法列表是存放在类对象中isa指针指向的元类对象中(类方法缓存)
- 方法列表中每个方法j _ Q p ? M结构体中记录着方法的名称,方法实现,以及参数类型,其实selector本质就是方法名称,通过这个方法名称就可以在方法列表中找到对. $ ! ; O应的方法实现.C i 4
- 当我们发送一个消息给一个NSObject对象时,这条消息会在对象的类对象方法列表里查找
- 当我们发送一个消息给一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()方法中释放
补充:对象的内存销毁时间表,分四个步骤
- 调用 -release :引用计数变为零
对象正在被销毁,k 0 m & w X生命周期即将结束.
不能再有新的 __weak 弱引用,否则将指向 nil.
调用 [self dealloc]- 父类调用 -dealloc
继承关系中最直接继承的父类再调用 -dealloc
如果是 MRC 代码 则会手动释放实例变量们(iVars)
继承关系中每一层的父类 都再调用 -dealloc- NSObject 调 -dealloc
只做一件事:调用 Objective-C runt– P 5 1 @ m 8 bime 中object_dit ; 6 Gspose() 方法- 调用 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的动作比较清晰:
- 首先在 Class 中的缓存查找 IMP (没缓存则初始化缓存),
- 如果没找到,则向父类的 Class 查找。
- 如果一直查找到根类仍旧没有实现,则用_objc_msgForward函数指针代替 IMP 。
- 最后,执行这个 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的实现源码,但是我们还是能得出结论:
_objc_msgForwarr i Q # Fd
是一个函数指针(和IMP
的类型一样),是用于消息转发的:当向一个对象发送o e u { 4 ;一条消息,但它并没有实现的时候,_objc_msgForward
会尝试做消| S – z o息转发。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为例,操作如下所示:
之后,运行时发送的所有消息都会打印到/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里,看到打印出来:
结合《NSObjectE 1 3 ? B u ! &官方文档》,排除掉 NSObject 做的事,剩下的就( @ U , * a是_objc_msgForward消息转发做的几件事:
- 调用
resolveInstanceMethod:
方法 (或resolveClassMethod
:)。允许用户在此时为该Class
动态添加实现。如果有实现了,则调s i N : . I用并返回YES
,那么重新开始. ? N 0 6objc_msgSend
流程。这一次对象会响应这个选择器,一般是因为它已经调用过class_addMethod
。如果仍没实现,继续下面的动作。- 调用
forwardingTargetForSelector
:方法,尝试找到一个能响应该消息的对象。如果获取到,则直接把消息转发给它,返回非 nil 对象。否则返回 nil ,继续下面的动作。注意,这里不要返回sm 2 [elf
,否则会形成死循环。- 调用
metl o p p # vhodSignatureForSc U e H . ` 8elector
:方法,尝试获得一个方法签名。@ M ]如果获取不到,则直接调用doesNotRecogX f D M / ,nizeSelector
抛出异常。如果能获取,则返回非nil
:创建一个NSlnvocation
并传给forwardInvocation
:。- 调用
forwardInvocation
:方法,将第3步获取到的J g k 7 x R F W方法签名包装成Invocation
传入,如何处理就在这里面了,并返回非nil。- 调用
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在进行消息转发的过程中会涉及以下这几个方法:
- resolveInstanceMethod:方法 (或 resolveClassMethod:)。
- forwardingTargetFoP A p Y O 8 s qrSelector:方法
- methodSignatureForSelector:方法
- forwardInvocation:方法
- 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_msgForward
是 IMP
类型,用于消息转发的:当向= 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),整个过程介绍如下:
- objc在向一个对象发送消息时,runtime库会根据对象的isa指针找到该对象实际所属的类
- 然后在该类中的方法列表以及其父类方法w i u列表中寻找方法运行
- 如果,在最顶层的父类(一般也就NSObject)中依然找不到相应的方法时,程序在运行时会挂掉并抛出异常unrecogniz, ( ` 5 o z } red selector sent to XXX
- 但是在) % 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)慢速转发的实现是methodSignatureForSelector
和forwardInvocation
的结合,提供了更细粒度的控制,先返回方法签名给Runtime
,然后让anInvocation
来把消息发送给提供的对象,最后由Runtime
提取结果然后传递给原始的消息发送者。
(5)如, – l R d @ Z u果在3次挽救机会:resolveInsto L / : = ? U 5anceMethod
,forwardingTargetForSz J H ] ! relector
,forwardInvocH o _ 4 P ~ O PatiB j ~ V #on
都没有处理时,就会报unrecognized selector sent to XXX
异常。此时程序会崩溃。
- 关于
resolveInstan r ^ # ynceMethod
方法又称为对象方法动态解析,它的流程大致如下:
- 检查是否8 h 8 t p实现了
+(BOOL)resolveInstanceMethod:(SEL)sel
类方法,如果没有实现则 m O X –直接返回(通过cls->ISA()
是拿到元类,因为类方法是存储在元类上的对象方法)- 如果当前实现了
+(BOOL)resolveInstanceMethod:(SEL)sel
类方法,则通过objc_msgSend
手动调用该类方法。- 完成调用后,再次查询
cls
中的iK 1 y % | V ~ 3mp
。- 如果
imp
找到了,则输出动态解析对象方法成功的日志。- 如果
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
走类方法动态解析流程:
- 判断是否是: R & b元类,如果不是,直接% q R @ ? { F =退出z = * D ; C ` n。
- 检查是否实现了
+(BOOL)resoc ; n s b J + t KlveClassMethod:(SEL)sel
类方法,如果没有实现则直接返回(通过cls-
是因为当前cls
就是元类,因为类方法是存储在元类上的对象方法)- 如果当前实现了
+(BOO; = / 8 g ? $ H =L)reu W Y 1 i t ^ ssolveClassMethod:(SEL)sel
类方法,则通| + o u m Y z n过objc_msgSend
手动调用该类方法,注意这里和动态解析对象方法不同,这里需要通过元类和对象来找到类,也就是_class_getNY S G p g v D 4 AonMetaClass
。- 完成调用后,再次查询
cls
中的imp
。- 如果
imp
找到了,则输出动态解析对象方法成功的日志。- 如果
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为快速消息转发:
forwardingTargetForSelector
是一种快速的消息转发流程,它( N j = a U l直接让其他对象来响应未知的消息。forwardingTargetForSelector
不能返回self
,否则会陷入6 & B # p A R :死循环,因为返回d @ @ ?self
又回去当前实例对象# B –身上走一遍消息查找流程,显然G ~ _又会来到forwardingTargetForSelector
。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
方法签名:
forwardInvocation
方法有两个任务:
(1): 查找可以响应inInvocation
中编码的消息的对象。对于所有l u 1 ` C Q n消息,此对象不必相同。
(2): 使用anInvocation
将消息发U f `送到该对象。anInvocation
将保存结果,运行时系统将提取结果并将其传递给原始发送者。forwardInvocatiu c P : N n #on
方法的实现不仅仅可以转发消息。for ~ ? | {wardIL T F w S l Bnvocation
还可以用于合并响应各种不同消息的代码,从而避免了必须为每个选择器编写单独方法的麻烦。forwardIn. w J $ g 5vocation
方法在对给定消息的响应中还可能涉及其他几个对象,而不是仅将其转发给一个对象。NSObject
的forwardInvocationf s Y h f ` f k B
实现:只会调用dosNotRecognizeSelector
:方法,它不会转发任何消息。因此,如果选择不实现`forwardInvocation,将无法识别的消息发送给对. Y ] M O D象将引发异常。
下面举例来说明:
假设我们调用[dog walk]方法,那么它会经历如下过程:
- 编译器会把
[dog walk]
转化为objc_msgSend(d8 ( D H / K `og,SET _ K W /L)
,SEL为@selector(walk)。- Runtime会在dog对象所对应的Dog类的方法缓存列表里查找方法的SEL
- 如果没( m w – 7 {有找到,则在Do= f 4 g类的方法分发表查找方法的SEL。(类由对象isa指针指向,方法分发表即w 7 Zmethg X * N 3odList)
- 如果没有找到,则在其v u e K j +父类(设Do{ i , 0 W 1 D e Ig类的父类为Animal类)的方法分发表里查找方法的SEL(父类由类的superClass指向)/ L –
- 如果没有找到,则沿继承体系继续下去,最终到达NSObject类。
- 如果在234的其中一步中找到,则定位了方法实现的入口,执行具体实现
- 如果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 _ | {调用
- 根据对象的isa去对应的类查找方法,isa:判断去哪个类查找对应的方法 指向方法调用的类
- 根据传入的X c u _ X方法编号SEL,里面有个哈希列表,在列表中找到对应方法Method(方法名)
- 根据方法名(函f [ [ # 8 X 3 E y数入口)找到函数实现,函数实现在方法区
1.1.8 什么是method swizzling(俗称黑魔法)
简$ k x d N N单说就是进行方法交换
- 在Objective-C中调用一个方法y ~ I,其实是向一个对象发送消息,查找消息的唯一依4 d % u ~ 据是selector的名字。利用Objective-C的动态特性,可以U ( +实现在运行时偷换selector对应的方法实现,达到给方法挂钩的目的
- 每个类都有一个方法列表,存放着方法的名字和方法实现的映射关系,selector的本质其实V U q Y i就是方法名,i x , RIMP有点类似函数指针,指向具体的Method实现,通过selector就可以找到对应的IMP
- 交换方法的几种实现方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 .给我提示,是否加载成功。
有三种方法解决这个需求问题:
- 解决方式 自定义UIImage类,缺点:每次用要导入自己的类
- 解决方法:UIImage分类扩充一个这样方法,缺点:需要导入,无法写super和self,会干掉. % 3 U : T .系统方法,解决:给系统方法加个前缀,与系? s D y T W w l统方法区分3 2 . O & ^,如:xmg_imageNamed:
- 交互方法实现,步骤: 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 %,你可能被问到的问题:
- 介绍下runtime的内存模型(isa、对象、类、metU 4 x Saclass、结构体的存储信息等)
- 为什么要设P J ` ^ $计metaclass
- class_copyIvarList & class_copyPropertyList区别
- class_rw_t 和 class_ro_t 的区别
- category如何被加载的,两3 2 L ^ q = =个category的load方法的加载顺序,两个category的同名方法的加! S ,载J + ? ] W顺序
- category & extension区别,能给NSObject添加ExteD G x T Ynsion吗,结果如何
- 消息转发机制,消息转发机制和其他语言的消息机制优) ~ D劣对比
- 在方法调用的时候,方法查询-> 动态解析-> 消息转发 之前做了什么
- IMP、SEL~ { o | G、Method的区别和使用场景
- load、initialize方法的区别什么?在继承关系中他们有什么区别
- 说说消息转发机制的优劣
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指针的指向:
- 一个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 class
父p 3 .类指向NSObject
类对象。根metaclass
(元类)中的sup? ^ ; S ~ eerCm - N q klass
指针指向根类,因为根metaclass
(元类)是通过继承根类产` x $ 5 n 5 ^ s生的。- 实例对象的
isa
指针, 指向他的类对象,类对象的isa
指针, 指向他的元类。系统判断一个对象r A m m P T x 7 (属于哪个类,也是通过这个对象的isa
指X j Y V G 2针的指向来判断。对象中的成员变量,存储在对象本身,b X _ W $ E k对象的实例方法,存储在他的isa
指针所指向的对象中。- 对象在调用减号方法的时候,系统会在对象的
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):
- 类对象由编译器创建。任何直接或间接继承了
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
- 类方法的调用:
- 当我们调用某个类方法时,它首先通过自己的
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类;- 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…