Runtime 04 - 應(yīng)用(動(dòng)態(tài)創(chuàng)建類、交換方法)

Runtime 04 - 應(yīng)用(動(dòng)態(tài)創(chuàng)建類、交換方法)

動(dòng)態(tài)創(chuàng)建類

需要?jiǎng)?chuàng)建的類結(jié)構(gòu)如下

// 人的抽象模型
@interface Person : NSObject

@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) int age;

- (void)sayHello;
- (void)run;

@end

@implementation Person

- (void)sayHello {
    NSLog(@"Hi啊央,我是%@眶诈,我今年%d了!", self.name, self.age);
}

- (void)run {
    NSLog(@"我跑啊跑啊跑啊跑瓜饥。逝撬。。");
}

@end

// 歌唱家
@protocol Singer

- (void)sing;

@end

// 歌唱明星
@interface Star : Person <Singer>
{
    @public
    NSString *_company;
}
@end

@implementation Star

- (void)sing {
    NSLog(@"la la la so mi ~");
}

@end

動(dòng)態(tài)創(chuàng)建類的示例

先定義一些全局函數(shù)乓土,后面用作 Person 類的屬性方法:

// 從一個(gè) Class 對(duì)象中獲取實(shí)例變量
#define GetIvar(class, name) class_getInstanceVariable(class, name)

// Person 類 name 屬性的 getter
NSString* name(id self, SEL _cmd) {
    return object_getIvar(self, GetIvar([self class], "_name"));
}

// Person 類 name 屬性的 setter
void setName(id self, SEL _cmd, NSString *name) {
    return object_setIvar(self, GetIvar([self class], "_name"), name);
}

// Person 類 age 屬性的 getter
int age(id self, SEL _cmd) {
    return [object_getIvar(self, GetIvar([self class], "_age")) intValue];
}

// Person 類 age 屬性的 setter
void setAge(id self, SEL _cmd, int age) {
    return object_setIvar(self, GetIvar([self class], "_age"), @(age));
}

用 Runtime 動(dòng)態(tài)創(chuàng)建一個(gè) Person 類

void allocatePersonClass() {
    // 創(chuàng)建 Person 類
    Class class = objc_allocateClassPair([NSObject class], "Person", 0);

    // 為 name 屬性添加成員變量
    class_addIvar(class, "_name", sizeof(NSString*), 1, @encode(NSString));
    // 為 age 屬性添加成員變量
    class_addIvar(class, "_age", sizeof(int), 2, @encode(int));

    // 為 name 屬性添加 getter宪潮、setter
    class_addMethod(class, sel_registerName("name"), (IMP)name, "@16@0:8");
    class_addMethod(class, sel_registerName("setName:"), (IMP)setName, "v24@0:8@16");

    // 為 age 屬性添加 getter、setter
    class_addMethod(class, sel_registerName("age"), (IMP)age, "i16@0:8");
    class_addMethod(class, sel_registerName("setAge:"), (IMP)setAge, "v20@0:8i16");
    
    // 為 Person 類添加 name 屬性
    objc_property_attribute_t nameAttrs[] = {
        {"T", "@\"NSString\""}, // 類型:NSString
        {"N", ""},              // 原子性:nonatomic
        {"C", ""},              // 內(nèi)存管理:copy
        {"V", "_name"}          // 變量名:_name
    };
    class_addProperty(class, "name", nameAttrs, 4);

    // 為 Person 類添加 age 屬性
    const objc_property_attribute_t ageAttrs[] = {
        {"T", "i"},             // 類型:int
        {"N", ""},              // 原子性:nonatomic
        {"V", "_age"}           // 變量名:_age
    }; // 內(nèi)存管理:默認(rèn) assign
    class_addProperty(class, "age", ageAttrs, 3);

    // 為 Person 類添加一個(gè) sayHello 方法
    class_addMethod(class, sel_registerName("sayHello"), (IMP)sayHello, "v16@0:8");

    // 通過(guò) Block 的方式為 Person 類添加一個(gè) run 方法
    class_addMethod(class, sel_registerName("run"), imp_implementationWithBlock(^(id self, SEL _cmd) {
        NSLog(@"我跑啊跑啊跑啊跑帐我。。愧膀。");
    }), "v16@0:8");

    // 注冊(cè) Person 類
    objc_registerClassPair(class);
}

用 Runtime 動(dòng)態(tài)創(chuàng)建一個(gè) Singer 協(xié)議

void allocateSingerProtocol() {
    // 創(chuàng)建 Singer
    Protocol *protocol = objc_allocateProtocol("Singer");
    // 為 Singer 協(xié)議添加一個(gè) sing 實(shí)例方法
    protocol_addMethodDescription(protocol, sel_registerName("sing"), "v16@0:8", YES, YES);
    // 注冊(cè) Singer 協(xié)議
    objc_registerProtocol(protocol);
}

