前言

之前,我们在根究动画及烘托相关原理的时分,我们输出了几篇文章,解答了iOS动画是怎样烘托,特效是怎样作业的疑问。我们深感系统设计者在创造这些系统结构的时分,是如此脑洞大开,也 深深意识到了解一门技术的底层原理关于从事该方面作业的重要性。

因此我们决议 进一步根究iOS底层原理的任务。继前面两篇文章分别介绍了Runtime的:

  • isa详解、class的结构、方法缓存cache_t
  • objc_msgSend的三个阶段(消息发送、动态解析方法、消息转发)、super的本质 之后,在本篇文章环绕Runtime在项目中的一些常见运用打开

一、 Runtime API

首要我们写一段OC代码,然后依据此代码对一些Runtime API的运用打开介绍。

// Person类继承自NSObject,包含run方法
@interface Person : NSObject
@property (nonatomic, strong) NSString *name;
- (void)run;
@end
#import "Person.h"
@implementation Person
- (void)run
{
    NSLog(@"%s",__func__);
}
@end
// Car类继承自NSObejct,包含run方法
#import "Car.h"
@implementation Car
- (void)run
{
    NSLog(@"%s",__func__);
}
@end 

1. 类相关API

1. 动态创建一个类(参数:父类,类名,额外的内存空间)
Class objc_allocateClassPair(Class superclass, const char *name, size_t extraBytes)
2. 注册一个类(要在类注册之前添加成员变量)
void objc_registerClassPair(Class cls) 
3. 销毁一个类
void objc_disposeClassPair(Class cls)
示例:
void run(id self , SEL _cmd) {
    NSLog(@"%@ - %@", self,NSStringFromSelector(_cmd));
}
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // 创建类 superclass:继承自哪个类 name:类名 size_t:分外的大小,创建类是否需求扩大空间
        // 回来一个类政策
        Class newClass = objc_allocateClassPair([NSObject class], "Student", 0);
        // 添加成员变量 
        // cls:添加成员变量的类 name:成员变量的名字 size:占有多少字节 alignment:内存对齐,最好写1 types:类型,int类型就是@encode(int) 也就是i
        class_addIvar(newClass, "_age", 4, 1, @encode(int));
        class_addIvar(newClass, "_height", 4, 1, @encode(float));
        // 添加方法
        class_addMethod(newClass, @selector(run), (IMP)run, "v@:");
        // 注册类
        objc_registerClassPair(newClass);
        // 创建实例政策
        id student = [[newClass alloc] init];
        // 通过KVC访问
        [student setValue:@10 forKey:@"_age"];
        [student setValue:@180.5 forKey:@"_height"];
        // 获取成员变量
        NSLog(@"_age = %@ , _height = %@",[student valueForKey:@"_age"], [student valueForKey:@"_height"]);
        // 获取类的占用空间
        NSLog(@"类政策占用空间%zd", class_getInstanceSize(newClass));
        // 调用动态添加的方法
        [student run];
    }
    return 0;
}
// 打印内容
// Runtime运用[25605:4723961] _age = 10 , _height = 180.5
// Runtime运用[25605:4723961] 类政策占用空间16
// Runtime运用[25605:4723961] <Student: 0x10072e420> - run
留心
类一旦注册完毕,就相当于类政策和元类政策里面的结构就现已创建好了。
因此有必要在注册类之前,添加成员变量。方法可以在注册之后再添加,由于方法是可以动态添加的。
创建的类假设不需求运用了 ,需求开释类。
4. 获取isa指向的Class,假设将类政策传入获取的就是元类政策,假设是实例政策则为类政策
Class object_getClass(id obj)
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *person = [[Person alloc] init];
        NSLog(@"%p,%p,%p",object_getClass(person), [Person class],
              object_getClass([Person class]));
    }
    return 0;
}
// 打印内容
Runtime运用[21115:3807804] 0x100001298,0x100001298,0x100001270
5. 设置isa指向的Class,可以动态的批改类型。例如批改了person政策的类型,也就是说批改了person政策的isa指针的指向,半途让政策去调用其他类的同名方法。
Class object_setClass(id obj, Class cls)
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *person = [[Person alloc] init];
        [person run];
        object_setClass(person, [Car class]);
        [person run];
    }
    return 0;
}
// 打印内容
Runtime运用[21147:3815155] -[Person run]
Runtime运用[21147:3815155] -[Car run]
终究其实调用了car的run方法 
6. 用于判别一个OC政策是否为Class
BOOL object_isClass(id obj)
// 判别OC政策是实例政策仍是类政策
NSLog(@"%d",object_isClass(person)); // 0
NSLog(@"%d",object_isClass([person class])); // 1
NSLog(@"%d",object_isClass(object_getClass([person class]))); // 1 
// 元类政策也是特殊的类政策 
7. 判别一个Class是否为元类
BOOL class_isMetaClass(Class cls)
8. 获取类政策父类
Class class_getSuperclass(Class cls) 

2. 成员变量相关API

1. 获取一个实例变量信息,描绘信息变量的名字,占用多少字节等
Ivar class_getInstanceVariable(Class cls, const char *name)
2. 仿制实例变量列表(终究需求调用free开释)
Ivar *class_copyIvarList(Class cls, unsigned int *outCount)
3. 设置和获取成员变量的值
void object_setIvar(id obj, Ivar ivar, id value)
id object_getIvar(id obj, Ivar ivar)
4. 动态添加成员变量(现已注册的类是不能动态添加成员变量的)
BOOL class_addIvar(Class cls, const char * name, size_t size, uint8_t alignment, const char * types)
5. 获取成员变量的相关信息,传入成员变量信息,回来C言语字符串
const char *ivar_getName(Ivar v)
6. 获取成员变量的编码,types
const char *ivar_getTypeEncoding(Ivar v)
示例:
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // 获取成员变量的信息
        Ivar nameIvar = class_getInstanceVariable([Person class], "_name");
        // 获取成员变量的名字和编码
        NSLog(@"%s, %s", ivar_getName(nameIvar), ivar_getTypeEncoding(nameIvar));
        Person *person = [[Person alloc] init];
        // 设置和获取成员变量的值
        object_setIvar(person, nameIvar, @"xx_cc");
        // 获取成员变量的值
        object_getIvar(person, nameIvar);
        NSLog(@"%@", object_getIvar(person, nameIvar));
        NSLog(@"%@", person.name);
        // 仿制实例变量列表
        unsigned int count ;
        Ivar *ivars = class_copyIvarList([Person class], &count);
        for (int i = 0; i < count; i ++) {
            // 取出成员变量
            Ivar ivar = ivars[i];
            NSLog(@"%s, %s", ivar_getName(ivar), ivar_getTypeEncoding(ivar));
        }
        free(ivars);
    }
    return 0;
}
// 打印内容
// Runtime运用[25783:4778679] _name, @"NSString"
// Runtime运用[25783:4778679] xx_cc
// Runtime运用[25783:4778679] xx_cc
// Runtime运用[25783:4778679] _name, @"NSString" 

