概述
点击上方“iOS开发”,选择“置顶公众号”
关键时刻,第一时间送达!
attribute
__attribute__是一套编译器指令,被GNU和LLVM编译器所支持,允许对于__attribute__增加一些参数,做一些高级检查和优化。
__attribute__的语法是,在后面加两个括号,然后写属性列表,属性列表以逗号分隔。在iOS中,很多例如NS_CLASS_AVAILABLE_IOS的宏定义,内部也是通过__attribute__实现的。
__attribute__((attribute1, attribute2));
下面是一些__attribute__的常用属性,更完整的属性列表可以到llvm的官网查看。
官网示例
objc_subclassing_restricted
objc_subclassing_restricted属性表示被修饰的类不能被其他类继承,否则会报下面的错误。
__attribute__((objc_subclassing_restricted))
TestObject : NSObject
(nonatomic, strong) NSObject *object;
(nonatomic, assign) NSInteger age;
Child : TestObject
错误信息:
Cannot subclass a class that was declared with the 'objc_subclassing_restricted' attribute
objc_requires_super
objc_requires_super属性表示子类必须调用被修饰的方法super,否则报黄色警告。
@interface TestObject : NSObject
- (void)testMethod __attribute__((objc_requires_super));
@end
@interface Child : TestObject
@end
警告信息:(不报错)
Method possibly missing a [super testMethod] call
constructor / destructor
constructor属性表示在main函数执行之前,可以执行一些操作。destructor属性表示在main函数执行之后做一些操作。constructor的执行时机是在所有load方法都执行完之后,才会执行所有constructor属性修饰的函数。
__attribute__((constructor)) static void beforeMain() {
NSLog(@"before main");
}
__attribute__((destructor)) static void afterMain() {
NSLog(@"after main");
}
int main(int argc, const char * argv[]) {
{
NSLog(@"execute main");
}
return 0;
}
执行结果:
debug-objc[23391:1143291] before main
debug-objc[23391:1143291] execute main
debug-objc[23391:1143291] after main
在有多个constructor或destructor属性修饰的函数时,可以通过设置优先级来指定执行顺序。格式是__attribute__((constructor(101)))的方式,在属性后面直接跟优先级。
__attribute__((constructor(103))) static void beforeMain3() {
NSLog(@"after main 3");
}
__attribute__((constructor(101))) static void beforeMain1() {
NSLog(@"after main 1");
}
__attribute__((constructor(102))) static void beforeMain2() {
NSLog(@"after main 2");
}
在constructor中根据优先级越低,执行顺序越高。而destructor则相反,优先级越高则执行顺序越高。
overloadable
overloadable属性允许定义多个同名但不同参数类型的函数,在调用时编译器会根据传入参数类型自动匹配函数。这个有点类似于C++的函数重载,而且都是发生在编译期的行为。
__attribute__((overloadable)) void testMethod(int age) {}
__attribute__((overloadable)) void testMethod(NSString *name) {}
__attribute__((overloadable)) void testMethod(BOOL gender) {}
int main(int argc, const char * argv[]) {
{
testMethod(18);
testMethod(@"lxz");
testMethod(YES);
}
return 0;
}
objc_runtime_name
objc_runtime_name属性可以在编译时,将Class或Protocol指定为另一个名字,并且新名字不受命名规范制约,可以以数字开头。
__attribute__((objc_runtime_name("TestObject")))
@interface Object : NSObject
@end
NSLog(@"%@", NSStringFromClass([TestObject class]));
执行结果:
TestObject
这个属性可以用来做代码混淆,例如写一个宏定义,宏定义内部实现混淆逻辑。例如通过MD5对Object做混淆,32位的混淆结果就是497031794414a552435f90151ac3b54b,谁能看出来这是什么类。如果怕彩虹表匹配出来,再增加加盐逻辑。
cleanup
通过cleanup属性,可以指定给一个变量,当变量释放之前执行一个函数。指定的函数执行的时间,是在dealloc之前的。在指定的函数中,可以传入一个形参,参数就是cleanup修饰的变量,形参是一个地址。
static void releaseBefore(NSObject **object) {
NSLog(@"%@", *object);
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
TestObject *object __attribute__((cleanup(releaseBefore))) = [[TestObject alloc] init];
}
return 0;
}
如果遇到同一个代码块中,同时出现多个cleanup属性时,在代码块作用域结束时,会以添加的顺序进行调用。
unused
还有一个属性很实用,在项目里经常会有未使用的变量,会报一个黄色警告。有时候可能会通过其他方式获取这个对象,所以不想出现这个警告,可以通过unused属性消除这个警告。
NSObject *object __attribute__((unused)) = [[NSObject alloc] init];
系统定义
在系统里也大量使用了__attribute__关键字,只不过系统不会直接在外部使用__attribute__,一般都是将其定义为宏定义,以宏定义的形式出现在外面。
// NSLog
FOUNDATION_EXPORT void NSLog(NSString *format, ...) NS_FORMAT_FUNCTION(1,2) NS_NO_TAIL_CALL;
// 必须调用父类的方法
// 指定初始化方法,必须直接或间接调用修饰的方法
ORM
对象关系映射(Object Relational Mapping),简称ORM,用于面向对象语言中不同系统数据之间的转换。
可以通过对象关系映射来实现JSON转模型,使用比较多的是Mantle、MJExtension、YYKit、JSONModel等框架,这些框架在进行转换的时候,都是使用Runtime的方式实现的。
Mantle使用和MJExtension有些类似,只不过MJExtension使用起来更加方便。Mantle在使用时主要是通过继承的方式处理,而MJExtension是通过Category处理,代码依赖性更小,无侵入性。
性能评测
这些第三方中Mantle功能最强大,但是太臃肿,使用起来性能比其他第三方都差一些。JSONModel、MJExtension这些第三方几乎都在一个水平级,YYKit相对来说性能可以比肩手写赋值代码,性价比最高。
对于模型转换需求不是太大的工程来说,尽量用YYKit来进行转换性能会更好一些。功能可能略逊于MJExtension,我个人还是比较习惯用MJExtension。
YYKit作者评测:https://blog.ibireme.com/2015/10/23/ios_model_framework_benchmark/
实现思路
也可以自己实现模型转换的逻辑,以字典转模型为例,大体逻辑如下:
创建一个Category用来做模型转换,对外提供方法并传入字典对象。
通过Runtime对应的函数,获取属性列表并遍历,根据属性名从字典中取出对应的对象。
通过KVC将从字典中取出的值,赋值给对象。
有时候会遇到多层嵌套的情况,例如字典包含数组,数组中还是一个字典。这种情况就可以做判断,如果模型对象是数组则取出字典对应字段的数组,然后遍历数组再调用字典赋值的方法。
下面简单实现了一个字典转模型的代码,通过Runtime遍历属性列表,并根据属性名取出字典中的对象,然后通过KVC进行赋值操作。调用方式和MJExtension、YYModel类似,直接通过模型类调用类方法即可。如果想在其他类中也使用的话,应该把下面的实现写在NSObject的Category中,这样所有类都可以调用。
// 调用部分
NSDictionary *dict = @{@"name" : @"lxz",
@"age" : @18,
@"gender" : @YES};
TestObject *object = [TestObject objectWithDict:dict];
// 实现代码
@interface TestObject : NSObject
@property (nonatomic, copy ) NSString *name;
@property (nonatomic, assign) NSInteger age;
@property (nonatomic, assign) BOOL gender;
+ (instancetype)objectWithDict:(NSDictionary *)dict;
@end
@implementation TestObject
+ (instancetype)objectWithDict:(NSDictionary *)dict {
return [[TestObject alloc] initWithDict:dict];
}
- (instancetype)initWithDict:(NSDictionary *)dict {
self = [super init];
if (self) {
unsigned int count = 0;
objc_property_t *propertys = class_copyPropertyList([self class], &count);
for (int i = 0; i < count; i++) {
objc_property_t property = propertys[i];
const char *name = property_getName(property);
NSString *nameStr = [[NSString alloc] initWithUTF8String:name];
id value = [dict objectForKey:nameStr];
[self setValue:value forKey:nameStr];
}
free(propertys);
}
return self;
}
@end
通过Runtime可以获取到对象的Method List、Property List等,不只可以用来做字典模型转换,还可以做很多工作。例如还可以通过Runtime实现自动归档和反归档,下面是自动进行归档操作。
// 1.获取所有的属性
unsigned int count = 0;
Ivar *ivars = class_copyIvarList([NJPerson class], &count);
// 遍历所有的属性进行归档
for (int i = 0; i < count; i++) {
// 取出对应的属性
Ivar ivar = ivars[i];
const char * name = ivar_getName(ivar);
// 将对应的属性名称转换为OC字符串
NSString *key = [[NSString alloc] initWithUTF8String:name];
// 根据属性名称利用KVC获取数据
id value = [self valueForKeyPath:key];
[encoder encodeObject:value forKey:key];
}
free(ivars);
我写了一个简单的Category,可以自动实现NSCoding、NSCopying协议。这是开源地址:https://github.com/DeveloperErenLiu/EasyNSCoding
Runtime面试题
题1
下面的代码输出什么?
@implementation Son : Father
- (id)init {
self = [super init];
if (self) {
NSLog(@"%@", NSStringFromClass([self class]));
NSLog(@"%@", NSStringFromClass([super class]));
}
return self;
}
@end
答案:都输出Son。
第一个NSLog输出Son肯定是不用说的。
第二个输出中,[super class]会被转换为下面代码。
struct objc_super objcSuper = {
self,
class_getSuperclass([self class]),
};
id (*sendSuper)(struct objc_super*, SEL) = (void *)objc_msgSendSuper;
sendSuper(&objcSuper, @selector(class));
super的调用会被转换为objc_msgSendSuper的调用,并传入一个objc_super类型的结构体。结构体有两个参数,第一个就是接受消息的对象,第二个是[super class]对应的父类。
struct objc_super {
__unsafe_unretained _Nonnull id receiver;
__unsafe_unretained _Nonnull Class super_class;
};
由此可知,虽然调用的是[super class],但是接受消息的对象还是self。然后来到父类Father的class方法中,输出self对应的类Son。
题2
下面代码的结果?
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]];
答案:
除了第一个是YES,其他三个都是NO。
在推测结果之前,首先要明白两个问题。isKindOfClass和isMemberOfClass的区别是什么?
isKindOfClass:class,调用该方法的对象所属的类,继承者链中包含传入的class则返回YES。
isMemberOfClass:class,调用改方法的对象所属的类,必须是传入的class则返回YES。
我们从Runtime源码的角度来分析一下结果。
+ (BOOL)isMemberOfClass:(Class)cls {
return object_getClass((id)self) == cls;
}
- (BOOL)isMemberOfClass:(Class)cls {
return [self class] == cls;
}
+ (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = object_getClass((id)self); tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}
- (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}
平时开发过程中只会接触到对象方法的isKindOfClass和isMemberOfClass,但是在NSObject类中还隐式的实现了类方法版本。不只这两个方法,其他NSObject中的对象方法,都有其对应的类方法版本。因为在OC中,类和元类也都是对象。这四个调用由于都是类对象发起调用的,所以最终执行的都是类方法版本。
先把Runtime的对象模型拿出来,方便后面的分析。
对象模型
第一次调用方是NSObject类对象,调用isKindOfClass方法传入的也是类对象。因为调用类的class方法,会把类自身直接返回,所以还是类对象自己。
然后进入到for循环中,会从NSObject的元类开始遍历,所以第一次NSObject meta class != NSObject class,匹配失败。第二次循环将tcls设置为superclass的NSObject class,NSObject class == NSObject class,匹配成功。
NSObject能匹配成功,是因为这个类比较特殊,在第二次获取superclass的时候,NSObject元类的superclass就是NSObject的类对象,所以会匹配成功。而其他三种匹配,则都会失败,各位同学可以去自己分析一下剩下三种。
题3
下面的代码会?Compile Error / Runtime Crash / NSLog…?
@interface NSObject (Sark)
+ (void)foo;
@end
@implementation NSObject (Sark)
- (void)foo {
NSLog(@"IMP: -[NSObject (Sark) foo]");
}
@end
// 测试代码
[NSObject foo];
[[NSObject new] foo];
答案:
全都正常输出,编译和运行都没有问题。
这道题和上一道题很相似,第二个调用肯定没有问题,第一个调用后会从元类中查找方法,然而方法并不在元类中,所以找元类的superclass。方法定义在是NSObject的Category,由于NSObject的对象模型比较特殊,元类的superclass是类对象,所以从类对象中找到了方法并调用。
题4
下面的代码会?Compile Error / Runtime Crash / NSLog…?
@interface Sark : NSObject
@property (nonatomic, copy) NSString *name;
@end
@implementation Sark
- (void)speak {
NSLog(@"my name's %@", self.name);
}
@end
// 测试代码
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
id cls = [Sark class];
void *obj = &cls;
[(__bridge id)obj speak];
}
@end
答案:
正常执行,不会导致Crash。
执行[Sark class]后获取到类对象,然后通过obj指针指向获取到的类对象首地址,这就构成了对象的基本结构,可以进行正常调用。
原题出处
http://blog.sunnyxx.com/2014/11/06/runtime-nuts/
题5
为什么MRC下没有weak?
其实MRC下并不是没有weak,在MRC环境下也可以通过Runtime源码调用weak源码的。weak源码定义在Private Headers私有文件夹下,需要引入#import "objc-internal.h"文件。
以以下ARC的源码为例,定义了一个TestObject类型的对象,并用一个weak指针指向已创建对象。
int main(int argc, const char * argv[]) {
@autoreleasepool {
TestObject *object = [[TestObject alloc] init];
__weak TestObject *newObject = object;
}
return 0;
}
这段代码会被编译器转移为下面代码,这段代码中的两个函数就是weak的实现函数,在MRC下也可以调用这两个函数。
objc_initWeak(&newObject, object);
objc_destroyWeak(&newObject);
题6
相同的一个类,创建不同的对象,怎样实现指定的某个对象在dealloc时打印一段文字?
这个问题最简单的方法就是在类的.h文件里,定义一个标记属性,如果属性被赋值为YES,则在dealloc中打印文字。但是,这种实现方式显然不是面试官想要的,会被直接pass~
可以参考KVO的实现方案,在运行时动态创建一个类,这个类是对象的子类,将新创建类的dealloc实现指向自定义的IMP,并在IMP中打印一段文字。将对象的isa设置为新创建的类,当执行dealloc方法时就会执行isa所指向的新类。
思考
小问题
什么叫做技术大牛,怎样就表示技术强?
我前段时间看过一句话,我感觉可以解释上面的问题:“市面上所有应用的功能,产品提出来我都能做”。
这句话并不够全面,应该不只是做出来,而是更好的做出来。这个好要从很多方面去评估,性能、可维护性、完成时间、产品效果等,如果这些都做的很好,那足以证明这个人技术很强大。
Runtime有什么用?
Runtime是比较偏底层的,但是研究这么深有什么用吗,有什么实际意义吗?
Runtime当然是由实际用处的,先不说整个OC都是通过Runtime实现的。例如现在需要实现消息转发的功能,这时候就需要用到Runtime,或者是拦截方法,也需要用到Method Swizzling,除了这些,还有更多的用法待我们去发掘。
不只是使用,其实最重要的是,通过Runtime了解一个语言的设计。Runtime中不只是各种函数调用,从整体来看,可以明白OC的对象模型是什么样的。
简书由于排版的问题,阅读体验并不好,布局、图片显示、代码等很多问题。所以建议到我Github上,下载Runtime PDF合集。把所有Runtime文章总计九篇,都写在这个PDF中,而且左侧有目录,方便阅读。
Runtime PDF
下载地址:https://github.com/DeveloperErenLiu/RuntimePDF
麻烦各位大佬点个赞,谢谢!
作者:刘小壮
链接:https://www.jianshu.com/p/ce97c66027cd
iOS开发整理发布,转载请联系作者授权
【点击成为源码大神】
最后
以上就是平淡期待为你收集整理的Runtime的应用的全部内容,希望文章能够帮你解决Runtime的应用所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复