一些提高开发效率的 Category

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

UIGestureRecognizer+Block

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

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

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

static const int target_key;
@implementation UIGestureRecognizer (Block)

+(instancetype)nvm_gestureRecognizerWithActionBlock:(NVMGestureBlock)block {
  return [[self alloc]initWithActionBlock:block];
}

- (instancetype)initWithActionBlock:(NVMGestureBlock)block {
  self = [self init];
  [self addActionBlock:block];
  [self addTarget:self action:@selector(invoke:)];
  return self;
}

- (void)addActionBlock:(NVMGestureBlock)block {
  if (block) {
    objc_setAssociatedObject(self, &target_key, block, OBJC_ASSOCIATION_COPY_NONATOMIC);
  }
}

- (void)invoke:(id)sender {
  NVMGestureBlock block = objc_getAssociatedObject(self, &target_key);
  if (block) {
    block(sender);
  }
}

@end

Github Gist

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

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

  1. 我一开始在类方法里面进行了动态绑定,错误代码如下:
//以下是错误代码:
+ (instancetype)initWithActionBlock:(KYGestureBlock)block {
  return [[self alloc] initWithTarget: [self _gestureRecognizerBlockTarget:block] selector:@selector(invoke:)];
}

+ (_KYGestureRecognizerBlockTarget *)_gestureRecognizerBlockTarget:(KYGestureBlock)block{
  _KYGestureRecognizerBlockTarget *target = objc_getAssociatedObject(self, &target_key);
  if (!target) {
    target = [[_KYGestureRecognizerBlockTarget alloc]initWithBlock:block];
    objc_setAssociatedObject(self, &target_key, target, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
  }
  return target;
}

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

  • 如果不使用动态绑定,使用如下的代码会产生怎样的结果?
//错误代码
+ (instancetype)initWithActionBlock:(KYGestureBlock)block {
  return [[self alloc] initWithTarget: [[_KYGestureRecognizerBlockTarget alloc]initWithBlock:block] selector:@selector(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 对象就释放了。所以,需要使用动态绑定强制持有。


UIView+ExtendTouchRect

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

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

实现代码如下:

void Swizzle(Class c, SEL orig, SEL new) {
  Method origMethod = class_getInstanceMethod(c, orig);
  Method newMethod = class_getInstanceMethod(c, new);
  if (class_addMethod(c, orig, method_getImplementation(newMethod), method_getTypeEncoding(newMethod))){
    class_replaceMethod(c, new, method_getImplementation(origMethod), method_getTypeEncoding(origMethod));
  } else {
    method_exchangeImplementations(origMethod, newMethod);
  }
}

@implementation UIView (ExtendTouchRect)

+ (void)load {
  Swizzle(self, @selector(pointInside:withEvent:), @selector(myPointInside:withEvent:));
}

- (BOOL)myPointInside:(CGPoint)point withEvent:(UIEvent *)event {
  if (UIEdgeInsetsEqualToEdgeInsets(self.touchExtendInset, UIEdgeInsetsZero) || self.hidden ||
      ([self isKindOfClass:UIControl.class] && !((UIControl *)self).enabled)) {
    return [self myPointInside:point withEvent:event]; // original implementation
  }
  CGRect hitFrame = UIEdgeInsetsInsetRect(self.bounds, self.touchExtendInset);
  hitFrame.size.width = MAX(hitFrame.size.width, 0); // don‘t allow negative sizes
  hitFrame.size.height = MAX(hitFrame.size.height, 0);
  return CGRectContainsPoint(hitFrame, point);
}

static char touchExtendInsetKey;
- (void)setTouchExtendInset:(UIEdgeInsets)touchExtendInset {
  objc_setAssociatedObject(self, &touchExtendInsetKey, [NSValue valueWithUIEdgeInsets:touchExtendInset],
                           OBJC_ASSOCIATION_RETAIN);
}

- (UIEdgeInsets)touchExtendInset {
  return [objc_getAssociatedObject(self, &touchExtendInsetKey) UIEdgeInsetsValue];
}

@end

Github Gist

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

时间: 2024-09-29 17:20:29

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

Android Studio添加Parcelable序列化小工具(快速提高开发效率)

Android Studio添加Parcelable序列化小工具(快速提高开发效率) Android Studio是google专门为开发Android提供的开发工具,在它内部可以直接的添加一些非常好用的开发小工具,这里就讲解怎样添加这些小工具,并且向大家推荐一个非常有用的对象传递时,必须要把对象序列化的接口Parcelable小工具; 这里先介绍下 Android中实现序列化的两个选择:一是实现Serializable接口(是JavaSE本身就支持的),一是实现Parcelable接口(是An

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

http://www.cnblogs.com/anderslly/archive/2009/02/16/vs2008-code-snippets.html http://www.cnblogs.com/jaic-xiao/archive/2008/10/14/Jie_Shao_Net_Gong_Ju_Code_Snippet_Yu_Sql_Server_2008_Gong_Ju_SSMS_Tools_Pack.html 前言 在谈谈VS中的模板中,我介绍了如何创建项目/项模板,这种方式可以在创建

iOS开发中使用宏定义提高开发效率

iOS开发中使用宏定义提高开发效率 (2013-07-10 10:47:33) 转载▼ iOS开发中,巧妙的使用宏定义,可以提高开发效率,本篇简单介绍一下宏的定义,设置,应用,并在未来实践中不断追加一些常用的宏定义. 调试Log iPhone应用程序开发调试的时候,在代码中加入NSLog的暴力调试方法是很频繁的,但是在release的时候要删除这些调试代码,那工作量是烦躁,这样的情况下,试用宏就会显得非常的方便. 看下面的例子: #ifdef DEBUG #define LOG(...) NSL

通过热部署提高开发效率

为什么用热部署? 我现在走的是java后端路线,在平时做项目或练习时有一个很重要的问题就是,把项目部署到服务器后调试,进行java代码修改后大部分情况下要重启服务器或重新部署,当项目比较大的时候,重启时间都要个几分钟,大大降低开发效率,后来接触到了热部署,大大提高开发效率,使用jrebel每年可以省去部署用的时间花费高达5.25个星期. 热部署的几种方法 我使用过几种IDE,netbeans,eclipse,MyEclipse与Intell IDEA,我记得netbeans是可以自己实现热部署的

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

原文:如何利用 Visual Studio 自带工具提高开发效率 Visual Stuido 是一款强大的Windows 平台集成开发工具,你是否好好地利用了它呢? 显示行号 有些时候(比如错误定位)的时候,显示行号将有利于我们进行快速定位. 如何显示 1. 工具 / 选项 / 文本编辑器 -> 选择对应的语言 2. 勾选 "行号" 使用书签 和平常意义的书签类似,当我们希望在日后某一时刻快速定位到一处代码时使用.比如在项目例会上,你需要演示本周你所做的一些改进,在开会前可以事先

“六神”——技术提高开发效率的一个方案

这个方案并不是我在系统设计方面的最早一次尝试.但它在提高开发效率方面,是效果最为显著的一个方案. 简介 "六神"框架提供了一套简单而通用的.从Web层到数据库操作(增加单个数据.删除单个数据.修改单个数据.查询单个数据.查分页列表.查不分页列表,六个操作,因此名为"六神")的基础组件.并且,它为复杂的数据库操作留下了扩展点. 在当时的技术背景下,这套框架使用Struts2.0+Spring+myBatis来实现.但是它的设计思路是可以适用于其它技术的. 在应用了这套

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

工欲善其事,必先利其器.对于程序员来说,Eclipse便是其中的一个“器”.本文会从Eclipse快捷键和实用技巧这两个篇章展开介绍.Eclipse快捷键用熟后,不用鼠标,便可进行编程开发,避免鼠标分了你的神.而掌握了Eclipse的一些实用技巧,则可以大大提高开发效率. 1.丢掉鼠标吧之Eclipse快捷键篇 1.1文件切换的三种方式 1.1.1  Ctrl + E,在右边显示出当前打开的所有文件 1.1.2 Ctrl + Pg Up ,Ctrl + Pg Dn ,逐个文件跳跃 按下Ctrl

Gson的详细使用(android必备,快速提高开发效率)

Gson的详细使用(android必备,快速提高开发效率) 接下来我要强烈给大家推荐一个google官方推荐使用的json解析库Gson.掌握好它之后,在以后的开发中就会减少非常多的代码,使用起来非常的方便. 概述: Gson是一个Java库,它不仅可以把Java对象转化为Json格式,它也能将一段Json格式的字符串转化为相对于的Java对象.Gson适用于所有Java对象,即使是那些你不知道源代码的对象. Gson的目标 提供简单易用的方法比如 toString() ,构造方法来转化JAVA

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

工欲善其事,必先利其器.对于程序员来说,Eclipse便是其中的一个“器”.本文会从Eclipse快捷键和实用技巧这两个篇章展开介绍.Eclipse快捷键用熟后,不用鼠标,便可进行编程开发,避免鼠标分了你的神.而掌握了Eclipse的一些实用技巧,则可以大大提高开发效率. 1.丢掉鼠标吧之Eclipse快捷键篇 1.1文件切换的三种方式 1.1.1  Ctrl + E,在右边显示出当前打开的所有文件 1.1.2 Ctrl + Pg Up ,Ctrl + Pg Dn ,逐个文件跳跃 按下Ctrl