3. 特色相关AIP

1. 获取一个特色
objc_property_t class_getProperty(Class cls, const char *name)
2. 仿制特色列表(终究需求调用free开释)
objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount)
3. 动态添加特色
BOOL class_addProperty(Class cls, const char *name, const objc_property_attribute_t *attributes,
                  unsigned int attributeCount)
4. 动态替换特色
void class_replaceProperty(Class cls, const char *name, const objc_property_attribute_t *attributes,
                      unsigned int attributeCount)
5. 获取特色的一些信息
const char *property_getName(objc_property_t property)
const char *property_getAttributes(objc_property_t property)

4.方法相关API

1. 获得一个实例方法、类方法
Method class_getInstanceMethod(Class cls, SEL name)
Method class_getClassMethod(Class cls, SEL name)
2. 方法完结相关操作
IMP class_getMethodImplementation(Class cls, SEL name) 
IMP method_setImplementation(Method m, IMP imp)
void method_exchangeImplementations(Method m1, Method m2) 
3. 仿制方法列表(终究需求调用free开释)
Method *class_copyMethodList(Class cls, unsigned int *outCount)
4. 动态添加方法
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)
5. 动态替换方法
IMP class_replaceMethod(Class cls, SEL name, IMP imp, const char *types)
6. 获取方法的相关信息(带有copy的需求调用free去开释)
SEL method_getName(Method m)
IMP method_getImplementation(Method m)
const char *method_getTypeEncoding(Method m)
unsigned int method_getNumberOfArguments(Method m)
char *method_copyReturnType(Method m)
char *method_copyArgumentType(Method m, unsigned int index)
7. 选择器相关
const char *sel_getName(SEL sel)
SEL sel_registerName(const char *str)
8. 用block作为方法完结
IMP imp_implementationWithBlock(id block)
id imp_getBlock(IMP anImp)
BOOL imp_removeBlock(IMP anImp) 

二、Runtime在项目中的常见运用

首要导入头文件#import <objc/runtime.h>

通过runtime的一系列方法,可以获取类的一些信息, 包含:特色列表,方法列表,成员变量列表,和遵从的协议列表。

1、获取列表

1.1 获取特色列表

有时分会有这样的需求,我们需求知道其时类中每个特色的名字。

    unsigned int count;
    // 获取列表
    objc_property_t *propertyList = class_copyPropertyList([self class], &count);
    for (unsigned int i=0; i<count; i++) {
        // 获取特色名
        const char *propertyName = property_getName(propertyList[i]);
        // 打印
        NSLog(@"property-->%@", [NSString stringWithUTF8String:propertyName]);
    }

1.2 获取方法列表

    Method *methodList = class_copyMethodList([self class], &count);
    for (unsigned int i; i<count; i++) {
        Method method = methodList[i];
        NSLog(@"method-->%@", NSStringFromSelector(method_getName(method)));
    }

1.3 获取成员变量列表

    Ivar *ivarList = class_copyIvarList([self class], &count);
    for (unsigned int i; i<count; i++) {
        Ivar myIvar = ivarList[i];
        const char *ivarName = ivar_getName(myIvar);
        NSLog(@"Ivar---->%@", [NSString stringWithUTF8String:ivarName]);
    }

1.4 获取协议列表

    __unsafe_unretained Protocol **protocolList = class_copyProtocolList([self class], &count);
    for (unsigned int i; i<count; i++) {
        Protocol *myProtocal = protocolList[i];
        const char *protocolName = protocol_getName(myProtocal);
        NSLog(@"protocol---->%@", [NSString stringWithUTF8String:protocolName]);
    }

2、动态添加

2.1 动态添加方法

中心方法:class_addMethod () 首要从外部隐式调用一个不存在的方法:

// 隐式调用方法
[target performSelector:@selector(resolveAdd:) withObject:@"test"];

然后,在target政策内部重写阻挠调用的方法,动态添加方法。

// 重写了阻挠调用的方法,并回来YES
+ (BOOL)resolveInstanceMethod:(SEL)sel{
    //给本类动态添加一个方法
    if ([NSStringFromSelector(sel) isEqualToString:@"resolveAdd:"]) {
        class_addMethod(self, sel, (IMP)runAddMethod, "v@:*");
    }
    return YES;
}
// 调用新增的方法
void runAddMethod(id self, SEL _cmd, NSString *string){
    NSLog(@"add C IMP ", string);  //withObject 参数
}

其间class_addMethod的四个参数分别是:

  • Class: cls 给哪个类添加方法,本例中是self
  • SEL name: 添加的方法,本例中是重写的阻挠调用传进来的selector
  • IMP imp: 方法的完结,C方法的方法完结可以直接获得。假设是OC方法,可以用+ (IMP)instanceMethodForSelector:(SEL)aSelector;获得方法的完结。
  • "v@:*":方法的签名,代表有一个参数的方法。

2.2 动态添加Ivar

  • 优点:
    • 动态添加Ivar我们可以通过遍历Ivar得到我们所添加的特色。
  • 缺点:
    • 不能在已存在的class中添加Ivar,所以说有必要通过objc_allocateClassPair动态创建一个class,才干调用class_addIvar创建Ivar,终究通过objc_registerClassPair注册class。
//在政策target上添加特色(现已存在的类不支持,可跳进去看注释),特色名propertyname,值value
-(void)addIvarWithtarget:(id)targetwithPropertyName:(NSString*)propertyNamewithValue:(id)value{
if(class_addIvar([targetclass],[propertyNameUTF8String],sizeof(id),log2(sizeof(id)),"@"))
{
NSLog(@"创建特色Ivar成功");
}
}
//获取政策target的指定特色值
-(id)getIvarValueWithTarget:(id)targetwithPropertyName:(NSString*)propertyName
{Ivarivar=class_getInstanceVariable([targetclass],[propertyNameUTF8String]);
if(ivar){
idvalue=object_getIvar(target,ivar);
returnvalue;
}else
{
returnnil;
}
}

2.3 动态添加property

首要用到class_addProperty,class_addMethod,class_replaceProperty,class_getInstanceVariable

