前言
之前,我们在根究动画及烘托相关原理的时分,我们输出了几篇文章,解答了
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中,字典转模型一般我们用第三方库MJExtension
,YYModel
来运用。
底子原理就是:
- 运用
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
来确保只加载一次 - 将
UIViewController
的viewWillAppear
方法,沟通为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
设置特色的set
和get
方法。
- (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
SwizzlesetNewText
. - 在
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 设置特色. -
需求国际化的控件还有
UITextField
,UIButton
等控件 -
尽管这种方法不见得能处理全部问题,但应该是可以处理 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的指针指向什么?有什么作用?
指向他的类政策,然后可以找到政策上的方法
详解:下图很好的描绘了政策,类,元类之间的联络:
图中实线是 super_class指针,虚线是isa指针。
- Root class (class)其实就是NSObject,NSObject是没有超类的,所以Root class(class)的superclass指向nil。
- 每个Class都有一个isa指针指向唯一的Meta class
- Root class(meta)的superclass指向Root class(class),也就是NSObject,构成一个回路。
- 每个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
在进行消息转发的进程中会触及以下这几个方法:
-
resolveInstanceMethod:
方法 (或resolveClassMethod:
)。 -
forwardingTargetForSelector:
方法 -
methodSignatureForSelector:
方法 -
forwardInvocation:
方法 -
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 class
的super class
,不等再继续取super class
,如此循环下去。
[NSObject class]
实行完之后调用isKindOfClass
,第一次判别先判别NSObject
和 NSObject
的meta class
是否相等,之前讲到meta class
的时分放了一张很详细的图,从图上我们也可以看出,NSObject
的meta class
与自身不等。接着第2次循环判别NSObject
与meta class
的superclass
是否相等。仍是从那张图上面我们可以看到:Root class(meta)
的superclass
就是 Root class(class)
,也就是NSObject自身。所以第2次循环相等,所以第一行res1输出应该为YES。
同理,[Sark class]
实行完之后调用isKindOfClass
,第一次for循环,Sark的Meta Class
与[Sark class]
不等,第2次for循环,Sark Meta Class
的super class
指向的是 NSObject Meta Class
, 和Sark Class
不相等。第三次for循环,NSObject Meta Class
的super class
指向的是NSObject Class
,和 Sark Class
不相等。第四次循环,NSObject Class
的super class
指向 nil, 和 Sark Class
不相等。第四次循环之后,退出循环,所以第三行的res3输出为NO。
isMemberOfClass
的源码完结是拿到自己的isa指针和自己比较,是否相等。 第二行isa 指向 NSObject
的 Meta 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
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
。方法定义在是NSObject
的Category
,由于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多态原理
、String
、Array
、Dictionary
、引用计数
、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-小程序结构烘托原理