用 Runtime 動(dòng)態(tài)創(chuàng)建一個(gè) Star 類

void allocateStarClass() {
    // 創(chuàng)建 Star 類拦键,繼承自 Person 類
    Class class = objc_allocateClassPair(objc_getClass("Person"), "Star", 0);
    // 為 Star 類添加 Singer 協(xié)議
    class_addProtocol(class, objc_getProtocol("Singer"));

    // 為 Star 類添加一個(gè) _company 成員變量
    class_addIvar(class, "_company", sizeof(NSString*), 3, @encode(NSString));

    // 為 Start 類實(shí)現(xiàn) Singer 協(xié)議中的 sing 方法
    class_addMethod(class, sel_registerName("sing"), imp_implementationWithBlock(^(id self, SEL _cmd) {
        NSLog(@"la la la so mi ~");
    }), "v16@0:8");

    // 注冊(cè) Star 類
    objc_registerClassPair(class);
}

用 Runtime API 查看 Person 類的結(jié)構(gòu)

  • 查看成員變量:

    void lookIvars(Class class) {
        unsigned count;
        Ivar *ivars = class_copyIvarList(class, &count); // 成員變量列表
    
        for (int i = 0; i < count; i++) {
            Ivar ivar = ivars[i]; // 成員變量
            const char *name = ivar_getName(ivar); // 變量名
            const char *typeEncoding = ivar_getTypeEncoding(ivar); // 類型編碼
            ptrdiff_t offset = ivar_getOffset(ivar); // 偏移量
            printf("%s %s %zd\n", name, typeEncoding, offset);
        }
    
        free(ivars);
    }
    
  • 查看屬性:

    void lookProperties(Class class) {
        unsigned int count;
        objc_property_t *properties = class_copyPropertyList(class, &count); // 屬性列表
    
        for (int i = 0; i < count; i++) {
            objc_property_t property = properties[i]; // 屬性
            const char *name = property_getName(property); // 屬性名
            const char *attrs_desc = property_getAttributes(property); // 屬性描述
            printf("%s %s\n", name, attrs_desc);
    
            unsigned attrsCount;
            objc_property_attribute_t *attrs = property_copyAttributeList(property, &attrsCount); // 描述屬性的屬性列表
    
            for (int j = 0; j < attrsCount; j++) {
                objc_property_attribute_t attr = attrs[j]; // 描述屬性的屬性
                printf("%s %s\n", attr.name, attr.value); // name:名稱,value:值
            }
    
            printf("\n");
        }
    
        free(properties);
    }
    
  • 查看方法:

    void lookMethods(Class class) {
        unsigned int count;
        Method *methods = class_copyMethodList(class, &count); // 方法列表
    
        for (int i = 0; i < count; i++) {
            Method method = methods[i]; // 方法
            const char *name = sel_getName(method_getName(method)); // 方法名
            const char *typeEncoding = method_getTypeEncoding(method); // 類型編碼
            printf("%s %s\n", name, typeEncoding);
        }
    
        free(methods);
    }
    
allocatePersonClass();
Class class = objc_getClass("Person");

printf("\n---- 變量 ----\n");
lookIvars(class);

printf("\n---- 屬性 ----\n");
lookProperties(class);

printf("\n---- 方法 ----\n");
lookMethods(class);

通過(guò)打印結(jié)果可以看到動(dòng)態(tài)創(chuàng)建的類結(jié)構(gòu)正常檩淋。


使用 Star 創(chuàng)建對(duì)象芬为,并訪問(wèn)屬性、成員變量蟀悦、方法

allocatePersonClass();
allocateSingerProtocol();
allocateStarClass();

Class class = objc_getClass("Star");
id star = [[class alloc] init];

[star setValue:@"鍋蓋" forKey:@"name"];
NSString *name = [star valueForKey:@"name"];

[star setValue:@22 forKey:@"age"];
int age = [[star valueForKey:@"age"] intValue];

object_setIvar(star, GetIvar(class, "_company"), @22);
NSString *company = object_getIvar(star, GetIvar(class, "_company"));

NSLog(@"Star:%@, %d, %@", name, age, company);

[star performSelector:NSSelectorFromString(@"sayHello")];
[star performSelector:NSSelectorFromString(@"run")];
[star performSelector:NSSelectorFromString(@"sing")];

通過(guò)打印結(jié)果可以看到 Star 可以正常使用媚朦。


交換方法實(shí)現(xiàn)

