Runtime

Runtime基礎(chǔ)

Objective-C是一門動(dòng)態(tài)性比較強(qiáng)的編程語(yǔ)言,跟C、C++等語(yǔ)言有著很大的不同,Objective-C的動(dòng)態(tài)性是由Runtime API來支撐的,Runtime API提供的接口基本都是C語(yǔ)言的,源碼由C\C++\匯編語(yǔ)言編寫

什么是Runtime?平時(shí)項(xiàng)目中有用過么?

OC是一門動(dòng)態(tài)性比較強(qiáng)的編程語(yǔ)言,允許很多操作推遲到程序運(yùn)行時(shí)再進(jìn)行,OC的動(dòng)態(tài)性就是由Runtime來支撐和實(shí)現(xiàn)的,Runtime是一套C語(yǔ)言的API,封裝了很多動(dòng)態(tài)性相關(guān)的函數(shù),平時(shí)編寫的OC代碼,底層都是轉(zhuǎn)換成了Runtime API進(jìn)行調(diào)用

具體應(yīng)用

利用關(guān)聯(lián)對(duì)象(AssociatedObject)給分類添加屬性
遍歷類的所有成員變量(修改textfield的占位文字顏色、字典轉(zhuǎn)模型、自動(dòng)歸檔解檔)
交換方法實(shí)現(xiàn)(交換系統(tǒng)的方法)
利用消息轉(zhuǎn)發(fā)機(jī)制解決方法找不到的異常問題

isa指針

要想學(xué)習(xí)Runtime,首先要了解它底層的一些常用數(shù)據(jù)結(jié)構(gòu),比如isa指針,在arm64架構(gòu)之前,isa就是一個(gè)普通的指針,存儲(chǔ)著Class、Meta-Class對(duì)象的內(nèi)存地址,從arm64架構(gòu)開始,對(duì)isa進(jìn)行了優(yōu)化,變成了一個(gè)共用體(union)結(jié)構(gòu),還使用位域來存儲(chǔ)更多的信息

// 掩碼,一般用來按位與(&)運(yùn)算的
#define TallMask 1
#define RichMask 2
#define HandsomeMask 4

#define TallMask 0b00000001
#define RichMask 0b00000010
#define HandsomeMask 0b00000100

#define TallMask (1<<0)
#define RichMask (1<<1)
#define HandsomeMask (1<<2)

設(shè)置一個(gè)setter和getter的bool類型

_tallRichHansome = 0b00000100;

- (void)setTall:(BOOL)tall
{
    if (tall) {
        _tallRichHansome |= TallMask;  //按位或
    } else {
        _tallRichHansome &= ~TallMask;  ////按位取反然后與
    }
}

- (BOOL)isTall
{
    return !!(_tallRichHansome & TallMask);  //按位與之后取兩次反
}

優(yōu)化方法,設(shè)置結(jié)構(gòu)體位域

// 位域
struct {
    char tall : 1;
    char rich : 1;
    char handsome : 1;
} _tallRichHandsome;

- (void)setTall:(BOOL)tall
{
    _tallRichHandsome.tall = tall;
}

return _tallRichHandsome.tall
直接取值會(huì)有一個(gè)問題就是得到的是一個(gè)二進(jìn)制位,如果得到的是 0b1,會(huì)變成0b1111 1111(會(huì)把1當(dāng)做符號(hào)位了),所以用取反去解決,因?yàn)?-1 是非0的,所以返回 YES
- (BOOL)isTall
{
    return !!_tallRichHandsome.tall;
}

優(yōu)化方法,union(共用體)

1.在存儲(chǔ)多個(gè)成員信息時(shí),編譯器會(huì)自動(dòng)給struct每個(gè)成員分配存儲(chǔ)空間,struct 可以存儲(chǔ)多個(gè)成員信息,而Union每個(gè)成員會(huì)用同一個(gè)存儲(chǔ)空間,只能存儲(chǔ)最后一個(gè)成員的信息。

2.都是由多個(gè)不同的數(shù)據(jù)類型成員組成,但在任何同一時(shí)刻,Union只存放了一個(gè)被先選中的成員,而結(jié)構(gòu)體的所有成員都存在。

3.對(duì)于Union的不同成員賦值,將會(huì)對(duì)其他成員重寫,原來成員的值就不存在了,而對(duì)于struct 的不同成員賦值 是互不影響的。

