iOS开发 你需要知道的不一样的Runtime (知识篇)

前言

如果你在浏览器中搜索Runtime会出现多如牛毛文章在介绍它,但是大多数文章都是讲的Runtime有多少用法云云...,也就是说单单是告诉你如何调用API,能达到什么效果。很多大神都会告诉你iOS进阶,Runtime学习是必备的,但是我们进阶就是为了知道如何调用API吗?当然不是,我们知道OC是一门面向对象编程的语言,无时无刻不在和Class(类)Object(实例对象)打交道,然而类中又包含了Ivar(成员变量),Method(方法),Protocol(协议),所以这遍文章就要来揭一揭Runtime与它们之间的面纱。

veil.jpg

本篇文章目录
一. Runtime简介

二. Class/Category/Extension(类/分类/扩展)
2.1、 Class的结构
2.2、Runtime中对类Class的操作

三. Object (对象)是如何初始化的?
3.1 + alloc方法分析
3.2 - init方法分析
3.3 对象操作相关的Runtime API

四. Ivar/Property(成员变量/属性)
4.1 property组成
4.2@synthesize和@dynamic的作用
4.3@synthesize的使用场景
4.4@property中有哪些属性关键字
4.5 Ivar/Property在runtime中的一些应用
4.6 Category中使用Property

五. Method/SEL/IMP (方法/方法名称/方法实现)
5.1 Method结构
5.2 消息(Message)的发送
5.3 消息(Message)的转发
5.4 Runtime相关的操作

六. Protocol(协议)
6.1 协议是什么?
6.2 如何写一个协议
6.3 协议中的方法由谁来实现
6.4协议的作用
6.5 Runtime中关于Protocol的API

一.Runtime简介

Objective-C 是基于 C 的,它为 C 添加了面向对象的特性。它将很多静态语言在编译和链接时期做的事放到了 runtime 运行时来处理,可以说 runtime 是我们 Objective-C 幕后工作者。(Objective-c面向对象, 在运行时处理其他语言编译和链接时期做的事),OC中对象的类型和对象所执行的方法都是在运行时阶段进行查找并确认的,这种机制被称为动态绑定。

二.Class介绍

本文过长,关于Category/Extension的介绍放在深入理解Objective-C:Category

2.1Class类的结构

我们都知道OC中NSObject是大部分类的基类,而在NSObject头文件中的结构如下:

NSObject.png
@interface NSObject <NSObject> {
    Class isa  OBJC_ISA_AVAILABILITY;
}

每一个继承自NSObject的类都包含Class isa的成员变量,这个isa的类为Class的结构如下:

Class.png

typedef struct objc_class *Class;

Class是一个objc_class的结构体:

objc_class.png

Objc2.0之前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;

Objc2.0之后objc_class 结构:

struct objc_object {
private:
    isa_t isa;
}

struct objc_class : objc_object {
    Class superclass;
    cache_t cache;             // formerly cache pointer and vtable
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags
}

union isa_t {
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }
    Class cls;
    uintptr_t bits;
}

Objc2.0之后objc_class的结构体继承自objc_object,事实上Object(实例对象)比如:Student *Bob Bob这个学生对象就是一个objc_object结构体,而我们常用的id对象也是objc_object结构体

Class.png

typedef struct objc_object *id;

那么我们可以理解为我们所说的类就是objc_class结构体,实例对象就是objc_object结构体,而objc_class继承自objc_object,那么类也是一个对象。
objc_class包含以下结构:

  • 1.isa_t isa 继承自objc_object
  • 2.Class superclass
  • 3.cache_t cache
  • 4.class_data_bits_t bits

1.isa_t isa 指向元类的指针
2.Class superclass 指向当前类的父类
对应关系的图如下图,下图很好的描述了对象,类,元类之间的关系:

isa.png

图中实线是 super_class指针,虚线是isa指针。
对象的 isa指向当前类,类中存储了对象的所有实例方法,成员变量等,类的 isa指向meta-class(元类),meta-class存储着一个类的所有类方法。Meta classisa指针都指向Root class (meta)NSObjectMeta class元类,Root class (meta)isa指向了自己。

objc-isa-meta-class.png

对象的实例方法调用时,通过对象的 isa找到对应的类,再从类的class_data_bits_t bits中查找对应的方法。
类对象的类方法调用时,通过类的 isa找到对应的元类,再从元类的class_data_bits_t bits中查找对应的方法。

Root class (class)其实就是NSObjectNSObject是没有超类的,所以Root class(class)superclass指向nil
Root class(meta)superclass指向Root class(class),也就是NSObject,形成一个回路。
从 NSObject 的初始化了解 isa可以让你深入了解isa

