IOS Object-C 中Runtime详解

最近了解了一下OC的Runtime,真的是OC中很强大的一个机制,看起来比较底层,但其实可以有很多活用的方式。

什么是Runtime

我们虽然是用Objective-C写的代码,其实在运行过程中都会被转化成C代码去执行。比如说OC的方法调用都会转成C函数 id objc_msgSend ( id self, SEL op, … ); 而OC中的对象其实在Runtime中都会用结构体来表示,这个结构体中包含了类名、成员变量列表、方法列表、协议列表、缓存等。

类在Runtime中的表示:

struct objc_class {
 Class isa;//指针,顾名思义,表示是一个什么,
 //实例的isa指向类对象,类对象的isa指向元类

#if !__OBJC2__
 Class super_class; //指向父类
 const char *name; //类名
 long version;
 long info;
 long instance_size
 struct objc_ivar_list *ivars //成员变量列表
 struct objc_method_list **methodLists; //方法列表
 struct objc_cache *cache;//缓存
 //一种优化,调用过的方法存入缓存列表,下次调用先找缓存
 struct objc_protocol_list *protocols //协议列表
 #endif
} OBJC2_UNAVAILABLE;
/* Use `Class` instead of `struct objc_class *` */

整个Runtime机制其实可以挖的点很多,这里只是简单的介绍一些常见的用法,如果将其细细解析,相信一定会对OC的理解加深几个层面。

获取属性/方法/协议列表

最直接的一种用法,就是获取我们的结构体中存储的对象的属性、方法、协议等列表,从而获取其所有这些信息。

要获取也比较简单,但是自己尝试之前需要注意几点:

一定要自己给类加几个属性、方法,遵循一些协议,否则当然是看不到输出信息的。

要使用这些获取的方法,需要导入头文件 #import

#import <objc/runtime.h>

// 输出类的一些信息
- (void)logInfo {
 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(@"property --> %@", [NSString stringWithUTF8String:propertyName]);
 }

 // 获取方法列表
 Method *methodList = class_copyMethodList([self class], &count);
 for (unsigned int i; i < count; i  ) {
 Method method = methodList[i];
 NSLog(@"method --> %@", NSStringFromSelector(method_getName(method)));
 }

 // 获取成员变量列表
 Ivar *ivarList = class_copyIvarList([self class], &count);
 for (unsigned int i; i < count; i  ) {
 Ivar myIvar = ivarList[i];
 const char *ivarName = ivar_getName(myIvar);
 NSLog(@"Ivar --> %@", [NSString stringWithUTF8String:ivarName]);
 }

 // 获取协议列表
 __unsafe_unretained Protocol **protocolList = class_copyProtocolList([self class], &count);
 for (unsigned int i; i < count; i  ) {
 Protocol *myProtocal = protocolList[i];
 const char *protocolName = protocol_getName(myProtocal);
 NSLog(@"protocol --> %@", [NSString stringWithUTF8String:protocolName]);
 }
}

方法调用的过程

调用方法分为调用实例方法和调用类方法,在结构体我们可以看到第一个就是一个 isa 指针,会指向类对象或者元类,什么叫元类呢?

每个实例对象有个isa的指针,他指向对象的类,而类里也有个isa的指针, 指向meteClass(元类)。元类保存了类方法的列表。当类方法被调用时,先会从本身查找类方法的实现,如果没有,元类会向他父类查找该方法。同时注意的是:元类(meteClass)也是类,它也是对象。元类也有isa指针,它的isa指针最终指向的是一个根元类(root meteClass)。根元类的isa指针指向本身,这样形成了一个封闭的内循环。

通过isa,就可以不断往上方去回溯自己的父类等,而方法的调用也利用了这个过程:

  1. 首先,当然在对象自己缓存的方法列表中去找要调用的方法,找到了就直接执行其实现。
  2. 缓存里没找到,就去上面说的它的方法列表里找,找到了就执行其实现。
  3. 还没找到,说明这个类自己没有了,就会通过isa去向其父类里执行1、2。
  4. 如果找到了根类还没找到,那么就是没有了,会转向一个拦截调用的方法,我们可以自己在拦截调用方法里面做一些处理。
  5. 如果没有在拦截调用里做处理,那么就会报错崩溃。

以上就是方法调用的过程。我们可以看到的是,所谓重写父类方法,并不是抹除了父类方法,父类的方法还是存在的,只是我们在子类里面找到了就不会再去父类里找了,是这个层面的“覆盖”。而我们熟悉的 super 调用父类的方法实现,就是告知要去调用父类实现的标识。

拦截调用与动态添加

