内容简介:下面是一些
__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的应用》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
服务设计与创新实践
宝莱恩 (Andy Polaine)、乐维亚 (Lavrans Lovlie)、里森 (Ben Reason) / 王国胜、张盈盈、付美平、赵芳 / 清华大学出版社 / 2015-6-1 / CNY 69.00
产品经济的时代渐行渐远,在以服务为主导的新经济时代,在强调体验和价值的互联网时代,如何才能做到提前想用户之所想?如何比用户想得更周到?如何设计可用、好用和体贴的服务?这些都可以从本书中找到答案。本书撷取以保险业为代表的金融服务、医疗服务、租车及其他种种服务案例,从概念到实践,有理有据地阐述了如何对服务进行重新设计?如何将用户体验和价值提前与产品设计融合在一起? 《服务设计与创新实践》适合产品......一起来看看 《服务设计与创新实践》 这本书的介绍吧!