iOS 探索類(下)---方法歸屬

這篇我們接著分析類。在iOS開發(fā)中我們經(jīng)常會(huì)使用到實(shí)例方法和類方法。那實(shí)例方法和對(duì)象方法是存放在哪里的呢?今天,我們就來一探究竟。

一、屬性、成員變量、實(shí)例變量

同樣使用LPPerson作為演示,分別給LPPerson添加兩個(gè)屬性和實(shí)例變量,再利用runtimeAPI獲取LPPerson的成員變量和屬性并打印。

void LogObjc_copyIvar_copyProperies(Class pClass){
    
    unsigned int count = 0;
    Ivar *ivars = class_copyIvarList(pClass, &count);
    for (unsigned int i=0; i < count; i++) {
        Ivar const ivar = ivars[i];
        //獲取實(shí)例變量名
        const char*cName = ivar_getName(ivar);
        NSString *ivarName = [NSString stringWithUTF8String:cName];
        NSLog(@"class_copyIvarList:%@",ivarName);
    }
    free(ivars);

    unsigned int pCount = 0;
    objc_property_t *properties = class_copyPropertyList(pClass, &pCount);
    for (unsigned int i=0; i < pCount; i++) {
        objc_property_t const property = properties[i];
        //獲取屬性名
        NSString *propertyName = [NSString stringWithUTF8String:property_getName(property)];
        //獲取屬性值
        NSLog(@"class_copyProperiesList:%@",propertyName);
    }
    free(properties);
}

@interface LPPerson : NSObject
{
    NSString *hobby;
    NSObject *objc;
}
@property (nonatomic, copy) NSString *nickName;
@property (nonatomic, strong) NSString *name;

@end


@implementation LPPerson

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {

        
         LPPerson *person = [LPPerson alloc];
         Class pClass     = object_getClass(person);
         LogObjc_copyIvar_copyProperies(pClass);

        
        NSLog(@"Hello, World!");
    }
    return 0;
}

我們運(yùn)行,看一下打印結(jié)果:

class_copyIvarList:hobby
class_copyIvarList:objc
class_copyIvarList:_nickName
class_copyIvarList:_name
class_copyProperiesList:nickName
class_copyProperiesList:name

通過結(jié)果,我們可以發(fā)現(xiàn):

  • 結(jié)合以前的知識(shí),我們可以知道,屬性 = 成員變量 + setter+ getter
  • 屬性的成員變量是帶下劃線_的,{}中的成員變量是不帶_

那什么叫實(shí)例變量呢:
實(shí)例變量本質(zhì)上就是成員變量,只是實(shí)例是針對(duì)類而言,實(shí)例是指類的聲明。{ }中的objc就是實(shí)例變量。NSObjectOC類,所以objc屬于實(shí)例變量。但是并不是所有的成員變量都是實(shí)例變量,OC常用基本數(shù)據(jù)類型申明的成員變量和NSString類型不是成員變量。
NSString雖然繼承于NSObject,但是仍然是常量類型, 因?yàn)椴荒芴砑訉傩?,如果定義在類中的{}中,是成員變量
也可以理解為:實(shí)例變量 + 基本數(shù)據(jù)類型 + NSString = 成員變量

二、方法的歸屬

繼續(xù)使用LPPerson,在它的.h中新增實(shí)例方法sayHello和類方法sayHappy,并在.m中實(shí)現(xiàn)

@interface LPPerson : NSObject
- (void)sayHello;
+ (void)sayHappy;

@end
@implementation LPPerson

- (void)sayHello{
    NSLog(@"LPPerson say : Hello!!!");
}

+ (void)sayHappy{
    NSLog(@"LPPerson say : Happy!!!");
}

@end

然后在main.m中實(shí)現(xiàn)如下代碼:

void LogObjc_copyMethodList(Class pClass){
    unsigned int count = 0;
    Method *methods = class_copyMethodList(pClass, &count);
    for (unsigned int i=0; i < count; i++) {
        Method const method = methods[i];
        //獲取方法名
        NSString *key = NSStringFromSelector(method_getName(method));
        
        NSLog(@"Method, name: %@", key);
    }
    free(methods);
}

void LpInstanceMethod_classToMetaclass(Class pClass){
    
    const char *className = class_getName(pClass);
    Class metaClass = objc_getMetaClass(className);
    
    Method method1 = class_getInstanceMethod(pClass, @selector(sayHello));
    Method method2 = class_getInstanceMethod(metaClass, @selector(sayHello));

    Method method3 = class_getInstanceMethod(pClass, @selector(sayHappy));
    Method method4 = class_getInstanceMethod(metaClass, @selector(sayHappy));
    
    NSLog(@"%s - %p-%p-%p-%p",__func__,method1,method2,method3,method4);
}

