日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

Category 的一些事

發布時間:2023/12/19 编程问答 24 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Category 的一些事 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

?

來源:伯樂在線 - Tsui YuenHong

鏈接:http://ios.jobbole.com/90422/

點擊 → 申請加入伯樂在線專欄作者

?

新增實踐部分:偏方 Hook 進某些方法來添加功能

?

Category – 簡介

?

Category(類別)是 Objective-C 2.0 添加的新特性(十年前的新特性 ?)。其作用可以擴展已有的類, 而不必通過子類化已有類,甚至也不必知道已有類的源碼,還有就是分散代碼,使已有類的體積大大減少,也利于分工合作。

?

在蘋果開源項目中,我們可以下載相關的源碼來查看 category 的資料。

?

在 AFNetworking 和 SDWebImage 中也大量用到 category 來擴展已有類和分散代碼。

?

關于 category 的定義可以在 objc-runtime-new.h 中找到。由其定義可以看出 category 可以正常實現功能有:添加實例方法、類方法、協議、實例屬性。( 在后面的實踐中,發現類屬性也是可以添加的 )

?

struct category_t {

????const char *name;

????classref_t cls;

????struct method_list_t *instanceMethods;

????struct method_list_t *classMethods;

????struct protocol_list_t *protocols;

????struct property_list_t *instanceProperties;

?

????method_list_t *methodsForMeta(bool isMeta) {

????????if (isMeta) return classMethods;

????????else return instanceMethods;

????}

?

????property_list_t *propertiesForMeta(bool isMeta) {

????????if (isMeta) return nil; // classProperties;

????????else return instanceProperties;

????}

};

?

隨便說一句,本文并不主要注重 category 的實現細節和工作原理。關于細節的方面可以看相關文章 深入理解Objective-C:Category(上) ?深入理解Objective-C:Category(下) 和 結合 category 工作原理分析 OC2.0 中的 runtime 。

?


?

Category – 能做什么

?

首先,我們先來創建一個 Person 類以及 Person 類的 category,可以看得出 category 的文件名就是 已有類名+自定義名。

?

// Person.h

@interface Person : NSObject

?

@property (nonatomic, copy) NSString *name;

?

+ (void)run;

- (void)talk;

?

@end

?

// Person.m

@implementation Person

?

// 原實例方法

- (void)talk{

????NSLog(@"\n我是原實例方法\n我是%@",self.name);

}

?

// 原類方法

+ (void)run{

????NSLog(@"\n我是原類方法\n我是跑得很快的的香港記者");

}

?

@end

?

// Person+OtherSkills.h

@interface Person (OtherSkills){

????//?? instance variables may not be placed in categories

????//int i;

????//NSString *str;

}

?

// 添加實例屬性

@property (nonatomic, copy) NSString *otherName;

// 添加類屬性

@property (class, nonatomic, copy) NSString *clsStr;

?

// 重寫已有類方法

+ (void)run;

- (void)talk;

?

// 為已有類添加方法

- (void)logInstProp;

+ (void)logClsProp;

?

// Person+OtherSkills.m

static NSString *_clsStr = nil;

static NSString *_otherName = nil;

?

@implementation Person (OtherSkills)

?

@dynamic otherName;

?

// 重寫類方法

+ (void)run{

????// 警告?? Category is implementing a method which will also be implemented by its primary class

????NSLog(@"\n我是重寫方法\n我是跑得很快的的香港記者");

}

?

// 重寫實例方法

- (void)talk{

????// 警告?? Category is implementing a method which will also be implemented by its primary class

????NSLog(@"\n我是重寫方法\n我是會談笑風生的%@",self.otherName);

}

?

// 輸出實例屬性

- (void)logInstProp{

????NSLog(@"\n輸出實例屬性\n我是會談笑風生的%@",self.otherName);

}

?

// 輸出類屬性

+ (void)logClsProp{

????NSLog(@"\n輸出類屬性\n我是會談笑風生的%@",self.clsStr);

}

?

+ (NSString *)clsStr{

????return _clsStr;

}

?

+ (void)setClsStr:(NSString *)clsStr{

????_clsStr = clsStr;

}

