分类(Category):
在不子类化的情况下,为已经存在的类增加功能;
分类中的方法会成为类的组成部分,并且会被子类继承;
扩展:
是一种匿名分类,可以声明实例变量、属性和方法,我们常见的.m文件中@interface的一段就是一个扩展;
通常用在类实现的.m文件中,声明私有的实例变量、属性和方法;
扩展和分类的区别在于 他可以声明实例变量和属性;
在runtime中Category定义的是如下的结构体:
Category | typedef struct objc_category * Categoy;
struct objc_category{ char * category_name; char * class_name; struct objc_method_list * instance_methods; struct objc_method_list * class_methods; struct objc_protocol_list * protocols; } |
我们注意到Category是一个执行该结构体的指针,和我们之前见到的Object、Class类似;
该结构体中包含了 对象方法列表,类方法列表,协议列表,相应的Category支持添加对象方法、类方法和协议,但不能保存成员变量;
如果想在Category中添加可用的属性可以使用 关联对象:
在分类中加入属性,虽然运行没有问题,但是不会生成对应的成员变量,getter和setter也没有;调用的时候也会报错;
使用关联对象在分类的实现文件.m中,使用objc_setAssociatedObject和objc_getAssociatedObject即可实现;
而且该属性还可以被子类调用;关联对象会在对象释放时,由Runtime查找并释放,不需要手动释放;
我们使用了runtime通过关联对象在分类中增加了属性,接下来看看runtime一些其他的操作;
Method Swizzling方法交换:
这个可以在程序运行时,修改一个方法的实现;下面示例我们要修改doSomethingSwizzled方法的实现;
1)动态添加方法-替换:
2)方法存在-直接交换:
为了保证方法只会被交换一次,可以把这个放在只会调用一次的方法中,然后再放到dispatch_once里;
(RSSwizzle可以使这个过程更简单)
打印类的属性、方法、成员变量和遵循的协议:
【Code-MyObject】(代码包含了方法替换的部分,关注最下边的实现就好)
#import <Foundation/Foundation.h>
@protocol MyObjectDelegate <NSObject>
-(void)MyObjectDelegateMethod;
@end
@interface MyObject : NSObject
@property (nonatomic , copy) NSString * pro1;
@property (nonatomic , copy) NSString * pro2;
@property (nonatomic , copy) NSString * pro3;
-(void)doSomethingOriginal;
-(void)doSomethingSwizzled;
-(void)method_swizzling;
-(void)printPropertyMethodIvarList;
@end
#import "MyObject.h"
#import "objc/runtime.h"
@interface MyObject()<MyObjectDelegate>{
int _ivar1;
int _ivar2;
int _ivar3;
}
@end
@implementation MyObject
-(void)MyObjectDelegateMethod{
}
-(void)doGreeting{
NSLog(@"%@",@"Hello");
}
-(void)doSomethingOriginal{
NSLog(@"%@",@"doSomethingOriginal");
}
-(void)doSomethingSwizzled{
NSLog(@"%@",@"doSomethingSwizzled");
}
-(void)method_swizzling{
Method greeting = class_getInstanceMethod([self class], @selector(doGreeting));
Method original = class_getInstanceMethod([self class], @selector(doSomethingOriginal));
Method swizzled = class_getInstanceMethod([self class], @selector(doSomethingSwizzled));
if (!class_addMethod([self class], @selector(doSomethingOriginal), method_getImplementation(greeting), method_getTypeEncoding(greeting))) {
NSLog(@"由于方法已存在 添加失败 直接交换(这样做是为了避免父类方法与子类方法交换带来的问题)");
method_exchangeImplementations(original, swizzled);
}else{
NSLog(@"方法不存在 添加方法成功 此添加方法的实现来自greeting 使用添加的方法 替换doSomethingSwizzled方法的实现");
original = class_getInstanceMethod([self class], @selector(doSomethingOriginal));
class_replaceMethod([self class], @selector(doSomethingSwizzled), method_getImplementation(original), method_getTypeEncoding(original));
}
}
-(void)printPropertyMethodIvarList{
NSLog(@"_________________打印属性_____________________");
{
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(@"%@",[NSString stringWithUTF8String:propertyName]);
}
free(propertyList);
}
NSLog(@"_________________打印方法_____________________");
{
unsigned int count;
Method * methodList = class_copyMethodList([self class], &count);
for (unsigned int i = 0; i < count; i++) {
Method method = (methodList[i]);
NSLog(@"%@",NSStringFromSelector(method_getName(method)));
}
free(methodList);
}
NSLog(@"_________________打印变量_____________________");
{
unsigned int count;
Ivar * ivarList = class_copyIvarList([self class], &count);
for (unsigned int i = 0; i < count; i++) {
Ivar ivar = (ivarList[i]);
const char * ivarName = ivar_getName(ivar);
NSLog(@"%@",[NSString stringWithUTF8String:ivarName]);
}
free(ivarList);
}
NSLog(@"_________________打印协议_____________________");
{
unsigned int count;
__unsafe_unretained Protocol ** protocolList = class_copyProtocolList([self class], &count);
for (unsigned int i = 0; i < count; i++) {
Protocol * protocol = (protocolList[i]);
const char * protocolName = protocol_getName(protocol);
NSLog(@"%@",[NSString stringWithUTF8String:protocolName]);
}
free(protocolList);
}
}
@end
log:
2019-01-29 11:21:37.617924+0800 test[94980:4682027] _________________打印属性_____________________
2019-01-29 11:21:37.618056+0800 test[94980:4682027] testP
2019-01-29 11:21:37.618138+0800 test[94980:4682027] pro1
2019-01-29 11:21:37.618224+0800 test[94980:4682027] pro2
2019-01-29 11:21:37.618293+0800 test[94980:4682027] pro3
2019-01-29 11:21:37.618381+0800 test[94980:4682027] hash
2019-01-29 11:21:37.618475+0800 test[94980:4682027] superclass
2019-01-29 11:21:37.618544+0800 test[94980:4682027] description
2019-01-29 11:21:37.618613+0800 test[94980:4682027] debugDescription
2019-01-29 11:21:37.618693+0800 test[94980:4682027] _________________打印方法_____________________
2019-01-29 11:21:37.618774+0800 test[94980:4682027] printPropertyMethodIvarList
2019-01-29 11:21:37.618856+0800 test[94980:4682027] setTestP:
2019-01-29 11:21:37.618925+0800 test[94980:4682027] testP
2019-01-29 11:21:37.619000+0800 test[94980:4682027] doGreeting
2019-01-29 11:21:37.619076+0800 test[94980:4682027] doSomethingOriginal
2019-01-29 11:21:37.619191+0800 test[94980:4682027] doSomethingSwizzled
2019-01-29 11:21:37.619361+0800 test[94980:4682027] MyObjectDelegateMethod
2019-01-29 11:21:37.619463+0800 test[94980:4682027] method_swizzling
2019-01-29 11:21:37.619624+0800 test[94980:4682027] pro1
2019-01-29 11:21:37.619809+0800 test[94980:4682027] setPro1:
2019-01-29 11:21:37.621500+0800 test[94980:4682027] pro2
2019-01-29 11:21:37.621576+0800 test[94980:4682027] setPro2:
2019-01-29 11:21:37.621633+0800 test[94980:4682027] pro3
2019-01-29 11:21:37.621710+0800 test[94980:4682027] setPro3:
2019-01-29 11:21:37.621784+0800 test[94980:4682027] .cxx_destruct
2019-01-29 11:21:37.621862+0800 test[94980:4682027] _________________打印变量_____________________
2019-01-29 11:21:37.621966+0800 test[94980:4682027] _ivar1
2019-01-29 11:21:37.622067+0800 test[94980:4682027] _ivar2
2019-01-29 11:21:37.622127+0800 test[94980:4682027] _ivar3
2019-01-29 11:21:37.622201+0800 test[94980:4682027] _pro1
2019-01-29 11:21:37.622273+0800 test[94980:4682027] _pro2
2019-01-29 11:21:37.622338+0800 test[94980:4682027] _pro3
2019-01-29 11:21:37.622499+0800 test[94980:4682027] _________________打印协议_____________________
2019-01-29 11:21:37.622682+0800 test[94980:4682027] MyObjectDelegate
Runtime用途-AOP:
可以提供对面向切面编程的支持(AOP);
比如我们要统计所有的Btn点击事件,表示出当前点击按钮的currentTitle;
我们新建一个UIButton+aop的分类,来实现下,这样做技能对业务逻辑分离,也能降低耦合:
【Code-UIButton+aop】
#import "UIButton+aop.h"
#import "objc/runtime.h"
@implementation UIButton (aop)
+(void)load{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[[self class] method_swizzling];
});
}
//-(void)sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event{
// [super sendAction:action to:target forEvent:event];
//}
-(void)sendForAopAction:(SEL)action to:(id)target forEvent:(UIEvent *)event{
NSString * title = [self currentTitle];
NSLog(@"%@",title);
[super sendAction:action to:target forEvent:event];
}
+(void)method_swizzling{
Method tmp = class_getInstanceMethod([self class], @selector(sendForAopAction:to:forEvent:));
Method original = class_getInstanceMethod([self class], @selector(sendAction:to:forEvent:));
Method swizzled = class_getInstanceMethod([self class], @selector(sendForAopAction:to:forEvent:));
if (!class_addMethod([self class], @selector(sendAction:to:forEvent:), method_getImplementation(tmp), method_getTypeEncoding(tmp))) {
NSLog(@"由于方法已存在 添加失败 直接交换(这样做是为了避免父类方法与子类方法交换带来的问题)");
method_exchangeImplementations(original, swizzled);
}else{
NSLog(@"方法不存在 添加方法成功 此添加方法的实现来自sendForAopAction:to:forEvent: 使用添加的方法 替换sendAction:to:forEvent:方法的实现");
original = class_getInstanceMethod([self class], @selector(sendAction:to:forEvent:));
class_replaceMethod([self class], @selector(sendForAopAction:to:forEvent:), method_getImplementation(original), method_getTypeEncoding(original));
}
}
@end
点击按钮log如下(使用了两种方式):