//在政策target上添加特色,特色名propertyname,值value
+(void)addPropertyWithtarget:(id)targetwithPropertyName:(NSString*)propertyNamewithValue:(id)value{
//先判别有没有这个特色,没有就添加,有就直接赋值
Ivarivar=class_getInstanceVariable([targetclass],[[NSStringstringWithFormat:@"_%@",propertyName]UTF8String]);
if(ivar)
{
return;
}
/*
objc_property_attribute_ttype={"T","@/"NSString/""};
objc_property_attribute_townership={"C",""};//C=copy
objc_property_attribute_tbackingivar={"V","_privateName"};
objc_property_attribute_tattrs[]={type,ownership,backingivar};
class_addProperty([SomeClassclass],"name",attrs,3);
*/
//objc_property_attribute_t所代表的意思可以调用getPropertyNameList打印,大约就能猜出。
objc_property_attribute_ttype={"T",[[NSStringstringWithFormat:@"@/%@/",NSStringFromClass([valueclass])]UTF8String]};
objc_property_attribute_townership={"&","N"};
objc_property_attribute_tbackingivar={"V",[[NSStringstringWithFormat:@"_%@",propertyName]UTF8String]};
objc_property_attribute_tattrs[]={type,ownership,backingivar};
if(class_addProperty([targetclass],[propertyNameUTF8String],attrs,3))
{
//添加get和set方法
class_addMethod([targetclass],NSSelectorFromString(propertyName),(IMP)getter,"@@:");
class_addMethod([targetclass],NSSelectorFromString([NSStringstringWithFormat:@"set%@:",[propertyNamecapitalizedString]]),(IMP)setter,"v@:@");
//赋值
[targetsetValue:valueforKey:propertyName];
NSLog(@"%@",[targetvalueForKey:propertyName]);
NSLog(@"创建特色Property成功");
}
else{
class_replaceProperty([targetclass],[propertyNameUTF8String],attrs,3);
//添加get和set方法
class_addMethod([targetclass],NSSelectorFromString(propertyName),(IMP)getter,"@@:");
class_addMethod([targetclass],NSSelectorFromString([NSStringstringWithFormat:@"set%@:",[propertyNamecapitalizedString]]),(IMP)setter,"v@:@");
//赋值
[targetsetValue:valueforKey:propertyName];
}
}
idgetter(idself1,SEL_cmd1)
{
NSString*key=NSStringFromSelector(_cmd1);
Ivarivar=class_getInstanceVariable([self1class],"_dictCustomerProperty");
//basicsViewController里面有个_dictCustomerProperty特色
NSMutableDictionary*dictCustomerProperty=object_getIvar(self1,ivar);
return[dictCustomerPropertyobjectForKey:key];
}
voidsetter(idself1,SEL_cmd1,idnewValue)
{
//移除set
NSString*key=[NSStringFromSelector(_cmd1)stringByReplacingCharactersInRange:NSMakeRange(0,3)withString:@""];
//首字母小写
NSString*head=[keysubstringWithRange:NSMakeRange(0,1)];
head=[headlowercaseString];
key=[keystringByReplacingCharactersInRange:NSMakeRange(0,1)withString:head];
//移除后缀":"
key=[keystringByReplacingCharactersInRange:NSMakeRange(key.length-1,1)withString:@""];
Ivarivar=class_getInstanceVariable([self1class],"_dictCustomerProperty");
//basicsViewController里面有个_dictCustomerProperty特色
NSMutableDictionary*dictCustomerProperty=object_getIvar(self1,ivar);
if(!dictCustomerProperty)
{
dictCustomerProperty=[NSMutableDictionarydictionary];
object_setIvar(self1,ivar,dictCustomerProperty);
}
[dictCustomerPropertysetObject:newValueforKey:key];
}
+(id)getPropertyValueWithTarget:(id)targetwithPropertyName:(NSString*)propertyName
{
//先判别有没有这个特色,没有就添加,有就直接赋值
Ivarivar=class_getInstanceVariable([targetclass],[[NSStringstringWithFormat:@"_%@",propertyName]UTF8String]);
if(ivar)
{
returnobject_getIvar(target,ivar);
}
ivar=class_getInstanceVariable([targetclass],"_dictCustomerProperty");
//basicsViewController里面有个_dictCustomerProperty特色
NSMutableDictionary*dict=object_getIvar(target,ivar);
if(dict&&[dictobjectForKey:propertyName]){
return[dictobjectForKey:propertyName];
}
else
{
returnnil;
}
}

2.4 动态添加方法

voiddynamicMethodIMP(idself,SEL_cmd){
//implementation....
}
@implementationMyClass
+(BOOL)resolveInstanceMethod:(SEL)aSEL
{
if(aSEL==@selector(resolveThisMethodDynamically)){
class_addMethod([selfclass],aSEL,(IMP)dynamicMethodIMP,"v@:");
returnYES;
}
return[superresolveInstanceMethod:aSEL];
}
@end

2.5 分类添加特色

@implementation NSObject (Property)
- (NSString *)name
{
    // 依据相关的key,获取相关的值。
    return objc_getAssociatedObject(self,_cmd);
}
- (void)setName:(NSString *)name
{
    // 第一个参数:给哪个政策添加相关
    // 第二个参数:相关的key,通过这个key获取
    // 第三个参数:相关的value
    // 第四个参数:相关的战略
    objc_setAssociatedObject(self,  @selector(name), name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end

4、动态重写方法

在没有一个类的完结源码的情况下,想改动其间一个方法的完结,除了继承它重写、和凭借Category重名方法之外,还有愈加灵敏的方法 Method Swizzle。 在OC中调用一个方法,其实是向一个政策发送消息,查找消息的唯一依据是selector的名字。 运用OC的动态特性,可以完结在工作时掉包selector对应的方法完结。

Method Swizzle 指的是,改动一个已存在的选择器对应的完结进程。OC中方法的调用可以在工作时,改动类的调度表中选择器到终究函数间的映射联络。

每个类都有一个方法列表,存放着selector的名字及其方法完结的映射联络。IMP有点相似函数指针,指向详细的方法完结。 运用 method_exchangeImplementations 来沟通2个方法中的IMP。 运用 class_replaceMethod 来批改类。 运用 method_setImplementation 来直接设置某个方法的IMP。

归根结底,都是掉包了selector的IMP

5、方法沟通

新建分类

#import <objc/runtime.h> 
+ (void)load {
    // 方法沟通应该被确保,在程序中只会实行一次
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        // 获得需求替换的系统方法
        SEL systemSel = @selector(didDisplay);
        // 自己完结的将要被沟通的方法
        SEL swizzSel = @selector(myDidDisplay);
        //两个方法的Method
        Method systemMethod = class_getInstanceMethod([self class], systemSel);
        Method swizzMethod = class_getInstanceMethod([self class], swizzSel);
        //首要动态添加方法,完结是被沟通的方法,回来值表示添加成功仍是失利
        BOOL isAdd = class_addMethod(self, systemSel, method_getImplementation(swizzMethod), method_getTypeEncoding(swizzMethod));
        if (isAdd) {
            //假设成功,阐明类中不存在这个方法的完结
            //将被沟通方法的完结替换到这个并不存在的完结
            class_replaceMethod(self, swizzSel, method_getImplementation(systemMethod), method_getTypeEncoding(systemMethod));
        } else {
            //否则,沟通两个方法的完结
            method_exchangeImplementations(systemMethod, swizzMethod);
        }
    });
}
-(void)myDidDisplay{
  //......do
}

