属性、方法获取的深入理解

属性的深入理解

要研究成员变量与属性关系,最简单方法就是利用clang生成C++文件,看C++代码如何实现的。

@interface SJPerson : NSObject

{
    NSString *hobby;
    NSObject *objc;
}

@property (nonatomic, copy) NSString *name;
@property (nonatomic, strong) NSString *nickName;
@property (atomic, strong) NSString *aName;

@end

@implementation SJPerson

@end

执行clang -rewrite-objc main.m -o main.cpp生成C++文件,SJPerson_IMPL结构体中可以看到5个成员变量和isa

struct SJPerson_IMPL {
    struct NSObject_IMPL NSObject_IVARS;
    NSString *hobby;
    NSObject *objc;
    NSString *_name;
    NSString *_nickName;
    NSString *_aName;
};
image

nameset方法是调用了objc_setProperty函数,而nickNameset方法则是通过指针偏移直接赋值,都是属性为什么会不一样呢,因为他们修饰的类型不一样,name是用copy修饰,nickName是用strong修饰。

我们都知道,声明一个属性,编译器会自动帮我们生成成员变量和getset方法。项目中有很多属性,他们getset方法会有很多,编译器不可能每个方法写一个实现,只能底层有一个通用的方法,getset都会执行这一个方法,而这个操作,在编译阶段就应该完成。那编译阶段完成的事情,我们想看下实现,只能去llvm源码中查找相关线索。

全局搜索objc_setProperty,找到getSetPropertyFn函数会创建objc_setProperty

Jietu20210622-223308.jpg

再全局搜索getSetPropertyFn

llvm::FunctionCallee CGObjCMac::GetPropertySetFunction() {
  return ObjCTypes.getSetPropertyFn();
}

GetPropertySetFunction函数中调用getSetPropertyFn,继续全局搜索GetPropertySetFunction

image

函数调用发现是跟这个case相关,再看下这个case的变量

image

变量strategy的结构类型是PropertyImplStrategy,我们再全局搜一下

