• 设为首页
  • 点击收藏
  • 手机版
    手机扫一扫访问
    迪恩网络手机版
  • 关注官方公众号
    微信扫一扫关注
    公众号

Objective-C:运行时runtime

原作者: [db:作者] 来自: [db:来源] 收藏 邀请
1.是否可以把比较耗时的操作放在通知中心中?
 

通知在哪一个线程发的,那么对通知事件的处理就在同一个线程中进行;

如果在异步线程发的通知,那么可以执行比较耗时的操作;
如果在主线程发的通知,那么就不可以执行比较耗时的操作。
 
2.Foundation对象和CoreFoundation对象有什么区别?
Foundation对象时OC的;
CoreFoundation对象是C的;
Foundation对象和CoreFoundation对象是可以互相转换的,数据类型之间的转换
ARC:__bridge_retaind、__bridge_transfer、CFBridgingRetain、CFBridgingRelease
非ARC : __bridge
 
 
3.什么是runtime?

1> runtime是一套底层的C语言API(包含很多强大实用的C语言数据类型、C语言函数)

2> 实际上,平时我们编写的OC代码,底层都是基于runtime实现的

* 也就是说,平时我们编写的OC代码,最终都是转成了底层的runtime代码(C语言代码)

 

runtime有啥用?

1> 能动态产生一个类、一个成员变量、一个方法

2> 能动态修改一个类、一个成员变量、一个方法

3> 能动态删除一个类、一个成员变量、一个方法

 

常见的函数、头文件

#import <objc/runtime.h> : 成员变量、类、方法

Ivar * class_copyIvarList : 获得某个类内部的所有成员变量

Method * class_copyMethodList : 获得某个类内部的所有方法

Method class_getInstanceMethod : 获得某个实例方法(对象方法,减号-开头)

Method class_getClassMethod : 获得某个类方法(加号+开头)

method_exchangeImplementations : 交换2个方法的具体实现

 

#import <objc/message.h> : 消息机制

objc_msgSend(….)

 

什么是iOS Swizzle? 利用运行时函数交换2个方法的实现

 

具体的距离如下:

1、测试运行时的消息机制:要在测试类头文件中导入<objc/message.h>

Person类:

#import <UIKit/UIKit.h>

@interface Person : NSObject //<NSCoding>
@property (copy,nonatomic)NSString *name;
@property (assign,nonatomic)NSInteger age;
@property (assign,nonatomic)CGFloat height;
-(void)run;
@end


#import "Person.h"
#import <objc/runtime.h>

@implementation Person
-(void)run
{
    NSLog(@"run-----");
}

@end

测试类:

//测试运行时的消息机制
-(void)testMessage
{
    //<objc/message.h>
    Person *p = [[Person alloc]init];
    
    p.age = 20;
    objc_msgSend(p, @selector(setAge:),20);  // <====> [p setAge:20]
    NSLog(@"%zi",p.age);
    
    objc_msgSend(p, @selector(age)); //<======> [p age]
    
    [p run];
    objc_msgSend(p, @selector(run));  // <====> [p run]
}

测试结果如下:

2016-01-29 13:16:27.397 Runtime-运行时[2881:130121] 20
2016-01-29 13:16:27.398 Runtime-运行时[2881:130121] eat------
2016-01-29 13:16:27.398 Runtime-运行时[2881:130121] eat------
2016-01-29 13:16:27.398 Runtime-运行时[2881:130121] eat------
2016-01-29 13:16:27.399 Runtime-运行时[2881:130121] eat------

 2、获取运行时的成员属性

//获取运行时的的成员属性
-(void)testRuntimeIvar
{
    //<objc/runtime.h>
    //Ivar:成员变量
    unsigned int count = 0;
    Ivar *ivars =  class_copyIvarList([Person class], &count);
    NSLog(@"%d",count);  //取得成员变量的数量
    for (int i=0; i<count; i++)
    {
        //取得i位置的成员变量
        Ivar ivar = ivars[i];
        
        //const char *ivar_getName(Ivar v) 获取属性名称
        const char *ivarName = ivar_getName(ivar);
        
        //const char *ivar_getTypeEncoding(Ivar v)  获取成员变量的类型
        const char *ivarType = ivar_getTypeEncoding(ivar);
        
        NSLog(@"%d %s %s",i,ivarName,ivarType);
    }
}

