为了账号安全,请及时绑定邮箱和手机立即绑定

UIGestureRecognizer+Block

标签:
iOS Swift

老司机又到周末啦平时我们工作中会写很多的工具类但是有一些使用工具类去使用又不是很方便我们就把它抽取封装成分类。来 看一下今天要写的关于手势的干货。

  • 以前我们是这样使用手势的

self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(tapClick(tap:))))
func tapClick(tap: UITapGestureRecognizer) {
      // 手势点击后要执行的代码  
}
  • 现在用了UIGestureRecognizer+Block简单来说你可以这样使用 UIGestureRecognizer

self.messageLabel.addGestureRecognizer(UITapGestureRecognizer(actionBlock: { [unowned self](tap) in
      // 要执行的代码
 }))

相比较来说不再需要繁琐地使用 selector 去实现也解决了代码分离的问题。就好像把delegate封装成了闭包比如很多人使用的蓝牙框架第三方BabyBluetooth就是这么干的它对系统的基于CoreBlueTooth的封装内部把代理实现都转变成了Block形式。这样做的目的是代码封装性好可读性强易于维护。


  • 实现详情如下

//  封装的手势闭包回调
import UIKit
extension UIGestureRecognizer {
    typealias MGGestureBlock = ((Any)->())
    // MARK:- RuntimeKey   动态绑属性
    // 改进写法【推荐】用枚举实现绑定的key 写的时候会有提示
    fileprivate struct RuntimeKey {
        static let mg_GestureBlockKey = UnsafeRawPointer.init(bitPattern: "mg_GestureBlockKey".hashValue)
        /// ...其他Key声明
    }
    // 便利构造方法
    convenience init(actionBlock: @escaping MGGestureBlock) {
        self.init()
        addActionBlock(actionBlock)
        addTarget(self, action: #selector(invoke(_:)))
    }
    // 内部方法 加上fileprivat防止外界直接调用
    fileprivate func addActionBlock(_ block: MGGestureBlock?) {
        if (block != nil) {
            objc_setAssociatedObject(self, UIGestureRecognizer.RuntimeKey.mg_GestureBlockKey, block!, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
        }
    }
    
	// 内部方法
    @objc fileprivate func invoke(_ sender: Any) {
        let block = objc_getAssociatedObject(self, UIGestureRecognizer.RuntimeKey.mg_GestureBlockKey) as? MGGestureBlock
        if (block != nil) {
            block!(sender);
        }
    }
}
  • #原理就是把 block 动态地绑成 UIGestureRecognizer 的一个变量invoke 的时候再调用这个 block。



  • 开发过程中遇到了的坑。

  • 我一开始在类方法里面进行了动态绑定错误代码如下
//以下是错误代码
convenience init(actionBlock block: MGGestureBlock) {
    return self.init(target: self.gestureRecognizerBlockTarget(block), selector: #selector(self.invoke))
}

class func gestureRecognizerBlockTarget(_ block: MGGestureBlock) -> MGGestureRecognizerBlockTarget {
    var target: MGGestureRecognizerBlockTarget? = objc_getAssociatedObject(self, target_key)
    if target == nil {
        target = MGGestureRecognizerBlockTarget(block: block)
        objc_setAssociatedObject(self, target_key, target, OBJC_ASSOCIATION_RETAIN_NONATOMIC)
    }
    return target!
}

这样导致的结果就是变量被绑到了这个类对象上因为在类方法里面 self 指的是这个类对象而类对象是常驻内存直到程序退出才释放的这也就导致了这个类上始终绑着第一次的 target之后无论怎样都不会改变。如果恰好你在 block 中有强制持有了 block 外的其他对象那就会导致这些对象都不会释放造成内存泄露。在实例方法中动态绑定即可解决。


  • 如果不使用动态绑定使用如下的代码会产生怎样的结果
//错误代码
convenience init(actionBlock block: MGGestureBlock) {
    return self.init(target: MGGestureRecognizerBlockTarget(block: block), selector: #selector(self.invoke))
}

结果就是出了这个作用域 target 对象释放。通过查阅文档我在《OC 编程概念》的 Target-Action 一节中看到苹果有提到这么一句 Control objects do not (and should not) retain their targets. 按照惯例如果有特殊情况苹果会特别提醒比如 NSTimer 的描述中就写到 target The timer maintains a strong reference to this object until it (the timer) is invalidated. 。所以 UIGestureRecognizer 是不会对 target 强引用。一旦出了作用域target 对象就释放了。所以需要使用动态绑定强制持有。




类似的UIButton也一样的

extension UIButton {
    typealias MGButtonBlock = ((Any)->())
    // MARK:- RuntimeKey   动态绑属性
    // 改进写法【推荐】
    fileprivate struct RuntimeKey {
        static let mg_BtnBlockKey = UnsafeRawPointer.init(bitPattern: "mg_BtnBlockKey".hashValue)
        /// ...其他Key声明
    }
    
    convenience init(actionBlock: @escaping MGButtonBlock) {
        self.init()
        addActionBlock(actionBlock)
        addTarget(self, action: #selector(invoke(_:)), for: .touchUpInside)
    }
    
    fileprivate func addActionBlock(_ block: MGButtonBlock?) {
        if (block != nil) {
            objc_setAssociatedObject(self, UIButton.RuntimeKey.mg_BtnBlockKey, block!, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
        }
    }
    
    @objc fileprivate func invoke(_ sender: Any) {
        let block = objc_getAssociatedObject(self, UIButton.RuntimeKey.mg_BtnBlockKey) as? MGButtonBlock
        if (block != nil) {
            block!(sender);
        }
    }
    
    convenience init(imageName: UIImage, title: String,actionBlock: @escaping MGButtonBlock) {
        self.init(actionBlock: actionBlock)
        // 1.设置按钮的属性
        setImage(imageName, for: .normal)
        setTitle(title, for: UIControlState.normal)
        titleLabel?.font = UIFont.systemFont(ofSize: 14)
        setTitleColor(UIColor.darkGray, for: UIControlState.normal)
        sizeToFit()
    }
    
    convenience init(title: String,actionBlock: @escaping MGButtonBlock) {
        self.init(actionBlock: actionBlock)
        // 1.设置按钮的属性
        setTitle(title, for: UIControlState.normal)
        titleLabel?.font = UIFont.systemFont(ofSize: 14)
        setTitleColor(UIColor.darkGray, for: UIControlState.normal)
        sizeToFit()
    }
    
    convenience init(norImage:UIImage,pressImage: UIImage,actionBlock: @escaping MGButtonBlock) {
        self.init(actionBlock: actionBlock)
        // 1.设置按钮的属性
        setImage(norImage, for: .normal)
        setImage(pressImage, for: .highlighted)
    }
    
    convenience init(norImage:UIImage,selectedImage: UIImage,actionBlock: @escaping MGButtonBlock) {
        self.init(actionBlock: actionBlock)
        // 1.设置按钮的属性
        setImage(norImage, for: .normal)
        setImage(selectedImage, for: .selected)
    }
}



  • 轻轻点击关注我简书


  • 扫一扫关注我

扫一扫关注我.jpg

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消