PropertyImplStrategy::PropertyImplStrategy(CodeGenModule &CGM,
                                     const ObjCPropertyImplDecl *propImpl) {
  const ObjCPropertyDecl *prop = propImpl->getPropertyDecl();
  ObjCPropertyDecl::SetterKind setterKind = prop->getSetterKind();

  IsCopy = (setterKind == ObjCPropertyDecl::Copy);
  IsAtomic = prop->isAtomic();
  HasStrong = false; // doesn't matter here.

  // Evaluate the ivar's size and alignment.
  ObjCIvarDecl *ivar = propImpl->getPropertyIvarDecl();
  QualType ivarType = ivar->getType();
  auto TInfo = CGM.getContext().getTypeInfoInChars(ivarType);
  IvarSize = TInfo.Width;
  IvarAlignment = TInfo.Align;

  // If we have a copy property, we always have to use getProperty/setProperty.
  // TODO: we could actually use setProperty and an expression for non-atomics.
  if (IsCopy) {
    Kind = GetSetProperty;
    return;
  }

  // Handle retain.
  if (setterKind == ObjCPropertyDecl::Retain) {
    // In GC-only, there's nothing special that needs to be done.
    if (CGM.getLangOpts().getGC() == LangOptions::GCOnly) {
      // fallthrough

    // In ARC, if the property is non-atomic, use expression emission,
    // which translates to objc_storeStrong.  This isn't required, but
    // it's slightly nicer.
    } else if (CGM.getLangOpts().ObjCAutoRefCount && !IsAtomic) {
      // Using standard expression emission for the setter is only
      // acceptable if the ivar is __strong, which won't be true if
      // the property is annotated with __attribute__((NSObject)).
      // TODO: falling all the way back to objc_setProperty here is
      // just laziness, though;  we could still use objc_storeStrong
      // if we hacked it right.
      if (ivarType.getObjCLifetime() == Qualifiers::OCL_Strong)
        Kind = Expression;
      else
        Kind = SetPropertyAndExpressionGet;
      return;

    // Otherwise, we need to at least use setProperty.  However, if
    // the property isn't atomic, we can use normal expression
    // emission for the getter.
    } else if (!IsAtomic) {
      Kind = SetPropertyAndExpressionGet;
      return;

    // Otherwise, we have to use both setProperty and getProperty.
    } else {
      Kind = GetSetProperty;
      return;
    }
  }

  // If we're not atomic, just use expression accesses.
  if (!IsAtomic) {
    Kind = Expression;
    return;
  }

  // Properties on bitfield ivars need to be emitted using expression
  // accesses even if they're nominally atomic.
  if (ivar->isBitField()) {
    Kind = Expression;
    return;
  }

  // GC-qualified or ARC-qualified ivars need to be emitted as
  // expressions.  This actually works out to being atomic anyway,
  // except for ARC __strong, but that should trigger the above code.
  if (ivarType.hasNonTrivialObjCLifetime() ||
      (CGM.getLangOpts().getGC() &&
       CGM.getContext().getObjCGCAttrKind(ivarType))) {
    Kind = Expression;
    return;
  }

  // Compute whether the ivar has strong members.
  if (CGM.getLangOpts().getGC())
    if (const RecordType *recordType = ivarType->getAs())
      HasStrong = recordType->getDecl()->hasObjectMember();

  // We can never access structs with object members with a native
  // access, because we need to use write barriers.  This is what
  // objc_copyStruct is for.
  if (HasStrong) {
    Kind = CopyStruct;
    return;
  }

  // Otherwise, this is target-dependent and based on the size and
  // alignment of the ivar.

  // If the size of the ivar is not a power of two, give up.  We don't
  // want to get into the business of doing compare-and-swaps.
  if (!IvarSize.isPowerOfTwo()) {
    Kind = CopyStruct;
    return;
  }

  llvm::Triple::ArchType arch =
    CGM.getTarget().getTriple().getArch();

  // Most architectures require memory to fit within a single cache
  // line, so the alignment has to be at least the size of the access.
  // Otherwise we have to grab a lock.
  if (IvarAlignment < IvarSize && !hasUnalignedAtomics(arch)) {
    Kind = CopyStruct;
    return;
  }

  // If the ivar's size exceeds the architecture's maximum atomic
  // access size, we have to use CopyStruct.
  if (IvarSize > getMaxAtomicAccessSize(CGM, arch)) {
    Kind = CopyStruct;
    return;
  }

  // Otherwise, we can use native loads and stores.
  Kind = Native;
}

在上面方法中,我们看到if (IsCopy) { Kind = GetSetProperty; return; }这个代码,这里赋值GetSetProperty就跟上面截图的case对应上了,也就是说用copy修饰属性时,set方法会调用objc_setProperty

image

上面的情况set方法会调用objc_setProperty

总结:用copy或者retain修饰的属性,set方法会调用objc_setProperty

分析完objc_setProperty,我们再看下什么时候会生成objc_getProperty,老套路继续全局搜索。
里面有跟之前差不多的代码,不过还有别的地方也有这个结果,我们这次从不同类型代码来看