6、归档解档

// 设置不需求归解档的特色
- (NSArray *)ignoredNames {
    return @[@"_aaa",@"_bbb",@"_ccc"];
}
// 解档方法
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
    if (self = [super initWithCoder:aDecoder]) {
        // 获取全部成员变量
        unsigned int outCount = 0;
        Ivar *ivars = class_copyIvarList([self class], &outCount);
        for (int i = 0; i < outCount; i++) {
            Ivar ivar = ivars[i];
            // 将每个成员变量名转化为NSString政策类型
            NSString *key = [NSString stringWithUTF8String:ivar_getName(ivar)];
            // 忽略不需求解档的特色
            if ([[self ignoredNames] containsObject:key]) {
                continue;
            }
            // 依据变量名解档取值,不论是什么类型
            id value = [aDecoder decodeObjectForKey:key];
            // 取出的值再设置给特色
            [self setValue:value forKey:key];
            // 这两步就相当于以前的 self.age = [aDecoder decodeObjectForKey:@"_age"];
        }
        free(ivars);
    }
    return self;
}
// 归档调用方法
- (void)encodeWithCoder:(NSCoder *)aCoder {
    // 获取全部成员变量
    unsigned int outCount = 0;
    Ivar *ivars = class_copyIvarList([self class], &outCount);
    for (int i = 0; i < outCount; i++) {
        Ivar ivar = ivars[i];
        // 将每个成员变量名转化为NSString政策类型
        NSString *key = [NSString stringWithUTF8String:ivar_getName(ivar)];
        // 忽略不需求归档的特色
        if ([[self ignoredNames] containsObject:key]) {
            continue;
        }
        // 通过成员变量名,取出成员变量的值
        id value = [self valueForKeyPath:key];
        // 再将值归档
        [aCoder encodeObject:value forKey:key];
        // 这两步就相当于 [aCoder encodeObject:@(self.age) forKey:@"_age"];
    }
    free(ivars);
}

7、字典转模型

在OC中,字典转模型一般我们用第三方库MJExtensionYYModel来运用。

底子原理就是:

  • 运用Runtime可以获取模型中全部特色这一特性,来对要进行转化的字典进行遍历
  • 运用KVC的方法去取值赋值
    • - (nullable id)valueForKeyPath:(NSString *)keyPath;
    • - (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;
    • 去取出模型特色并作为字典中相对应的key,来取出其所对应的value,并把value赋值给模型特色。

下面来个简略的字典转模型的比方

- (void)transformDict:(NSDictionary *)dict {
    Class class = self.class;
    // count:成员变量个数
    unsigned int count = 0;
    // 获取成员变量数组
    Ivar *ivars = class_copyIvarList(class, &count);
    // 遍历全部成员变量
    for (int i = 0; i < count; i++) {
        // 获取成员变量
        Ivar ivar = ivars[i];
        // 获取成员变量名字
        NSString *key = [NSString stringWithUTF8String:ivar_getName(ivar)];
        // 成员变量名转为特色名(去掉下划线 _ )
        key = [key substringFromIndex:1];
        // 取出字典的值
        id value = dict[key];
        // 假设模型特色数量大于字典键值对数理,模型特色会被赋值为nil而报错
        if (value == nil) continue;
        // 运用KVC将字典中的值设置到模型上
        [self setValue:value forKeyPath:key];
    }
    //开释指针
    free(ivars);
}

8、页面计算

添加一个UIViewController的分类:

  • 通过load方法和dispatch_once_t来确保只加载一次
  • UIViewControllerviewWillAppear方法,沟通为swizz_viewWillAppear方法
+ (void)load
{
  static dispatch_once_t onceToken;
  dispatch_once(&onceToken, ^{
    Class class = [self class];
    SEL originalSEL = @selector(viewWillAppear:);
    SEL swizzledSEL = @selector(swizz_viewWillAppear:);
        // 沟通方法
    [JJRuntimeTool jj_MethodSwizzlingWithClass:class oriSEL:originalSEL swizzledSEL:swizzledSEL];
  });
}
- (void)swizz_viewWillAppear:(BOOL)animated
{
    // 这儿是调用沟通方法的viewWillAppear,不是递归
  [self swizz_viewWillAppear:animated];
  NSLog(@"计算页面: %@", [self class]);
}

9、防止按钮屡次点击工作

这儿我们要配合分类UIControl,运用相关政策来添加特色。delayInterval来控制按钮点击几秒后才可以继续照应工作。

@interface UIControl (Swizzling)
// 是否忽略工作
@property (nonatomic, assign) BOOL ignoreEvent;
// 推延多少秒可继续实行
@property (**nonatomic, assign) NSTimeInterval delayInterval;
@end

设置特色的setget方法。

- (void)setIgnoreEvent:(BOOL)ignoreEvent
{
  objc_setAssociatedObject(self, @"associated_ignoreEvent", @(ignoreEvent), OBJC_ASSOCIATION_ASSIGN);
}
- (BOOL)ignoreEvent
{
  return [objc_getAssociatedObject(self, @"associated_ignoreEvent") boolValue];
}
- (void)setDelayInterval:(NSTimeInterval)delayInterval
{
  objc_setAssociatedObject(self, @"associated_delayInterval", @(delayInterval), OBJC_ASSOCIATION_ASSIGN);
}
- (NSTimeInterval)delayInterval
{
  return [objc_getAssociatedObject(self, @"associated_delayInterval") doubleValue];
}

这儿的完结方法也是通过沟通照应工作sendAction:to:forEvent:方法来完结推延照应工作。

+ (void)load
{
  static dispatch_once_t onceToken;
  dispatch_once(&onceToken, ^{
    Class class = [self class];
    SEL originalSEL = @selector(sendAction:to:forEvent:);
        SEL swizzledSEL = @selector(swizzl_sendAction:to:forEvent:);
    [JJRuntimeTool jj_MethodSwizzlingWithClass:class oriSEL:originalSEL swizzledSEL:swizzledSEL];
  });
}
- (void)swizzl_sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event
{
    // 假设忽略照应,就return
  if (self.ignoreEvent) return;
  if (self.delayInterval > 0) {
        //添加了推延,ignoreEvent就设置为YES,让上面来阻挠。
    self.ignoreEvent = YES;
        // 推延delayInterval秒后,让ignoreEvent为NO,可以继续照应
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(self.delayInterval * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
      self.ignoreEvent = NO;
    });
  }
    // 调用系统的sendAction方法
  [self swizzl_sendAction:action to:target forEvent:event];
}

10、 稳定性治理

10.1 防止数组越界溃散

沟通数组的objectAtIndex方法,检测是否越界,来防止溃散。

