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)
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,657评论 6 505
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,889评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,057评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,509评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,562评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,443评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,251评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,129评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,561评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,779评论 3 335
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,902评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,621评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,220评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,838评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,971评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,025评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,843评论 2 354

推荐阅读更多精彩内容

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