if (mustSynthesizeSetterGetterMethod(IMD, PD, true /*getter*/)) {
    bool GenGetProperty =
        !(Attributes & ObjCPropertyAttribute::kind_nonatomic) &&
        (Attributes & (ObjCPropertyAttribute::kind_retain |
                       ObjCPropertyAttribute::kind_copy));
    std::string Getr;
    if (GenGetProperty && !objcGetPropertyDefined) {
      objcGetPropertyDefined = true;
      // FIXME. Is this attribute correct in all cases?
      Getr = "\nextern \"C\" __declspec(dllimport) "
            "id objc_getProperty(id, SEL, long, bool);\n";
    }
    RewriteObjCMethodDecl(OID->getContainingInterface(),
                          PID->getGetterMethodDecl(), Getr);
    Getr += "{ ";
    // Synthesize an explicit cast to gain access to the ivar.
    // See objc-act.c:objc_synthesize_new_getter() for details.
    if (GenGetProperty) {
      // return objc_getProperty(self, _cmd, offsetof(ClassDecl, OID), 1)
      Getr += "typedef ";
      const FunctionType *FPRetType = nullptr;
      RewriteTypeIntoString(PID->getGetterMethodDecl()->getReturnType(), Getr,
                            FPRetType);
      Getr += " _TYPE";
      if (FPRetType) {
        Getr += ")"; // close the precedence "scope" for "*".

        // Now, emit the argument types (if any).
        if (const FunctionProtoType *FT = dyn_cast(FPRetType)){
          Getr += "(";
          for (unsigned i = 0, e = FT->getNumParams(); i != e; ++i) {
            if (i) Getr += ", ";
            std::string ParamStr =
                FT->getParamType(i).getAsString(Context->getPrintingPolicy());
            Getr += ParamStr;
          }
          if (FT->isVariadic()) {
            if (FT->getNumParams())
              Getr += ", ";
            Getr += "...";
          }
          Getr += ")";
        } else
          Getr += "()";
      }
      Getr += ";\n";
      Getr += "return (_TYPE)";
      Getr += "objc_getProperty(self, _cmd, ";
      RewriteIvarOffsetComputation(OID, Getr);
      Getr += ", 1)";
    }
    else
      Getr += "return " + getIvarAccessString(OID);
    Getr += "; }";
    InsertText(startGetterSetterLoc, Getr);
  }

这里生成了代码的字符串Getr,也就是GenGetPropertyYES的时候才会执行下面,

bool GenGetProperty =
        !(Attributes & ObjCPropertyAttribute::kind_nonatomic) &&
        (Attributes & (ObjCPropertyAttribute::kind_retain |
                       ObjCPropertyAttribute::kind_copy));

这个判断写的很清楚了,当属性不用nonatomic修饰,并且用retain或者copy修饰时候,GenGetPropertyYES

objc_setProperty在下图判断也更直观:

image

分析完了我们再验证一波

@property (nonatomic, copy) NSString *name1;
@property (nonatomic, strong) NSString *name2;
@property (nonatomic, retain) NSString *name3;
@property (atomic, copy) NSString *name4;
@property (atomic, strong) NSString *name5;
@property (atomic, retain) NSString *name6;

@property (nonatomic) NSString *name7;
@property (atomic) NSString *name8;
@property (copy) NSString *name9;
@property (strong) NSString *name10;
@property (retain) NSString *name11;

@property NSString *name12;
static NSString * _I_SJPerson_name1(SJPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_SJPerson$_name1)); }
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);

static void _I_SJPerson_setName1_(SJPerson * self, SEL _cmd, NSString *name1) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct SJPerson, _name1), (id)name1, 0, 1); }

static NSString * _I_SJPerson_name2(SJPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_SJPerson$_name2)); }
static void _I_SJPerson_setName2_(SJPerson * self, SEL _cmd, NSString *name2) { (*(NSString **)((char *)self + OBJC_IVAR_$_SJPerson$_name2)) = name2; }

static NSString * _I_SJPerson_name3(SJPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_SJPerson$_name3)); }
static void _I_SJPerson_setName3_(SJPerson * self, SEL _cmd, NSString *name3) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct SJPerson, _name3), (id)name3, 0, 0); }

extern "C" __declspec(dllimport) id objc_getProperty(id, SEL, long, bool);

static NSString * _I_SJPerson_name4(SJPerson * self, SEL _cmd) { typedef NSString * _TYPE;
return (_TYPE)objc_getProperty(self, _cmd, __OFFSETOFIVAR__(struct SJPerson, _name4), 1); }
static void _I_SJPerson_setName4_(SJPerson * self, SEL _cmd, NSString *name4) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct SJPerson, _name4), (id)name4, 1, 1); }

static NSString * _I_SJPerson_name5(SJPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_SJPerson$_name5)); }
static void _I_SJPerson_setName5_(SJPerson * self, SEL _cmd, NSString *name5) { (*(NSString **)((char *)self + OBJC_IVAR_$_SJPerson$_name5)) = name5; }

