最近工作陆续生产了一些方便开发的工具类,尽管最终没被收入使用,但不妨碍个人使用,故特此开一篇博文,也记录一些自己踩的坑。

UIGestureRecognizer+Block

简单来说,你可以这样使用 UIGestureRecognizer:

  1. [self.view addGestureRecognizer:[UITapGestureRecognizer gestureRecognizerWithActionBlock:^(id gestureRecognizer) {
  2. //...
  3. }]];

不再需要繁琐地使用 selector 反射,也解决了代码分离的问题。 实现代码如下:

  1. static const int target_key;
  2. @implementation UIGestureRecognizer (Block)
  3.  
  4. +(instancetype)nvm_gestureRecognizerWithActionBlock:(NVMGestureBlock)block {
  5. return [[self alloc]initWithActionBlock:block];
  6. }
  7.  
  8. - (instancetype)initWithActionBlock:(NVMGestureBlock)block {
  9. self = [self init];
  10. [self addActionBlock:block];
  11. [self addTarget:self action:@selector(invoke:)];
  12. return self;
  13. }
  14.  
  15. - (void)addActionBlock:(NVMGestureBlock)block {
  16. if (block) {
  17. objc_setAssociatedObject(self, &target_key, block, OBJC_ASSOCIATION_COPY_NONATOMIC);
  18. }
  19. }
  20.  
  21. - (void)invoke:(id)sender {
  22. NVMGestureBlock block = objc_getAssociatedObject(self, &target_key);
  23. if (block) {
  24. block(sender);
  25. }
  26. }
  27.  
  28. @end

Github Gist

原理就是把 block 动态地绑成 UIGestureRecognizer 的一个变量,invoke 的时候再调用这个 block。