4.當(dāng)共用體中存儲(chǔ)的數(shù)據(jù)不一樣的時(shí)候,eg:存儲(chǔ)1和1000,這個(gè)時(shí)候如果賦值兩個(gè),取值其中一個(gè)的話,是得不到準(zhǔn)確值的

注:在很多地方需要對(duì)結(jié)構(gòu)體的成員變量進(jìn)行修改。只是部分成員變量,那么就不能用聯(lián)合體Union,因?yàn)閁nion的所有成員變量占一個(gè)內(nèi)存。eg:在鏈表中對(duì)個(gè)別數(shù)值域進(jìn)行賦值就必須用struct。

union {
    int bits;
    
    //增加可讀性,其實(shí)并沒有使用,只使用了bits
    struct {
        char tall : 1;
        char rich : 1;
        char handsome : 1;
        char thin : 1;
    };
} _tallRichHandsome;

- (void)setTall:(BOOL)tall
{
    if (tall) {
        _tallRichHandsome.bits |= TallMask;
    } else {
        _tallRichHandsome.bits &= ~TallMask;
    }
}

- (BOOL)isTall
{
    return !!(_tallRichHandsome.bits & TallMask);
}

所以 arm64下的isa結(jié)構(gòu)是這樣的


isa結(jié)構(gòu).png

Class、meta-class的地址值,最后三位永遠(yuǎn)都是0,比如:

ViewController -> 0x106331e28,8 = 0x1000

NSObject -> 0x106331e50,0 = 0x0000

位運(yùn)算

定義一個(gè)枚舉

typedef enum {
//    OptionsNone = 0,    // 0b0000
    OptionsOne = 1<<0,   // 0b0001
    OptionsTwo = 1<<1,   // 0b0010
    OptionsThree = 1<<2, // 0b0100
    OptionsFour = 1<<3   // 0b1000
} Options;

設(shè)置對(duì)應(yīng)的變量
- (void)setOptions:(Options)options
{
    if (options & OptionsOne) {
        NSLog(@"包含了OptionsOne");
    }
    
    if (options & OptionsTwo) {
        NSLog(@"包含了OptionsTwo");
    }
    
    if (options & OptionsThree) {
        NSLog(@"包含了OptionsThree");
    }
    
    if (options & OptionsFour) {
        NSLog(@"包含了OptionsFour");
    }
}

調(diào)用
[self setOptions: OptionsOne | OptionsFour];

類的結(jié)構(gòu)

class的結(jié)構(gòu)如下圖所示


class的結(jié)構(gòu).png
class_rw_t

class_rw_t里面的methods、properties、protocols是二維數(shù)組,是可讀可寫的,包含了類的初始內(nèi)容、分類的內(nèi)容


class_rw_t結(jié)構(gòu).png
class_ro_t

class_ro_t里面的baseMethodList、baseProtocols、ivars、baseProperties是一維數(shù)組,是只讀的,包含了類的初始內(nèi)容

class_ro_t結(jié)構(gòu).png
method_t

method_t是對(duì)方法\函數(shù)的封裝


method_t結(jié)構(gòu)體.png

IMP代表函數(shù)的具體實(shí)現(xiàn)


IMP.png

SEL代表方法\函數(shù)名,一般叫做選擇器,底層結(jié)構(gòu)跟char *類似
可以通過@selector()和sel_registerName()獲得
可以通過sel_getName()和NSStringFromSelector()轉(zhuǎn)成字符串
不同類中相同名字的方法,所對(duì)應(yīng)的方法選擇器是相同的

image.png
Type Encoding

iOS中提供了一個(gè)叫做@encode的指令,可以將具體的類型表示成字符串編碼


Type Encoding.png

方法緩存

Class內(nèi)部結(jié)構(gòu)中有個(gè)方法緩存(cache_t),用散列表(哈希表)來緩存曾經(jīng)調(diào)用過的方法,可以提高方法的查找速度


cache_t結(jié)構(gòu).png

緩存查找

objc-cache.mm
bucket_t * cache_t::find(cache_key_t k, id receiver)