static NSString * _I_SJPerson_name6(SJPerson * self, SEL _cmd) { typedef NSString * _TYPE;
return (_TYPE)objc_getProperty(self, _cmd, __OFFSETOFIVAR__(struct SJPerson, _name6), 1); }
static void _I_SJPerson_setName6_(SJPerson * self, SEL _cmd, NSString *name6) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct SJPerson, _name6), (id)name6, 1, 0); }

static NSString * _I_SJPerson_name7(SJPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_SJPerson$_name7)); }
static void _I_SJPerson_setName7_(SJPerson * self, SEL _cmd, NSString *name7) { (*(NSString **)((char *)self + OBJC_IVAR_$_SJPerson$_name7)) = name7; }

static NSString * _I_SJPerson_name8(SJPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_SJPerson$_name8)); }
static void _I_SJPerson_setName8_(SJPerson * self, SEL _cmd, NSString *name8) { (*(NSString **)((char *)self + OBJC_IVAR_$_SJPerson$_name8)) = name8; }

static NSString * _I_SJPerson_name9(SJPerson * self, SEL _cmd) { typedef NSString * _TYPE;
return (_TYPE)objc_getProperty(self, _cmd, __OFFSETOFIVAR__(struct SJPerson, _name9), 1); }
static void _I_SJPerson_setName9_(SJPerson * self, SEL _cmd, NSString *name9) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct SJPerson, _name9), (id)name9, 1, 1); }

static NSString * _I_SJPerson_name10(SJPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_SJPerson$_name10)); }
static void _I_SJPerson_setName10_(SJPerson * self, SEL _cmd, NSString *name10) { (*(NSString **)((char *)self + OBJC_IVAR_$_SJPerson$_name10)) = name10; }

static NSString * _I_SJPerson_name11(SJPerson * self, SEL _cmd) { typedef NSString * _TYPE;
return (_TYPE)objc_getProperty(self, _cmd, __OFFSETOFIVAR__(struct SJPerson, _name11), 1); }
static void _I_SJPerson_setName11_(SJPerson * self, SEL _cmd, NSString *name11) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct SJPerson, _name11), (id)name11, 1, 0); }

static NSString * _I_SJPerson_name12(SJPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_SJPerson$_name12)); }
static void _I_SJPerson_setName12_(SJPerson * self, SEL _cmd, NSString *name12) { (*(NSString **)((char *)self + OBJC_IVAR_$_SJPerson$_name12)) = name12; }

总结:

  1. copy或者retain修饰的属性,set方法会调用objc_setProperty
  2. copy或者retain修饰,并且不用nonatomic修饰的属性,get方法会调用objc_getProperty
  3. 剩下的情况在getset方法里会直接使用内存平移。

静态分析完了,我们跑真机验证下,打几个符号断点,首先objc_setProperty底层有好几个对应的方法,属性关键字不同时,执行的方法也不一样,所以我们需要每个方法都打符号断点。
objc_setPropertyobjc_setProperty_atomicobjc_setProperty_nonatomicobjc_setProperty_atomic_copyobjc_setProperty_nonatomic_copyobjc_getProperty,这些方法打符号断点。

我们会发现name1name4name5name6name8name9name10name11name12这些属性都会走objc_setPropertyobjc_getProperty方法,跟我们上面分析的就不一样了。

属性默认修饰我们需要知道,也就是name12默认修饰关键字是什么?答:atomicstrong。这个我们可以打印所有属性的attribute

unsigned int count = 0;
objc_property_t *list = class_copyPropertyList([SJPerson class], &count);
for (int i = 0; i < count; i++) {
    objc_property_t property = list[i];
    NSLog(@"%s", property_getName(property));    
    NSLog(@"%s", property_getAttributes(property));
    NSLog(@"---------------");
}
name1
T@"NSString",C,N,V_name1
---------------
name2
T@"NSString",&,N,V_name2
---------------
name3
T@"NSString",&,N,V_name3
---------------
name4
T@"NSString",C,V_name4
---------------
name5
T@"NSString",&,V_name5
---------------
name6
T@"NSString",&,V_name6
---------------
name7
T@"NSString",&,N,V_name7
---------------
name8
T@"NSString",&,V_name8
---------------
name9
T@"NSString",C,V_name9
---------------
name10
T@"NSString",&,V_name10
---------------
name11
T@"NSString",&,V_name11
---------------
name12
T@"NSString",&,V_name12
---------------