3.cache_t cache缓存经常调用的方法,当调用方法时,优先在Cache查找,如果没有找到,再到methodLists查找
4.class_data_bits_t bits 存储类的方法、属性和遵循的协议等信息的地方
深入解析 ObjC 中方法的结构深入了解class_data_bits_t

以上就是关于类Class的结构介绍,接下来说一说在Runtime中对类的一些操作。

2.2Runtime中对类Class的操作

runtime中类的操作方法大部分是以class_为前缀,比如
class_getProperty,class_getClassMethod等。

类名Class Name
// 获取类名
const char *class_getName(Class cls) 
父类super_class
// 获取类的父类
Class class_getSuperclass ( Class cls );
元类meta_class
// 判断给定的Class是否是一个元类
BOOL class_isMetaClass ( Class cls );
成员变量Ivar
// 获取类中指定名称实例成员变量的信息
Ivar class_getInstanceVariable ( Class cls, const char *name );

// 获取类成员变量的信息
Ivar class_getClassVariable ( Class cls, const char *name );

// 获取整个成员变量列表
Ivar * class_copyIvarList ( Class cls, unsigned int *outCount );

// 添加成员变量
BOOL class_addIvar ( Class cls, const char *name, size_t size, uint8_t alignment, const char *types );

1.class_copyIvarList可以获得成员变量数组,数组中的成员变量信息都是objc_ivar结构体的指针。outCount指针返回数组的大小。在结束时须使用free()来释放数组。
2.class_addIvar 需要注意的是OC不支持向已存在的类中添加Ivar实例变量,因此除非通过运行时来创建的类,其他类中都不能添加Ivar,在运行时添加Ivarclass_addIvar方法只能在objc_allocateClassPair函数与objc_registerClassPair之间调用。

举个栗子🌰:

unsigned int outCount;
Ivar * ivarList = class_copyIvarList([UIView class], &outCount);
for (int i = 0; i < outCount; i++) {
   Ivar ivar = ivarList[i];
   NSLog(@"%s",ivar_getName(ivar));
   NSLog(@"%@",[NSString stringWithUTF8String:ivar_getName(ivar)]);
}
free(ivarList);
2017-07-19 16:07:06.385 Runtime[3016:281958] _constraintsExceptingSubviewAutoresizingConstraints
2017-07-19 16:07:06.385 Runtime[3016:281958] _cachedTraitCollection
2017-07-19 16:07:06.386 Runtime[3016:281958] _layer
2017-07-19 16:07:06.386 Runtime[3016:281958] _layerRetained
2017-07-19 16:07:06.386 Runtime[3016:281958] _gestureInfo
2017-07-19 16:07:06.386 Runtime[3016:281958] _gestureRecognizers
2017-07-19 16:07:06.387 Runtime[3016:281958] _window
2017-07-19 16:07:06.387 Runtime[3016:281958] _subviewCache
2017-07-19 16:07:06.387 Runtime[3016:281958] _templateLayoutView
2017-07-19 16:07:06.387 Runtime[3016:281958] _charge
2017-07-19 16:07:06.387 Runtime[3016:281958] _tag
2017-07-19 16:07:06.388 Runtime[3016:281958] _viewDelegate
.........
属性Property
// 获取指定的属性
objc_property_t class_getProperty ( Class cls, const char *name );

// 获取属性列表
objc_property_t * class_copyPropertyList ( Class cls, unsigned int *outCount );

// 为类添加属性
BOOL class_addProperty ( Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount );

// 替换类的属性
void class_replaceProperty ( Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount );
方法Method
// 添加方法
BOOL class_addMethod ( Class cls, SEL name, IMP imp, const char *types );

// 获取实例方法
Method class_getInstanceMethod ( Class cls, SEL name );

// 获取类方法
Method class_getClassMethod ( Class cls, SEL name );

// 获取所有方法的数组
Method * class_copyMethodList ( Class cls, unsigned int *outCount );

// 替代方法的实现
IMP class_replaceMethod ( Class cls, SEL name, IMP imp, const char *types );

// 返回方法的具体实现
IMP class_getMethodImplementation ( Class cls, SEL name );
IMP class_getMethodImplementation_stret ( Class cls, SEL name );

// 类实例是否响应指定的selector
BOOL class_respondsToSelector ( Class cls, SEL sel );

class_addMethod的实现会覆盖父类的方法实现,但不会取代本类中已存在的实现,如果本类中包含一个同名的实现,则函数会返回NO。如果要修改已存在实现,可以使用class_replaceMethodmethod_setImplementation

举两个栗子🌰🌰:

 Method *methodList = class_copyMethodList([UIView class], &outCount);
 for (int i = 0; i < outCount; i++) {
     Method method = methodList[i];
     NSLog(@"%@",NSStringFromSelector( method_getName(method)));
 }
 free(methodList);