首先聲明一下,@selector(test)是SEL類型,我們可以理解為一個(gè)字符串,標(biāo)識(shí)作用
緩存具體實(shí)現(xiàn)就是將@selector(test)這個(gè)當(dāng)做key與散列表的長(zhǎng)度(mask)的值做&運(yùn)算,得出的結(jié)果就是散列表的下標(biāo),然后將方法存儲(chǔ)在對(duì)應(yīng)下標(biāo)的value里面,下次再次取這個(gè)值的時(shí)候依然做&運(yùn)算,就可以做到O(1)復(fù)雜度的速度。有種情況兩個(gè)方法獲取的key是相同的,arm64是向上查找,x86_64是向下查找,如果獲取的結(jié)果是重復(fù)的,那么會(huì)發(fā)現(xiàn)key和傳入的方法不匹配,則會(huì)根據(jù)規(guī)則向上或者向下查找,查找到盡頭則會(huì)循環(huán)。如果為空,則填入方法。如果列表已經(jīng)滿了,則會(huì)2倍擴(kuò)容散列表,擴(kuò)容后會(huì)清空緩存列表。

objc_msgSend

OC中的方法調(diào)用,其實(shí)都是轉(zhuǎn)換為objc_msgSend函數(shù)的調(diào)用

objc_msgSend.png
objc_msgSend([Person class], @selector(initialize));
消息接收者(receiver):[Person class]
消息名稱:initialize
    
OC的方法調(diào)用:消息機(jī)制,給方法調(diào)用者發(fā)送消息
    
objc_msgSend如果找不到合適的方法進(jìn)行調(diào)用,會(huì)報(bào)錯(cuò)unrecognized selector sent to instance

objc_msgSend的執(zhí)行流程可以分為3大階段

消息發(fā)送

根據(jù)對(duì)象和方法名查找(有序的是二分查找,無序的是線性查找)
Method meth = getMethodNoSuper_nolock(curClass, sel);

將方法添加到緩存中,無論方法實(shí)在哪一級(jí)的父類中,都會(huì)緩存到消息接收者的緩存中
log_and_fill_cache(cls, imp, sel, inst, curClass);

如果都沒有找到方法,則進(jìn)入下一階段

動(dòng)態(tài)方法解析

動(dòng)態(tài)方法解析.png

實(shí)例方法解析:+resolveInstanceMethod:
類方法解析:+resolveClassMethod:

原理如下
if (! cls->isMetaClass()) {
        // try [cls resolveInstanceMethod:sel]
        resolveInstanceMethod(inst, sel, cls);
    } 
    else {
        // try [nonMetaClass resolveClassMethod:sel]
        // and [cls resolveInstanceMethod:sel]
        resolveClassMethod(inst, sel, cls);
        if (!lookUpImpOrNilTryCache(inst, sel, cls)) {
            resolveInstanceMethod(inst, sel, cls);
        }
    }
}

方法一

struct method_t {
    SEL sel;
    char *types;
    IMP imp;
};

+ (BOOL)resolveInstanceMethod:(SEL)sel
{
    if (sel == @selector(test)) {
        // 獲取其他方法
        struct method_t *method = (struct method_t *)class_getInstanceMethod(self, @selector(other));

        // 動(dòng)態(tài)添加test方法的實(shí)現(xiàn)
        class_addMethod(self, sel, method->imp, method->types);

        // 返回YES代表有動(dòng)態(tài)添加方法
        return YES;
    }
    return [super resolveInstanceMethod:sel];

方法二

- (void)other
{
    NSLog(@"%s", __func__);
}

+ (BOOL)resolveInstanceMethod:(SEL)sel
{
    if (sel == @selector(test)) {
        // 獲取其他方法
        Method method = class_getInstanceMethod(self, @selector(other));

        // 動(dòng)態(tài)添加test方法的實(shí)現(xiàn)
        class_addMethod(self, sel,
                        method_getImplementation(method),
                        method_getTypeEncoding(method));

        // 返回YES代表有動(dòng)態(tài)添加方法
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}

方法三

+ (BOOL)resolveInstanceMethod:(SEL)sel
{
    if (sel == @selector(test)) {
        // 動(dòng)態(tài)添加test方法的實(shí)現(xiàn)
        class_addMethod(self, sel, (IMP)c_other, "v16@0:8");

        // 返回YES代表有動(dòng)態(tài)添加方法
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}

方法四

void c_other(id self, SEL _cmd)
{
    NSLog(@"c_other - %@ - %@", self, NSStringFromSelector(_cmd));
}

+ (BOOL)resolveClassMethod:(SEL)sel
{
    if (sel == @selector(test)) {
        // 第一個(gè)參數(shù)是object_getClass(self)
        class_addMethod(object_getClass(self), sel, (IMP)c_other, "v16@0:8");
        return YES;
    }
    return [super resolveClassMethod:sel];
}

\color{red}{無論什么方法都是相當(dāng)于重塑了一個(gè)其他方法,包括參數(shù)、地址、方法名}

消息轉(zhuǎn)發(fā)

實(shí)例方法

本質(zhì)是經(jīng)過了消息發(fā)送,消息的動(dòng)態(tài)解析,都沒有找到方法的實(shí)現(xiàn),所以就會(huì)重新定向一個(gè)對(duì)象,去這個(gè)對(duì)象里查找發(fā)送的方法

@implementation Person
- (id)forwardingTargetForSelector:(SEL)aSelector
{
    if (aSelector == @selector(test)) {
        // objc_msgSend([[Cat alloc] init], aSelector)
        return [[Cat alloc] init];
    }
    return [super forwardingTargetForSelector:aSelector];
}
@end

如果在 forwardingTargetForSelector 方法中并未找到合適的對(duì)象,則會(huì)執(zhí)行下面方法

 方法簽名:返回值類型、參數(shù)類型
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
    if (aSelector == @selector(test)) {
        return [NSMethodSignature signatureWithObjCTypes:"v16@0:8"];
    }
    return [super methodSignatureForSelector:aSelector];
}

 NSInvocation封裝了一個(gè)方法調(diào)用,包括:方法調(diào)用者、方法名、方法參數(shù)
    anInvocation.target 方法調(diào)用者
    anInvocation.selector 方法名
    [anInvocation getArgument:NULL atIndex:0]
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
//    anInvocation.target = [[Cat alloc] init];
//    [anInvocation invoke];

    [anInvocation invokeWithTarget:[[Cat alloc] init]];
}

類方法

類方法的調(diào)用和實(shí)例方法類似,都是要用消息接收者去調(diào)用,所以要用【+】去實(shí)現(xiàn),因?yàn)樵诰帉戇@個(gè)方法的時(shí)候是無法關(guān)聯(lián)出來的,所以很多網(wǎng)上的資料說沒有類方法的實(shí)現(xiàn),這是不準(zhǔn)確的