+ (void)load {
  static dispatch_once_t onceToken;
  dispatch_once(&onceToken, ^{
    Class class = NSClassFromString(@"__NSArrayM");
    SEL originalSEL = @selector(objectAtIndex:);
    SEL swizzledSEL = @selector(swizz_objectAtIndex:);
    [JJRuntimeTool jj_MethodSwizzlingWithClass:class oriSEL:originalSEL swizzledSEL:swizzledSEL];
  });
}
- (id)swizz_objectAtIndex:(NSUInteger)index
{
  if (index < self.count) {
    return [self swizz_objectAtIndex:index];
  }else {
    @try {
      return [self swizz_objectAtIndex:index];
    } @catch (NSException *exception) {
      NSLog(@"------- %s Crash Bacause Method %s --------- \n", class_getName(self.class), __func__ );
      NSLog(@"%@", [exception callStackSymbols]);
      return nil;
    } @finally {
    }
  }
}

10.2 防止找不到方法完结溃散

假设调用objc_msgSend后,找不到IMP完结方法,就会来到消息转发机制resolveInstanceMethod,这时分,我们可以动态添加一个方法,让sel指向我们的动态完结的IMP,来防止溃散。

void testFun(){
    NSLog(@"test Fun");
}
+(BOOL)resolveInstanceMethod:(SEL)sel{
    if ([super resolveInstanceMethod:sel]) {
        return YES;
    }else{
        class_addMethod(self, sel, (IMP)testFun, "v@:");
        return YES;
    }
}

10.3 …

11、切面开发

11.1 Aspects

面向切面编程Aspects,不批改原本的函数,可以在函数的实行前后刺进一些代码。这个是我在公司的老项目发现用的库,觉得有意思,也写下来。

中心是方法aspect_hookSelector

/**
作用域:针对全部政策收效
selector: 需求hook的方法
options:是个枚举,首要定义了切面的机会(调用前、替换、调用后)
block: 需求在selector前后刺进实行的代码块
error: 错误信息
*/
+ (id<AspectToken>)aspect_hookSelector:(SEL)selector
                           withOptions:(AspectOptions)options
                            usingBlock:(id)block
                                 error:(NSError **)error;
/**
作用域:针对其时政策收效
*/
- (id<AspectToken>)aspect_hookSelector:(SEL)selector
                           withOptions:(AspectOptions)options
                            usingBlock:(id)block
                                 error:(NSError **)error;

AspectOptions

AspectOptions是个枚举,用来定义切面的机会,即原有方法调用前、调用后、替换原有方法、只实行一次(调用完就删去切面逻辑)

typedef NS_OPTIONS(NSUInteger, AspectOptions) {
    AspectPositionAfter   = 0,            /// 原有方法调用后
    AspectPositionInstead = 1,            /// 替换原有方法
    AspectPositionBefore  = 2,            /// 原有方法调用前实行
    AspectOptionAutomaticRemoval = 1 << 3 /// 实行完之后就康复切面操作,即撤销hook
};

AspectsDemo

我们想在其时控制器调用viewWillAppear后,进行操作内容。

- (void)viewDidLoad {
  [super viewDidLoad];
  [self aspect_hookSelector:@selector(viewWillAppear:) withOptions:AspectPositionAfter usingBlock:^{
    NSLog(@"CCCCCC");
  } error:nil];
}
- (void)viewWillAppear:(BOOL)animated
{
  NSLog(@"AAAAAA");
  [super viewWillAppear:animated];
  NSLog(@"BBBBBB");
}

工作结果:

022-01-14 15:32:49.962692+0800 AspectsDemo[68165:356953] AAAAAA
2022-01-14 15:32:49.962785+0800 AspectsDemo[68165:356953] BBBBBB
2022-01-14 15:32:49.962847+0800 AspectsDemo[68165:356953] CCCCCC

附上gitHub地址:Aspects

12、言语国际化

12.1 对setText进行方法沟通

国际化首要的作业就是在 setText 之前需求调用 NSLocalizedString 生成国际化后的字符串。

现在代码使我们纠结的地方是我们就直接运用 setText 了。我们期望在setText时刺进一段国际化的代码。

我们期望在实行某个函数之前刺进一段代码,Runtime的 Method Swizzling 可以完结这样的功用。

@implementation UILabel(NewLabel)
+ (void)load {
    [UILabel configSwizzled];
}
+ (void)configSwizzled {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Class class = [self class];
        SEL originalSelector = @selector(setText:);
        SEL swizzledSelector = @selector(setNewText:);
        Method originalMethod = class_getInstanceMethod(class, originalSelector);
        Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
        BOOL didAddMethod =
        class_addMethod(class,
                        originalSelector,
                        method_getImplementation(swizzledMethod),
                        method_getTypeEncoding(swizzledMethod));
        if (didAddMethod) {
            class_replaceMethod(class,
                                swizzledSelector,
                                method_getImplementation(originalMethod),
                                method_getTypeEncoding(originalMethod));
        } else {
            method_exchangeImplementations(originalMethod,swizzledMethod);
        }
    });
}
- (void)setNewText:(NSString *)text {
    [self setNewText:NSLocalizedString(text, nil)];
}
@end
  • 我们运用分类扩展 UILabel
  • 然后重写 load 这个函数,在里面进行Swizzle的初始化。
  • 在这儿我们把 setText Swizzle setNewText.
  • setNewText 中我们我们调用 NSLocalizedString 进行国际化处理。

好了,这样我们处理了在代码中 setText 的国际化问题。

12.2 xib、storyboard言语国际化

这儿我们发现,Xib StoryBoard 中设置特色的控件不会调用 setText

那这我们怎样处理呢? 让他们调用一下 setText 吧。那我们需求怎样做? Xib StoryBoard 的控件,必然会走 initWithCoder 这个初始化函数。我们在再次运用 Runtime 的黑魔法,让 initWithCoder 实行完后,我们在调用一下 setText

直接看代码吧:

+ (void)configSwizzled {
...
dispatch_once(&onceToken2, ^{
        Class class = [self class];
        SEL originalSelector = ;
        SEL swizzledSelector = ;
        Method originalMethod = class_getInstanceMethod(class, originalSelector);
        Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
        BOOL didAddMethod =
        class_addMethod(class,
                        originalSelector,
                        method_getImplementation(swizzledMethod),
                        method_getTypeEncoding(swizzledMethod));
        if (didAddMethod) {
            class_replaceMethod(class,
                                swizzledSelector,
                                method_getImplementation(originalMethod),
                                method_getTypeEncoding(originalMethod));
        } else {
            method_exchangeImplementations(originalMethod,swizzledMethod);
        }
    });
}
- (instancetype)initNewWithCoder:(NSCoder *)aDecoder {
    id result = [self initNewWithCoder:aDecoder];
    [self setText:self.text];
    return result;
}

12.3 允许一些控件关闭言语国际化

我们可以添加一个变量来控制代码是否进行国际化。那就运用相关政策(Associated Object)吧。