void LpClassMethod_classToMetaclass(Class pClass){
    
    const char *className = class_getName(pClass);
    Class metaClass = objc_getMetaClass(className);
    
    Method method1 = class_getClassMethod(pClass, @selector(sayHello));
    Method method2 = class_getClassMethod(metaClass, @selector(sayHello));

    Method method3 = class_getClassMethod(pClass, @selector(sayHappy));
    Method method4 = class_getClassMethod(metaClass, @selector(sayHappy));
    
    NSLog(@"%s-%p-%p-%p-%p",__func__,method1,method2,method3,method4);
}

void LpIMP_classToMetaclass(Class pClass){
    
    const char *className = class_getName(pClass);
    Class metaClass = objc_getMetaClass(className);

    IMP imp1 = class_getMethodImplementation(pClass, @selector(sayHello));
    IMP imp2 = class_getMethodImplementation(metaClass, @selector(sayHello));

    IMP imp3 = class_getMethodImplementation(pClass, @selector(sayHappy));
    IMP imp4 = class_getMethodImplementation(metaClass, @selector(sayHappy));

    NSLog(@"%p-%p-%p-%p",imp1,imp2,imp3,imp4);
    NSLog(@"%s",__func__);
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {

        LPPerson *person = [LPPerson alloc];
        Class pClass     = object_getClass(person);
        LogObjc_copyMethodList(pClass);

        LpInstanceMethod_classToMetaclass(pClass);
        LpClassMethod_classToMetaclass(pClass);
        LpIMP_classToMetaclass(pClass)
        NSLog(@"Hello, World!");
    }
    return 0;
}

看下運(yùn)行結(jié)果:

Method, name: sayHello
lgInstanceMethod_classToMetaclass:------ 0x1000031b0---0x0---0x0---0x100003148
lgClassMethod_classToMetaclass:------ 0x0---0x0---0x100003148---0x100003148
lgIMP_classToMetaclass:------ 0x100001d10---0x7fff6f198580---0x7fff6f198580---0x100001d40

由上篇中isasuperClass走位圖我們可以知道:

  • 實(shí)例對(duì)象和其父類的關(guān)系是:
    類->父類->根類-> nil
  • 元類和其父類的關(guān)系是:
    元類->根元類->根類->nil
    方法查找時(shí)也是按照上面的順序進(jìn)行查找,如果找到就返回,沒有找到則返回NULL

我們根據(jù)打印結(jié)果來分析下:

LogObjc_copyMethodList

去獲取LPPerson的方法列表發(fā)現(xiàn),只有一個(gè)實(shí)例方法:sayHello
實(shí)例方法是存在類中,所以此時(shí)只能找實(shí)例方法sayHello。

LpInstanceMethod_classToMetaclass

class_getInstanceMethod是用來在類中獲取實(shí)例方法,如果在傳入的類或者類的父類中沒有找到指定的實(shí)例方法,則返回NULL

  • 第一個(gè)參數(shù)表示在哪個(gè)類中獲取方法
  • 第二個(gè)是需要獲取的方法

我們?cè)賮矸治鱿?code>LpInstanceMethod_classToMetaclass:
LPPersonLPPerson的元類中,分別獲取sayHellosayHappy的實(shí)例方法

-method1:在LPPerson中獲取sayHello實(shí)例方法,LPPerson類中有,所以可以獲取

  • method2:在LPPerson的元類中獲取sayHello實(shí)例方法,LPPerson的元類中沒有,根元類沒有,根類沒有,所以返回NULL,地址就為0x0
  • method3:在LPPerson的獲取sayHappy實(shí)例方法,LPPerson中沒有,LPPerson的父類NSObsject沒有,所以返回NULL
  • method4:在LPPerson元類中的獲取sayHappy實(shí)例方法,LPPerson中沒有,所以去LPPerson的元類中找,而類方法就是元類的是實(shí)例方法,所以LPPerson的元類獲取到了。

LpClassMethod_classToMetaclass

class_getClassMethod:是用來類中獲取類方法,如果在傳入的類或者類的父類中沒有找到指定的實(shí)例方法,則返回NULL
我們?cè)倏聪?code>class_getClassMethod源碼:

//獲取類方法
Method class_getClassMethod(Class cls, SEL sel)
{
    if (!cls  ||  !sel) return nil;

    return class_getInstanceMethod(cls->getMeta(), sel);
}

??
//獲取元類
 // NOT identical to this->ISA when this is a metaclass 判斷是否是元類,是元類就直接返回,反之,繼續(xù)找isa指向
Class getMeta() {
    if (isMetaClass()) return (Class)this;
    else return this->ISA();
}