+ (id)forwardingTargetForSelector:(SEL)aSelector
{
    // objc_msgSend([[Cat alloc] init], @selector(test))
    // [[[Cat alloc] init] test]
    if (aSelector == @selector(test)) return [[Cat alloc] init]; 
//返回的是個(gè)實(shí)例對(duì)象,這個(gè)時(shí)候就是調(diào)用實(shí)例方法了
    return [super forwardingTargetForSelector:aSelector];
}

+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
    if (aSelector == @selector(test)) return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    
    return [super methodSignatureForSelector:aSelector];
}

+ (void)forwardInvocation:(NSInvocation *)anInvocation
{
    NSLog(@"1123");
}

常見題目

@dynamic和@synthesize

@property (assign, nonatomic) int age;

@synthesize age = _age
//自動(dòng)生成setter和getter方法和_age成員變量

@dynamic age;
//提醒編譯器不要自動(dòng)生成setter和getter的實(shí)現(xiàn)(但不影響聲明,外部可以調(diào)用,但是會(huì)報(bào)錯(cuò),因?yàn)闆]有實(shí)現(xiàn))、不要自動(dòng)生成成員變量

super

[super message]的底層實(shí)現(xiàn)

1.消息接收者仍然是子類對(duì)象

2.從父類開始查找方法的實(shí)現(xiàn)

臨時(shí)的結(jié)構(gòu)體
struct objc_super {
    __unsafe_unretained _Nonnull id receiver; // 消息接收者
    __unsafe_unretained _Nonnull Class super_class; // 消息接收者的父類
};

NSLog(@"[self class] = %@", [self class]); // Student
NSLog(@"[self superclass] = %@", [self superclass]); // Person

objc_msgSendSuper({self, [Person class]}, @selector(class));
//這里self就是消息接收者,[Person class]就是父類,@selector(class)是方法名
NSLog(@"[super class] = %@", [super class]); // Student
NSLog(@"[super superclass] = %@", [super superclass]); // Person

真正的底層是objc_super2

objc_msgSendSuper2({self, [Person class]}, @selector(class));
這里第二個(gè)參數(shù)傳入的就是self但是內(nèi)部處理的時(shí)候會(huì)執(zhí)行 self->superClass,這樣就還是從父類開始查找了

isKindOfClass 和 isMemberOfClass

  • 區(qū)別
//這里使用的是isa指針,因?yàn)槭穷惙椒?,所以指針指向的是元類,這里的判斷是指,本身類的指針是否指向傳入類,就是說傳入類是否是本身類的元類
+ (BOOL)isMemberOfClass:(Class)cls {
    return self->ISA() == cls;
}

