深入category的高級使用及解惑 category的高級使用

category的高級使用

1. 分類爲什麼會覆蓋掉類的同名方法,對應的類方法是不存在了麼?
2. 怎麼解除分類對類方法的覆蓋?
3.category怎麼關聯對象的?
4.多個category,哪個方法優先執行?

Category是在Objective-C 2.0時提供的新的語言特性,其原因簡單,不管類設計的多麼完美,總有無法預測的狀況,Category就是作爲一種方式來擴展類的。

關於基礎部分,網上很多相關文檔可供查閱,此文不贅述。
作者只做分類的底層及高級用法作使用說明。

1. 分類爲什麼會覆蓋掉類的同名方法,對應的類方法是不存在了麼?

1. category的方法沒有“完全替換掉”原來類已經有的方法,也就是說如果category和原來類都有methodA,那麼category附加完成之後,類的方法列表裏會有兩個methodA
2. category的方法被放到了新方法列表的前面,而原來類的方法被放到了新方法列表的後面,這也就是我們平常所說的category的方法會“覆蓋”掉原來類的同名方法,這是因爲運行時在查找方法的時候是順着方法列表的順序查找的,它只要一找到對應名字的方法,就會罷休^_^,殊不知後面可能還有一樣名字的方法。

2. 怎麼解除分類對類方法的覆蓋?

/*
怎麼調用到原來類中被category覆蓋掉的方法?
對於這個問題,我們已經知道category其實並不是完全替換掉原來類的同名方法,只是category在方法列表的前面而已,所以我們只要順着方法列表找到最後一個對應名字的方法,就可以調用原來類的方法:
*/
+ (void)useClassMethodInsteadCayegoryMethod: (SEL)seletor {
    
    if (self) {
        unsigned int methodCount;
        Method *methodList = class_copyMethodList([self class], &methodCount);
        IMP lastImp = NULL;
        SEL lastSel = NULL;
        for (NSInteger i = 0; i < methodCount; i++) {
            Method method = methodList[i];
            NSString *methodName = [NSString stringWithCString:sel_getName(method_getName(method))
                                                      encoding:NSUTF8StringEncoding];
            NSString *selectorName = NSStringFromSelector(seletor);
            if ([selectorName isEqualToString:methodName]) {
                lastImp = method_getImplementation(method);
                lastSel = method_getName(method);
            }
        }
        typedef void (*fn)(id,SEL);
        
        if (lastImp != NULL) {
            fn f = (fn)lastImp;
            f(self,lastSel);
        }
        free(methodList);
    }
}

3.category怎麼關聯對象的?

#import <Foundation/Foundation.h>
#import "LearnCategoryClass.h"

@interface LearnCategoryClass (Addition)

@property(nonatomic, strong) NSString *newName;



#import "LearnCategoryClass+Addition.h"
#import <objc/runtime.h>

@implementation LearnCategoryClass (Addition)

- (void)testCategory {
    NSLog(@"分類裏是%@,方法名是%s",[self class], __PRETTY_FUNCTION__);
}

//運行時動態添加set和get方法(Xcode9 更新了提醒功能,超級牛逼。比如此處在分類裏聲明瞭屬性,但不添加set/get方法會warnning,這裏說下,厲害了我的蘋果)
- (void)setNewName:(NSString *)newName
{
    objc_setAssociatedObject(self,
                             "newName",
                             newName,
                             OBJC_ASSOCIATION_COPY);
}

- (NSString*)newName
{
    NSString *nameObject = objc_getAssociatedObject(self, "newName");
    return nameObject;
}
@end

4.多個category,哪個方法優先執行?

好吧,這其實不是一個問題,是一串問題。
可以分爲:

  1. category裏的方法是什麼時候註冊到Class的method_list的,在+load階段麼?
  1. 如果有多個category,怎麼辦?

我們知道,在類class和category中都可以有+load方法,那麼有兩個問題:

  1. 在class的+load方法調用的時候,我們可以調用category中聲明的方法麼?
  1. 這麼多個+load方法,調用順序是咋樣的呢?

答案是

  1. 可以調用,而且附加category到類的工作會先於+load方法的執行;
  2. +load的執行順序是先class,後category,而category的+load執行順序是根據編譯順序決定的。

可以這麼理解方法的調用。
有一個類似有壓棧入棧的棧結構,先把class裏的methodA添加到methodList中,然後添加分類中的方法,編譯器會從上到下找分類,先找到的分類就先放入methodList中,後找到的就後放入。
所以最終形成了一個Class裏的方法在最底層,最後編譯的分類在最上層的棧結構。
而方法的調用是從上到下執行的,冉調用對應的方法,就會從methodList裏找對應的SEL,找到就停止,所以Class和前面便輕易的分類雖然都在methodList裏,但因爲找到了就不會繼續查找。
以上就是方法掉用順序的原理。
當然,編譯順序可以在 Build Phases裏的Compile Source裏修改。

奇葩問答:

  • 如果在分類裏聲明瞭,但不實現,method_list裏不會有,當然也不會調用分類方法。但若是實現了,即使不在.h文件聲明,也會調用分類的方法;
  • 如果分類裏是- (void)testCategory,而Class裏是- (int)testCategory;返回值不同,也會覆蓋,因爲雖然返回值不同,但OC裏,這兩者依然是一個方法。
  • 對於多個分類,方法的調用是執行最後編譯的分類方法;

文末

關於category及其OC的深入研究,請轉移到Github—>OCDeepLearning
歡迎star和issues參與討論。

之於category的更底層實現,可以參照:參考文章

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章