可以得出class_getClassMethod的實(shí)現(xiàn)是獲取類的類方法,其本質(zhì)就是獲取元類的實(shí)例方法,最終還是會(huì)走到class_getInstanceMethod,但是在這里需要注意的一點(diǎn)是:在getMeta源碼中,如果判斷出cls是元類,那么就不會(huì)再繼續(xù)往下遞歸查找,會(huì)直接返回this,其目的是為了防止元類的無限遞歸查找

再來分析下LpClassMethod_classToMetaclass
LPPersonLPPerson的元類中,分別獲取sayHellosayHappy的類方法

  • method1:在LPPerson中獲取sayHello類方法,LPPerson中沒有,LPPerson的父類NSObsject也沒有,nil也沒有所以返回NULL
  • method2:在LPPerson的元類中獲取sayHello類方法,LPPerson的元類中沒有,根元類沒有,根類沒有,所以返回NULL,地址就為0x0
  • method3:在LPPerson的獲取sayHappy類方法,LPPerson中沒有,但是LPPerson元類中找,LPPerson元類存儲(chǔ)的實(shí)例方法就是LPPerson類方法,所以可以找到
  • method4:在LPPerson元類中的獲取sayHappy類方法,當(dāng)前傳入的是LPPerson元類,所以開始在LPPerson元類中去查找實(shí)例方法,即class_getInstanceMethod(cls->getMeta(), sel),而LPPerson的類方法,就是LPPerson元類存儲(chǔ)的實(shí)例方法,所以可以獲取到。

lpIMP_classToMetaclass

class_getMethodImplementation
其大致含義就是:該函數(shù)在向類實(shí)例發(fā)送消息時(shí)會(huì)被調(diào)用,并返回一個(gè)指向方法實(shí)現(xiàn)函數(shù)的指針。這個(gè)函數(shù)會(huì)比method_getImplementation(class_getInstanceMethod(cls, name))更快。返回的函數(shù)指針可能是一個(gè)指向runtime內(nèi)部的函數(shù),而不一定是方法的實(shí)際實(shí)現(xiàn)。如果類實(shí)例無法響應(yīng)selector,則返回的函數(shù)指針將是運(yùn)行時(shí)消息轉(zhuǎn)發(fā)機(jī)制的一部分

再次查看源碼:

IMP class_getMethodImplementation(Class cls, SEL sel)
{
    IMP imp;

    if (!cls  ||  !sel) return nil;

    //查找方法實(shí)現(xiàn)
    imp = lookUpImpOrNil(nil, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER);

    //如果沒有找到,則進(jìn)行消息轉(zhuǎn)發(fā)
    if (!imp) {
        return _objc_msgForward;
    }

    return imp;
}
  • imp1:在LPPerson中查找sayHello實(shí)現(xiàn),因?yàn)?code>sayHello是實(shí)例方法,存在類中,所以可以找到返回對(duì)應(yīng)的實(shí)現(xiàn)
  • imp2:在LPPerson元類中查找sayHello實(shí)現(xiàn),因?yàn)?code>LPPerson元類沒有,所以進(jìn)行了消息轉(zhuǎn)發(fā)
  • imp3:在LPPerson類中查找sayHappy實(shí)現(xiàn),因?yàn)?code>sayHappy是類方法,類方法是存在元類中的,LPPerson類沒有,所以進(jìn)行了消息轉(zhuǎn)發(fā)
  • imp4:在LPPerson元類中查找sayHappy實(shí)現(xiàn),因?yàn)?code>sayHappy是類方法,類方法是存在元類中的,LPPerson元類中,所以可以找到返回對(duì)應(yīng)的實(shí)現(xiàn)

三、經(jīng)典題目分析:

//-----使用 iskindOfClass & isMemberOfClass 類方法
BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];       //
BOOL re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];     //
BOOL re3 = [(id)[LGPerson class] isKindOfClass:[LGPerson class]];       //
BOOL re4 = [(id)[LGPerson class] isMemberOfClass:[LGPerson class]];     //
NSLog(@" re1 :%hhd\n re2 :%hhd\n re3 :%hhd\n re4 :%hhd\n",re1,re2,re3,re4);

//------iskindOfClass & isMemberOfClass 實(shí)例方法
BOOL re5 = [(id)[NSObject alloc] isKindOfClass:[NSObject class]];       //
BOOL re6 = [(id)[NSObject alloc] isMemberOfClass:[NSObject class]];     //
BOOL re7 = [(id)[LGPerson alloc] isKindOfClass:[LGPerson class]];       //
BOOL re8 = [(id)[LGPerson alloc] isMemberOfClass:[LGPerson class]];     //
NSLog(@" re5 :%hhd\n re6 :%hhd\n re7 :%hhd\n re8 :%hhd\n",re5,re6,re7,re8);