属性的描述,C代表copyN代表nonatomic,没有N代表atomic&代表strong或者retain。也就是说,属性的默认修饰是atomicstrong
所以更正下结论:用copy或者atomic修饰的属性,set方法会调用objc_setPropertyget方法会调用objc_getProperty。其他不会调用。至于为什么看llvm源码和生成的C++文件与真机结果不一样,猜测是这些源码有些细节并没有全部写出来,有了解的欢迎在评论区交流。

方法获取的深入理解

首先在SJPerson里面分别写一个实例方法和类方法并实现。

@interface SJPerson : NSObject

- (void)sayHello;
+ (void)sayHappy;

@end

@implementation SJPerson
- (void)sayHello{
    NSLog(@"%s", __func__);
}

+ (void)sayHappy{
    NSLog(@"%s", __func__);
}
@end
  1. 通过class_getInstanceMethod获取方法:
void sjInstanceMethod_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));
    
    SJLog(@"%s -\n%p\n%p\n%p\n%p",__func__,method1,method2,method3,method4);
}

执行代码:

SJPerson *p = [[SJPerson alloc] init];
Class pClass = object_getClass(p);
        
sjInstanceMethod_classToMetaclass(pClass);

按正常的理解,1、4应该有值,2、3没有值

sjInstanceMethod_classToMetaclass

结果如我们预期,没问题。

  1. 通过class_getClassMethod获取方法:
void sjClassMethod_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));

//    - (void)sayHello;
//    + (void)sayHappy;
    Method method3 = class_getClassMethod(pClass, @selector(sayHappy));
    Method method4 = class_getClassMethod(metaClass, @selector(sayHappy));
    
    SJLog(@"%s -\n%p\n%p\n%p\n%p",__func__,method1,method2,method3,method4);
}

这个结果应该是1、2、4没有值,3有值,执行代码sjClassMethod_classToMetaclass(pClass);,结果如下:

sjClassMethod_classToMetaclass

发现结果跟我们预想的不一样,4也有值了,我们从源码看下class_getClassMethod都走了哪些方法:

Method class_getClassMethod(Class cls, SEL sel)
{
    if (!cls  ||  !sel) return nil;

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

发现class_getClassMethod底层也是走的class_getInstanceMethod方法。再看下cls->getMeta()

Class getMeta() {
        if (isMetaClassMaybeUnrealized()) return (Class)this;
        else return this->ISA();
    }

这个方法判断如果是元类,返回它自己,如果不是元类,返回->ISA(),也就是元类。原来问题出在这,这时候我们看3和4在底层最后走的都是一样的方法,所以都有值。

  1. 通过class_getMethodImplementation获取方法
void sjIMP_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));

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

这个结果应该是1、2、4没有值,3有值,执行代码sjIMP_classToMetaclass(pClass);,结果如下:

sjIMP_classToMetaclass

发现4个imp都有值,为什么呢?继续从源码中找线索

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

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

    lockdebug_assert_no_locks_locked_except({ &loadMethodLock });

    imp = lookUpImpOrNilTryCache(nil, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER);

    // Translate forwarding function to C-callable external version
    if (!imp) {
        return _objc_msgForward;
    }

    return imp;
}

我们可以看到,class_getMethodImplementation走的是方法查找的流程,当imp为空时,返回了一个_objc_msgForward函数,消息转发依赖这个函数,这个以后再说。4个imp结果如下:

imp

理解了以上内容,我们再看个非常有意思的面试题:

