一些提高开发效率的 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
原理就是把 block 动态地绑成 UIGestureRecognizer 的一个变量,invoke 的时候再调用这个 block。
开发过程中遇到了两个坑。
- 我一开始在类方法里面进行了动态绑定,错误代码如下:
//以下是错误代码:
+ (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(-, -, -, -)
实现代码如下:
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, ); // don't allow negative sizes
hitFrame.size.height = MAX(hitFrame.size.height, );
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
实现思路就是替换 pointInside:withEvent:
或者 hitTest:withEvent:
方法。顺便再复习一下响应链传递机制:当手指触摸屏幕,UIWindow 从最底层开始向上分发事件,每到一个视图,先调用 hitTest:withEvent:
,在 hitTest:withEvent:
里调用 pointInside:withEvent:
判断触摸点是否在当前区域,如果在,遍历它的子视图递归调用 hitTest:withEvent:
。画成二叉树图就是一个反向深度优先遍历,一旦找到第一个最深的包含触摸点的后裔就停止遍历。
一些提高开发效率的 Category的更多相关文章
- 如何利用 Visual Studio 自带工具提高开发效率
Visual Stuido 是一款强大的Windows 平台集成开发工具,你是否好好地利用了它呢? 显示行号 有些时候(比如错误定位)的时候,显示行号将有利于我们进行快速定位. 如何显示 1. 工具 ...
- 成吨提高开发效率:Intellij Shortcuts精简子集与思维模式
在线精简cheatsheet备查表:intellij.linesh.twGithub项目:intellij-mac-frequent-keymap Intellij的快捷键多而繁杂,从官方推荐的key ...
- 善用VS中的Code Snippet来提高开发效率
http://www.cnblogs.com/anderslly/archive/2009/02/16/vs2008-code-snippets.html http://www.cnblogs.com ...
- 实用手册:130+ 提高开发效率的 vim 常用命令
Vim 是从 vi 发展出来的一个文本编辑器.代码补完.编译及错误跳转等方便编程的功能特别丰富,在程序员中被广泛使用.和 Emacs 并列成为类 Unix 系统用户最喜欢的编辑器.这里收录了130+程 ...
- 提高开发效率的 Eclipse 实用操作
工欲善其事,必先利其器.对于程序员来说,Eclipse便是其中的一个“器”.本文会从Eclipse快捷键和实用技巧这两个篇章展开介绍.Eclipse快捷键用熟后,不用鼠标,便可进行编程开发,避免鼠标分 ...
- 10 款提高开发效率的 jQuery/CSS3 组件
前端开发是一项十分繁琐而又耗体力的工作,如何更有效率的开发我们的应用,很多人会选择适当地使用一些jQuery插件.今天就要给大家分享10款可以提高开发效率的jQuery/CSS3组件.部分插件可以下载 ...
- 能够提高开发效率的Eclipse实用操作
工欲善其事,必先利其器.对于程序员来说,Eclipse便是其中的一个“器”.本文会从Eclipse快捷键和实用技巧这两个篇章展开介绍.Eclipse快捷键用熟后,不用鼠标,便可进行编程开发,避免鼠标分 ...
- tomcat免重启随意更改java代码 提高开发效率
转载:http://developer.51cto.com/art/201012/241243.htm 做为了一个java开发人员,总是为因为要增加一个类,或是增加删除一个方法,甚至修改一个小处代码而 ...
- 能够提高开发效率的 Eclipse 实用操作
工欲善其事,必先利其器.对于程序员来说,Eclipse便是其中的一个“器”.本文会从Eclipse快捷键和实用技巧这两个篇章展开介绍.Eclipse快捷键用熟后,不用鼠标,便可进行编程开发,避免鼠标分 ...
随机推荐
- UWP app HelloWorld 的创建
步骤 1:在 Visual Studio 中创建新项目 启动 Visual Studio 2015 RC.将出现 Visual Studio 2015 RC 起始页. (从现在开始,我们将 Visua ...
- (转)Css样式兼容IE6,IE7,FIREFOX的写法
根据FF和IE对一些符号识别的差异,我们可以单独对FF以及IE定义样式,例子: 区别IE6与FF: background:orange;*background:blue; 区别I ...
- css.day01
1.CSS 初步认识 web 标准 w3c 结构 xhtml table p hr br td img a 表现 CSS CSS 最大的好处 就是让 结构和表现 ...
- web-请求无缓存
<head><META HTTP-EQUIV="pragma" CONTENT="no-cache"><META HTTP-EQU ...
- Phonegap 极光推送api 服务器端推送代码
.net 版本 极光推送 后台接口 HttpWebResponseUtility类 using System; using System.Collections.Generic; using Syst ...
- (转)ThinkPHP find方法 查询一条数据记录
find() ThinkPHP find() 方法是和 select() 用法类似的一个方法,不同之处 find() 查询出来的始终只有一条数据,即系统自动加上了 LIMIT 1 限制. 当确认查询的 ...
- 防止sql注入 参数化解决方案
StringBuilder strSql=new StringBuilder(); strSql.Append("insert into T_SysLog("); strSql.A ...
- Web服务图片压缩,nginx+lua生成缩略图
背景 目前而言,用移动端访问Web站点的用户越来越多,图片对流量的消耗是比较大的,之前一个用户用我们网站的app浏览的时候,2个小时耗去了2个G的流量,这是个很严重的问题,需要对图片进行压缩,减少对用 ...
- 【USACO 2.1.4】荷斯坦奶牛
[题目描述] 纪念“逝去”的Wecing 农民JOHN以拥有世界上最健康的奶牛为傲.他知道每种饲料中所包含的牛所需的最低的维他命量是多少.请你帮助农夫喂养他的牛,以保持它们的健康,使喂给牛的饲料的种数 ...
- 路由器to路由器
本文主要介绍一个主路由器连接两个子路由器的方法: 主路由器: 设置主路由器的开始ip地址为192.168.1.100,结束ip地址为192.168.150: 子路由器A: 第一步:LAN设置 第二步: ...