//傳進(jìn)來的這個(gè)類對(duì)象是否等于本身的類對(duì)象
- (BOOL)isMemberOfClass:(Class)cls {
    return [self class] == cls;
}

//這里使用的是isa指針,因?yàn)槭穷惙椒?,所以指針指向的是元類,這里的判斷是指,本身類的指針是否指向傳入類,就是說傳入類是否是本身類的元類
//與isMemberOfClass的不同之處,是包含了父元類的判斷,這里說明下,NSObject的元類對(duì)象又指向NSObject的類對(duì)象
所以:
[Person isKindOfClass:[NSObject class]] 是 YES
+ (BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = self->ISA(); tcls; tcls = tcls->getSuperclass()) {
        if (tcls == cls) return YES;
    }
    return NO;
}

//走了一個(gè)循環(huán),從傳進(jìn)來的cls是否是等于[self class]開始,若不相等,則去看父類,若還不相等依次向上看父類,直到nil,返回NO
//如果傳進(jìn)來的類對(duì)象與本身的類對(duì)象相等,或者本身的類對(duì)象是傳進(jìn)來的類對(duì)象的子類都會(huì)返回YES
- (BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = [self class]; tcls; tcls = tcls->getSuperclass()) {
        if (tcls == cls) return YES;
    }
    return NO;
}

指針指向

NSString *test = @"123";

id cls = [Person class];
void *obj = &cls;//類對(duì)象的isa
[(__bridge id)obj print];//在類對(duì)象里面找到print方法

這個(gè)方法,等價(jià)于:

Person *person = [[Person alloc] init];
[person print];

-------------------------------------------------------

- (void)print
{
    NSLog(@"my name is %@", self->_name);
}

又因?yàn)榫植孔兞糠峙湓跅?臻g,??臻g分配,從高地址到低地址

void test()
{
    long long a = 4; // 0x7ffee638bff8
    long long b = 5; // 0x7ffee638bff0
    long long c = 6; // 0x7ffee638bfe8
    long long d = 7; // 0x7ffee638bfe0
}

所以print方法打印出來的是"my name is 123",為什么會(huì)有這種打印呢:


字符串聲明在前面.png
struct Person_IMPL
{
    Class isa;
    NSString *_name;
};

棧是先進(jìn)后出的,先聲明的在高地址,使用的時(shí)候是低地址到高地址的
obj->cls->test,這個(gè)尋址方法可以看到,打印self->name就是忽略掉isa指針的8個(gè)字節(jié),往下尋找,但是是用的obj指向的person的類對(duì)象,并不是常規(guī)意義的對(duì)象結(jié)構(gòu)體,所以沒有name這個(gè)屬性,根據(jù)棧空間分配的邏輯,可以知道cls的高地址是test字符串,所以打印結(jié)果是 "my name is 123"

如果上面不寫 NSString *test = @"123";那么打印出來的就是"my name is <Viewcontroller:0x00001111>",為什么會(huì)打印這個(gè)呢,因?yàn)樵蹅儠?huì)有一個(gè)"[super viewDidoad]"方法,這個(gè)方法的本質(zhì)就是

struct abc = {
    self,
    [ViewController class]
};
objc_msgSendSuper2(abc, sel_registerName("viewDidLoad"));

由此可知,如果我們高地址什么都不寫,就是cls前面什么都沒有的話,默認(rèn)前面是self對(duì)象

沒有聲明任何字符串,上面是[super viewDidLoad].png

LLVM的中間代碼

Objective-C在變?yōu)闄C(jī)器代碼之前,會(huì)被LLVM編譯器轉(zhuǎn)換為中間代碼(Intermediate Representation)

可以使用以下命令行指令生成中間代碼

clang -emit-llvm -S main.m

語(yǔ)法簡(jiǎn)介
@ - 全局變量
% - 局部變量
alloca - 在當(dāng)前執(zhí)行的函數(shù)的堆棧幀中分配內(nèi)存,當(dāng)該函數(shù)返回到其調(diào)用者時(shí),將自動(dòng)釋放內(nèi)存
i32 - 32位4字節(jié)的整數(shù)
align - 對(duì)齊
load - 讀出,store 寫入
icmp - 兩個(gè)整數(shù)值比較,返回布爾值
br - 選擇分支,根據(jù)條件來轉(zhuǎn)向label,不根據(jù)條件跳轉(zhuǎn)的話類似 goto
label - 代碼標(biāo)簽
call - 調(diào)用函數(shù)

