iOS 中 Category 的一些事

前幾天遇到了一個由于分類引起的 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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末照棋,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子武翎,更是在濱河造成了極大的恐慌烈炭,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,284評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件宝恶,死亡現(xiàn)場離奇詭異符隙,居然都是意外死亡趴捅,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,115評論 3 395
  • 文/潘曉璐 我一進店門霹疫,熙熙樓的掌柜王于貴愁眉苦臉地迎上來拱绑,“玉大人,你說我怎么就攤上這事丽蝎×圆Γ” “怎么了?”我有些...
    開封第一講書人閱讀 164,614評論 0 354
  • 文/不壞的土叔 我叫張陵屠阻,是天一觀的道長红省。 經(jīng)常有香客問我,道長栏笆,這世上最難降的妖魔是什么类腮? 我笑而不...
    開封第一講書人閱讀 58,671評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮蛉加,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘缸逃。我一直安慰自己针饥,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,699評論 6 392
  • 文/花漫 我一把揭開白布需频。 她就那樣靜靜地躺著丁眼,像睡著了一般。 火紅的嫁衣襯著肌膚如雪昭殉。 梳的紋絲不亂的頭發(fā)上苞七,一...
    開封第一講書人閱讀 51,562評論 1 305
  • 那天,我揣著相機與錄音挪丢,去河邊找鬼蹂风。 笑死,一個胖子當(dāng)著我的面吹牛乾蓬,可吹牛的內(nèi)容都是我干的惠啄。 我是一名探鬼主播,決...
    沈念sama閱讀 40,309評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼任内,長吁一口氣:“原來是場噩夢啊……” “哼撵渡!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起死嗦,我...
    開封第一講書人閱讀 39,223評論 0 276
  • 序言:老撾萬榮一對情侶失蹤趋距,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后越除,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體节腐,經(jīng)...
    沈念sama閱讀 45,668評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡外盯,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,859評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了铜跑。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片门怪。...
    茶點故事閱讀 39,981評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖锅纺,靈堂內(nèi)的尸體忽然破棺而出掷空,到底是詐尸還是另有隱情,我是刑警寧澤囤锉,帶...
    沈念sama閱讀 35,705評論 5 347
  • 正文 年R本政府宣布坦弟,位于F島的核電站,受9級特大地震影響官地,放射性物質(zhì)發(fā)生泄漏酿傍。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,310評論 3 330
  • 文/蒙蒙 一驱入、第九天 我趴在偏房一處隱蔽的房頂上張望赤炒。 院中可真熱鬧,春花似錦亏较、人聲如沸莺褒。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,904評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽遵岩。三九已至,卻和暖如春巡通,著一層夾襖步出監(jiān)牢的瞬間尘执,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,023評論 1 270
  • 我被黑心中介騙來泰國打工宴凉, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留誊锭,地道東北人。 一個月前我還...
    沈念sama閱讀 48,146評論 3 370
  • 正文 我出身青樓跪解,卻偏偏與公主長得像炉旷,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子叉讥,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,933評論 2 355

推薦閱讀更多精彩內(nèi)容

  • *面試心聲:其實這些題本人都沒怎么背,但是在上海 兩周半 面了大約10家 收到差不多3個offer,總結(jié)起來就是把...
    Dove_iOS閱讀 27,143評論 30 470
  • (一)Category category是Objective-C 2.0之后添加的語言特性窘行,別人口中的分類、類別其...
    小李龍彪閱讀 2,959評論 0 10
  • 1.項目經(jīng)驗 2.基礎(chǔ)問題 3.指南認(rèn)識 4.解決思路 ios開發(fā)三大塊: 1.Oc基礎(chǔ) 2.CocoaTouch...
    陽光的大男孩兒閱讀 4,986評論 0 13
  • 1.Difference between shallow copy and deep copy? 淺復(fù)制和深復(fù)制的...
    用心在飛閱讀 991評論 0 9
  • 我坐在高高的屋頂上图仓,手里一壺桂花釀罐盔,看著天上又大又圓,像個餅的月亮救崔。過一會兒是該練功了惶看,我這境界捏顺,是該提升一下了,...
    肖夢瑤閱讀 1,215評論 5 21