上面说到了拦截调用,就是在所有地方都找不到方法的实现的话,会出发拦截调用,可以自己去做一些处理避免程序崩溃。那在什么地方做处理呢?NSObject有四个方法可以用来做处理:

// 调用不存在的类方法时触发,默认返回NO,可以加上自己的处理后返回YES
  (BOOL)resolveClassMethod:(SEL)sel;

// 调用不存在的实例方法时触发,默认返回NO,可以加上自己的处理后返回YES
  (BOOL)resolveInstanceMethod:(SEL)sel;

// 将调用的不存在的方法重定向到一个其他声明了这个方法的类里去,返回那个类的target
- (id)forwardingTargetForSelector:(SEL)aSelector;

// 将调用的不存在的方法打包成 NSInvocation 给你,自己处理后调用 invokeWithTarget: 方法让某个类来触发
- (void)forwardInvocation:(NSInvocation *)anInvocation;

假设我们成功拦截下来了,那我们可以做什么处理呢?这个其实就是根据具体情况看你要怎么处理了。但这里有一个久闻其名的东西就可以派上用场了:动态添加方法。

我们知道OC是动态的,也就是说很多东西它不是在编译时去判断,而是在运行时去处理的,这其实带来了很大的灵活性,比如这里我们对于一些不存在的方法的调用,就可以动态去添加上一个方法来代替我们要调用的方法。

比如我们添加一个Button,点击事件我们关联到一个没有具体实现的实例方法,这种情况下如果不做任何处理那么点击Button后一定会崩溃,但是如果我们拦截并动态添加一个方法来报警,就不会崩溃了:

@interface ViewController ()
@property (nonatomic, strong) UIButton *logBtn;

- (void)notHas;// 要调用的实例方法,没有具体实现
@end

- (void)viewDidLoad {
 [super viewDidLoad];

 // 添加按钮
 self.logBtn = [[UIButton alloc] initWithFrame:CGRectMake(100, 200, 100, 20)];
 [self.logBtn setTitle:@"测 试" forState:UIControlStateNormal];
 [self.logBtn setTitleColor:[UIColor lightGrayColor] forState:UIControlStateNormal];
 // 添加没有实现的点击事件
 [self.logBtn addTarget:self action:@selector(notHas) forControlEvents:UIControlEventTouchUpInside];
 [self.view addSubview:self.logBtn];

}

// 拦截对不存在的方法的调用
  (BOOL)resolveInstanceMethod:(SEL)sel {
 NSLog(@"notFind!");
 // 给本类动态添加一个方法
 if ([NSStringFromSelector(sel) isEqualToString:@"notHas"]) {
 class_addMethod(self, sel, (IMP)runAddMethod, "v@:*");
 }
 // 注意要返回YES
 return YES;
}

// 要动态添加的方法,这是一个C方法
 void runAddMethod(id self, SEL _cmd, NSString *string) {
 NSLog(@"动态添加一个方法来提示");
}

按照上面的处理,点击按钮后就不会崩溃,而是转到了对 runAddMethod 方法的调用。其实更明确地说,应该是重现了对要调用的方法的实现,将原本要调用的方法的实现,改为了一个新的实现。class_addMethod 方法的第二个参数是要重写的方法,这里用的就是传进来的参数sel,第三个参数就是重写后的实现。第四个参数是方法的签名。

关联对象

什么叫关联对象?说通俗一点,我们都知道用Category类别可以给一些已经存在的,比如系统的类添加方法,但是不能添加新属性,那怎么添加属性呢?一种直接的方法是继承,但如果只是为了添加一个属性就去做一次继承,还是有点重,这时候,就可以用关联对象的方法。

有两个相关的方法:

objc_setAssociatedObject 方法用来给类关联一个属性;
objc_getAssociatedObject 方法用来获取之前关联的属性。

比如说给自己这个类关联一个字符串:

 // 关联对象
 static char associatedObjectKey;
 objc_setAssociatedObject(self, &associatedObjectKey, @"我就是要关联的字符串对象内容", OBJC_ASSOCIATION_RETAIN_NONATOMIC);
 NSString *theString = objc_getAssociatedObject(self, &associatedObjectKey);
 NSLog(@"关联对象:%@", theString);

我们先给self关联了一个字符串内容,然后通过get方法获取了关联的字符串内容,并输出。

从代码中其实也可以猜到各个参数的意思,self的参数就是要处理的类;associatedObjectKey 的参数其实就类似于key,用来标识区分你要关联的这个对象;第三个参数是要关联的对象;第四个参数是关联的策略,用命名就可以看出来全是在添加@property属性时用到的一些修饰符,有五种策略:

enum {
 OBJC_ASSOCIATION_ASSIGN = 0,
 OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, 
 OBJC_ASSOCIATION_COPY_NONATOMIC = 3,
 OBJC_ASSOCIATION_RETAIN = 01401,
 OBJC_ASSOCIATION_COPY = 01403 
};

熟悉@property属性修饰符的应该能直接明白了,不熟悉的可以看这篇文章:传送门:iOS中assign、retain、copy、weak、strong的区别以及nonatomic的含义

当然,你也可以和类别一起用,创建两个方法用来关联和获取对象,比如下面这样:

//添加关联对象
- (void)addAssociatedObject:(id)object{
 objc_setAssociatedObject(self, @selector(getAssociatedObject), object, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

//获取关联对象
- (id)getAssociatedObject{
 return objc_getAssociatedObject(self, _cmd);
}

这样就既能通过Category类别来添加方法,用一起顺便提供了对属性的添加了。

以上是对Runtime的一点浅薄的理解和使用,Runtime的天地应该是很广阔的,也能挖出很多高级的使用方法来,对于理解OC的运行机制是很有帮助的。

源码下载:http://xiazai.jb51.net/201703/yuanma/RuntimeDemo-master(jb51.net).rar

感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!

IOS Object-C 中Runtime详解及实例代码的更多相关文章

  1. xamarin.ios – 没有找到ViewController ::.ctor(System.IntPtr)的构造函数

    我有一个问题,我的Monotouch应用程序有时在收到内存警告后才会崩溃.请参见下面的堆栈跟踪.堆栈跟踪是正确的,因为指定的类缺少构造函数获取IntPtr参数.但是这是有意的,因为我在应用程序中根本不使用InterfaceBuilder.那为什么会这样呢?

  2. iOS8 / Swift和MobileVLCKit构建失败

    我正在努力用MobileVLCKit和cocoapods构建我的项目.在不添加任何VLCKit代码的情况下,我在模拟器或设备上构建应用程序时会出错.Pod文件:这是我得到的错误我的部署目标是8.0.我在Debug中将“BuildActiveArchitectureOnly”设置为YES.非常感谢你的帮助!解决方法除了做MukeshThawani所说的话;ChangeC++StandardLibra

  3. 3Swift\OC\Java中字符串的比较

    判断字符串是否为同一对象用==if(!

  4. 对UIColor的扩展OC和Swift

    UIcolor这个类中,系统给的颜色太少了,虽然给我们提供了一个方法(光的三原色,根据红,绿,蓝光的比例调出很多颜色,RGB)colorWithRed:green:blue:alpha,但是用起来还是很麻烦,在这人给大家介绍一个简单的方法,就是自己对UIColor扩展,写一个方法先介绍一下要写什么样的方法:这个方法里我们需要传一个字符串(比如:@"00ff00"),就是一个颜色对应的RGB值,然后

  5. Swift与OC混合编译

    SWift调用OC新建swift文件此时系统自动生成-Bridging-Header.h文件并且TARGETS->BuildSettings->Objective-CBridgingHeader(搜索bridg)选项中会自动填入以上头文件的路径在-Bridging-Header.h中#import要调用的OC对象头文件OC调用Swift在OC文件中#import“

  6. OC To Swift And Swift To OC

    Swift工程混编OC代码:http://www.cocoachina.com/bbs/read.PHP?tid=204738OC工程混编Swift代码:http://my.oschina.net/u/1418722/blog/275363

  7. swift语言的学习笔记九(OC与Swift混编)

    swift语言出来后,可能新的项目直接使用swift来开发,但可能在过程中会遇到一些情况,某些已用OC写好的类或封装好的模块,不想再在swift中再写一次,哪就使用混编。这个在IOS8中是允许的。先中简单的入手,先研究在同一个工程目录下混合使用的情况。

  8. OC与Swift混合开发技巧

    在苹果推出了swift语言之后,很多人担心OC很快会被取代,但是苹果方面表示2年内不会摒弃OC。有的开发团队已经开始基于swift开发,但是有很多旧的框架还没来得及用swift写出来,并且某些swift的功能你还不会写,想用OC写。因此在swift开发的程序中时不时会用到OC的类,怎么让两门语言在一个程序里无缝衔接?

  9. Swift教程14-func函数,函数类型_对比Oc

    Swift的函数和函数类型是非常重要的内容.而且Swift中也把函数的形式和方法的形式,定义的完全一致;只不过,函数不是在类中.1.函数的定义func函数名(参数列表)[->返回值类型]{//函数体}解释:func是关键字,是必须写上的前缀,代表它是一个函数或方法;函数名:遵从标识符的规则形参列表,可以为空,也可以很多;例如str:String,a:Int,和声明变量的方法类似->后面跟的是返回值

  10. oc 和 swift 混编

    swift语言出来后,可能新的项目直接使用swift来开发,但可能在过程中会遇到一些情况,某些已用OC写好的类或封装好的模块,不想再在swift中再写一次,哪就使用混编。这个在IOS8中是允许的。先中简单的入手,先研究在同一个工程目录下混合使用的情况。

随机推荐

  1. iOS实现拖拽View跟随手指浮动效果

    这篇文章主要为大家详细介绍了iOS实现拖拽View跟随手指浮动,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  2. iOS – genstrings:无法连接到输出目录en.lproj

    使用我桌面上的项目文件夹,我启动终端输入:cd然后将我的项目文件夹拖到终端,它给了我路径.然后我将这行代码粘贴到终端中找.-name*.m|xargsgenstrings-oen.lproj我在终端中收到此错误消息:genstrings:无法连接到输出目录en.lproj它多次打印这行,然后说我的项目是一个目录的路径?没有.strings文件.对我做错了什么的想法?

  3. iOS 7 UIButtonBarItem图像没有色调

    如何确保按钮图标采用全局色调?解决方法只是想将其转换为根注释,以便为“回答”复选标记提供更好的上下文,并提供更好的格式.我能想出这个!

  4. ios – 在自定义相机层的AVFoundation中自动对焦和自动曝光

    为AVFoundation定制图层相机创建精确的自动对焦和曝光的最佳方法是什么?

  5. ios – Xcode找不到Alamofire,错误:没有这样的模块’Alamofire’

    我正在尝试按照github(https://github.com/Alamofire/Alamofire#cocoapods)指令将Alamofire包含在我的Swift项目中.我创建了一个新项目,导航到项目目录并运行此命令sudogeminstallcocoapods.然后我面临以下错误:搜索后我设法通过运行此命令安装cocoapodssudogeminstall-n/usr/local/bin

  6. ios – 在没有iPhone6s或更新的情况下测试ARKit

    我在决定下载Xcode9之前.我想玩新的框架–ARKit.我知道要用ARKit运行app我需要一个带有A9芯片或更新版本的设备.不幸的是我有一个较旧的.我的问题是已经下载了新Xcode的人.在我的情况下有可能运行ARKit应用程序吗?那个或其他任何模拟器?任何想法或我将不得不购买新设备?解决方法任何iOS11设备都可以使用ARKit,但是具有高质量AR体验的全球跟踪功能需要使用A9或更高版本处理器的设备.使用iOS11测试版更新您的设备是必要的.

  7. 将iOS应用移植到Android

    我们制作了一个具有2000个目标c类的退出大型iOS应用程序.我想知道有一个最佳实践指南将其移植到Android?此外,由于我们的应用程序大量使用UINavigation和UIView控制器,我想知道在Android上有类似的模型和实现.谢谢到目前为止,guenter解决方法老实说,我认为你正在计划的只是制作难以维护的糟糕代码.我意识到这听起来像很多工作,但从长远来看它会更容易,我只是将应用程序的概念“移植”到android并从头开始编写.

  8. ios – 在Swift中覆盖Objective C类方法

    我是Swift的初学者,我正在尝试在Swift项目中使用JSONModel.我想从JSONModel覆盖方法keyMapper,但我没有找到如何覆盖模型类中的Objective-C类方法.该方法的签名是:我怎样才能做到这一点?解决方法您可以像覆盖实例方法一样执行此操作,但使用class关键字除外:

  9. ios – 在WKWebView中获取链接URL

    我想在WKWebView中获取tapped链接的url.链接采用自定义格式,可触发应用中的某些操作.例如HTTP://我的网站/帮助#深层链接对讲.我这样使用KVO:这在第一次点击链接时效果很好.但是,如果我连续两次点击相同的链接,它将不报告链接点击.是否有解决方法来解决这个问题,以便我可以检测每个点击并获取链接?任何关于这个的指针都会很棒!解决方法像这样更改addobserver在observeValue函数中,您可以获得两个值

  10. ios – 在Swift的UIView中找到UILabel

    我正在尝试在我的UIViewControllers的超级视图中找到我的UILabels.这是我的代码:这是在Objective-C中推荐的方式,但是在Swift中我只得到UIViews和CALayer.我肯定在提供给这个方法的视图中有UILabel.我错过了什么?我的UIViewController中的调用:解决方法使用函数式编程概念可以更轻松地实现这一目标.

返回
顶部