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