Swift 运行时动态绑定属性
在iOS开发中属性封装了成员变量的setter,getter方法,在开发中会遇到很多需要类目的地方,类目可以为已有的类添加方法,但是不能添加成员变量(在创建类的时候类所占用的内存空间已经根据类的属性确定),如果需要添加属性的话,需要手动生成setter,getter方法 在没有setter,getter方法的时候生成属性就需要靠强大的运行时
在OC中我们通常这样写:
#import <objc/runtime.h>
static const void *HUDKey = &HUDKey;
@implementation UIView (HUDExtension)
#pragma mark - 动态绑定HUD属性
- (MBProgressHUD *)HUD
{
return objc_getAssociatedObject(self, HUDKey);
}
- (void)setHUD:(MBProgressHUD * _Nullable)HUD
{
objc_setAssociatedObject(self, HUDKey, HUD,OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
复制代码
在swift中我们通常这样写:
extension UIView {
// MARK:- RuntimeKey 动态绑属性
struct RuntimeKey {
static let kProgressHud = UnsafeRawPointer.init(bitPattern: "kProgressHud".hashValue)
}
var HUD: MBProgressHUD? {
set {
objc_setAssociatedObject(self, UIView.RuntimeKey.kProgressHud!, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
get {
return objc_getAssociatedObject(self, UIView.RuntimeKey.kProgressHud!) as? MBProgressHUD
}
}
}
复制代码
这样便有了swift和oc一样的动态绑定属性的方法了
在我们在swift还会用到运行时的交换方法:
/// 交换方法 并且只执行一次 程序已进入就执行交换
class func exchangeMethod() {
let sel = Selector.init(("executeReloadDataBlock"))
let firstMethod = class_getInstanceMethod(self.classForCoder(),sel)
let secondMethod = class_getInstanceMethod(self.classForCoder(),#selector(mk_reloadData))
method_exchangeImplementations(firstMethod!, secondMethod!)
}
复制代码
在之前是可以写load方法进行再已进入程序就进行交换操作的,swift3.0后苹果废除了load方法,但是可以调用
open override static func initialize() {
// Method Swizzling
if self == UIScrollView.classForCoder() {
UIScrollView.exchangeMethod()
}
}
复制代码
这样来进行交换方法,但是在swift4.0以后initialize方法也被废除了,我们可以在AppDelegate中调用这个方法
通过这些简单的运行时方法可以方便我们的代码 我的项目每个列表页在没有加载到数据的时候都会有一张占位图,这当然可以通过继承来操作,继承UITableview自定义一个列表页,重写reloadData方法,在这个方法里面判断cell的个数,来操作占位图的显示
但是,通过运行时,我们会有更爽快的操作,我的项目中使用了MJRefresh,本来想使用交换reloadData的方法来进行操作的,但是我发现reloadData方法已经在MJRefresh中被交换过了(UIScrollView+MJRefresh.m 135行以后),但是这并不意味这不能操作了,我们发现在每次调用reloadData后便会调用executeReloadDataBlock这个方法,所以我们决定对这个方法进行交换,而MJRefresh也帮我们写好了计算cell的个数的方法mj_totalDataCount
/// 交换方法 并且只执行一次 程序已进入就执行交换
class func exchangeMethod() {
let sel = Selector.init(("executeReloadDataBlock"))
let firstMethod = class_getInstanceMethod(self.classForCoder(),sel)
let secondMethod = class_getInstanceMethod(self.classForCoder(),#selector(mk_reloadData))
method_exchangeImplementations(firstMethod!, secondMethod!)
}
/// 用于替换获取cell的个数 并附加载cell个数为0时显示背景图
@objc func mk_reloadData() {
mk_reloadData()
let count = mj_totalDataCount()
if self.LoadingStateView != nil {//保证没有刷新的控件不受影响 例如轮播图
if count == 0 {
self.showLoadingView(show: true)
var ofset : CGFloat = 0
if self.mj_header != nil {
if self.mj_header.ignoredScrollViewContentInsetTop != 0 {
ofset += self.mj_header.ignoredScrollViewContentInsetTop
}
}
//
if self.isKind(of: UITableView.classForCoder()) {
let tableview : UITableView = self as! UITableView
if tableview.tableHeaderView != nil {
ofset += (tableview.tableHeaderView?.height)!
}
}
LoadingStateView?.snp.updateConstraints { (maker) in
maker.centerX.equalTo(self.snp.centerX)
maker.centerY.equalTo(self.snp.centerY).offset(ofset/2)
}
}else{
showLoadingView(show: false)
}
}
// MARK:- 当scroll得contentSize小于frame是footer隐藏
if self.mj_footer != nil {
let footer : MJRefreshAutoNormalFooter = self.mj_footer as! MJRefreshAutoNormalFooter
self.layoutSubviews()
if self.contentSize.height < self.height{
footer.stateLabel.isHidden = true
footer.isRefreshingTitleHidden = true
}else{
footer.stateLabel.isHidden = false
footer.isRefreshingTitleHidden = false
}
}
}
复制代码
当然self.LoadingStateView 也是通过runtime进行动态生成的
extension UIView{
// static var kBgonce = 0
// MARK:- RuntimeKey 动态绑属性
struct BackgroundRuntimeKey {
/// 动态绑定没有数据的背景图
static let kBackgroundLoadingStateView = UnsafeRawPointer.init(bitPattern: "kBackgroundLoadingStateView".hashValue)
static let kBackgroundLoadingHint = UnsafeRawPointer.init(bitPattern: "kBackgroundLoadingHint".hashValue)
}
var loadingHint : String?{
set {
showLoadingView(show: true)
self.LoadingStateView?.hint = newValue
objc_setAssociatedObject(self, UIScrollView.BackgroundRuntimeKey.kBackgroundLoadingHint!, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
get {
return objc_getAssociatedObject(self, UIScrollView.BackgroundRuntimeKey.kBackgroundLoadingHint!) as? String
}
}
var LoadingStateView: LoadingView? {
set {
objc_setAssociatedObject(self, UIScrollView.BackgroundRuntimeKey.kBackgroundLoadingStateView!, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
get {
return objc_getAssociatedObject(self, UIScrollView.BackgroundRuntimeKey.kBackgroundLoadingStateView!) as? LoadingView
}
}
/// 创建背景视图
func createLoadingStateView() {
if self.LoadingStateView != nil {
return
}
let loadView : LoadingView = LoadingView.viewFromXib() as! LoadingView
loadView.isHidden = true
let loadH : CGFloat = self.height/3
loadView.frame = CGRect.init(x: 0, y: 0, width: loadH*0.75, height: loadH)
// loadView.transform = CGAffineTransform(scaleX: 0.75, y: 0.75);
self.addSubview(loadView)
loadView.snp.makeConstraints { (maker) in
maker.centerX.equalTo(self.snp.centerX)
maker.centerY.equalTo(self.snp.centerY)
}
self.LoadingStateView = loadView
}
func showLoadingView(show:Bool) {
//如果cell的个数不为0 就不显示
if self.isKind(of: UITableView.classForCoder()) {
if show == true {
let tableview : UITableView = self as! UITableView
if tableview.mj_totalDataCount() != 0 {
return
}
}
}else if self.isKind(of: UICollectionView.classForCoder()){
if show == true {
let tableview : UICollectionView = self as! UICollectionView
if tableview.mj_totalDataCount() != 0 {
return
}
}
}
if LoadingStateView == nil {
createLoadingStateView()
}
self.bringSubview(toFront: LoadingStateView!)
LoadingStateView?.isHidden = !show
}
}
复制代码
这样写了只要tableview用到了MJRefresh就可以自动计算占位图的隐藏和出现,如果没有tableview的情况下可以通过
rightTable.showLoadingView(show: true)
rightTable.loadingHint = "该分类您已全部入驻"
复制代码
来控制UIView的占位图,和占位文字,当然也可以控制占位图片,当然UICollectionview和UITableview同属于UIScrollview的子类,所以UICollectionview也可以用这些方法