首页 > 代码库 > runtime MethodSwizzle 实践之扩展 NIAttributedLabel
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:)];}
以上解决方案有一定风险,如果被交换的NIAttributedLabel 方法名字被作者修改,项目又重新更新了库,则没有效果。
runtime MethodSwizzle 实践之扩展 NIAttributedLabel