@interface UILabel (NewLabel)
@property (nonatomic, assign)IBInspectable BOOL localizedEnlabe;
@end
@implementation UILabel(NewLabel)
static char *localizedEnlabeChar = "LocalizedEnlabe";
- (void)setLocalizedEnlabe:(BOOL)localizedEnlabe {
    objc_setAssociatedObject(self, &localizedEnlabeChar, [NSNumber numberWithBool:localizedEnlabe], OBJC_ASSOCIATION_ASSIGN);
}
- (BOOL)localizedEnlabe {
    NSNumber *value = objc_getAssociatedObject(self, &localizedEnlabeChar);
    if (value) {
        return [value boolValue];
    }
    return YES;
}
@end
  • 这儿我运用 IBInspectable 特色方便 Xib StoryBoard 设置特色.

  • 需求国际化的控件还有 UITextFieldUIButton 等控件

  • 尽管这种方法不见得能处理全部问题,但应该是可以处理 80% 的问题的。

十、面试题(转)

通过前面几篇文章的对Runtime的根究,我们可以用一些常见的Runtime面试题来查验一下学习作用.

1.objc在向一个政策发送消息时,发生了什么?

objc在向一个政策发送消息时,runtime会依据政策的isa指针找到该政策实践所属的类,然后在该类中的方法列表以及其父类方法列表中寻找方法工作,假设一直到根类还没找到,转向阻挠调用,走消息转发机制,一旦找到 ,就去实行它的完结IMP

2.objc中向一个nil政策发送消息将会发生什么?

假设向一个nil政策发送消息,首要在寻找政策的isa指针时就是0地址回来了,所以不会呈现任何错误。也不会溃散。

详解: 假设一个方法回来值是一个政策,那么发送给nil的消息将回来0(nil);

假设方法回来值为指针类型,其指针大小为小于或许等于sizeof(void*) ,float,double,long double 或许long long的整型标量,发送给nil的消息将回来0;

假设方法回来值为结构体,发送给nil的消息将回来0。结构体中各个字段的值将都是0;

假设方法的回来值不是上述说到的几种情况,那么发送给nil的消息的回来值将是未定义的。

3.objc中向一个政策发送消息[obj foo]和objc_msgSend()函数之间有什么联络?

在objc编译时,[obj foo] 会被转意为:objc_msgSend(obj, @selector(foo));

4.什么时分会报unrecognized selector的失常?

objc在向一个政策发送消息时,runtime库会依据政策的isa指针找到该政策实践所属的类,然后在该类中的方法列表以及其父类方法列表中寻找方法工作,假设,在最顶层的父类中仍然找不到相应的方法时,会进入消息转发阶段,假设消息三次转发流程仍未完结,则程序在工作时会挂掉并抛出失常unrecognized selector sent to XXX 。

5.能否向编译后得到的类中添加实例变量?能否向工作时创建的类中添加实例变量?为什么?

不能向编译后得到的类中添加实例变量;

能向工作时创建的类中添加实例变量;

1.由于编译后的类现已注册在 runtime 中,类结构体中的 objc_ivar_list 实例变量的链表和 instance_size 实例变量的内存大小现已承认,一起runtime会调用 class_setvarlayout 或 class_setWeaklvarLayout 来处理strong weak 引用.所以不能向存在的类中添加实例变量。

2.工作时创建的类是可以添加实例变量,调用class_addIvar函数. 可是的在调用 objc_allocateClassPair 之后,objc_registerClassPair 之前,原因同上.

6.给类添加一个特色后,在类结构体里哪些元素会发生变化?

instance_size :实例的内存大小;objc_ivar_list *ivars:特色列表

7.一个objc政策的isa的指针指向什么?有什么作用?

指向他的类政策,然后可以找到政策上的方法

详解:下图很好的描绘了政策,类,元类之间的联络:

14-根究iOS底层原理|Runtime的相关使用

图中实线是 super_class指针,虚线是isa指针。

  1. Root class (class)其实就是NSObject,NSObject是没有超类的,所以Root class(class)的superclass指向nil。
  2. 每个Class都有一个isa指针指向唯一的Meta class
  3. Root class(meta)的superclass指向Root class(class),也就是NSObject,构成一个回路。
  4. 每个Meta class的isa指针都指向Root class (meta)。

8.[self class] 与 [super class]

下面的代码输出什么?

@implementation Son : Father
- (id)init
{
   self = [super init];
   if (self) {
       NSLog(@"%@", NSStringFromClass([self class]));
       NSLog(@"%@", NSStringFromClass([super class]));
   }
   return self;
}
@end

NSStringFromClass([self class]) = Son NSStringFromClass([super class]) = Son

详解:这个标题首要是考察关于 Objective-C 中对 self 和 super 的了解。

self 是类的隐藏参数,指向其时调用方法的这个类的实例;

super 本质是一个编译器标明符,和 self 是指向的同一个消息接受者。不同点在于:super 会告知编译器,当调用方法时,去调用父类的方法,而不是本类中的方法。

当运用 self 调用方法时,会从其时类的方法列表中初步找,假设没有,就从父类中再找;而当运用 super 时,则从父类的方法列表中初步找。然后调用父类的这个方法。

在调用[super class]的时分,runtime会去调用objc_msgSendSuper方法,而不是objc_msgSend

OBJC_EXPORT void objc_msgSendSuper(void /* struct objc_super *super, SEL op, ... */ )
/// Specifies the superclass of an instance. 
struct objc_super {
    /// Specifies an instance of a class.
    __unsafe_unretained id receiver;
    /// Specifies the particular superclass of the instance to message. 
#if !defined(__cplusplus)  &&  !__OBJC2__
    /* For compatibility with old objc-runtime.h header */
    __unsafe_unretained Class class;
#else
    __unsafe_unretained Class super_class;
#endif
    /* super_class is the first class to search */
};

在objc_msgSendSuper方法中,第一个参数是一个objc_super的结构体,这个结构体里面有两个变量,一个是接纳消息的receiver,一个是其时类的父类super_class。

objc_msgSendSuper的作业原理应该是这样的: 从objc_super结构体指向的superClass父类的方法列表初步查找selector,找到后以objc->receiver去调用父类的这个selector。留心,终究的调用者是objc->receiver,而不是super_class!

那么objc_msgSendSuper终究就转变成:

// 留心这儿是从父类初步msgSend,而不是从本类初步
objc_msgSend(objc_super->receiver, @selector(class))
/// Specifies an instance of a class.  这是类的一个实例
    __unsafe_unretained id receiver;   
// 由所以实例调用,所以是减号方法
- (Class)class {
    return object_getClass(self);
}

由于找到了父类NSObject里面的class方法的IMP,又由于传入的入参objc_super->receiver = self。self就是son,调用class,所以父类的方法class实行IMP之后,输出仍是son,终究输出两个都相同,都是输出son。

9.runtime怎样通过selector找到对应的IMP地址?