需求是為所有 ViewController 的生命周期方法進(jìn)行埋點(diǎn),可以通過(guò) Runtime 已少量的代碼實(shí)現(xiàn)日戈,并且不需要在每個(gè) ViewController 的生命周期方法中增加代碼询张。

@interface UIViewController (Hook)

@end

@implementation UIViewController (Hook)

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^ {
        // hook:鉤子函數(shù)
        Method method1 = class_getInstanceMethod(self, @selector(viewDidLoad));
        Method method2 = class_getInstanceMethod(self, @selector(hook_viewDidLoad));
        method_exchangeImplementations(method1, method2);
    });
}

- (void)hook_viewDidLoad {
    // do someting...
    [self hook_viewDidLoad];
}

@end

objc_property_attribute_t

用來(lái)描述一個(gè)屬性的類型、讀寫(xiě)性浙炼、內(nèi)存管理份氧、原子性、變量名弯屈。

typedef struct {
     const char * _Nonnull name;
     const char * _Nonnull value;
 } objc_property_attribute_t;
  • 類型:

    • name:T
    • value:
      • 基本數(shù)據(jù)類型:i - int蜗帜,f - float,d - double资厉,q - NSInteger厅缺,Q - NSUInteger
      • id:@
      • 對(duì)象類型:@"ClassName",例如 @"NSString"
      • Block:@?
  • 讀寫(xiě)性:

    • name:readwrite - 無(wú)(默認(rèn))宴偿,readonly - R
    • value:""
  • 內(nèi)存管理:

    • name:assign - 無(wú)(基本數(shù)據(jù)類型時(shí)為默認(rèn))湘捎,strong - &(對(duì)象類型時(shí)為默認(rèn)),weak - W窄刘,copy - C
    • value:""
  • 原子性:

    • name:atomic - 無(wú)(默認(rèn))消痛,nonatomic - N
    • value:""
  • 變量名:

    • name:V
    • value:變量名

使用 property_getAttributes() 函數(shù)獲取的是一個(gè)屬性所有 objc_property_attribute_t 的字符串拼接:

  • 類型、讀寫(xiě)性都哭、內(nèi)存管理秩伞、原子性逞带、變量名。
  • (attr1.name + attr1.value), (attr2.name + attr2.value)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末纱新,一起剝皮案震驚了整個(gè)濱河市展氓,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌脸爱,老刑警劉巖遇汞,帶你破解...
    沈念sama閱讀 216,544評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異簿废,居然都是意外死亡空入,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)族檬,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)歪赢,“玉大人,你說(shuō)我怎么就攤上這事单料÷窨” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,764評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵扫尖,是天一觀的道長(zhǎng)白对。 經(jīng)常有香客問(wèn)我,道長(zhǎng)换怖,這世上最難降的妖魔是什么甩恼? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,193評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮沉颂,結(jié)果婚禮上媳拴,老公的妹妹穿的比我還像新娘。我一直安慰自己兆览,他們只是感情好屈溉,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,216評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著抬探,像睡著了一般子巾。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上小压,一...
    開(kāi)封第一講書(shū)人閱讀 51,182評(píng)論 1 299
  • 那天线梗,我揣著相機(jī)與錄音,去河邊找鬼怠益。 笑死仪搔,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的蜻牢。 我是一名探鬼主播烤咧,決...
    沈念sama閱讀 40,063評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼偏陪,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了煮嫌?” 一聲冷哼從身側(cè)響起笛谦,我...
    開(kāi)封第一講書(shū)人閱讀 38,917評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎昌阿,沒(méi)想到半個(gè)月后饥脑,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,329評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡懦冰,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,543評(píng)論 2 332
  • 正文 我和宋清朗相戀三年灶轰,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片刷钢。...
    茶點(diǎn)故事閱讀 39,722評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡笋颤,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出闯捎,到底是詐尸還是另有隱情椰弊,我是刑警寧澤许溅,帶...
    沈念sama閱讀 35,425評(píng)論 5 343
  • 正文 年R本政府宣布瓤鼻,位于F島的核電站,受9級(jí)特大地震影響贤重,放射性物質(zhì)發(fā)生泄漏茬祷。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,019評(píng)論 3 326
  • 文/蒙蒙 一并蝗、第九天 我趴在偏房一處隱蔽的房頂上張望祭犯。 院中可真熱鬧,春花似錦滚停、人聲如沸沃粗。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,671評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)最盅。三九已至,卻和暖如春起惕,著一層夾襖步出監(jiān)牢的瞬間涡贱,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,825評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工惹想, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留问词,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,729評(píng)論 2 368
  • 正文 我出身青樓嘀粱,卻偏偏與公主長(zhǎng)得像激挪,于是被迫代替她去往敵國(guó)和親辰狡。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,614評(píng)論 2 353