MG--Swift3.0 好用的分类


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

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

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)
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • • 深拷贝同浅拷贝的区别:浅拷贝是指针拷贝,对一个对象进行浅拷贝,相当于对指向对象的指针进行复制,产生一个新的指向...
    WSGNSLog阅读 5,033评论 0 1
  • UIKit 1.UIView 和 CALayer 是什么关系? UIView 继承 UIResponder,而 U...
    Sephiroth_Ma阅读 6,720评论 0 25
  • 父类实现深拷贝时,子类如何实现深度拷贝。父类没有实现深拷贝时,子类如何实现深度拷贝。• 深拷贝同浅拷贝的区别:浅拷...
    JonesCxy阅读 4,721评论 1 7
  • 作者si1ence2016.05.20 10:24* http://www.jianshu.com/p/bc3f8...
    Kiddz阅读 4,941评论 0 12
  • 1.1 谈一谈GCD和NSOperation的区别? 首先二者都是多线程相关的概念,当然在使用中也是根据不同情境进...
    John_LS阅读 5,105评论 0 12