测试结果如下:

2016-01-29 13:19:01.013 Runtime-运行时[2921:134136] 3
2016-01-29 13:19:01.014 Runtime-运行时[2921:134136] 0 _name @"NSString"
2016-01-29 13:19:01.014 Runtime-运行时[2921:134136] 1 _age q
2016-01-29 13:19:01.014 Runtime-运行时[2921:134136] 2 _height d

扩展:利用这个上面获取属性的这个方法,可以很轻松的实现对大量的类的属性进行归档和解归档

//归档
-(void)encodeWithCoder:(NSCoder *)encoder
{
    unsigned int count = 0;
    Ivar *ivars =  class_copyIvarList([Person class], &count);
    for (int i=0; i<count; i++)
    {
        Ivar ivar = ivars[i];
        const char *ivarName = ivar_getName(ivar);
        NSString *key = [NSString stringWithUTF8String:ivarName];
        
        [encoder encodeObject:[self valueForKey:key] forKey:key];
    }
}

//解归档
- (id)initWithCoder:(NSCoder *)decoder
{
    self = [super init];
    if (self)
    {
        unsigned int count = 0;
        Ivar *ivars =  class_copyIvarList([Person class], &count);
        for (int i=0; i<count; i++)
        {
            Ivar ivar = ivars[i];
            const char *ivarName = ivar_getName(ivar);
            NSString *key = [NSString stringWithUTF8String:ivarName];
            key = [decoder decodeObjectForKey:key];
        }
    }
    return self;
}

3、获得运行时的成员方法

//获取运行时的的成员方法
-(void)testRuntimeMethod
{
    //<objc/runtime.h>
    //Method: 成员方法
    unsigned int count = 0;
    Method *methods = class_copyMethodList([Person class], &count);
    NSLog(@"%d",count);  //取得成员方法的数量
    for (int i=0; i<count; i++)
    {
        //取得i位置的成员方法
        Method method = methods[i];
        
        //SEL method_getName(Method m) 获取方法名称
        SEL sel = method_getName(method);
        const char *selName =  sel_getName(sel);
        NSLog(@"%s",selName);
        
        //const char *method_getTypeEncoding(Method m)
        //Returns a string describing a method's parameter and return types
        const char *methodType = method_getTypeEncoding(method);
        NSLog(@"%s",methodType);
        
        //char *method_copyReturnType(Method m) 获取成员方法的返回值类型
        char *method_return_type = method_copyReturnType(method);
        NSLog(@"%s",method_return_type);
        
        //unsigned int method_getNumberOfArguments(Method m) 获取成员方法的参数个数
        count = method_getNumberOfArguments(method);
        NSLog(@"%zi",count);
    }
}

测试结果如下:

 

2016-01-29 13:21:33.081 Runtime-运行时[2975:138791] 10
2016-01-29 13:21:33.081 Runtime-运行时[2975:138791] setAge:
2016-01-29 13:21:33.081 Runtime-运行时[2975:138791] [email protected]0:8q16
2016-01-29 13:21:33.082 Runtime-运行时[2975:138791] v
2016-01-29 13:21:33.082 Runtime-运行时[2975:138791] 3
2016-01-29 13:21:33.082 Runtime-运行时[2975:138791] age
2016-01-29 13:21:33.082 Runtime-运行时[2975:138791] [email protected]0:8
2016-01-29 13:21:33.082 Runtime-运行时[2975:138791] q
2016-01-29 13:21:33.082 Runtime-运行时[2975:138791] 2

4、获得运行时的协议

-(void)testRuntimeProtocol
{
    //<objc/runtime.h>
    //Protocol:协议
    //unsigned int count = 0;
    //Protocol * __unsafe_unretained *protocol =  class_copyProtocolList([Person class], &count);
    //.........................
}

5、在运行时中动态添加方法、属性、协议等

