博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
一些提高开发效率的 Category
阅读量:4451 次
发布时间:2019-06-07

本文共 4335 字,大约阅读时间需要 14 分钟。

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

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。

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

  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 编程概念》的 一节中,看到苹果有提到这么一句: 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

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

转载于:https://www.cnblogs.com/mysticCoder/p/5518723.html

你可能感兴趣的文章
DB太大?一键帮你收缩所有DB文件大小(Shrink Files for All Databases in SQL Server)
查看>>
二叉树
查看>>
Leetcode: Convert Sorted Array to Binary Search Tree
查看>>
#python#类和实例绑定属性和方法的总结
查看>>
C#委托事件的理解猫与老鼠的故事
查看>>
TS4
查看>>
tomcat server.xml配置详解
查看>>
java05
查看>>
前后端通信 (3种方式简单介绍)
查看>>
java条件语句练习题
查看>>
文件操作open,r,w,a三种模式
查看>>
开发工具安装密钥
查看>>
十一周总结学习笔记
查看>>
dubbo控制器xml文件报错
查看>>
extjs 控件属性
查看>>
HDU 6264(思维)
查看>>
延迟、延时、定时调用
查看>>
MongoDB的更新操作符
查看>>
TreeMap的学习
查看>>
【SICP练习】2 练习1.6
查看>>