zoukankan      html  css  js  c++  java
  • runtime MethodSwizzle 实践之扩展 NIAttributedLabel

    runtime MethodeSwizzle 提供 简单的方法交换已知类的  Method IMP. 

    Method 可以是 外部可访问的 public 或者 private Method .所谓的属性或私有变量 也不过是 getter/setter Method 而已。

    MethodeSwizzle 技术 几乎可以实现你要使用 已知类的所有东西。

    so Powerful。

    代码实现:

    #import <Foundation/Foundation.h>
    
    @interface NSObject (Swizzle)
    + (void)swizzleInstanceSelector:(SEL)originalSelector
                    withNewSelector:(SEL)newSelector;
    @end
    #import "NSObject+Swizzle.h"
    #import <objc/runtime.h>
    
    @implementation NSObject (Swizzle)
    + (void) swizzleInstanceSelector:(SEL)originalSelector
                     withNewSelector:(SEL)newSelector
    {
        Method originalMethod = class_getInstanceMethod(self, originalSelector);
        Method newMethod = class_getInstanceMethod(self, newSelector);
        
        BOOL methodAdded = class_addMethod([self class],
                                           originalSelector,
                                           method_getImplementation(newMethod),
                                           method_getTypeEncoding(newMethod));
        
        if (methodAdded) {
            class_replaceMethod([self class],
                                newSelector,
                                method_getImplementation(originalMethod),
                                method_getTypeEncoding(originalMethod));
        } else {
            method_exchangeImplementations(originalMethod, newMethod);
        }
    }
    @end

    考虑通用性,这里使用NSObject 分类实现。

     MethodeSwizzle 应用之解决实际问题:

     最近使用

    NIAttributedLabel 实现 文本渲染,图文混排等功能。还是挺不错的。

    它提供简单的方法实现 插入文本链接, 设置delegate 回调 处理链接动作。

    NIAttributedLabel.m 内部实现,

    - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event 

    - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event 

    - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event 

    - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event 

    并在

    - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event 

     中检测是否触发链接,并触发回调。

    ///////////////////////////////////////////////////////////////////////////////////////////////////
    - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
      [super touchesEnded:touches withEvent:event];
    
      [self.longPressTimer invalidate];
      self.longPressTimer = nil;
    
      UITouch* touch = [touches anyObject];
      CGPoint point = [touch locationInView:self];
    
      if (nil != self.originalLink) {
        if ([self isPoint:point nearLink:self.originalLink]) {
          // This old-style method is deprecated, please update to the newer delegate method that supports
          // more data types.
          NIDASSERT(![self.delegate respondsToSelector:@selector(attributedLabel:didSelectLink:atPoint:)]);
    
          if ([self.delegate respondsToSelector:@selector(attributedLabel:didSelectTextCheckingResult:atPoint:)]) {
            [self.delegate attributedLabel:self didSelectTextCheckingResult:self.originalLink atPoint:point];
          }
        }
      }
    
      self.touchedLink = nil;
      self.originalLink = nil;
    
      [self setNeedsDisplay];
    }

    其中

     if (nil != self.originalLink) {
        if ([self isPoint:point nearLink:self.originalLink]) { 
    其中
    self.originalLink 用方法
    ///////////////////////////////////////////////////////////////////////////////////////////////////
    - (NSTextCheckingResult *)linkAtPoint:(CGPoint)point {
      if (!CGRectContainsPoint(CGRectInset(self.bounds, 0, -kVMargin), point)) {
        return nil;
      }
    
      CFArrayRef lines = CTFrameGetLines(self.textFrame);
      if (!lines) return nil;
      CFIndex count = CFArrayGetCount(lines);
    
      NSTextCheckingResult* foundLink = nil;
    
      CGPoint origins[count];
      CTFrameGetLineOrigins(self.textFrame, CFRangeMake(0,0), origins);
    
      CGAffineTransform transform = [self _transformForCoreText];
      CGFloat verticalOffset = [self _verticalOffsetForBounds:self.bounds];
    
      for (int i = 0; i < count; i++) {
        CGPoint linePoint = origins[i];
    
        CTLineRef line = CFArrayGetValueAtIndex(lines, i);
        CGRect flippedRect = [self getLineBounds:line point:linePoint];
        CGRect rect = CGRectApplyAffineTransform(flippedRect, transform);
    
        rect = CGRectInset(rect, 0, -kVMargin);
        rect = CGRectOffset(rect, 0, verticalOffset);
    
        if (CGRectContainsPoint(rect, point)) {
          CGPoint relativePoint = CGPointMake(point.x-CGRectGetMinX(rect),
                                              point.y-CGRectGetMinY(rect));
          CFIndex idx = CTLineGetStringIndexForPosition(line, relativePoint);
          foundLink = [self linkAtIndex:idx];
          if (foundLink) {
            return foundLink;
          }
        }
      }
      return nil;
    }

    获得。

    这两个条件成立,则触发链接,否则就返回了。??

    实际情况可能是 我要检测 链接是否触发,没有触发的话我要自定义动作。

    而且这两个方法还都是 NIAttributedLabel 类得私有方法, 举步维艰之际想到了强大的MethodSwizzle

    思路:在分类中 定义两个函数  然后分别与 NIAttributedLabel  中的以上两个方法 调换。

    #import "NIAttributedLabel+XYNIAttributedLabel.h"
    #import <objc/runtime.h>
    #import "NSObject+XYSwizzle.h"
    @implementation NIAttributedLabel (XYNIAttributedLabel)
    
    +(void)load{
        [self swizzleInstanceSelector:@selector(linkAtPoint:) withNewSelector:@selector(swizzleLinkAtPoint:)];
        [self swizzleInstanceSelector:@selector(isPoint:nearLink:) withNewSelector:@selector(swizzleIsPoint:nearLink:)];
    }
    -(BOOL)isTriggerLink:(CGPoint )point{
        NSTextCheckingResult *textCheckingResult = [self swizzleLinkAtPoint:point];
        if (nil != textCheckingResult) {
            if ([self swizzleIsPoint:point nearLink:textCheckingResult]) {
                return YES;
            }
        }
        return NO;
    }
    
    -(NSTextCheckingResult *)swizzleLinkAtPoint:(CGPoint)point{
        return  [self swizzleLinkAtPoint:point];
    }
    
    -(BOOL)swizzleIsPoint:(CGPoint)point nearLink:(NSTextCheckingResult *)link{
        BOOL resulte = [self swizzleIsPoint:point nearLink:link];
        return resulte;
    }
    @end

        :上面

    +(void)load 方法中 linkAtPoint 、isPoint:nearLink: 有可能会报编译器警告。无法找到相关sel ,因为它们是私有方法。不要理他,这个是在runtime 生效。
    我在demo 里有警告,但到了项目里好像没有了。不管它吧。

    并提供 

    -(BOOL)isTriggerLink:(CGPoint )point; 对外调用 检测是否触发链接。

     

    so。 问题得意 轻松解决。

    废话一句: 实例方法 在 类对象中保持。

    +(void)load{
        [self swizzleInstanceSelector:@selector(linkAtPoint:) withNewSelector:@selector(swizzleLinkAtPoint:)];
        [self swizzleInstanceSelector:@selector(isPoint:nearLink:) withNewSelector:@selector(swizzleIsPoint:nearLink:)];
    }

    注: 以上解决方案有一定风险,目前支持NimbusKit-AttributedLabel (1.0.0)

    ,如果被交换的NIAttributedLabel 方法名字被作者修改,项目又重新更新了库,则没有效果。

    以下是新增内容:

     *  新增功能:
     *
     *  1,检测是否触发链接
     *
     *  2,检测是否触发图片链接(原库中包含添加图文混排的方法,但如果没有链接文本,NIAttributedLabel 将会关闭用户交互)
     *
     *  3,判断NIAttributedLabel  是否包含图片
     *
     *  4,插入的图片支持图片链接,且可自定义触发图片链接的回调block
     *
     *  5,支持图片链接、文字链接 混用且数量不限,可以准确定位触发源并自定义block 回调处理

    demo 可以这里下载:git clone  https://github.com/githhhh/Test_Pod.git

     

  • 相关阅读:
    RESTful API 架构解读
    在阿里云 ECS 搭建 nginx https nodejs 环境(三、nodejs)
    在阿里云 ECS 搭建 nginx https nodejs 环境(二、https)
    linux 常见操作指令
    前端数据存储方案集合(cookie localStorage等)以及详解 (二)
    无法从其“Checked”属性的字符串表示形式“checked”创建“System.Boolean”类型
    VS2012使用验证控件出现[ASP.NET]WebForms UnobtrusiveValidationMode 需要 'jquery' 的 ScriptResourceMapping。請加入 ScriptResourceMapping 命名的 jquery (區分大小寫)。的解决办法。
    使用SHFB(Sandcastle Help File Builder)建立MSDN风格的代码文档
    常用正则表达式总结
    sql 中有关时间的语句
  • 原文地址:https://www.cnblogs.com/DamonTang/p/4137838.html
Copyright © 2011-2022 走看看