iOS runtime
这里先给大家看一些小实例,在后面我会简单剖析一下原理。
一、交换两个方法的实现,拦截系统自带的方法调用功能。
首先#import<objc/runtime.h>
1.两个方法的交换:
@implementationViewController
- (void)viewDidLoad {
[super viewDidLoad];
[ViewController play];
[ViewController sing];
[self dance];
[self run];
}
+ (void)play{
NSLog(@"play-----%s",__func__);
}
+ (void)sing{
NSLog(@"sing-----%s",__func__);
}
- (void)dance{
NSLog(@"dance-----%s",__func__);
}
- (void)run{
NSLog(@"run-----%s",__func__);
}
- (IBAction)exchange:(id)sender {
Methodmethod1 =class_getClassMethod([ViewControllerclass],@selector(play));
Methodmethod2 =class_getClassMethod([ViewControllerclass],@selector(sing));
//交换两个类方法实现
method_exchangeImplementations(method1, method2);
[ViewControllerplay];
[ViewControllersing];
Methodmethod3 =class_getInstanceMethod([ViewControllerclass],@selector(dance));
Methodmethod4 =class_getInstanceMethod([ViewControllerclass],@selector(run));
//交换两个对象方法
method_exchangeImplementations(method3, method4);
[selfdance];
[selfrun];
}
打印结果如下:
运行结果2.拦截系统自带方法:其实就是把系统方法和自定义的方法进行交换
应用场景1:利用运行时交换系统的ImageNamed:方法。一些iOS7的老项目可能没有考虑到iOS8的适配,当项目上有几百处地方用到ImageNamed:方法时,如果选用最直接的办法,在该方法之前进行判断,如果为ios8就显示另外一张图片,这样的工作量明显会很大,所以可以用运行时的方法来解决。新建一个项目,准备两张图片,一张图片名为pic为ios7而准备,一张pic_ios8为ios8而准备。在Main.storyboard拖进一个imageview。
原始- (void)viewDidLoad {
self.pic.image=[UIImageimageNamed:@"pic"];
}
这时候写一个UIImage的分类,来进行方法的交换
在load方法里面实现方法的交换,该方法只会被加载一次
@interfaceUIImage (Exchange)
+ (UIImage*)hjy_imageNamed:(NSString*)name;
@end
交换方法后的图@implementationUIImage (Exchange)
+(void)load{
//运行时交换两个方法的实现
Methodm1 =class_getClassMethod([UIImageclass],@selector(imageNamed:));
Methodm2 =class_getClassMethod([UIImageclass],@selector(hjy_imageNamed:));
method_exchangeImplementations(m1, m2);
}
+ (UIImage*)hjy_imageNamed:(NSString*)name{
NSString*imgN = name;
doubleversion = [[UIDevicecurrentDevice].systemVersiondoubleValue];
NSLog(@"%f",version);
if(version >=8.0) {
imgN = [NSStringstringWithFormat:@"%@_ios8",name];
}
return[selfhjy_imageNamed:imgN];
}
@end
二.在分类中设置属性
分类中是无法设置属性的,如果在分类的声明中写@property 只能为其生成get 和 set 方法的声明,但无法生成成员变量,就是虽然点语法能调用出来,但程序执行后会crash。全局变量程序整个执行过程中内存中只有一份,我们创建多个对象修改其属性值都会修改同一个变量,这样就无法保证像属性一样每个对象都拥有其自己的属性值。这时我们就需要借助runtime为分类增加属性的功能了。关联对象是Runtime的一个特性,Runtime中定义了三个允许你讲将任何键值在Runtime关联到对象上的函数:
objc_setAssociatedObject:设置关联对象:
void objc_setAssociatedObject(id object,const void*key, id value, objc_AssociationPolicy policy)
object:需要设置关联对象的对象
key:关联对象的key,推荐使用selector,一个属性对应一个Key,将来可以通过key取出这个存储的值,key 可以是任何类型:double、int 等,建议用char 可以节省字节.
value:关联对象的值,id类型,就是给属性设定的值。
policy:关联对象的策略,属性可以根据定义在枚举类型objc_AssociationPolicy上的行为被关联在对象上。类似于@property创建时设置的关键字。存储策略 (assign 、copy 、 retain就是strong)
objc_getAssociatedObject获取关联对象
id objc_getAssociatedObject(id object,const void*key)
objc_removeAssociatedObjects
移除某个对象的所有关联对象,此方法不常用。
通过提供的方法我们就可以对存在的类在拓展中添加自定义的属性了。
实例:
@interfaceUIImage(downLoadURL)
@property(nonatomic , strong)NSString*downLoadURL;
@end
@implementationUIImage(downLoadURL)
-(void)setDownLoadURL:(NSString*)downLoadURL{
objc_setAssociatedObject(self,@selector(downLoadURL), downLoadURL,OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
-(NSString*)downLoadURL
{
return objc_getAssociatedObject(self,@selector(downLoadURL));
}
@end
有名的第三方库LTNavigationBar在实现滑动界面时导航栏显隐功能时也使用了Associated Objects,为UINavigation类添加了一个UIView类的属性overlay,使功能实现起来更加简便。
#import"UINavigationBar+Awesome.h"
#import
#define SYSTEM_VERSION_LESS_THAN(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedAscending)
@implementationUINavigationBar (Awesome)
staticcharoverlayKey;
- (UIView*)overlay
{
returnobjc_getAssociatedObject(self, &overlayKey);
}
- (void)setOverlay:(UIView*)overlay
{
objc_setAssociatedObject(self, &overlayKey, overlay,OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (void)lt_setBackgroundColor:(UIColor*)backgroundColor
{
if(!self.overlay) {
[selfsetBackgroundImage:[UIImagenew]forBarMetrics:UIBarMetricsDefault];
self.overlay= [[UIViewalloc]initWithFrame:CGRectMake(0,0,CGRectGetWidth(self.bounds),CGRectGetHeight(self.bounds) +20)];
self.overlay.userInteractionEnabled=NO;
self.overlay.autoresizingMask=UIViewAutoresizingFlexibleWidth;// Should not set `UIViewAutoresizingFlexibleHeight`
[[self.subviewsfirstObject]insertSubview:self.overlayatIndex:0];
}
self.overlay.backgroundColor= backgroundColor;
}
- (void)lt_setTranslationY:(CGFloat)translationY
{
self.transform=CGAffineTransformMakeTranslation(0, translationY);
}
- (void)lt_setElementsAlpha:(CGFloat)alpha
{
[[selfvalueForKey:@"_leftViews"]enumerateObjectsUsingBlock:^(UIView*view,NSUIntegeri,BOOL*stop) {
view.alpha= alpha;
}];
[[selfvalueForKey:@"_rightViews"]enumerateObjectsUsingBlock:^(UIView*view,NSUIntegeri,BOOL*stop) {
view.alpha= alpha;
}];
UIView*titleView = [selfvalueForKey:@"_titleView"];
titleView.alpha= alpha;
//when viewController first load, the titleView maybe nil
[[selfsubviews]enumerateObjectsUsingBlock:^(UIView*obj,NSUIntegeridx,BOOL*stop) {
if([objisKindOfClass:NSClassFromString(@"UINavigationItemView")]) {
obj.alpha= alpha;
}
if([objisKindOfClass:NSClassFromString(@"_UINavigationBarBackIndicatorView")]) {
obj.alpha= alpha;
}
}];
}
- (void)lt_reset
{
[selfsetBackgroundImage:nilforBarMetrics:UIBarMetricsDefault];
[self.overlayremoveFromSuperview];
self.overlay=nil;
}
@end
三.获得一个类的所有成员变量
未完待续。。。