开发过程中遇到了两个坑。

  1. 我一开始在类方法里面进行了动态绑定,错误代码如下:
  1. //以下是错误代码:
  2. + (instancetype)initWithActionBlock:(KYGestureBlock)block {
  3. return [[self alloc] initWithTarget: [self _gestureRecognizerBlockTarget:block] selector:@selector(invoke:)];
  4. }
  5.  
  6. + (_KYGestureRecognizerBlockTarget *)_gestureRecognizerBlockTarget:(KYGestureBlock)block{
  7. _KYGestureRecognizerBlockTarget *target = objc_getAssociatedObject(self, &target_key);
  8. if (!target) {
  9. target = [[_KYGestureRecognizerBlockTarget alloc]initWithBlock:block];
  10. objc_setAssociatedObject(self, &target_key, target, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
  11. }
  12. return target;
  13. }

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

  • 如果不使用动态绑定,使用如下的代码会产生怎样的结果?
  1. //错误代码
  2. + (instancetype)initWithActionBlock:(KYGestureBlock)block {
  3. return [[self alloc] initWithTarget: [[_KYGestureRecognizerBlockTarget alloc]initWithBlock:block] selector:@selector(invoke:)];
  4. }

结果就是出了这个作用域 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 对象就释放了。所以,需要使用动态绑定强制持有。


UIView+ExtendTouchRect

一行代码扩大视图点击区域:

  1. self.button.touchExtendInset = UIEdgeInsetsMake(-, -, -, -)

实现代码如下:

  1. void Swizzle(Class c, SEL orig, SEL new) {
  2. Method origMethod = class_getInstanceMethod(c, orig);
  3. Method newMethod = class_getInstanceMethod(c, new);
  4. if (class_addMethod(c, orig, method_getImplementation(newMethod), method_getTypeEncoding(newMethod))){
  5. class_replaceMethod(c, new, method_getImplementation(origMethod), method_getTypeEncoding(origMethod));
  6. } else {
  7. method_exchangeImplementations(origMethod, newMethod);
  8. }
  9. }
  10.  
  11. @implementation UIView (ExtendTouchRect)
  12.  
  13. + (void)load {
  14. Swizzle(self, @selector(pointInside:withEvent:), @selector(myPointInside:withEvent:));
  15. }
  16.  
  17. - (BOOL)myPointInside:(CGPoint)point withEvent:(UIEvent *)event {
  18. if (UIEdgeInsetsEqualToEdgeInsets(self.touchExtendInset, UIEdgeInsetsZero) || self.hidden ||
  19. ([self isKindOfClass:UIControl.class] && !((UIControl *)self).enabled)) {
  20. return [self myPointInside:point withEvent:event]; // original implementation
  21. }
  22. CGRect hitFrame = UIEdgeInsetsInsetRect(self.bounds, self.touchExtendInset);
  23. hitFrame.size.width = MAX(hitFrame.size.width, ); // don't allow negative sizes
  24. hitFrame.size.height = MAX(hitFrame.size.height, );
  25. return CGRectContainsPoint(hitFrame, point);
  26. }
  27.  
  28. static char touchExtendInsetKey;
  29. - (void)setTouchExtendInset:(UIEdgeInsets)touchExtendInset {
  30. objc_setAssociatedObject(self, &touchExtendInsetKey, [NSValue valueWithUIEdgeInsets:touchExtendInset],
  31. OBJC_ASSOCIATION_RETAIN);
  32. }
  33.  
  34. - (UIEdgeInsets)touchExtendInset {
  35. return [objc_getAssociatedObject(self, &touchExtendInsetKey) UIEdgeInsetsValue];
  36. }
  37.  
  38. @end

Github Gist

实现思路就是替换 pointInside:withEvent: 或者 hitTest:withEvent: 方法。顺便再复习一下响应链传递机制:当手指触摸屏幕,UIWindow 从最底层开始向上分发事件,每到一个视图,先调用 hitTest:withEvent: ,在 hitTest:withEvent: 里调用 pointInside:withEvent: 判断触摸点是否在当前区域,如果在,遍历它的子视图递归调用 hitTest:withEvent:。画成二叉树图就是一个反向深度优先遍历,一旦找到第一个最深的包含触摸点的后裔就停止遍历。

一些提高开发效率的 Category的更多相关文章

  1. 如何利用 Visual Studio 自带工具提高开发效率

    Visual Stuido 是一款强大的Windows 平台集成开发工具,你是否好好地利用了它呢? 显示行号 有些时候(比如错误定位)的时候,显示行号将有利于我们进行快速定位. 如何显示 1. 工具 ...

  2. 成吨提高开发效率:Intellij Shortcuts精简子集与思维模式

    在线精简cheatsheet备查表:intellij.linesh.twGithub项目:intellij-mac-frequent-keymap Intellij的快捷键多而繁杂,从官方推荐的key ...

  3. 善用VS中的Code Snippet来提高开发效率

    http://www.cnblogs.com/anderslly/archive/2009/02/16/vs2008-code-snippets.html http://www.cnblogs.com ...

  4. 实用手册:130+ 提高开发效率的 vim 常用命令

    Vim 是从 vi 发展出来的一个文本编辑器.代码补完.编译及错误跳转等方便编程的功能特别丰富,在程序员中被广泛使用.和 Emacs 并列成为类 Unix 系统用户最喜欢的编辑器.这里收录了130+程 ...

  5. 提高开发效率的 Eclipse 实用操作

    工欲善其事,必先利其器.对于程序员来说,Eclipse便是其中的一个“器”.本文会从Eclipse快捷键和实用技巧这两个篇章展开介绍.Eclipse快捷键用熟后,不用鼠标,便可进行编程开发,避免鼠标分 ...

  6. 10 款提高开发效率的 jQuery/CSS3 组件

    前端开发是一项十分繁琐而又耗体力的工作,如何更有效率的开发我们的应用,很多人会选择适当地使用一些jQuery插件.今天就要给大家分享10款可以提高开发效率的jQuery/CSS3组件.部分插件可以下载 ...

  7. 能够提高开发效率的Eclipse实用操作

    工欲善其事,必先利其器.对于程序员来说,Eclipse便是其中的一个“器”.本文会从Eclipse快捷键和实用技巧这两个篇章展开介绍.Eclipse快捷键用熟后,不用鼠标,便可进行编程开发,避免鼠标分 ...

  8. tomcat免重启随意更改java代码 提高开发效率

    转载:http://developer.51cto.com/art/201012/241243.htm 做为了一个java开发人员,总是为因为要增加一个类,或是增加删除一个方法,甚至修改一个小处代码而 ...

  9. 能够提高开发效率的 Eclipse 实用操作

    工欲善其事,必先利其器.对于程序员来说,Eclipse便是其中的一个“器”.本文会从Eclipse快捷键和实用技巧这两个篇章展开介绍.Eclipse快捷键用熟后,不用鼠标,便可进行编程开发,避免鼠标分 ...

随机推荐

  1. .NET使用js验证服务器控件

    <asp:TextBox ID="txtName" runat="server" Width="150px" CssClass=&qu ...

  2. C#操作word封装

    在项目中添加Microsoft.Office.Interop.Word.dll引用 Codepublic  class  WordAPI{    private object _template;   ...

  3. angular.js学习手册(二)

    如何使用angularjs? 各个 angular.js 版本下载: https://github.com/angular/angular.js/releases 下载完之后,在你需要使用angula ...

  4. 0.0C语言重点问题回顾

    左值和右值得区别:左值是用来表明变量的身份的,右值更加侧重于值本身: void*是个例外,它只有基地址没有类型信息,所以无法解引用. int *p = malloc(100); char *s = m ...

  5. 使用gdb调试(转: http://www.cnblogs.com/luchen927/archive/2012/02/07/2339003.html)

    一般来说GDB主要调试的是C/C++的程序.要调试C/C++的程序,首先在编译时,我们必须要把调试信息加到可执行文件中.使用编译器(cc/gcc/g++)的 -g 参数可以做到这一点.如: > ...

  6. 用jquery修改默认的单选框radio或者复选框checkbox选择框样式

    默认的radio和checkbox选框很难看.我去看了一下qq注册的页面.发现单选和复选框并没有用<input>,居然是用是A标签.然后用css背景图片展示选择框,用JavaScript控 ...

  7. 7 Reverse Integer(数字反转Easy)

    题目意思:int数字反转 考虑:越界问题 class Solution { public: int reverse(int x) { ; while(x){ ans=ans*+x%; x=x/; } ...

  8. C#委托基础

    转载自 http://woshixy.blog.51cto.com/5637578/1070976     C#委托基础1——委托基础   委托和其委托的方法必须具有相同的签名.签名相同:1.参数类型 ...

  9. href 里面 链接前面加/与不加的区别?(绝对路径与相对路径)

    在写href链接时,有绝对路径与相对路径,href 里面 链接前面加/与不加的区别? href="/cp/images/lis.jpg" 相对路径 cp前面/会获取当前路径,组合成 ...

  10. MySql存储过程—3、变量

    1.变量的定义 在Mysql里面可以像我们写代码中一样定义变量来保持中间结果,看下面的格式: DECLARE variable_name datatype(size) DEFAULT default_ ...