2017-07-19 16:07:06.831 Runtime[3016:281958] convertPoint:fromView:
2017-07-19 16:07:06.831 Runtime[3016:281958] subviews
2017-07-19 16:07:06.832 Runtime[3016:281958] setOpaque:
2017-07-19 16:07:06.832 Runtime[3016:281958] addGestureRecognizer:
2017-07-19 16:07:06.832 Runtime[3016:281958] removeGestureRecognizer:
2017-07-19 16:07:06.832 Runtime[3016:281958] addSubview:
2017-07-19 16:07:06.833 Runtime[3016:281958] sizeThatFits:
2017-07-19 16:07:06.833 Runtime[3016:281958] nextResponder
2017-07-19 16:07:06.833 Runtime[3016:281958] becomeFirstResponder
2017-07-19 16:07:06.834 Runtime[3016:281958] convertRect:fromView:
2017-07-19 16:07:06.834 Runtime[3016:281958] convertPoint:toView:
2017-07-19 16:07:06.835 Runtime[3016:281958] drawRect:
2017-07-19 16:07:06.835 Runtime[3016:281958] setFrameOrigin:
2017-07-19 16:07:06.835 Runtime[3016:281958] isHiddenOrHasHiddenAncestor
2017-07-19 16:07:06.836 Runtime[3016:281958] willRemoveSubview:
2017-07-19 16:07:06.836 Runtime[3016:281958] setAutoresizingMask:
2017-07-19 16:07:06.837 Runtime[3016:281958] charge
2017-07-19 16:07:06.837 Runtime[3016:281958] setCharge:
2017-07-19 16:07:06.838 Runtime[3016:281958] origin
2017-07-19 16:07:06.838 Runtime[3016:281958] setOrigin:
......
@interface Bob : NSObject
@end
@implementation Bob

@end

@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
  // 将要添加method实例方法的SEL
  SEL methodSEL = @selector(name);
  // 根据SEL获取Method
  Method methodName = class_getInstanceMethod([self class], methodSEL);
  // 根据SEL获取方法(Method)的实现
  IMP methodIMP = class_getMethodImplementation([self class], methodSEL);
  // Method Encoding
  const char *methodEncoding = method_getTypeEncoding(methodName);
  // 1.当类中没有name方法时添加方法
  BOOL isSuccess = class_addMethod([Bob class], methodSEL, methodIMP, methodEncoding);
  // 2.当类中有name方法时,使用class_replaceMethod
  //IMP replacedMethodIMP = class_replaceMethod([Bob class], methodSEL, methodIMP, methodEncoding);
  // 3.当类中有name方法时,使用method_setImplementation
  //Method BobMethodName = class_getInstanceMethod([Bob class], methodSEL);
  //IMP BobMethodIMP = class_getMethodImplementation([Bob class], methodSEL);
  method_setImplementation(BobMethodName, methodIMP);

  if (isSuccess) {
      [[Bob new] performSelector:@selector(name)];
  }
}
- (void)name{
    NSLog(@"My name is Bob");
}
@end
  // 三种方法都可以实现,打印如下
 Runtime[2779:265550] My name is Bob
协议protocol
// 添加协议
BOOL class_addProtocol ( Class cls, Protocol *protocol );

// 返回类是否实现指定的协议
BOOL class_conformsToProtocol ( Class cls, Protocol *protocol );

// 返回类实现的协议列表
Protocol * class_copyProtocolList ( Class cls, unsigned int *outCount );
对象Object 创建
id class_createInstance(Class cls, size_t extraBytes)

把对象Object 创建放在最后是因为接下来要说Object 对象,当然对象的创建也是必须要说的。

三. Object 对象

关于对象在Class中已经基本介绍清楚了,我们常见的对象是一个objc_object结构体

struct objc_object {
private:
    isa_t isa;
}

isa指向对象Object所属的类Class,实例对象Object的方法,成员变量都保存在对象的类Class中.
那么问题来了,对象的创建[[NSObject alloc] init]是如何完成的呢?

3.1 + alloc方法分析

+ alloc的实现

+ (id)alloc {
    return _objc_rootAlloc(self);
}
// alloc 方法的实现真的是非常的简单, 它直接调用了另一个私有方法 id _objc_rootAlloc(Class cls)
id _objc_rootAlloc(Class cls) {
    return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}

static id callAlloc(Class cls, bool checkNil, bool allocWithZone=false) {
    id obj = class_createInstance(cls, 0);
    return obj;
}

