Swift 运行时动态绑定属性
在iOS开发中属性封装了成员变量的setter,getter方法,在开发中会遇到很多需要类目的地方,类目可以为已有的类添加方法,但是不能添加成员变量(在创建类的时候类所占用的内存空间已经根据类的属性确定),如果需要添加属性的话,需要手动生成setter,getter方法 在没有setter,getter方法的时候生成属性就需要靠强大的运行时
在OC中我们通常这样写:
#importstatic 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也可以用这些方法