void sjKindofTest(void){
    BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];       //
    BOOL re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];     //
    BOOL re3 = [(id)[SJPerson class] isKindOfClass:[SJPerson class]];       //
    BOOL re4 = [(id)[SJPerson class] isMemberOfClass:[SJPerson class]];     //
    SJLog(@" re1 :%hhd\n re2 :%hhd\n re3 :%hhd\n re4 :%hhd\n",re1,re2,re3,re4);

    BOOL re5 = [(id)[NSObject alloc] isKindOfClass:[NSObject class]];       //
    BOOL re6 = [(id)[NSObject alloc] isMemberOfClass:[NSObject class]];     //
    BOOL re7 = [(id)[SJPerson alloc] isKindOfClass:[SJPerson class]];       //
    BOOL re8 = [(id)[SJPerson alloc] isMemberOfClass:[SJPerson class]];     //
    SJLog(@" re5 :%hhd\n re6 :%hhd\n re7 :%hhd\n re8 :%hhd\n",re5,re6,re7,re8);
}

打印结果如何呢,不清楚,那我们在源码中找下isKindOfClassisMemberOfClass方法实现。

+ (BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = self->ISA(); tcls; tcls = tcls->getSuperclass()) {
        if (tcls == cls) return YES;
    }
    return NO;
}

- (BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = [self class]; tcls; tcls = tcls->getSuperclass()) {
        if (tcls == cls) return YES;
    }
    return NO;
}

+ (BOOL)isMemberOfClass:(Class)cls {
    return self->ISA() == cls;
}

- (BOOL)isMemberOfClass:(Class)cls {
    return [self class] == cls;
}

re1应该执行+ (BOOL)isKindOfClass:(Class)cls方法,for循环,cls元类,判断不相等,元类getSuperClass就是NSObject类,与NSObject相等,所以re1等于1。

re2应该执行+ (BOOL)isMemberOfClass:(Class)cls方法,NSObject->ISA()NSObject不相等,所以re2等于0。

re3应该执行+ (BOOL)isKindOfClass:(Class)cls方法,for循环,cls元类,判断不相等,元类getSuperClass就是NSObject元类,不相等,再getSuperClassNSObject类,与NSObject类不相等,再getSuperClass是空,所以re3等于0。

re4应该执行+ (BOOL)isMemberOfClass:(Class)cls方法,SJPerson->ISA()SJPerson不相等,所以re4等于0。

re5应该执行- (BOOL)isKindOfClass:(Class)cls方法,for循环,clsNSObject类,与NSObject相等,所以re5等于1。

re6应该执行- (BOOL)isMemberOfClass:(Class)cls方法,NSObject类,与NSObject相等,所以re6等于1。

re7应该执行- (BOOL)isKindOfClass:(Class)cls方法,for循环,clsSJPerson类,与SJPerson相等,所以re7等于1。

re8应该执行- (BOOL)isMemberOfClass:(Class)cls方法,SJPerson类,与SJPerson相等,所以re8等于1。

看下输出结果:

image

与我们分析的一致,是不是皆大欢喜????答案肯定是否定的,你怎么知道isKindOfClass一定会走到源码里面的isKindOfClass呢?就因为方法名一样就这么草率的判断了吗?要看到真实执行方法,还是要看汇编:

image

isMemberOfClass这个方法没毛病,我们上面分析的也没问题,但是isKindOfClass发现这里面并没有这个方法,取而代之是objc_opt_isKindOfClass,我们在源码中找下有没有这个方法,刚好有

// Calls [obj isKindOfClass]
BOOL
objc_opt_isKindOfClass(id obj, Class otherClass)
{
#if __OBJC2__
    if (slowpath(!obj)) return NO;
    Class cls = obj->getIsa();
    if (fastpath(!cls->hasCustomCore())) {
        for (Class tcls = cls; tcls; tcls = tcls->getSuperclass()) {
            if (tcls == otherClass) return YES;
        }
        return NO;
    }
#endif
    return ((BOOL(*)(id, SEL, Class))objc_msgSend)(obj, @selector(isKindOfClass:), otherClass);
}

也就是说,我们之前分析的isKindOfClass都要重新分析。
如果objnil,直接返回NOclsobj->getIsa(),即对象对应的类或者元类,大概率进入到if里面,如果没有进去,走上面我们分析流程,没问题。进入if,也是判断和当前类相等不相等,不相等执行superclass,再判断,直到superclassnil。按这样分析,上面结果也不变,但是我们分析问题不能简单理想化,看具体执行代码还是要以汇编为准!

你可能感兴趣的:(属性、方法获取的深入理解)