上面打印結(jié)果是什么?各位可以先思考一下,再看下面的結(jié)果

結(jié)果如下:

 re1 :1
 re2 :0
 re3 :0
 re4 :0

 re5 :1
 re6 :1
 re7 :1
 re8 :1

在分析結(jié)果之前,我們先看一下isKindOfClassisMemberOfClass的源碼:

isKindOfClass源碼,分為類方法和實(shí)例方法:

//--isKindOfClass---類方法、對(duì)象方法
//+ isKindOfClass:第一次比較是 獲取類的元類 與 傳入類對(duì)比,再次之后的對(duì)比是獲取上次結(jié)果的父類 與 傳入 類進(jìn)行對(duì)比
+ (BOOL)isKindOfClass:(Class)cls {
    // 獲取類的元類 vs 傳入類
    // 根元類 vs 傳入類
    // 根類 vs 傳入類
    // 舉例:LGPerson vs 元類 (根元類) (NSObject)
    for (Class tcls = self->ISA(); tcls; tcls = tcls->superclass) {
        if (tcls == cls) return YES;
    }
    return NO;
}

//- isKindOfClass:第一次是獲取對(duì)象類 與 傳入類對(duì)比,如果不相等,后續(xù)對(duì)比是繼續(xù)獲取上次 類的父類 與傳入類進(jìn)行對(duì)比
- (BOOL)isKindOfClass:(Class)cls {
/*
獲取對(duì)象的類 vs 傳入的類 
父類 vs 傳入的類
根類 vs 傳入的類
nil vs 傳入的類
*/
    for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
        if (tcls == cls) return YES;
    }
    return NO;
}

isMemberOfClass源碼,同樣分為類方法和實(shí)例方法

//-----類方法
//+ isMemberOfClass : 獲取類的元類,與 傳入類對(duì)比
+ (BOOL)isMemberOfClass:(Class)cls {
    return self->ISA() == cls;
}
//-----實(shí)例方法
//- isMemberOfClass : 獲取對(duì)象的類,與 傳入類對(duì)比
- (BOOL)isMemberOfClass:(Class)cls {
    return [self class] == cls;
}

根據(jù)源碼,我們可以總結(jié)如下:

  • isKindOfClass
    類方法:元類 --> 根元類 --> 根類 --> nil 與 傳入類的對(duì)比
    實(shí)例方法:對(duì)象的類 --> 父類 --> 根類 --> nil 與 傳入類的對(duì)比
  • isMemberOfClass
    類方法: 類的元類 與 傳入類 對(duì)比
    實(shí)例方法:對(duì)象的父類 與 傳入類 對(duì)比

接下來,我們?cè)賮矸治錾厦骖}目:

  • re1:
    [NSObject class]執(zhí)行的是isKindOfClass類方法,所以:
    NSObject的元類是根元類,根元類!=NSObject,
    繼續(xù)尋找根元類的父類,即根類,根類即為NSObject。
    NSObject==NSObject所以為1。

  • re2:
    [NSObject class]執(zhí)行的是isMemberOfClass類方法,所以:
    NSObject的元類是根元類,根元類!=NSObject,所以為0

  • re3:
    [LGPerson class]執(zhí)行的是isKindOfClass類方法,所以:
    LGPerson的元類!=LGPerson
    繼續(xù)LGPerson的元類的父類是根元類,根元類 != LGPerson,
    繼續(xù)根元類的父類即根類,根類!=LGPerson
    根類的父類是nil,nil != LGPerson,
    所以為0

  • re4:
    [LGPerson class] 執(zhí)行的是isMemberOfClass類方法,所以:
    LGPerson的元類 != LGPerson,所以為0

  • re5:
    [NSObject alloc] 執(zhí)行的是isKindOfClass實(shí)例方法,所以:
    [NSObject alloc]的類NSObject,NSObject==NSObject,所以為1。

  • re6:
    [NSObject alloc] 執(zhí)行的是isMemberOfClass實(shí)例方法,所以:
    [NSObject alloc]的類NSObjectNSObject==NSObject,所以為1

  • re7:
    [LGPerson alloc]執(zhí)行的是isKindOfClass實(shí)例方法,所以:
    [LGPerson alloc]的類是LGPerson,LGPerson== LGPerson,所以為1

  • re8:
    [LGPerson alloc] 執(zhí)行的是isMemberOfClass實(shí)例方法,所以:
    [LGPerson alloc] 的類是LGPerson,LGPerson == LGPerson,所以為1

覺得不錯(cuò)記得點(diǎn)贊哦!聽說看完點(diǎn)贊的人逢考必過,逢獎(jiǎng)必中。?( ′???` )比心

?著作權(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ù)。

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