id class_createInstance(Class cls, size_t extraBytes) {
    return _class_createInstanceFromZone(cls, extraBytes, nil);
}
// 对象初始化中最重要的操作都在 _class_createInstanceFromZone 方法中执行:
static id _class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone, bool cxxConstruct = true, size_t *outAllocatedSize = nil) {
    size_t size = cls->instanceSize(extraBytes);

    id obj = (id)calloc(1, size);
    if (!obj) return nil;
    obj->initInstanceIsa(cls, hasCxxDtor);

    return obj;
}
// 在使用 calloc 为对象分配一块内存空间之前,我们要先获取对象在内存的大小:
size_t instanceSize(size_t extraBytes) {
    size_t size = alignedInstanceSize() + extraBytes;
    if (size < 16) size = 16;
    return size;
}

uint32_t alignedInstanceSize() {
    return word_align(unalignedInstanceSize());
}

//实例大小 instanceSize 会存储在类的 isa_t 结构体中,然后经过对齐最后返回,在获取对象大小之后,直接调用 calloc 函数就可以为对象分配内存空间了
uint32_t unalignedInstanceSize() {
    assert(isRealized());
    return data()->ro->instanceSize;
}
isa 的初始化
// 在对象的初始化过程中除了使用 calloc 来分配内存之外,还需要根据类初始化 isa_t 结构体:
inline void objc_object::initIsa(Class cls, bool indexed, bool hasCxxDtor) { 
    if (!indexed) {
        isa.cls = cls;
    } else {
        isa.bits = ISA_MAGIC_VALUE;
        isa.has_cxx_dtor = hasCxxDtor;
        isa.shiftcls = (uintptr_t)cls >> 3;
    }
}
3.2 - init方法分析
// NSObject 的 - init 方法只是调用了 _objc_rootInit 并返回了当前对象:
- (id)init {
    return _objc_rootInit(self);
}

id _objc_rootInit(id obj) {
    return obj;
}

总的来说,在 iOS 中一个对象的初始化过程只是分配内存空间、然后初始化 isa_t 结构体。

3.3 对象操作相关的Runtime API
/************** object_ *************/
// 获取对象的类名
const char * object_getClassName ( id obj );

// 拷贝一个对象并返回拷贝好的对象
id object_copy(id obj, size_t size)

// 释放指定对象占用的内存
id object_dispose(id obj)

// 获取对象的类
Class object_getClass(id obj)

// 设置对象的类
Class object_setClass(id obj, Class cls)

// 判断某个对象是不是一个类
BOOL object_isClass(id obj)

// 获取指定对象,指定实例变量的值(对象)
id object_getIvar(id obj, Ivar ivar)

// 设置对象中实例变量的值
void object_setIvar(id obj, Ivar ivar, id value)

// 修改类实例的实例变量的值
Ivar object_setInstanceVariable(id obj, const char *name, void *value)

// 获取对象实例变量的值
Ivar object_getInstanceVariable(id obj, const char *name, void **outValue)

四. Ivar/Property成员变量/属性

4.1 property组成

Ivar成员变量与Property属性的关系如下:

@property = ivar + getter + setter

即属性property是由成员变量ivargetter 、setter存取方法组成的。
Objective-C 对象通常会把其所需要的数据保存为各种实例变量。实例变量一般通过“存取方法”(access method)来访问。其中,“获取方法” (getter)用于读取变量值,而“设置方法” (setter)用于写入变量值。这个概念已经定型,并且经由“属性”这一特性而成为Objective-C 2.0的一部分。 而在正规的Objective-C 编码风格中,存取方法有着严格的命名规范。 正因为有了这种严格的命名规范,所以 Objective-C这门语言才能根据名称自动创建出存取方法。

property通过自动合成(autosynthesis)来生成ivar、getter 、setter并添加到类中。
完成属性定义后,编译器会自动编写访问这些属性所需的方法,此过程叫做“自动合成”( autosynthesis)。需要强调的是,这个过程由编译 器在编译期执行,所以编辑器里看不到这些“合成方法”(synthesized method)的源代码。除了生成方法代码 getter、setter之外,编译器还要自动向类中添加适当类型的实例变量,并且在属性名前面加下划线,以此作为实例变量的名字。也可以在类的实现代码里通过 @synthesize语法来指定实例变量的名字。

@implementation Bob 
@synthesize bookName = _BobBookName; 
@synthesize age = _BobAge; 
@end
4.2@synthesize@dynamic的作用

1)@property有两个对应的词,一个是@synthesize,一个是@dynamic。如果@synthesize@dynamic都没写,那么默认的就是@syntheszie var = _var;

2)@synthesize的语义是如果你没有手动实现setter方法和getter方法,那么编译器会自动为你加上这两个方法。

3)@dynamic告诉编译器:属性的settergetter方法由用户自己实现,不自动生成。(当然对于readonly的属性只需提供getter即可)。假如一个属性被声明为@dynamic var,然后你没有提供@setter方法和@getter方法,编译的时候没问题,但是当程序运行到instance.var = someVar,由于缺setter方法会导致程序崩溃;或者当运行到 someVar = var时,由于缺getter方法同样会导致崩溃。编译时没问题,运行时才执行相应的方法,这就是所谓的动态绑定。

