Swift 中的运行时小技巧

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也可以用这些方法

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值