每一个类政策中都一个方法列表,方法列表中记载着方法的称谓,方法完结,以及参数类型,其实selector本质就是方法称谓,通过这个方法称谓就可以在方法列表中找到对应的方法完结.

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

_objc_msgForward是 IMP 类型,用于消息转发的:当向一个政策发送一条消息,但它并没有完结的时分,_objc_msgForward会尝试做消息转发。

详解:_objc_msgForward在进行消息转发的进程中会触及以下这几个方法:

  1. resolveInstanceMethod:方法 (或 resolveClassMethod:)。
  2. forwardingTargetForSelector:方法
  3. methodSignatureForSelector:方法
  4. forwardInvocation:方法
  5. doesNotRecognizeSelector: 方法

11. runtime怎样完结weak变量的自动置nil?知道SideTable吗?

runtime 对注册的类会进行布局,关于 weak 修饰的政策会放入一个 hash 表中。 用 weak 指向的政策内存地址作为 key,当此政策的引用计数为0的时分会 dealloc,假设 weak 指向的政策内存地址是a,那么就会以a为键, 在这个 weak 表中查找,找到全部以a为键的 weak 政策,然后设置为 nil。

更细一点的答复:

1.初始化时:runtime会调用objc_initWeak函数,初始化一个新的weak指针指向政策的地址。
2.添加引用时:objc_initWeak函数会调用objc_storeWeak() 函数, objc_storeWeak() 的作用是更新指针指向,创建对应的弱引用表。
3.开释时,调用clearDeallocating函数。clearDeallocating函数首要依据政策地址获取全部weak指针地址的数组,然后遍历这个数组把其间的数据设为nil,终究把这个entry从weak表中删去,终究清理政策的记载。

SideTable结构体是担任办理类的引用计数表和weak表,

详解:参考自《Objective-C高档编程》一书 1.初始化时:runtime会调用objc_initWeak函数,初始化一个新的weak指针指向政策的地址。

{
    NSObject *obj = [[NSObject alloc] init];
    id __weak obj1 = obj;
}

当我们初始化一个weak变量时,runtime会调用 NSObject.mm 中的objc_initWeak函数。

// 编译器的模仿代码
 id obj1;
 objc_initWeak(&obj1, obj);
/*obj引用计数变为0,变量作用域完毕*/
 objc_destroyWeak(&obj1);

通过objc_initWeak函数初始化“附有weak修饰符的变量(obj1)”,在变量作用域完毕时通过objc_destoryWeak函数开释该变量(obj1)。

2.添加引用时:objc_initWeak函数会调用objc_storeWeak() 函数, objc_storeWeak() 的作用是更新指针指向,创建对应的弱引用表。

objc_initWeak函数将“附有weak修饰符的变量(obj1)”初始化为0(nil)后,会将“赋值政策”(obj)作为参数,调用objc_storeWeak函数。

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

也就是说:

weak 修饰的指针默认值是 nil (在Objective-C中向nil发送消息是安全的)

然后obj_destroyWeak函数将0(nil)作为参数,调用objc_storeWeak函数。

objc_storeWeak(&obj1, 0);

前面的源代码与下列源代码相同。

// 编译器的模仿代码
id obj1;
obj1 = 0;
objc_storeWeak(&obj1, obj);
/* ... obj的引用计数变为0,被置nil ... */
objc_storeWeak(&obj1, 0);

objc_storeWeak函数把第二个参数的赋值政策(obj)的内存地址作为键值,将第一个参数__weak修饰的特色变量(obj1)的内存地址注册到 weak 表中。假设第二个参数(obj)为0(nil),那么把变量(obj1)的地址从weak表中删去。

由于一个政策可一起赋值给多个附有__weak修饰符的变量中,所以关于一个键值,可注册多个变量的地址。

可以把objc_storeWeak(&a, b)了解为:objc_storeWeak(value, key),并且当key变nil,将value置nil。在b非nil时,a和b指向同一个内存地址,在b变nil时,a变nil。此刻向a发送消息不会溃散:在Objective-C中向nil发送消息是安全的。

3.开释时,调用clearDeallocating函数。clearDeallocating函数首要依据政策地址获取全部weak指针地址的数组,然后遍历这个数组把其间的数据设为nil,终究把这个entry从weak表中删去,终究清理政策的记载。

当weak引用指向的政策被开释时,又是怎样去处理weak指针的呢?当开释政策时,其底子流程如下:

1.调用objc_release
2.由于政策的引用计数为0,所以实行dealloc
3.在dealloc中,调用了_objc_rootDealloc函数
4.在_objc_rootDealloc中,调用了object_dispose函数
5.调用objc_destructInstance
6.终究调用objc_clear_deallocating

政策被开释时调用的objc_clear_deallocating函数:

1.从weak表中获取扔掉政策的地址为键值的记载
2.将包含在记载中的全部附有 weak修饰符变量的地址,赋值为nil
3.将weak表中该记载删去
4.从引用计数表中删去扔掉政策的地址为键值的记载

总结:

其实Weak表是一个hash(哈希)表,Key是weak所指政策的地址,Value是weak指针的地址(这个地址的值是所指政策指针的地址)数组。

12.isKindOfClass 与 isMemberOfClass

下面代码输出什么?

@interface Sark : NSObject
@end
@implementation Sark
@end
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        BOOL res1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];
        BOOL res2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];
        BOOL res3 = [(id)[Sark class] isKindOfClass:[Sark class]];
        BOOL res4 = [(id)[Sark class] isMemberOfClass:[Sark class]];
        NSLog(@"%d %d %d %d", res1, res2, res3, res4);
    }
    return 0;
}

1000

详解:

isKindOfClass中有一个循环,先判别class是否等于meta class,不等就继续循环判别是否等于meta classsuper class,不等再继续取super class,如此循环下去。

[NSObject class]实行完之后调用isKindOfClass,第一次判别先判别NSObjectNSObjectmeta class是否相等,之前讲到meta class的时分放了一张很详细的图,从图上我们也可以看出,NSObjectmeta class与自身不等。接着第2次循环判别NSObjectmeta classsuperclass是否相等。仍是从那张图上面我们可以看到:Root class(meta)superclass就是 Root class(class),也就是NSObject自身。所以第2次循环相等,所以第一行res1输出应该为YES。

同理,[Sark class]实行完之后调用isKindOfClass,第一次for循环,Sark的Meta Class[Sark class]不等,第2次for循环,Sark Meta Classsuper class 指向的是 NSObject Meta Class, 和Sark Class不相等。第三次for循环,NSObject Meta Classsuper class指向的是NSObject Class,和 Sark Class 不相等。第四次循环,NSObject Classsuper class 指向 nil, 和 Sark Class不相等。第四次循环之后,退出循环,所以第三行的res3输出为NO。

isMemberOfClass的源码完结是拿到自己的isa指针和自己比较,是否相等。 第二行isa 指向 NSObjectMeta Class,所以和 NSObject Class不相等。第四行,isa指向Sark的Meta Class,和Sark Class也不等,所以第二行res2和第四行res4都输出NO。