4.3 有了autosynthesis(自动合成),@synthesize的使用场景

@synthesize只有在不会自动合成的时候使用,以下情况@property不会自动合成:

  • 1.同时重写了settergetter
  • 2.重写了只读属性的getter
  • 3.使用了@dynamic
  • 4.在@protocol 中定义的所有属性
  • 5.在 category 中定义的所有属性
  • 6.在子类中重载了父类中的属性

除了后三条,对其他几个我们可以总结出一个规律:当你想手动管理@property的所有内容时,你就会尝试通过实现@property的所有“存取方法”)或者使用@dynamic来达到这个目的,这时编译器就会认为你打算手动管理@property,于是编译器就禁用了autosynthesis(自动合成)。

因为有了autosynthesis(自动合成),大部分开发者已经习惯不去手动定义ivar,而是依赖于autosynthesis(自动合成),但是一旦你需要使用ivar,而autosynthesis(自动合成)又失效了,如果不去手动定义ivar,那么你就得借助@synthesize来手动合成ivar

4.4 @property中有哪些属性关键字

1.原子、非原子性(nonatomicatomic)
2.读写、只读(readwritereadonly
3.内存管理(assign、strong、 weak、unsafe_unretained、copy
4.方法名(@property (nonatomic, getter=isNewVersion) BOOL newVersion;
5.是否可以为空 (nonnull,null_resettable,nullable)

在默认情况下,由编译器合成的方法会通过锁定机制确保其原子性(atomic), nonatomic则不使用同步锁,但是需要注意的是atomic 只能保证存数据的set方法加锁,并不能保证直接使用成员变量取值的安全性。
copy关键字使用在block、NSString、NSArray、NSDictionary等中,block中的copyblock由栈中拷贝到堆中,而NSString、NSArray、NSDictionary中的copy可以保证数据不被更改,因为你不能保证传入的是一个可变的数据(NSMutable...

weak 此特质表明该属性定义了一种“非拥有关系” (nonowning relationship)。为这种属性设置新值时,设置方法既不保留新值,也不释放旧值。此特质同assign类似, 然而在属性所指的对象遭到摧毁时,属性值也会清空(nil out)。 而 assign 的“设置方法”只会执行针对“纯量类型” (scalar type,例如 CGFloatNSlnteger 等)的简单赋值操作。

assigin 可以用非OC对象,而weak必须用于OC对象

4.5 Ivar/Propertyruntime中的一些应用

Ivar的一些操作

// 获取成员变量名
const char *ivar_getName(Ivar v)
// 获取成员变量类型编码
const char *ivar_getTypeEncoding(Ivar v)
// 获取成员变量的偏移量
ptrdiff_t ivar_getOffset(Ivar v)

Property的一些操作

// 获取属性名
const char *property_getName(objc_property_t property)
// 获取属性特性描述字符串
const char *property_getAttributes(objc_property_t property)
// 获取属性中指定的特性
objc_property_attribute_t *property_copyAttributeList(objc_property_t property, unsigned int *outCount)
// 获取属性的特性列表
char *property_copyAttributeValue(objc_property_t property, const char *attributeName)
4.6 Category中使用Property

我们都知道Category使用 Property不会将它添加到类中的,这些将会在深入理解Objective-C:Category中介绍。我们可以通过属性关联的方式来使Category可以使用Property

@interface Student (Name)
/* name */
@property (nonatomic, strong) NSString *name;
@end
static char nameKey;
#pragma mark - - 动态添加属性
- (void)setName:(NSString *)name{
    return objc_setAssociatedObject(self, & nameKey, name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (NSString *)name{
    return objc_getAssociatedObject(self, & nameKey);
}

五.Method/SEL/IMP (方法/方法名称/方法实现)

5.1 Method结构

方法分为2种,实例方法(Instance Method)与类方法(Class Method)
实例方法(Instance Method)保存在对象(Instance)的类(Class)中
类方法(Class Method)保存在元类(meta-class)中

Class结构:

struct objc_object {
private:
    isa_t isa;
}
struct objc_class : objc_object {
    Class superclass;
    cache_t cache;      
    class_data_bits_t bits; 
}

class_data_bits_t bits中保存了Ivar、Property、Method、Protocol
同样Method 也是一个结构体,它的结构如下:

struct method_t {
    SEL name;
    const char *types;
    IMP imp;
};

SEL name 方法名字 ,@selector()用于表示运行时方 法的名字。Objective-C在编译时,会依据每一个方法的名字、参数序列,生成一个唯一的整型标识(Int类型的地址),这个标识就是SEL。本质上,SEL只是一个指向方法的指针(准确的说,只是一个根据方法名hash化了的KEY值,能唯一代表一个方法),它的存在只是为了加快方法的查询速度。
const char *types 类型编码,你可以通过官方文档来了解它
IMP imp 方法的实现,IMP是一个函数指针,指向方法实现的首地址

5.2 消息(Message)的发送

如果你稍微了解一点Runtime,那么你肯定知道,在 Objective-C中,所有的消息传递中的“消息“都会被转换成一个 selector 作为objc_msgSend 函数的参数,

[object speak] -> objc_msgSend(object, @selector(speak))

消息发送的流程:
1.检测这个 selector是不是要忽略的。

2.检查target是不是为nil。如果这里有相应的nil的处理函数,就跳转到相应的函数中。如果没有处理nil的函数,就自动清理现场并返回。这一点就是为何在OC中给nil发送消息不会崩溃的原因。

3.确定不是给nil发消息之后,在该class的缓存中查找方法对应的IMP实现。如果找到,就跳转进去执行。如果没有找到,就在方法分发表里面继续查找,一直找到NSObject为止。

4.如果还没有找到,那就需要开始消息转发阶段了。至此,发送消息Messaging阶段完成。这一阶段主要完成的是通过select()快速查找IMP的过程。

5.3 消息(Message)的转发

我们都知道调用一个没有实现的方法时,会crash,在程序crash之前还会执行消息转发,那么我们来看看消息转发的机制:

  • 消息转发第一步:+(BOOL)resolveInstanceMethod:(SEL)sel,当调用一个方法,但没有实现时,消息会通过上面方法寻找是否能找到实现。
void functionForMethod1(id self, SEL _cmd) {
   NSLog(@"%@, %p", self, _cmd);
}

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    NSString *selectorString = NSStringFromSelector(sel);
    if ([selectorString isEqualToString:@"method1"]) {
        class_addMethod(self.class, @selector(method1), (IMP)functionForMethod1, "@:");
    }
    return [super resolveInstanceMethod:sel];
}
  • 如果上一步没有实现,那么进入-(id)forwardingTargetForSelector:(SEL)aSelector ,这一步是替消息找备援接收者,如果这一步返回的是nil,那么补救措施就完全的失效了,Runtime系统会向对象发送methodSignatureForSelector:消息.
-(id)forwardingTargetForSelector:(SEL)aSelector{
    Class class = NSClassFromString(@"BLView");
    UIView *view = class.new;
    if (aSelector == NSSelectorFromString(@"calculate")) {
        return vc;
    }
    return nil;
}
  • -(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector 取到返回的方法签名用于生成NSInvocation对象。为接下来的完整的消息转发生成一个NSMethodSignature对象。NSMethodSignature 对象会被包装成 NSInvocation 对象,forwardInvocation: 方法里就可以对NSInvocation进行处理了。

  • -(void)forwardInvocation:(NSInvocation *)anInvocation

- (void)forwardInvocation:(NSInvocation *)anInvocation{
    if ([someOtherObject respondsToSelector:
         [anInvocation selector]])
        [anInvocation invokeWithTarget:someOtherObject];
    else
        [super forwardInvocation:anInvocation];
}
消息发送与转发.jpg
5.4 Runtime相关的操作
Method
// 调用指定方法的实现
id method_invoke ( id receiver, Method m, ... );

// 调用返回一个数据结构的方法的实现
void method_invoke_stret ( id receiver, Method m, ... );

// 获取方法名
SEL method_getName ( Method m );

// 返回方法的实现
IMP method_getImplementation ( Method m );

// 获取描述方法参数和返回值类型的字符串
const char * method_getTypeEncoding ( Method m );

// 获取方法的返回值类型的字符串
char * method_copyReturnType ( Method m );

// 获取方法的指定位置参数的类型字符串
char * method_copyArgumentType ( Method m, unsigned int index );

// 通过引用返回方法的返回值类型字符串
void method_getReturnType ( Method m, char *dst, size_t dst_len );

// 返回方法的参数的个数
unsigned int method_getNumberOfArguments ( Method m );

// 通过引用返回方法指定位置参数的类型字符串
void method_getArgumentType ( Method m, unsigned int index, char *dst, size_t dst_len );

// 返回指定方法的方法描述结构体
struct objc_method_description * method_getDescription ( Method m );

// 设置方法的实现
IMP method_setImplementation ( Method m, IMP imp );

// 交换两个方法的实现
void method_exchangeImplementations ( Method m1, Method m2 );

关于方法交换举个例子🌰:

@implementation NSObject (Swizzling)

+ (void)swizzleMethod:(SEL)originalSelector swizzledSelector:(SEL)swizzledSelector {
    Class class = [self class];
    //原有方法
    Method originalMethod = class_getInstanceMethod(class, originalSelector);
    //替换原有方法的新方法
    Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
    //先尝试給源SEL添加IMP,这里是为了避免源SEL没有实现IMP的情况
    BOOL didAddMethod = class_addMethod(class,originalSelector,
                                        method_getImplementation(swizzledMethod),
                                        method_getTypeEncoding(swizzledMethod));
    if (didAddMethod) {//添加成功:说明源SEL没有实现IMP,将源SEL的IMP替换到交换SEL的IMP
        class_replaceMethod(class,swizzledSelector,
                            method_getImplementation(originalMethod),
                            method_getTypeEncoding(originalMethod));
    } else {//添加失败:说明源SEL已经有IMP,直接将两个SEL的IMP交换即可
        method_exchangeImplementations(originalMethod, swizzledMethod);
    }
}
@end

需要注意的是方法的交换需要写在方法所在类的+(void)load;

#import "NSArray+safe.h"
#import <objc/message.h>
#import "NSObject+Swizzling.h"
@implementation NSArray (safe)

+ (void)load{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        @autoreleasepool {
            [objc_getClass("__NSArray0") swizzleMethod:@selector(objectAtIndex:) swizzledSelector:@selector(emptyObjectIndex:)];
            [objc_getClass("__NSArrayI") swizzleMethod:@selector(objectAtIndex:) swizzledSelector:@selector(arrObjectIndex:)];
            [objc_getClass("__NSArrayM") swizzleMethod:@selector(objectAtIndex:) swizzledSelector:@selector(mutableObjectIndex:)];
            [objc_getClass("__NSArrayM") swizzleMethod:@selector(insertObject:atIndex:) swizzledSelector:@selector(mutableInsertObject:atIndex:)];
            [objc_getClass("__NSArrayM") swizzleMethod:@selector(integerValue) swizzledSelector:@selector(replace_integerValue)];
        }
    });
}

- (id)emptyObjectIndex:(NSInteger)index{
    return nil;
}

- (id)arrObjectIndex:(NSInteger)index{
    if (index >= self.count || index < 0) {
        return nil;
    }
    return [self arrObjectIndex:index];
}

- (id)mutableObjectIndex:(NSInteger)index{
    if (index >= self.count || index < 0) {
        return nil;
    }
    return [self mutableObjectIndex:index];
}

- (void)mutableInsertObject:(id)object atIndex:(NSUInteger)index{
    if (object) {
        [self mutableInsertObject:object atIndex:index];
    }
}

- (NSInteger)replace_integerValue {
    return 0;
}
@end
SEL
// 返回给定选择器指定的方法的名称
const char * sel_getName ( SEL sel );

// 在Objective-C Runtime系统中注册一个方法,将方法名映射到一个选择器,并返回这个选择器
SEL sel_registerName ( const char *str );

// 在Objective-C Runtime系统中注册一个方法
SEL sel_getUid ( const char *str );

// 比较两个选择器
BOOL sel_isEqual ( SEL lhs, SEL rhs );
IMP
// 创建一个指针函数的指针,该函数调用时会调用特定的block
IMP imp_implementationWithBlock ( id block );
 
// 返回与IMP(使用imp_implementationWithBlock创建的)相关的block
id imp_getBlock ( IMP anImp );
 
// 解除block与IMP(使用imp_implementationWithBlock创建的)的关联关系,并释放block的拷贝
BOOL imp_removeBlock ( IMP anImp );
@interface MyRuntimeBlock : NSObject
@end
 
@implementation MyRuntimeBlock
@end
 
// 测试代码
IMP imp = imp_implementationWithBlock(^(id obj, NSString *str) {
    NSLog(@"%@", str);
});
 
class_addMethod(MyRuntimeBlock.class, @selector(testBlock:), imp, "v@:@");
MyRuntimeBlock *runtime = [[MyRuntimeBlock alloc] init];
[runtime performSelector:@selector(testBlock:) withObject:@"hello world!"];

六、Protocol

6.1 协议是什么?

协议声明了任何类都能够选择实现的程序接口。协议能够使两个不同继承树上的类相互交流并完成特定的目的,因此它提供了除继承外的另一种选择。任何能够为其他类提供有用行为的类都能够声明接口来匿名的传达这个行为。任何其他类都能够选择遵守这个协议并实现其中的一个或多个方法,从而利用这个行为。如果协议遵守者实现了协议中的方法,那么声明协议的类就能够通过遵守者调用协议中的方法。

6.2 如何写一个协议

协议中能够声明方法,以及属性,协议的继承。
ProtocolCategory相同,属性是不会添加到类中的,我们需要使用objc_setAssociatedObject 、objc_getAssociatedObject关联属性。
@required 表示必须实现,@optional代表可选

@protocol UITableViewDataSource<NSObject>

@required

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section;

// Row display. Implementers should *always* try to reuse cells by setting each cell's reuseIdentifier and querying for available reusable cells with dequeueReusableCellWithIdentifier:
// Cell gets various attributes set automatically based on table (separators) and data source (accessory views, editing controls)

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;

@optional

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView;              // Default is 1 if not implemented

- (nullable NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section;    // fixed font style. use custom view (UILabel) if you want something different
- (nullable NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section;
@end

6.3 协议中的方法由谁来实现

实现:遵守协议者及其子类
调用:遵守协议者、其子类、id <协议名>

6.4协议的作用

某一个类需要委托其他类处理某些事件,最具代表性性的便是UITableView的那些代理方法。这些方法其实还是代理的方法,只不过定义的地方可能会在委托者类中,通过调用这些方法,可以:将委托者中的数据传递给代理;将代理的数据传递给委托者;将委托者的事件抛给代理去处理...

给某几个特定的类添加统一的接口,这些接口是从这些类中抽象出的共同的行为,这样便可以减少重复的代码。

6.5 Runtime中关于Protocol的API
// 返回指定的协议
Protocol * objc_getProtocol ( const char *name );

// 获取运行时所知道的所有协议的数组
Protocol ** objc_copyProtocolList ( unsigned int *outCount );

// 创建新的协议实例
Protocol * objc_allocateProtocol ( const char *name );

// 在运行时中注册新创建的协议
void objc_registerProtocol ( Protocol *proto );

// 为协议添加方法
void protocol_addMethodDescription ( Protocol *proto, SEL name, const char *types, BOOL isRequiredMethod, BOOL isInstanceMethod );

// 添加一个已注册的协议到协议中
void protocol_addProtocol ( Protocol *proto, Protocol *addition );

// 为协议添加属性
void protocol_addProperty ( Protocol *proto, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount, BOOL isRequiredProperty, BOOL isInstanceProperty );

// 返回协议名
const char * protocol_getName ( Protocol *p );

// 测试两个协议是否相等
BOOL protocol_isEqual ( Protocol *proto, Protocol *other );

// 获取协议中指定条件的方法的方法描述数组
struct objc_method_description * protocol_copyMethodDescriptionList ( Protocol *p, BOOL isRequiredMethod, BOOL isInstanceMethod, unsigned int *outCount );

// 获取协议中指定方法的方法描述
struct objc_method_description protocol_getMethodDescription ( Protocol *p, SEL aSel, BOOL isRequiredMethod, BOOL isInstanceMethod );

// 获取协议中的属性列表
objc_property_t * protocol_copyPropertyList ( Protocol *proto, unsigned int *outCount );

// 获取协议的指定属性
objc_property_t protocol_getProperty ( Protocol *proto, const char *name, BOOL isRequiredProperty, BOOL isInstanceProperty );

// 获取协议采用的协议
Protocol ** protocol_copyProtocolList ( Protocol *proto, unsigned int *outCount );

// 查看协议是否采用了另一个协议
BOOL protocol_conformsToProtocol ( Protocol *proto, Protocol *other );

总而言之

Runtime的所有知识基本都围绕两个中心
(1)类的各个方面(Class、Object、Ivar、Property、Method、Protocol)的动态配置。
(2)消息传递,Message Send & Message Forward.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,752评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,100评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,244评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,099评论 1 286
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,210评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,307评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,346评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,133评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,546评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,849评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,019评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,702评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,331评论 3 319
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,030评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,260评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,871评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,898评论 2 351

推荐阅读更多精彩内容

  • 转至元数据结尾创建: 董潇伟,最新修改于: 十二月 23, 2016 转至元数据起始第一章:isa和Class一....
    40c0490e5268阅读 1,694评论 0 9
  • 我们常常会听说 Objective-C 是一门动态语言,那么这个「动态」表现在哪呢?我想最主要的表现就是 Obje...
    Ethan_Struggle阅读 2,188评论 0 7
  • 发烧,是正邪相争的一个战场,所有的临床化验出来的细菌、病毒等等中医统称为“病邪”,而人体内部正气运作时,就会发热。...
    mz梅子阅读 842评论 0 0
  • 隐士 读牌:隐士身着灰色长袍,灰色兜帽也戴上了,但白色卷曲的长须赫然可见,他一手拄着拐杖一手拿着一盏星星灯,脚踏在...
    宋佳Sabrina阅读 146评论 0 0
  • 乔珊珊突然才明白过来,每一个能写出感人作品的歌手都是一个有故事的人,而自己的人生到现在没有故事可以讲。所以她...
    俗底阅读 307评论 0 1