属性的深入理解
要研究成员变量与属性关系,最简单方法就是利用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;
};
name
的set
方法是调用了objc_setProperty
函数,而nickName
的set
方法则是通过指针偏移直接赋值,都是属性为什么会不一样呢,因为他们修饰的类型不一样,name
是用copy
修饰,nickName
是用strong
修饰。
我们都知道,声明一个属性,编译器会自动帮我们生成成员变量和get
、set
方法。项目中有很多属性,他们get
、set
方法会有很多,编译器不可能每个方法写一个实现,只能底层有一个通用的方法,get
、set
都会执行这一个方法,而这个操作,在编译阶段就应该完成。那编译阶段完成的事情,我们想看下实现,只能去llvm
源码中查找相关线索。
全局搜索objc_setProperty
,找到getSetPropertyFn
函数会创建objc_setProperty
。
再全局搜索getSetPropertyFn
llvm::FunctionCallee CGObjCMac::GetPropertySetFunction() {
return ObjCTypes.getSetPropertyFn();
}
GetPropertySetFunction
函数中调用getSetPropertyFn
,继续全局搜索GetPropertySetFunction
函数调用发现是跟这个case
相关,再看下这个case
的变量
变量
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
。
上面的情况
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
,也就是GenGetProperty
为YES
的时候才会执行下面,
bool GenGetProperty =
!(Attributes & ObjCPropertyAttribute::kind_nonatomic) &&
(Attributes & (ObjCPropertyAttribute::kind_retain |
ObjCPropertyAttribute::kind_copy));
这个判断写的很清楚了,当属性不用nonatomic
修饰,并且用retain
或者copy
修饰时候,GenGetProperty
为YES
。
objc_setProperty
在下图判断也更直观:
分析完了我们再验证一波
@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; }
总结:
- 用
copy
或者retain
修饰的属性,set
方法会调用objc_setProperty
。 - 用
copy
或者retain
修饰,并且不用nonatomic
修饰的属性,get
方法会调用objc_getProperty
。 - 剩下的情况在
get
和set
方法里会直接使用内存平移。
静态分析完了,我们跑真机验证下,打几个符号断点,首先objc_setProperty
底层有好几个对应的方法,属性关键字不同时,执行的方法也不一样,所以我们需要每个方法都打符号断点。
objc_setProperty
、objc_setProperty_atomic
、objc_setProperty_nonatomic
、objc_setProperty_atomic_copy
、objc_setProperty_nonatomic_copy
、objc_getProperty
,这些方法打符号断点。
我们会发现name1
、name4
、name5
、name6
、name8
、name9
、name10
、name11
、name12
这些属性都会走objc_setProperty
和objc_getProperty
方法,跟我们上面分析的就不一样了。
属性默认修饰我们需要知道,也就是name12
默认修饰关键字是什么?答:atomic
、strong
。这个我们可以打印所有属性的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
代表copy
,N
代表nonatomic
,没有N
代表atomic
,&
代表strong
或者retain
。也就是说,属性的默认修饰是atomic
和strong
。
所以更正下结论:用copy
或者atomic
修饰的属性,set
方法会调用objc_setProperty
,get
方法会调用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
- 通过
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没有值
结果如我们预期,没问题。
- 通过
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);
,结果如下:
发现结果跟我们预想的不一样,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在底层最后走的都是一样的方法,所以都有值。
- 通过
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);
,结果如下:
发现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
结果如下:
理解了以上内容,我们再看个非常有意思的面试题:
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);
}
打印结果如何呢,不清楚,那我们在源码中找下isKindOfClass
与isMemberOfClass
方法实现。
+ (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
元类,不相等,再getSuperClass
是NSObject
类,与NSObject
类不相等,再getSuperClass
是空,所以re3
等于0。
re4
应该执行+ (BOOL)isMemberOfClass:(Class)cls
方法,SJPerson->ISA()
与SJPerson
不相等,所以re4
等于0。
re5
应该执行- (BOOL)isKindOfClass:(Class)cls
方法,for
循环,cls
是NSObject
类,与NSObject
相等,所以re5
等于1。
re6
应该执行- (BOOL)isMemberOfClass:(Class)cls
方法,NSObject
类,与NSObject
相等,所以re6
等于1。
re7
应该执行- (BOOL)isKindOfClass:(Class)cls
方法,for
循环,cls
是SJPerson
类,与SJPerson
相等,所以re7
等于1。
re8
应该执行- (BOOL)isMemberOfClass:(Class)cls
方法,SJPerson
类,与SJPerson
相等,所以re8
等于1。
看下输出结果:
与我们分析的一致,是不是皆大欢喜????答案肯定是否定的,你怎么知道isKindOfClass
一定会走到源码里面的isKindOfClass
呢?就因为方法名一样就这么草率的判断了吗?要看到真实执行方法,还是要看汇编:
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
都要重新分析。
如果obj
是nil
,直接返回NO
。cls
为obj->getIsa()
,即对象对应的类或者元类,大概率进入到if
里面,如果没有进去,走上面我们分析流程,没问题。进入if
,也是判断和当前类相等不相等,不相等执行superclass
,再判断,直到superclass
为nil
。按这样分析,上面结果也不变,但是我们分析问题不能简单理想化,看具体执行代码还是要以汇编为准!