前幾天遇到了一個由于分類引起的 Bug囚霸, 折騰了好久才找到根源點复斥,過程甚是糾結(jié)。于是對分類這塊有一些想法啦,想想平常是否對這塊遺漏了什么扔嵌?特此總結(jié)下。
- 分類 和 繼承 的區(qū)別
- 分類常用的寫法
- 使用分類時遇到的BUG
一夺颤、分類 和 繼承 的區(qū)別
昨天在寫一個類的擴展的時候痢缎,我第一反應(yīng)是用了繼承,然后事后被小伙伴說此處已經(jīng)有了寫好的分類了世澜,然后我就在想為什么我第一反應(yīng)會用繼承呢独旷?為什么不是用分類的呢?也就自然想到了我們面試經(jīng)典題: 分類 和 繼承 的區(qū)別寥裂。
網(wǎng)上的答案:
- 分類:分類是對一個功能完備的類的一種補充嵌洼,就像是一個東西的主要基本功能都完成了,可以用類別為這個類添加不同的組件封恰,使得這個類能夠適應(yīng)不同情況的需求麻养。比如animal這個類,具有 eat 和 run 等方法诺舔,想給這個類添加一個 bark 的方法鳖昌,可以用分類。
- 繼承:多個類具有相同的實例變量和方法時低飒,考慮用繼承许昨。即子類可以繼承父類的相同特性。如 animal 具有年齡和體重兩個屬性褥赊,dog 也具有年齡和體重兩 個屬性糕档,dog 可以繼承 animal的這兩個屬性,即為繼承拌喉。
- 區(qū)別:
1速那、分類是對方法的擴展,不能添加成員變量司光。繼承可以在原來父類的成員變量的基礎(chǔ)上琅坡,添加新的成員變量
2、分類只能添加新的方法残家,不能修改和刪除原來的方法榆俺。繼承可以增加、修改和刪除方法。
3茴晋、分類不提倡對原有的方法進行重載陪捷。繼承可以通過使用super對原來方法進行重載。
4诺擅、分類可以被繼承市袖,如果一個父類中定義了分類,那么其子類中也會繼承此分類烁涌。
而我個人感覺用時的簡單感受就是: ** 繼承修改老方法苍碟,分類添加新方法**。
#import <UIKit/UIKit.h>
@interface PQTextField : UITextField
@property (nonatomic, assign) CGFloat leftPadding; // 有左邊View 離邊框的距離
@property (nonatomic, assign) CGFloat rightPadding; // 有右邊View 離邊框的距離
@property (nonatomic, assign) CGFloat leftPlaceholderNormalPadding; //代表(沒有View)離左邊的距離
@end
#import "PQTextField.h"
@implementation PQTextField
- (instancetype)init {
if (self = [super init]) {
// 設(shè)置默認(rèn)的一些情況
_leftPadding = 8;
_rightPadding = 8;
_leftPlaceholderNormalPadding = 10;
}
return self;
}
// 左邊View 距離邊界的距離
- (CGRect)leftViewRectForBounds:(CGRect)bounds {
CGRect leftRect = [super leftViewRectForBounds:bounds];
leftRect.origin.x += _leftPadding;
return leftRect;
}
// 右邊View 距離邊界的距離
- (CGRect)rightViewRectForBounds:(CGRect)bounds {
CGRect rightRect = [super rightViewRectForBounds:bounds];
rightRect.origin.x -= _rightPadding;
return rightRect;
}
//UITextField 文字與輸入框的距離
- (CGRect)textRectForBounds:(CGRect)bounds{
if (self.leftView) {
return CGRectInset(bounds, _leftPadding * 2 + self.leftView.frame.size.width, 0);
}
return CGRectInset(bounds, _leftPlaceholderNormalPadding, 0);
}
//控制編輯文本的位置
- (CGRect)editingRectForBounds:(CGRect)bounds{
if (self.leftView) {
return CGRectInset(bounds, _leftPadding * 2 + self.leftView.frame.size.width, 0);
}
return CGRectInset(bounds, _leftPlaceholderNormalPadding, 0);
}
@end
上面這個就是我用到的一個繼承的例子撮执,對 UITextField 進行擴展微峰,重寫該類的方法。
二抒钱、分類常用的寫法
- 例子一:對 UIButton 點擊方法擴展 (直接增加方法)
#import <UIKit/UIKit.h>
typedef void (^PQClickButtonHandler)(UIButton *button);
@interface UIButton (PQHandler)
- (void)pq_addClickHandler:(PQClickButtonHandler)handler;
@end
#import "UIButton+PQHandler.h"
#import <objc/runtime.h>
NSString const *pq_button_handler = @"pq_button_handler";
@implementation UIButton (PQHandler)
- (void)pq_addClickHandler:(PQClickButtonHandler)handler {
objc_setAssociatedObject(self, &pq_button_handler, handler, OBJC_ASSOCIATION_COPY_NONATOMIC);
[self addTarget:self action:@selector(clickAction:) forControlEvents:UIControlEventTouchUpInside];
}
- (void)clickAction:(UIButton *)button {
PQClickButtonHandler handler = objc_getAssociatedObject(self, &pq_button_handler);
if (handler) {
handler(button);
}
}
@end
- 例子二:對 UIButton 點擊熱區(qū)域擴大 的擴展 (直接增加屬性)
#import <UIKit/UIKit.h>
@interface UIButton (PQTouchExtraInsets)
@property (nonatomic, assign) UIEdgeInsets pq_touchExtraInsets;
@end
#import "UIButton+PQTouchExtraInsets.h"
#import <objc/runtime.h>
@implementation UIButton (PQTouchExtraInsets)
- (void)setPq_touchExtraInsets:(UIEdgeInsets)pq_touchExtraInsets {
objc_setAssociatedObject(self, @selector(pq_touchExtraInsets), [NSValue valueWithUIEdgeInsets:pq_touchExtraInsets], OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (UIEdgeInsets)pq_touchExtraInsets {
return [objc_getAssociatedObject(self, _cmd) UIEdgeInsetsValue];
}
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
UIEdgeInsets touchAreaInsets = self.pq_touchExtraInsets;
CGRect bounds = self.bounds;
bounds = CGRectMake(bounds.origin.x - touchAreaInsets.left,
bounds.origin.y - touchAreaInsets.top,
bounds.size.width + touchAreaInsets.left + touchAreaInsets.right,
bounds.size.height + touchAreaInsets.top + touchAreaInsets.bottom);
return CGRectContainsPoint(bounds, point);
}
@end
- 例子三:對 UIButton 背景顏色添加狀態(tài) 的擴展 (擴展方法)
#import <UIKit/UIKit.h>
@interface UIButton (PQBackgroundColor)
- (void)pq_setBackgroundColor:(UIColor *)color state:(UIControlState)state;
@end
#import "UIButton+PQBackgroundColor.h"
@implementation UIButton (PQBackgroundColor)
- (void)pq_setBackgroundColor:(UIColor *)color state:(UIControlState)state {
[self setBackgroundImage:[self pq_imageWithColor:color] forState:state];
}
- (UIImage *)pq_imageWithColor:(UIColor *)color {
CGRect rect = CGRectMake(0.0f, 0.0f, 1.0f, 1.0f);
UIGraphicsBeginImageContext(rect.size);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context, [color CGColor]);
CGContextFillRect(context, rect);
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
@end
- 例子四:對 UIView 父UIViewController 的擴展
#import "UIView+PQViewController.h"
@implementation UIView (PQViewController)
- (UIViewController *)viewController {
//通過響應(yīng)者鏈蜓肆,取得此視圖所在的視圖控制器
UIResponder *next = self.nextResponder;
do {//判斷響應(yīng)者對象是否是視圖控制器類型
if ([next isKindOfClass:[UIViewController class]]) {
return (UIViewController *)next;
}
next = next.nextResponder;
}while(next != nil);
return nil;
}
@end
- 例子五:對 UIViewController dealloc 的擴展
#import "UIViewController+PQDealloc.h"
#import <objc/runtime.h>
@implementation UIViewController (PQDealloc)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];
Method setTextMethod = class_getInstanceMethod(class, NSSelectorFromString(@"dealloc"));
Method replaceSetTextMethod = class_getInstanceMethod(class, NSSelectorFromString(@"pq_dealloc"));
method_exchangeImplementations(setTextMethod, replaceSetTextMethod);
});
}
- (void)pq_dealloc {
NSLog(@"%@--->>>>已經(jīng)釋放了",[self class]);
[self pq_dealloc];
}
@end
上述需要注意的確實 Runtime。
- 注意 例子一 和 例子二處 _cmd 和 const void *key 的區(qū)別
- OBJC_ASSOCIATION_RETAIN_NONATOMIC 和 OBJC_ASSOCIATION_COPY_NONATOMIC 的區(qū)別
- Method_exchangeImplementations 和 AssociatedObject 的運用場景
- 給自己的方法添加上特殊名字(pq_), 避免覆寫系統(tǒng)類的方法
在此特別推薦一下 JKCategories谋币,里面中的分類真的超多仗扬,上述有幾個例子就是從此處學(xué)習(xí)的。另外蕾额,Objective-C 基礎(chǔ)類的一些實用 Category 這里面推薦的也可以看看早芭。
三、使用分類時遇到的BUG
使用 method_exchangeImplementations 時 replaceSetTextMethod 的設(shè)置不當(dāng)
一般這種設(shè)置不當(dāng)是由于某些特殊的場景導(dǎo)致的凡简, replaceSetTextMethod 里面的參數(shù)和判斷有變化而成的逼友。分類中 dealloc 設(shè)置錯了
當(dāng)然我遇到的這個是處于iOS8 某個版本中,這個 BUG:UITextField textInputView: message sent to deallocated instance, 就是 dealloc 方法中寫的有問題秤涩,當(dāng)初可也是莫名其妙的帜乞。
總的來說,一些莫名其妙的問題筐眷,例如根本不知道 崩 在什么地方情況下黎烈,就得考慮下是否是分類中有問題,或者說哪里運用到了一些 Runtime 的方案匀谣。
PS: Category 進一步理解
備注參考:
http://www.cnblogs.com/williamliuwen/p/5370155.html
https://github.com/shaojiankui/JKCategories
http://www.reibang.com/p/1c7d34dbf671