Runtime应用之关联对象和MethodSwizzling

最近用到了sunnyxx的forkingdog系列《UIView-FDCollapsibleConstraints》,纪录下关联对象和MethodSwizzling在实际场景中的应用。

基本概念

关联对象

  • 关联对象操作函数
    • 设置关联对象:

    • 获取关联对象:

    其中设置关联对象的策略有以下5种:

    • 和MRC的内存操作retain、assign方法效果差不多
      • 比如设置的关联对象是一个UIView,并且这个UIView已经有父控件时,可以使用OBJC_ASSOCIATION_ASSIGN

关联对象在一些第三方框架的分类中常常见到,这里在分析前先看下分类的结构:

从以上的分类结构,可以看出,分类中是不能添加成员变量的,也就是Ivar类型。所以,如果想在分类中存储某些数据时,关联对象就是在这种情况下的常用选择。

需要注意的是,关联对象并不是成员变量,关联对象是由一个全局哈希表存储的键值对中的值。

全局哈希表的定义如下:

其中的AssociationsHashMap就是那个全局哈希表,而注释中也说明的很清楚了:哈希表中存储的键值对是(源对象指针 : 另一个哈希表)。而这个value,即ObjectAssociationMap对应的哈希表如下:

其中的ObjectAssociationMap就是value的类型。同时,也可以知道ObjectAssociationMap的键值对类型为(关联对象对应的key : 关联对象),也就是函数objc_setAssociatedObject的对应的key:value参数。

大部分情况下,关联对像会使用getter方法的SEL当作key(getter方法中可以这样表示:_cmd)。

更多和关联对象有关的底层信息,可以查看Dive into Category

MethodSwizzling

MethodSwizzling主要原理就是利用runtime的动态特性,交换方法对应的实现,也就是IMP
通常,MethodSwizzling的封装为:

为了便于区别,这里列出Method的结构:

实现MethodSwizzling需要了解的有以下几个常用函数:

介绍MethodSwizzling的文章很多,更多和MethodSwizzling有关的信息,可以查看Objective-C的hook方案(一): Method Swizzling

针对UIView-FDCollapsibleConstraints的应用

UIView-FDCollapsibleConstraints是sunnyxx阳神写的一个UIView分类,可以实现仅在IB中对UIView上的约束进行设置,就达到以下效果,而不需要编写改变约束的代码:(图片来源UIView-FDCollapsibleConstraints

11608238-296a8a19dbe21a10

UIView下
12608238-52c26dd14497fd41

UITableView下

这里介绍下自己对这个分类的理解:

  • 实现思路
    • 将需要和UIView关联且需要动态修改的约束添加进一个和UIView绑定的特定的数组里面
    • 根据UIView的内容是否为nil,对这个特定数组中的约束值进行统一设置

而在分类不能增加成员变量的情况下,和UIView绑定的特定的数组就是用关联对象实现的。

先从分类的头文件开始:

头文件

分析几点:

  • IBOutletCollection,详情参考IBAction / IBOutlet / IBOutletCollection
    • 表示将SB中相同的控件连接到一个数组中;这里使用这个方式,将在SB中的NSLayoutConstraint添加到fd_collapsibleConstraints数组中,以便后续对约束进行统一操作
    • IBOutletCollectionh和IBOutlet操作方式一样,需要在IB中进行相应的拖拽才能把对应的控件加到数组中(UIView->NSLayoutConstraint
    • 设置了IBOutletCollection之后,当从storybooard或者xib中加载进行解档时,最终会调用fd_collapsibleConstraints的setter方法,然后就可以在其setter方法中做相应的操作了
  • IBInspectable 表示这个属性可以在IB中更改,如下图
    13608238-1393abcc16d5ea98

    Snip20150704_1.png

主文件

NSLayoutConstraint (_FDOriginalConstantStorage)
  • 因为在修改约束值后,需要还原操作,但是分类中无法添加成员变量,所以在这个分类中,给NSLayoutConstraint约束关联一个存储约束初始值的浮点数,以便在修改约束值后,可以还原

UIView (FDCollapsibleConstraints)
  • 同样,因为需要对UIView上绑定的约束进行改动,所以需要在分类中添加一个可以记录所有约束的对象,需要用到关联对象
  • 实现fd_collapsibleConstraints属性的setter和getter方法 (关联一个存储约束的对象)
    • getter方法中创建关联对象constraints(和懒加载的方式类似,不过不是创建成员变量)
    • setter方法中设置约束的初始值,并添加进关联对象constraints中,方便统一操作
  • 从IB中关联的约束,最终会调用setFd_collapsibleConstraints:方法,也就是这一步不需要手动调用,系统自己完成(在awakeFromNib之前完成IB这些值的映射)

    • 使用Method Swizzling交换自己的和系统的-setValue:forKey:方
      • 实现自己的KVC的-setValue:forKey:方法

  • 上面使用Method Swizzling的原因作者认为是这种类型的IBOutlet不会触发其setter方法,但是经过测试,注释掉这段代码后,系统还是自己触发了setter方法,说明这种IBOutlet还是可以触发setter方法的。所以,即使没有这一段代码,应该也是可行的
14608238-3180b2ff9aaa5875

操作结果
  • 设置对应的约束值
    • 这里给UIView对象提供一个关联对象,来判断是否将约束值清零
    • 注意,这里只要传入的是YES,那么,这个UIView对应存入constraints关联对象的所有约束,都会置为0

UIView (FDAutomaticallyCollapseByIntrinsicContentSize)
  • 使用Method Swizzling交换自己实现的-fd_updateConstraints和系统的updateConstraints方法
    • [self fd_updateConstraints]调用的是self的updateConstraints方法,fd_updateConstraints和updateConstraints方法的IMP,即方法实现已经调换了
    • 可以看到,加入这里不使用Method Swizzling,那么要实现在更新约束时就需要重写updateConstraints方法,而这只能在继承UIView的情况下才能完成的;而实用了Method Swizzling,就可以直接在分类中实现在调用系统updateConstraints的前提下,又添加自己想要执行的附加代码
    • intrinsicContentSize(控件的内置大小)默认为UIViewNoIntrinsicMetric,当控件中没有内容时,调用intrinsicContentSize返回的即为默认值,详情参考(intrinsicContentSize和Content Hugging Priority

  • 设置一些动态属性(关联对象)
    • 给UIView关联一个对象,来判断是否需要自动对约束值进行清零

总结

总体来说,在分类中要想实现相对复杂的逻辑,却不能添加成员变量,也不想对需要操作的类进行继承,这时就需要runtime中的关联对象和MethodSwizzling技术了。

forkingdog系列分类都用到了runtime的一些知识,代码简洁注释齐全风格也不错,比较适合需要学习runtime应用知识的我。

1 收藏 评论

相关文章

可能感兴趣的话题



直接登录
跳到底部
返回顶部