如有不同看法,欢迎讨论。
这里主要是讲Runtime在项目中的一些常用方法。其实讲这个的文章已经很多了,这里之所以记录一下,是方便自己以后查找。
一、方法交换
在开发中常遇到的一个场景就是当导入某个三方包或系统的原有方法不能满足需求的时候,或当改动量过大的时候,可以考虑将原有方法替换重写。这里就应用到了Runtime将方法替换。
在这里用了一个例子,替换并重写系统方法:
1.创建category。
2.在load方法中通过Runtime取出要替换的方法并进行替换。
category中的方法实现
+ (void)load{
Method method1 = class_getClassMethod(self, @selector(imageNamed:));
Method method2 = class_getClassMethod(self, @selector(imageNamed_CY:));
NSLog(@"iiiiiiii");
method_exchangeImplementations(method1, method2);
// objc_class
}
+ (UIImage *)imageNamed_CY:(NSString *)imageName{
UIImage *img = [UIImage imageNamed_CY:imageName];
if(img){
NSLog(@"图片存在");
}else{
NSLog(@"图片不存在");
}
return img;
}
在需要调用的地方调用方法
UIImageView *image = [[UIImageView alloc]initWithImage:[UIImage imageNamed:@"WechatIMG16314.jpeg"]];
打印结果
![](https://img.haomeiwen.com/i3510986/32b95ec75dd6e853.jpeg)
这里要多提一下load方法,在一个程序开始运行之前(在main函数开始执行之前),在库开始被程序加载,load函数就会开始被执行。所以在category中可以在load方法里置换方法。
二、动态添加属性
面试中一个常见问题就是category可不可以添加属性。这里考察的是在category中能否动态为我们生成set/get方法。当我们直接在category中声明属性或成员变量时,系统是不会主动为我们添加set/get方法。这就也就是为什么当直接category中声明属性或成员变量时,会报错:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[UIImage setArr:]: unrecognized selector sent to instance 0x6000000b3e00'
上边的报错就是在UIImage的category中直接声明了一个属性arr,并赋值。
但实际上,通过Runtime是可以在category中自己添加set/get方法以达到动态的添加属性的目的。实现方式:
- (void)setArr:(NSArray *)arr{
// 参数1给哪个对象添加属性
// 参数2属性名称
// 参数3属性对应的值
// 安全策略,即我们常用的strong等等的宏定义
objc_setAssociatedObject(self, @"arr", arr, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (NSArray *)arr{
return objc_getAssociatedObject(self, @"arr");
}
点进OBJC_ASSOCIATION_RETAIN_NONATOMIC可以看见
/**
* Policies related to associative references.
* These are options to objc_setAssociatedObject()
*/
typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
OBJC_ASSOCIATION_ASSIGN = 0, /**< Specifies a weak reference to the associated object. */
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, /**< Specifies a strong reference to the associated object.
* The association is not made atomically. */
OBJC_ASSOCIATION_COPY_NONATOMIC = 3, /**< Specifies that the associated object is copied.
* The association is not made atomically. */
OBJC_ASSOCIATION_RETAIN = 01401, /**< Specifies a strong reference to the associated object.
* The association is made atomically. */
OBJC_ASSOCIATION_COPY = 01403 /**< Specifies that the associated object is copied.
* The association is made atomically. */
};
添加不同的属性时选择不同的Policies。
三、实现NSCoding的自动归档和解档
像平时的归档操作,如果一个对象有多个属性时,我们需要对每一个对象进行encodeObject 和 decodeObjectForKey操作,即
@interface MyClass : NSObject<NSCoding>
- (void)showAge;
- (void)showName:(NSString *)name;
@property (nonatomic,copy) NSString *pro1;
@property (nonatomic,copy) NSString *pro2;
@property (nonatomic,copy) NSString *pro3;
@end
- (void)encodeWithCoder:(NSCoder *)aCoder
{
[aCoder encodeObject:_pro1 forKey:@"pro1"];
[aCoder encodeObject:_pro2 forKey:@"pro2"];
[aCoder encodeObject:_pro3 forKey:@"pro3"];
}
- (id)initWithCoder:(NSCoder *)aDecoder
{
if (self = [super init]) {
self.pro1 = [aDecoder decodeObjectForKey:@"pro1"];
self.pro2 = [aDecoder decodeObjectForKey:@"pro2"];
self.pro3 = [aDecoder decodeObjectForKey:@"pro3"];
}
return self;
}
如果属性过多的时候,这么写就很蛋疼。应用Runtime,我们可以用更简洁的代码实现归档操作。
- (void)encodeWithCoder:(NSCoder *)encoder
{
unsigned int count = 0;
Ivar *ivars = class_copyIvarList([MyClass class], &count);
for (int i = 0; i<count; i++) {
// 取出i位置对应的成员变量
Ivar ivar = ivars[i];
// 查看成员变量
const char *name = ivar_getName(ivar);
// 归档
NSString *key = [NSString stringWithUTF8String:name];
id value = [self valueForKey:key];
[encoder encodeObject:value forKey:key];
}
free(ivars);
}
- (id)initWithCoder:(NSCoder *)aDecoder
{
if (self = [super init]) {
unsigned int count = 0;
Ivar *ivars = class_copyIvarList([MyClass class], &count);
for (int i = 0; i<count; i++) {
// 取出i位置对应的成员变量
Ivar ivar = ivars[i];
// 查看成员变量
const char *name = ivar_getName(ivar);
// 归档
NSString *key = [NSString stringWithUTF8String:name];
id value = [aDecoder decodeObjectForKey:key];
// 设置到成员变量身上
[self setValue:value forKey:key];
}
free(ivars);
}
return self;
}
这样和之前的办法比就简洁且通用。
四、总结
实际上除了以上几种之外,Runtime给我们提供了丰富的接口,比如获取协议列表,获取类方法,实例方法,替换原方法,交换方法,等等等等。这种东西是可以轻易查到的,在此就不列举了。总体来说,Runtime简单又强大,是OC动态语言的一个展示和应用,在很多情况下,通过Runtime可以减少不必要的工作量。大体Runtime就先到这了,如果以后有不同理解,会再来补。
网友评论