内容简介:下面是一些
__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)) @interface TestObject : NSObject @property (nonatomic, strong) NSObject *object; @property (nonatomic, assign) NSInteger age; @end @interface Child : TestObject @end 错误信息: 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[]) { @autoreleasepool { 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[]) { @autoreleasepool { 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; #define NS_FORMAT_FUNCTION(F,A) __attribute__((format(__NSString__, F, A))) // 必须调用父类的方法 #define NS_REQUIRES_SUPER __attribute__((objc_requires_super)) // 指定初始化方法,必须直接或间接调用修饰的方法 #define NS_DESIGNATED_INITIALIZER __attribute__((objc_designated_initializer)) 复制代码
ORM
对象关系映射 (Object Relational Mapping)
,简称 ORM
,用于面向对象语言中不同系统数据之间的转换。 可以通过对象关系映射来实现 JSON
转模型,使用比较多的是 Mantle
、 MJExtension
、 YYKit
、 JSONModel
等框架,这些框架在进行转换的时候,都是使用 Runtime
的方式实现的。
Mantle
使用和 MJExtension
有些类似,只不过 MJExtension
使用起来更加方便。 Mantle
在使用时主要是通过继承的方式处理,而 MJExtension
是通过 Category
处理,代码依赖性更小,无侵入性。
性能评测
这些第三方中 Mantle
功能最强大,但是太臃肿,使用起来性能比其他第三方都差一些。 JSONModel
、 MJExtension
这些第三方几乎都在一个水平级, YYKit
相对来说性能可以比肩手写赋值代码,性价比最高。
对于模型转换需求不是太大的工程来说,尽量用 YYKit
来进行转换性能会更好一些。功能可能略逊于 MJExtension
,我个人还是比较习惯用 MJExtension
。
实现思路
也可以自己实现模型转换的逻辑,以字典转模型为例,大体逻辑如下:
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
协议。这是开源地址: 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] performSelector:@selector(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
指针指向获取到的类对象首地址,这就构成了对象的基本结构,可以进行正常调用。
原题出处
题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 麻烦各位大佬点个赞,谢谢!:grin:
以上所述就是小编给大家介绍的《探秘Runtime - Runtime的应用》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Fluent Python
Luciano Ramalho / O'Reilly Media / 2015-8-20 / USD 39.99
Learn how to write idiomatic, effective Python code by leveraging its best features. Python's simplicity quickly lets you become productive with it, but this often means you aren’t using everything th......一起来看看 《Fluent Python》 这本书的介绍吧!