-(void)testRuntimeAdd
{
    //动态添加方法
    //BOOL class_addMethod(Class cls, SEL name, IMP imp,const char *types)
    
    //动态替换方法
    //IMP class_replaceMethod(Class cls, SEL name, IMP imp,const char *types)
    
    //动态添加成员变量
    //BOOL class_addIvar(Class cls, const char *name, size_t size,uint8_t alignment, const char *types)
    
    //动态添加协议
    //BOOL class_addProtocol(Class cls, Protocol *protocol)
    
    //动态添加属性
    //BOOL class_addProperty(Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount)
    
    //动态替换属性
    //void class_replaceProperty(Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount)
    
    
    //........................
}

6、在运行时中动态的交换两个实现方法

-(void)testRuntimeChangedMethod
{
    //什么是iOS Swizzle? 利用运行时函数交换2个方法的实现
    
    //class_getClassMethod(__unsafe_unretained Class cls, SEL name)    //类方法
    //class_getInstanceMethod(__unsafe_unretained Class cls, SEL name) //实例方法
    //method_exchangeImplementations(Method m1, Method m2)             //动态交换实现方法
}

 

下面我就来验证动态的交换两个实现方法:

例子一:动态交换类方法和实例方法的实现方法

<1>在Person类中声明和定义一个run方法

#import <UIKit/UIKit.h>

@interface Person : NSObject 
-(void)run;
@end



#import "Person.h"
#import <objc/runtime.h>

@implementation Person
-(void)run
{
    NSLog(@"run-----");
}

<2>在Person类扩展中定义eat方法和加载内存时交换这两个方法的实现

#import "Person.h"
#import <objc/runtime.h>

@implementation Person (Extension)

//程序一运行就会加载
+(void)load
{
    //获取实例方法和类方法
    Method classMethod = class_getClassMethod([Person class], @selector(eat));
    Method instanceMethod = class_getInstanceMethod([Person class], @selector(run));
    
    //交换实例实现方法和类实现方法
    method_exchangeImplementations(classMethod, instanceMethod);
}

+(void)eat
{
    NSLog(@"eat------");
    Person *p = [[Person alloc]init];   //死循环
    [p run];
}
@end

<3>测试如下:

    Person *p = [[Person alloc]init];
    [p run];

当Person类对象调用run方法时出现死循环:我只给出一部分结果

2016-01-29 13:38:22.268 Runtime-运行时[3200:161656] eat------
2016-01-29 13:38:22.268 Runtime-运行时[3200:161656] eat------
2016-01-29 13:38:22.268 Runtime-运行时[3200:161656] eat------
2016-01-29 13:38:22.268 Runtime-运行时[3200:161656] eat------
2016-01-29 13:38:22.268 Runtime-运行时[3200:161656] eat------
2016-01-29 13:38:22.268 Runtime-运行时[3200:161656] eat------
2016-01-29 13:38:22.269 Runtime-运行时[3200:161656] eat------
2016-01-29 13:38:22.269 Runtime-运行时[3200:161656] eat------

解释原因:因为Person类一存在的时候,就会调用+(void)load方法,将run实例方法和eat类方法进行了交换,即实际上[p run]方法被没有执行,而是执行了[Person eat]方法,在[Person eat]中第一次输出NSLog(@"eat-------")后,紧接着又创建了一个新的Person对象,这个对象也调用了[p run]方法,就又调用了[Person eat]类方法,又一次输出

NSLog(@"eat-------"),一直如此循环下去...........

 

例子二:给OC内置的方法做手脚,用自定义的方法交换实现方法(以数组和可变数组为例)

<1>给NSObject、NSArray、NSMutableArray都创建扩展类。

在NSObejct扩展中创建两个类方法,用来交换实例方法和类方法

在NSArray扩展中创建两个方法,一个+(void)load方法实现交换,另一个是自定义的用来覆盖OC内置的方法ObjectAtIndex:

和NSMutableArray创建三个方法,一个+(void)load方法实现交换,一个是自定义的用来覆盖OC内置的方法ObjectAtIndex:,还有一个用来覆盖OC内置的方法addObject:

如下:

#import <UIKit/UIKit.h>
#import <objc/runtime.h>