?

- (NSString *)otherName{

????return _otherName;

}

?

- (void)setOtherName:(NSString *)otherName{

????_otherName = otherName;

}

?

創建完代碼之后,下面我們來看看 category 到底能干什么。

?

順便一提,我是在網上看到很多文章說 category 不能添加屬性,這是說法是不對的,如 Person+OtherSkills.h 中就添加了一個 otherName 的屬性。正確的說法應該是 category 不能添加實例變量,否則編譯器會報錯 instance variables may not be placed in categories。正常情況下,因為 category 不能添加實例變量,也會導致屬性的 setter & getter 方法不能正常工作。( 當然,可以利用 Runtime 為 category 動態關聯屬性,最后會介紹兩種使 category 屬性正常工作的方法)

?

category 可以為已有類添加實例屬性。

?

如 Person+OtherSkills.h 中就添加了一個 otherName 的屬性。可以出來能正常工作。

?

// 運行代碼

Person *p1 = [[Person alloc] init];

?

// 實例屬性

p1.otherName = @"小花";

[p1 logInstProp];

?

p1.otherName = @"小明";

[p1 logInstProp];

?

// 輸出結果

2016-09-11 09:45:09.935 category[37281:1509791]

輸出實例屬性

我是會談笑風生的小花

2016-09-11 09:45:09.936 category[37281:1509791]

輸出實例屬性

我是會談笑風生的小明

?

category 可以為已有類添加類屬性。

?

雖然,category_t 中是沒有定義 clssProperties,但是根據實際操作卻顯示 category 的確可以為已有類添加類屬性并且成功執行。

?

// 運行代碼

Person.clsStr = @"小東";

[Person logClsProp];

?

// 輸出結果

2016-09-11 09:45:09.936 category[37281:1509791]

輸出類屬性

我是會談笑風生的小東

?

category 可以為已有類添加實例方法和類方法。

?

在上面的兩個例子中已經體現了 category 可以為已有類添加實例方法和類方法。這里將討論加入 category 重寫了已有類的方法會怎么樣,在創建的代碼中我們已經重寫了 run 和 talk 方法,那這時我們來調用看看。

?

// 運行代碼

// 調用類方法

[Person run];

// 調用實例方法????

Person *p1 = [[Person alloc] init];

[p1 talk];

?

// 輸出結果

2016-09-11 11:22:05.817 category[37733:1562534]

我是重寫方法

我是跑得很快的的香港記者

2016-09-11 11:22:05.817 category[37733:1562534]

我是重寫方法

我是會談笑風生的(null)

?

可以看得出來,這時候無論是已有類中的類方法和實例方法都可以被 category 替換到其中的重寫方法,即使我現在是沒有導入 Person+OtherSkills.h 。這就帶來一個很嚴重的問題,如果在 category 中不小心重寫了已有類的方法將導致原方法無法正常執行。所以使用 category 添加方法時候請注意是否和已有類重名了,正如 《 Effective Objective-C 2.0 》 中的第 25 條所建議的:

?

在給第三方類添加 category 時添加方法時記得加上你的專有前綴

?

然而,因為 category 重寫方法是并不是替換掉原方法,而是往已有類中繼續添加方法,所以還是有機會去調用到原方法。這里利用 class_copyMethodList 獲取 Person 類的全部類方法和實例方法。

?

// 獲取 Person 的方法列表

unsigned int personMCount;

// 獲取實例方法

//Method *personMList = class_copyMethodList([Person class], &personMCount);

// 獲取類方法

Method *personMList = class_copyMethodList(object_getClass([Person class]), &personMCount);

NSMutableArray *mArr = [NSMutableArray array];

?

// 這里是倒序獲取,所以 mArr 第一個方法對應的是 Person 類中最后一個方法

for (int i = personMCount - 1; i >= 0; i--) {

?

?? SEL sel = NULL;

?? IMP imp = NULL;

?

?? Method method = personMList[i];

?? NSString *methodName = [NSString stringWithCString:sel_getName(method_getName(method))

???????????????????????????????????????????? encoding:NSUTF8StringEncoding];

?? [mArr addObject:methodName];

?

?? if ([@"run" isEqualToString:methodName]) {

?????? imp = method_getImplementation(method);

?????? sel = method_getName(method);

?????? ((void (*)(id, SEL))imp)(p1, sel); // 這里的 sel 有什么用呢 ?!

?????? //break;

?? }

}