常用方法

動(dòng)態(tài)創(chuàng)建一個(gè)類(參數(shù):父類,類名,額外的內(nèi)存空間)
Class objc_allocateClassPair(Class superclass, const char *name, size_t extraBytes)

注冊(cè)一個(gè)類(要在類注冊(cè)之前添加成員變量)
void objc_registerClassPair(Class cls) 

銷毀一個(gè)類
void objc_disposeClassPair(Class cls)

獲取isa指向的Class
Class object_getClass(id obj)

設(shè)置isa指向的Class
Class object_setClass(id obj, Class cls)

判斷一個(gè)OC對(duì)象是否為Class
BOOL object_isClass(id obj)

判斷一個(gè)Class是否為元類
BOOL class_isMetaClass(Class cls)

獲取父類
Class class_getSuperclass(Class cls)
成員變量
獲取一個(gè)實(shí)例變量信息
Ivar class_getInstanceVariable(Class cls, const char *name)

拷貝實(shí)例變量列表(最后需要調(diào)用free釋放)
Ivar *class_copyIvarList(Class cls, unsigned int *outCount)

設(shè)置和獲取成員變量的值
void object_setIvar(id obj, Ivar ivar, id value)
id object_getIvar(id obj, Ivar ivar)

動(dòng)態(tài)添加成員變量(已經(jīng)注冊(cè)的類是不能動(dòng)態(tài)添加成員變量的)
BOOL class_addIvar(Class cls, const char * name, size_t size, uint8_t alignment, const char * types)

獲取成員變量的相關(guān)信息
const char *ivar_getName(Ivar v)
const char *ivar_getTypeEncoding(Ivar v)

//     獲取成員變量信息
Ivar ageIvar = class_getInstanceVariable([Person class], "_age");
NSLog(@"%s %s", ivar_getName(ageIvar), ivar_getTypeEncoding(ageIvar));
    
//     設(shè)置和獲取成員變量的值
Ivar nameIvar = class_getInstanceVariable([Person class], "_name");
    
Person *person = [[Person alloc] init];
object_setIvar(person, nameIvar, @"123");
object_setIvar(person, ageIvar, (__bridge id)(void *)10);
NSLog(@"%@ %d", person.name, person.age);
屬性
獲取一個(gè)屬性
objc_property_t class_getProperty(Class cls, const char *name)

拷貝屬性列表(最后需要調(diào)用free釋放)
objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount)

動(dòng)態(tài)添加屬性
BOOL class_addProperty(Class cls, const char *name, const objc_property_attribute_t *attributes,
                  unsigned int attributeCount)

動(dòng)態(tài)替換屬性
void class_replaceProperty(Class cls, const char *name, const objc_property_attribute_t *attributes,
                      unsigned int attributeCount)

獲取屬性的一些信息
const char *property_getName(objc_property_t property)
const char *property_getAttributes(objc_property_t property)

獲取apple隱藏的屬性
unsigned int count;
Ivar *ivars = class_copyIvarList([UITextField class], &count);
for (int i = 0; i < count; i++) {
    // 取出i位置的成員變量
    Ivar ivar = ivars[i];
    NSLog(@"%s %s", ivar_getName(ivar), ivar_getTypeEncoding(ivar));
}
free(ivars);

字典轉(zhuǎn)模型的使用
方法交換

方法交換做了處理,可以添加一些自己的邏輯在里面,避免崩潰,數(shù)據(jù)統(tǒng)計(jì)等功能

#import "NSMutableArray+Extension.h"
#import <objc/runtime.h>

@implementation NSMutableArray (Extension)

+ (void)load
{
//保證只調(diào)用一次
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        // 類簇:NSString、NSArray、NSDictionary,真實(shí)類型是其他類型
        Class cls = NSClassFromString(@"__NSArrayM");
        Method method1 = class_getInstanceMethod(cls, @selector(insertObject:atIndex:));
        Method method2 = class_getInstanceMethod(cls, @selector(_insertObject:atIndex:));
        method_exchangeImplementations(method1, method2);
    });
}

- (void)_insertObject:(id)anObject atIndex:(NSUInteger)index
{
    if (anObject == nil) return;
    
    [self performSelector:<#(SEL)#> withObject:<#(id)#>]
    [self _insertObject:anObject atIndex:index];
}

@end
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容