好久不用,再次使用runtime重寫代碼。就用高性能添加圖片圓角來再一次實踐一下runtime的基本用法。runtime使用場景:- category添加關聯屬性- MethodSwizzle替換/交換系統方法平常使用cornerRadius和maskToBounds組合設置圓角
category添加關聯屬性
廢話不多說,直接上代碼創建一個`NSError`的category,添加一個`errorMsg`的屬性。因為category本身不能添加屬性,這里是使用runtime動態添加關聯屬性
MethodSwizzle交換系統方法
創建一個`UIImageView`的category,在分類中交換(exchange,注意這里不是替換replace)系統的`setImage:` 方法.我們要交換一個類的系統方法,那么首先想想在哪個方法中交換最合適呢?我們知道app在啟動之后,會進行類注冊,調用類的`+load()`方法,且只調用一次。(注意與`+initialize`的區別)[第51條:load與initialize的區別](http://polomana.com/2016/03/23/Effective-Objective-C-2-0-要點/)所以我們在分類中重寫`load`方法中添加交換方法。首先我們借用AFNetworking中AFURLSessionManager中定義的swizzle的內聯函數
{% codeblock lang:objc %}static inline void hl_swizzleSelector(Class theClass, SEL originalSelector, SEL swizzledSelector) {? ? Method originalMethod = class_getInstanceMethod(theClass, originalSelector);? ? Method swizzledMethod = class_getInstanceMethod(theClass, swizzledSelector);? ? method_exchangeImplementations(originalMethod, swizzledMethod);}static inline BOOL hl_addMethod(Class theClass, SEL selector, Method method) {? ? /*? ? YES if the method was added successfully, otherwise NO (for example, the class already contains a method implementation with that name)? ? */? ? return class_addMethod(theClass, selector,? method_getImplementation(method),? method_getTypeEncoding(method));}{% endcodeblock %}然后在`+load`方法中添加如下代碼:
```objc? hl_swizzleSelector([self class], @selector(setImage:), @selector(hl_setImage:));```
/* 這里參考的是AFURLSessionManager中的方式來添加方法,但是實際上在這個情況下是永遠返回NO的 * 原因:因為我們是在UIImageView的category中添加了hl_setImage:,所以在UIImageView中已經存在了該方法,再調用class_addMethod就會返回NO。 * 因此下面這些代碼如果寫在category中是不需要的了。 */```objc Method hlSetImageMethod = class_getInstanceMethod([self class],@selector(hl_setImage:)); BOOL result = hl_addMethod([self class],@selector(hl_setImage:), hlSetImageMethod); NSLog(@"%d",result); if (result) { }```測試一下代碼:```objcUIImageView * v = [[UIImageView alloc]initWithFrame:CGRectMake(0, 0, 240, 240)];v.center = self.view.center;[self.view addSubview:v];UIImage * image = [UIImage imageNamed:@"IMG_0730.jpg"];//會產生混合圖層v.layer.cornerRadius = v.frame.size.width / 2;v.layer.masksToBounds = YES;//采用UIGraphicImageContext重繪后,解決混合圖層問題[v setImage:[image hl_imageByCroppingForSize:CGSizeMake(240, 240) fillColor:[UIColor whiteColor]]];//使用Method swizzle交換setImage和hl_setImage:之后:v.image = image;```
其中的性能問題
我們在UIImage+HLAdd分類中使用UIGraphicImageContext來重新繪圖,這肯定是要耗時的操作。```objcCFTimeInterval start = CACurrentMediaTime();...//繪圖...NSLog(@"%f",CACurrentMediaTime() - start);``````c2016-11-22 17:53:42.614 iOS-Kick-On[47630:2598373] 0.003525```大概耗時在0.003~0.006之間,以納秒進行運算的cpu來說,還是一個稍微耗時的操作,因為我們可以把它放在后臺線程來做,然后采用回調來獲取返回的image```objcdispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{? ? UIImage * result = [self hl_imageByCroppingCorner:radius forSize:targetSize fillColor:[UIColor whiteColor]];? ? dispatch_async(dispatch_get_main_queue(), ^{? ? ? ? if (completion) {? ? ? ? ? ? completion(result);? ? ? ? }? ? });});```[本篇文章代碼](https://github.com/koalahl/UIImageViewHack)