@implementation NSObject(Extension)
+(void)swizlleClassMethod:(Class)class originMethod:(SEL)originMethod otherMethod:(SEL)otherMethod
{
    //获取实例方法
    Method classMethod1 = class_getClassMethod(class, originMethod);
    Method classMethod2 = class_getClassMethod(class, otherMethod);
    
    //交换实例实现方法
    method_exchangeImplementations(classMethod1, classMethod2);
}

+(void)swizlleInstanceMethod:(Class)class originMethod:(SEL)originMethod otherMethod:(SEL)otherMethod
{
    //获取实例方法
    Method instanceMethod1 = class_getInstanceMethod(class, originMethod);
    Method instanceMethod2 = class_getInstanceMethod(class, otherMethod);
    
    //交换实例实现方法
    method_exchangeImplementations(instanceMethod1, instanceMethod2);
}
@end


@implementation NSArray(Extension)

//程序一运行就会加载
+(void)load
{
    [self swizlleInstanceMethod:NSClassFromString(@"__NSArrayI") originMethod:@selector(objectAtIndex:) otherMethod:@selector(Test_objectAtIndex:)];
}
-(id)Test_objectAtIndex:(NSUInteger)index
{
    if (index < self.count)
    {
        //如果索引小于数组个数,就调用交换后的系统的objectAtIndex:方法返回该位置的值
        return [self Test_objectAtIndex:index];
    }
    else
    {
        //超界,返回空值
        return nil;
    }
}

@end


@implementation NSMutableArray(Extension)

//程序一运行就会加载
+(void)load
{
    //获取实例方法
    [self swizlleInstanceMethod:NSClassFromString(@"__NSArrayM") originMethod:@selector(addObject:) otherMethod:@selector(Test_addObject:)];
    
    //交换实例实现方法
    [self swizlleInstanceMethod:NSClassFromString(@"__NSArrayM") originMethod:@selector(objectAtIndex:) otherMethod:@selector(Test_objectAtIndex:)];

}

-(void)Test_addObject:(id)object
{
    if (object != nil)
    {
        //如果对象不为空,就调用交换后的系统的addObject:方法添加对象到可变数组中
        [self Test_addObject:object];
    }
}

-(id)Test_objectAtIndex:(NSUInteger)index
{
    if (index < self.count)
    {
         //如果索引小于数组个数,就调用交换后的系统的objectAtIndex:方法返回该位置的值
        return [self Test_objectAtIndex:index];
    }
    else
    {
        //超界,返回空值
        return nil;
    }
}
@end

<2>测试:

声明数组:

@interface ViewController ()
@property (strong,nonatomic)NSMutableArray *names;
@property (strong,nonatomic)NSArray *books;
@end

NSArray:

    self.books = @[@"水浒传",@"西游记"];
    NSLog(@"%@",self.books[1]); //[self.books objectAtIndex:1] --> [self.books Test_objectAtIndex:1]
    NSLog(@"%@",self.books[4]); //[self.books objectAtIndex:4] --> [self.books Test_objectAtIndex:4]

测试结果:

2016-01-29 14:15:01.436 Runtime-运行时[3575:199688] 西游记
2016-01-29 14:15:01.437 Runtime-运行时[3575:199688] (null)

解释原因:

因为程序一运行,就调用了+(void)load中的[self swizlleInstanceMethod:NSClassFromString(@"__NSArrayI") originMethod:@selector(objectAtIndex:) otherMethod:@selector(Test_objectAtIndex:)]方法,

所以交换后就成了这种情况: self.books[1]---> [self.books objectAtIndex:1] --> [self.books Test_objectAtIndex:1]


鲜花

握手

雷人

路过

鸡蛋
该文章已有0人参与评论

请发表评论

全部评论

专题导读
上一篇:
objective-c 类别发布时间:2022-07-12
下一篇:
Objective-C中严谨的单例模式发布时间:2022-07-12
热门推荐
热门话题
阅读排行榜

扫描微信二维码

查看手机版网站

随时了解更新最新资讯

139-2527-9053

在线客服(服务时间 9:00~18:00)

在线QQ客服
地址:深圳市南山区西丽大学城创智工业园
电邮:jeky_zhao#qq.com
移动电话:139-2527-9053

Powered by 互联科技 X3.4© 2001-2213 极客世界.|Sitemap