?

free(personMList);

?

其中輸出的類方法和實例方法分別如下,顯示原方法的確可以被調用。

不過我這里有個疑問,使用 imp 時第二個參數 sel 到底有什么用呢?

?

2016-09-11 11:52:44.795 category[37893:1582677]

我是原類方法

我是跑得很快的的香港記者

2016-09-11 11:52:44.796 category[37893:1582677]

我是重寫方法

我是跑得很快的的香港記者

2016-09-11 11:52:44.796 category[37893:1582677] (

? ? run, // 原方法

? ? run, // 重寫方法

? ? "setClsStr:",

? ? logClsProp,

? ? clsStr

)

?

2016-09-11 11:54:14.545 category[37927:1584029]

我是原實例方法

我是(null)

2016-09-11 11:54:14.545 category[37927:1584029]

我是重寫方法

我是會談笑風生的(null)

2016-09-11 11:54:14.545 category[37927:1584029] (

? ? "setName:",

? ? name,

? ? ".cxx_destruct",

? ? "setOtherName:",

? ? logInstProp,

? ? tanxiaofengsheng,

? ? otherName,

? ? talk, //原方法

? ? talk ?//重寫方法

?


?

category 可以為已有類添加協議。

?

這里先添加一個新的 category,負責處理他談笑風生的行為,和寫個協議讓他上電視。

?

// Person+Delegate.h

#import "Person.h"

?

// 添加協議

@protocol PersonDelegate

?

- (void)showInTV;

?

@end

?

@interface Person (Delegate)

?

// 添加 delegate

@property (nonatomic, weak) id delegate;

?

- (void)tanxiaofengsheng;

?

@end

?

// Person+Delegate.m

#import "Person+Delegate.h"

#import

?

@implementation Person (Delegate)

?

- (id)delegate{

????return objc_getAssociatedObject(self, @selector(delegate));

}

?

- (void)setDelegate:(id)delegate{

????objc_setAssociatedObject(self, @selector(delegate), delegate, OBJC_ASSOCIATION_ASSIGN);

}

?

- (void)tanxiaofengsheng{

????for (int i = 0 ; i

?

在相應的代理里面添加 showInTV 的方法

?

// 運行代碼

Person *p1 = [[Person alloc] init];

p1.delegate = self;

?

// 開始談笑風生了

[p1 tanxiaofengsheng];

?

// ShowInTV 方法的實現

- (void)showInTV{

????UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(100, 100, 150, 150)];

????imageView.image = [UIImage imageNamed:@"naive.jpg"];

????[self.view addSubview:imageView];

}

?

這樣就利用 category 為已有類添加了協議。

?

關于 category 的基本應用就介紹到這里了。下面就來分享一下 category 的實踐中的使用。

?


?

Category – 實踐

?

偏方:Hook 進某些方法來添加功能

?

一般來說,為原方法添加功能都是利用 Runtime 來 Method Swizzling。不過這里也有個奇淫技巧來實現同樣的功能,例如我要在所有 VC 的 - (void)viewDidLoad 里面打印一個句話,就可以用 category 重寫已有類的方法,因為 category 重寫方法不是通過替換原方法來實現的,而是在原方法列表又增添一個新的同名方法,這就創造了機會給我們重新調用原方法了。

?

// 待 Hook 類

// ViewController.m

// 待替換方法 無參

- (void)viewDidLoad {

????[super viewDidLoad];

????[self testForHook:@"Hello World"];

????NSLog(@"執行原方法");

}

?

// 待替換方法 有參

- (void)testForHook:(NSString *)str1{

????NSLog(@"%@",str1);

}

?

// category 實現方法

// ViewController+HookOriginMethod.m

// category 重寫原方法

- (void)viewDidLoad {

????NSLog(@"HOOK SUCCESS! \n--%@-- DidLoad !",[self class]);

????IMP imp = [self getOriginMethod:@"viewDidLoad"];

????((void (*)(id, SEL))imp)(self, @selector(viewDidLoad));

}

?

// category 重寫原方法

- (void)testForHook:(NSString *)str1{

????NSLog(@"HOOK SUCCESS \n--%s-- 執行",_cmd);

????IMP imp = [self getOriginMethod:@"testForHook:"];

????((void (*)(id, SEL, ...))imp)(self, @selector(testForHook:), str1);

}

?

// 獲取原方法的 IMP

- (IMP)getOriginMethod:(NSString *)originMethod{

????// 獲取 Person 的方法列表

????unsigned int methodCount;

????// 獲取實例方法

????Method *VCMethodList = class_copyMethodList([self class], &methodCount);

?

????IMP imp = NULL;

?

????// 這里是倒序獲取,所以 mArr 第一個方法對應的是 Person 類中最后一個方法

????for (int i = methodCount - 1; i >= 0; i--) {

?

????????Method method = VCMethodList[i];

????????NSString *methodName = [NSString stringWithCString:sel_getName(method_getName(method))

??????????????????????????????????????????????????encoding:NSUTF8StringEncoding];

?

????????if ([originMethod isEqualToString:methodName]) {

????????????imp = method_getImplementation(method);

????????????break;

????????}

????}

?

????free(VCMethodList);

????return imp;

}

?

// 執行代碼

// ViewController.m

- (void)viewDidLoad {

????[super viewDidLoad];

????[self testForHook:@"Hello World"];

????NSLog(@"執行原方法");

}

?

// 輸出結果

2016-09-12 23:00:15.887 category[63655:2375379] HOOK SUCCESS!?

--ViewController-- DidLoad !

2016-09-12 23:00:15.888 category[63655:2375379] HOOK SUCCESS?

--testForHook:-- 執行

2016-09-12 23:00:15.889 category[63655:2375379] Hello World

2016-09-12 23:00:15.889 category[63655:2375379] 執行原方法

?

查看輸出結果,可以看得出來我們的 Hook 掉 viewDidLoad 來實現打印成功了。

?


?

UIButton 實現點擊事件可以“傳參”。

?

一般創建UIButton的時候都會使用 addTarget ...這個方法來為button添加點擊事件,不過這個方法有個不好的地方就是無法傳自己想要的參數。例如下面代碼中聲明了str,我的意圖是點擊button就使控制臺或者屏幕顯示str的內容。如果按照這樣來寫的我想到的解決辦法就是將str設置為屬性或者成員變量,不過這樣都是比較麻煩而且不直觀的(代碼分散)。

?

NSString *str = @"hi";

UIButton *button = [[UIButton alloc] initWithFrame:CGRectMake(100, 250, 150, 100)];

button.backgroundColor = [UIColor redColor];

[button addTarget:self action:@selector(click:) forControlEvents:UIControlEventTouchDown];

[self.view addSubview:button];

?

// 點擊事件

- (void)click:(UIButton *)button{

????...????

}

?

我想到較好的解決辦法應該在創建button,就為它設置具體的點擊響應事件。實現方法就是為 UIButton 添加 block 屬性或者添加可傳入 block 的方法。具體代碼如下:

?

// UIButton+Category.h

#import

?

typedef void(^ActionHandlerBlock)(void);

?

@interface UIButton (Category)

?

// 點擊響應的 block

@property (nonatomic, copy) ActionHandlerBlock actionHandlerBlock;

?

// 設置 UIButton 的點擊事件

- (void)kk_addActionHandler: (ActionHandlerBlock )actionHandlerBlock ForControlEvents:(UIControlEvents )controlEvents;

?

@end

?

// UIButton+Category.m

#import "UIButton+Category.h"

#import

?

static const void *kk_actionHandlerBlock = &kk_actionHandlerBlock;

?

@implementation UIButton (Category)

?

- (void)kk_addActionHandler:(ActionHandlerBlock)actionHandler ForControlEvents:(UIControlEvents)controlEvents{

?

????// 關聯 actionHandler

????objc_setAssociatedObject(self, kk_actionHandlerBlock, actionHandler, OBJC_ASSOCIATION_COPY_NONATOMIC);

?

????// 設置點擊事件

????[self addTarget:self action:@selector(handleAction) forControlEvents:controlEvents];

}

?

// 處理點擊事件

- (void)handleAction{

?

????ActionHandlerBlock actionHandlerBlock = objc_getAssociatedObject(self, kk_actionHandlerBlock);

?

????if (actionHandlerBlock) {

????????actionHandlerBlock();

????}

}

?

- (ActionHandlerBlock)actionHandlerBlock{

????return objc_getAssociatedObject(self, @selector(actionHandlerBlock));

}

?

- (void)setActionHandlerBlock:(ActionHandlerBlock)actionHandlerBlock{

????objc_setAssociatedObject(self, @selector(actionHandlerBlock), actionHandlerBlock, OBJC_ASSOCIATION_COPY_NONATOMIC);

}

?

@end

?

那現在我們來看看調用的結果,例如我現在想要的點擊事件是 button 顏色隨機變換。

?

UIButton *button = [[UIButton alloc] initWithFrame:CGRectMake(100, 250, 150, 100)];

button.backgroundColor = [UIColor redColor];

[self.view addSubview:button];

?

// 1. 通過實例方法傳入 block 來修改??

UIButton *button2 = [[UIButton alloc] initWithFrame:CGRectMake(100, 400, 150, 100)];

button2.backgroundColor = [UIColor redColor];

[button2 kk_addActionHandler:^{

?? button.backgroundColor = [UIColor colorWithRed:arc4random_uniform(256) / 255.0 green:arc4random_uniform(256) / 255.0 blue:arc4random_uniform(256) / 255.0 alpha:1.0];

} ForControlEvents:UIControlEventTouchDown];

[self.view addSubview:button2];

?

// 2. 通過修改 block 屬性來修改

UIButton *button3 = [[UIButton alloc] initWithFrame:CGRectMake(100, 550, 150, 100)];

button3.backgroundColor = [UIColor redColor];

button3.actionHandlerBlock = ^{

?? button.backgroundColor = [UIColor colorWithRed:arc4random_uniform(256) / 255.0 green:arc4random_uniform(256) / 255.0 blue:arc4random_uniform(256) / 255.0 alpha:1.0];

};

[button3 addTarget:self action:@selector(click:) forControlEvents:UIControlEventTouchUpInside];

[self.view addSubview:button3];

?

?

// 響應事件

- (void)click:(UIButton *)button{

????if (button.actionHandlerBlock) {

????????button.actionHandlerBlock();

????}

}

?

顯然,方法1和方法2在這個例子中實現的效果是相同的。不過,在不同場合這兩個方法適用的范圍也不同。

?

  • 直接調用實例方法傳入 block 會使代碼更加簡潔和集中,但不適合 block 需要傳值的情景。

  • 相反,設置 block 屬性要在 @selector() 中的方法中調用 block,比較麻煩,不過在需要的情況下可以傳入合適的參數。

  • ?

    p.s. 以后會繼續補充實踐部分。

    ?

    最后說一下,兩種使 category 屬性正常工作的方法:

    ?

  • 因為 category 不能創建實例變量,那就直接使用靜態變量,如最開始為 ohterName 和clsStr 屬性設置 setter & getter的做法。

  • 使用objc_setAssociatedObject,其中 key 的選擇有以下幾種,個人比較喜歡第四種。

    • static char *key1; // SDWebImage & AFNetworking 中的做法,比較簡單,而且 &key1 肯定唯一。key 取 &key1

    • static const char * const key2 = "key2"; // 網上看到的做法,指針不可變,指向內容不可變,但是這種情況必須在賦值確保 key2 指向內容的值是唯一。key 取 key2。

    • static const void *key3 = &key3; // 最取巧的方法,指向自己是為了不創建額外空間,而 const 修飾可以確保無法修改 key3 指向的內容。key 取 key3。

    • key 取 @selector(屬性名),最方便,輸入有提示,只要你確保屬性名添加上合適的前綴就不會出問題。

      ?

    ?

    總結

    以上是生活随笔為你收集整理的Category 的一些事的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。