13.运用runtime Associate方法相关的政策,需求在主政策dealloc的时分开释么?

不论在MRC下仍是ARC下均不需求,被相关的政策在生命周期内要比政策自身开释的晚许多,它们会在被 NSObject -dealloc 调用的object_dispose()方法中开释。

详解:

1、调用 -release :引用计数变为零
政策正在被销毁,生命周期行将完毕. 
不能再有新的 __weak 弱引用,否则将指向 nil.
调用 [self dealloc]
2、 父类调用 -dealloc 
继承联络中最直接继承的父类再调用 -dealloc 
假设是 MRC 代码 则会手动开释实例变量们(iVars)
继承联络中每一层的父类 都再调用 -dealloc
>3、NSObject 调 -dealloc 
只做一件事:调用 Objective-C runtime 中object_dispose() 方法
>4. 调用 object_dispose()
为 C++ 的实例变量们(iVars)调用 destructors
为 ARC 状态下的 实例变量们(iVars) 调用 -release 
免除全部运用 runtime Associate方法相关的政策 
免除全部 __weak 引用 
调用 free()

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

简略说就是进行方法沟通

在Objective-C中调用一个方法,其实是向一个政策发送消息,查找消息的唯一依据是selector的名字。运用Objective-C的动态特性,可以完结在工作时掉包selector对应的方法完结,到达给方法挂钩的目的。

每个类都有一个方法列表,存放着方法的名字和方法完结的映射联络,selector的本质其实就是方法名,IMP有点相似函数指针,指向详细的Method完结,通过selector就可以找到对应的IMP。

换方法的几种完结方式

  • 运用 method_exchangeImplementations 沟通两个方法的完结
  • 运用 class_replaceMethod 替换方法的完结
  • 运用 method_setImplementation 来直接设置某个方法的IMP

14-根究iOS底层原理|Runtime的相关使用

15.Compile Error / Runtime Crash / NSLog…?

下面的代码会?Compile Error / Runtime Crash / NSLog…?

@interface NSObject (Sark)
+ (void)foo;
- (void)foo;
@end
@implementation NSObject (Sark)
- (void)foo {
    NSLog(@"IMP: -[NSObject (Sark) foo]");
}
@end
// 测验代码
[NSObject foo];
[[NSObject new] performSelector:@selector(foo)];

IMP: -[NSObject(Sark) foo] ,全都正常输出,编译和工作都没有问题。

详解:

这道题和上一道题很相似,第二个调用必定没有问题,第一个调用后会从元类中查找方法,但是方法并不在元类中,所以找元类的superclass。方法定义在是NSObjectCategory,由于NSObject的政策模型比较特殊,元类的superclass是类政策,所以从类政策中找到了方法并调用。

专题系列文章

1.前常识

  • 01-根究iOS底层原理|综述
  • 02-根究iOS底层原理|编译器LLVM项目【Clang、SwiftC、优化器、LLVM】
  • 03-根究iOS底层原理|LLDB
  • 04-根究iOS底层原理|ARM64汇编

2. 依据OC言语根究iOS底层原理

  • 05-根究iOS底层原理|OC的本质
  • 06-根究iOS底层原理|OC政策的本质
  • 07-根究iOS底层原理|几种OC政策【实例政策、类政策、元类】、政策的isa指针、superclass、政策的方法调用、Class的底层本质
  • 08-根究iOS底层原理|Category底层结构、App启动时Class与Category装载进程、load 和 initialize 实行、相关政策
  • 09-根究iOS底层原理|KVO
  • 10-根究iOS底层原理|KVC
  • 11-根究iOS底层原理|根究Block的本质|【Block的数据类型(本质)与内存布局、变量捕获、Block的品种、内存办理、Block的修饰符、循环引用】
  • 12-根究iOS底层原理|Runtime1【isa详解、class的结构、方法缓存cache_t】
  • 13-根究iOS底层原理|Runtime2【消息处理(发送、转发)&&动态方法解析、super的本质】
  • 14-根究iOS底层原理|Runtime3【Runtime的相关运用】
  • 15-根究iOS底层原理|RunLoop【两种RunloopMode、RunLoopMode中的Source0、Source1、Timer、Observer】
  • 16-根究iOS底层原理|RunLoop的运用
  • 17-根究iOS底层原理|多线程技术的底层原理【GCD源码分析1:主队伍、串行队伍&&并行队伍、大局并发队伍】
  • 18-根究iOS底层原理|多线程技术【GCD源码分析1:dispatch_get_global_queue与dispatch_(a)sync、单例、线程死锁】
  • 19-根究iOS底层原理|多线程技术【GCD源码分析2:栅门函数dispatch_barrier_(a)sync、信号量dispatch_semaphore】
  • 20-根究iOS底层原理|多线程技术【GCD源码分析3:线程调度组dispatch_group、工作源dispatch Source】
  • 21-根究iOS底层原理|多线程技术【线程锁:自旋锁、互斥锁、递归锁】
  • 22-根究iOS底层原理|多线程技术【原子锁atomic、gcd Timer、NSTimer、CADisplayLink】
  • 23-根究iOS底层原理|内存办理【Mach-O文件、Tagged Pointer、政策的内存办理、copy、引用计数、weak指针、autorelease

3. 依据Swift言语根究iOS底层原理

关于函数枚举可选项结构体闭包特色方法swift多态原理StringArrayDictionary引用计数MetaData等Swift底子语法和相关的底层原理文章有如下几篇:

  • Swift5中心语法1-根底语法
  • Swift5中心语法2-面向政策语法1
  • Swift5中心语法2-面向政策语法2
  • Swift5常用中心语法3-其它常用语法
  • Swift5运用实践常用技术点

其它底层原理专题

1.底层原理相关专题

  • 01-计算机原理|计算机图形烘托原理这篇文章
  • 02-计算机原理|移动终端屏幕成像与卡顿

2.iOS相关专题

  • 01-iOS底层原理|iOS的各个烘托结构以及iOS图层烘托原理
  • 02-iOS底层原理|iOS动画烘托原理
  • 03-iOS底层原理|iOS OffScreen Rendering 离屏烘托原理
  • 04-iOS底层原理|因CPU、GPU资源消耗导致卡顿的原因和处理计划

3.webApp相关专题

  • 01-Web和类RN大前端的烘托原理

4.跨渠道开发计划相关专题

  • 01-Flutter页面烘托原理

5.阶段性总结:Native、WebApp、跨渠道开发三种计划功用比较

  • 01-Native、WebApp、跨渠道开发三种计划功用比较

6.Android、HarmonyOS页面烘托专题

  • 01-Android页面烘托原理
  • 02-HarmonyOS页面烘托原理 (待输出)

7.小程序页面烘托专题

  • 01-小程序结构烘托原理