补充说明结构体嵌套
typedef struct person{
char a;
int b;
short c;
}myPerson;
struct p{
int d;
double e;
char f;
myPerson g;
short h;
}per;
结构体嵌套所需开辟的内存空间是结构体内最大长度数据成员(非结构体)
所占大小的整数倍
。
如图所示:
-
myPerson
本身作为结构体,遵循内存对齐原则
,故而所占12个字节
- 而
per
作为嵌套结构体,加入myPerson
结构体时应该是myPerson
内最大成员变量
的整数倍开始,由代码可知,myPerson
中最大为int类型
,占4个字节
,而myPerson
的起始位置为17
,根据min(17,4)
得出起始位置应该为20
,并且其自身占12个字节
故而所占位数为(20-31)
- 根据
内存规则
,所占内存必须为最大成员
所占的整数倍
,故而为8的整数倍
,最小即为min(33,8)= 40
- 总结:结构体嵌套时,
本身
和嵌套的结构体
都要满足内存对齐规则
,不足的自动补齐
类的结构中class_rw_t
与class_ro_t
的区别
class_ro_t
struct class_ro_t {
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize;
#ifdef __LP64__
uint32_t reserved;
#endif
const uint8_t * ivarLayout;
const char * name;
method_list_t * baseMethodList;
protocol_list_t * baseProtocols;
const ivar_list_t * ivars;
const uint8_t * weakIvarLayout;
property_list_t *baseProperties;
...
};
-
class_ro_t
存储了当前类在编译期
就已经确定的属性
、方法
以及遵循的协议
,里面是没有分类的方法的。那些运行时
添加的方法将会存储在运行时生成的class_rw_t
中。 -
ro
即表示read only
,是无法进行修改
的。
class_rw_t
struct class_rw_t {
// Be warned that Symbolication knows the layout of this structure.
uint32_t flags;
uint16_t witness;
#if SUPPORT_INDEXED_ISA
uint16_t index;
#endif
explicit_atomic ro_or_rw_ext;
Class firstSubclass;
Class nextSiblingClass;
...
}
-
ObjC
类中的属性、方法还有遵循的协议
等信息都保存在class_rw_t
中:
class_rw_t
生成时机
class_rw_t
生成在运行时,在编译期间,class_ro_t
结构体就已经确定,objc_class
中的bits
的data
部分存放着该结构体的地址
。在runtime
运行之后,具体说来是在运行runtime
的realizeClass
方法时,会生成class_rw_t
结构体,该结构体包含了class_ro_t
,并且更新data
部分,换成class_rw_t
结构体的地址。
类的realizeClass
运行之前如下图所示:
类的
realizeClass
运行之后如下图所示:
由此可见,
class_rw_t
与class_ro_t
中的成员变量有一些是相同的,区别在于:class_ro_t
存放的是编译期间就确定的;而class_rw_t
是在runtime
时才确定,它会先将class_ro_t
的内容拷贝
过去,然后再将当前类的分类
的这些属性、方法
等拷贝到其中。所以可以说class_rw_t
是class_ro_t
的超集,当然实际访问类的方法、属性
等也都是访问的class_rw_t
中的内容
引自class_rw_t与class_ro_t的区别
之前我们在打印类的结构时,只能读取到其中的属性以及实例方法,但是其中的成员变量
与类方法
均没有读取到,接下来我们就分析一下:
@interface LGPerson : NSObject
{
NSString *hobby;
NSObject *objc;
id age;
}
@property (nonatomic, copy) NSString *nickName;
@property (nonatomic, strong) NSString *name;
@end
成员变量、实例变量、属性之间的关系
- 如上面代码所示,
{}
中的都是成员变量
,而其中的objc
就是实例变量
,(id是OC特有的类,本质上等于(void *))
,所以age
也是实例变量
-
实例变量
就是Class
类通过实例化
出来的对象,是一种特殊的成员变量
,实例变量+基本数据类型变量=成员变量
-
成员变量
一般用于类内部
,不会生成setter、getter
方法,外界无法获取到
-
-
属性变量
就是自动生成setter、getter
方法,其他对象可以进行访问
copy与strong的实现
static NSString * _I_LGPerson_nickName(LGPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_nickName)); }
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);
static void _I_LGPerson_setNickName_(LGPerson * self, SEL _cmd, NSString *nickName) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct LGPerson, _nickName), (id)nickName, 0, 1); }
static NSString * _I_LGPerson_name(LGPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_name)); }
static void _I_LGPerson_setName_(LGPerson * self, SEL _cmd, NSString *name) { (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_name)) = name; }
首先通过clang
生成的main.cpp
文件中可以看出通过copy
修饰的属性会含有objc_setProperty
,而通过strong
修饰的就没有
copy
是在编译时
就已经进行处理调用objc_setProperty
方法
//通过调用GetOptimizedPropertySetFunction()方法去判断对应的属性,是否为atomic及是否为copy
llvm::FunctionType *FTy =
Types.GetFunctionType( Types.arrangeBuiltinFunctionDeclaration(Ctx.VoidTy, Params));
const char *name;
if (atomic && copy)
name = "objc_setProperty_atomic_copy";
else if (atomic && !copy)
name = "objc_setProperty_atomic";
else if (!atomic && copy)
name = "objc_setProperty_nonatomic_copy";
else
name = "objc_setProperty_nonatomic";
//根据对应的属性修饰查找
//通过runtime返回对应的sel进行value赋值
return CGM.CreateRuntimeFunction(FTy, name);
strong
方法通过引用计数retain
进行查看
void
objc_storeStrong(id *location, id obj)
{
id prev = *location;
if (obj == prev) {
return;
}
// 新值赋值
objc_retain(obj);
*location = obj;
// 旧值释放
objc_release(prev);
}
通过llvm
中进行查找
case BlockCaptureEntityKind::ARCStrong: {
llvm::Value *srcValue = Builder.CreateLoad(srcField, "blockcopy.src");
// At -O0, store null into the destination field (so that the
// storeStrong doesn't over-release) and then call storeStrong.
// This is a workaround to not having an initStrong call.
if (CGM.getCodeGenOpts().OptimizationLevel == 0) {
auto *ty = cast(srcValue->getType());
llvm::Value *null = llvm::ConstantPointerNull::get(ty);
Builder.CreateStore(null, dstField);
EmitARCStoreStrongCall(dstField, srcValue, true);
} else {
EmitARCRetainNonBlock(srcValue);
if (!needsEHCleanup(captureType.isDestructedType()))
cast(dstField.getPointer())->eraseFromParent();
}
break;
}
llvm::Value *CodeGenFunction::EmitARCStoreStrongCall(Address addr,
llvm::Value *value,
bool ignored) {
assert(addr.getElementType() == value->getType());
llvm::Function *&fn = CGM.getObjCEntrypoints().objc_storeStrong;
if (!fn) {
fn = CGM.getIntrinsic(llvm::Intrinsic::objc_storeStrong);
setARCRuntimeFunctionLinkage(CGM, fn);
}
llvm::Value *args[] = {
Builder.CreateBitCast(addr.getPointer(), Int8PtrPtrTy),
Builder.CreateBitCast(value, Int8PtrTy)
};
EmitNounwindRuntimeCall(fn, args);
if (ignored) return nullptr;
return value;
}
当case BlockCaptureEntityKind:为ARCStrong
时,->EmitARCStoreStrongCall()
->objc_stroeStrong
方法,进而可以找到上述底层编码
获取成员变量及类方法所在位置
首先读取读取
LGperson
类去获取对应的bits
根据上面
class_rw_t
与class_ro_t
的区别得出,class_rw_t
本身是包含ro
的,所以我们通过上图去读取对应的ro
,得出其中除了基础类型baseProperties
外还有ivars
,通过读取ivars
可以看出成员变量
是存在中的,因此得出成员变量
本身是不能被外界所读取
。
我们通过查看
LGPerson
的isa
去查看对应的元类
信息,看出其中是包含LGPerson
中的类方法 :say666
,故而我们得出类方法是存在于其对应的元类
中。
方法签名
- 每一个方法都会包含
sel(方法编号)
与imp(函数指针,指向函数方法的实现)
- 第一个参数:返回值
v(void) @(id)
- 第二个参数:开辟的内存
总字节数
- 第三个参数:传入的参数,位置从0开始,占用(0-7)
- 第四个参数:
:代表sel(方法编号)
,位置从8开始占用(8-15)
下面我们打印了一部分,具体的对应关系可以去苹果官网Type Encoding 查看
面试题解析
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);
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);
首先我们通过方法查找到对应的类方法
和实例方法
+ (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = self->ISA(); tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}
- (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}
+ (BOOL)isMemberOfClass:(Class)cls {
return self->ISA() == cls;
}
- (BOOL)isMemberOfClass:(Class)cls {
return [self class] == cls;
}
+ (Class)class {
return self;
}
解析
- 首先
[NSObject class]
为类,需要调用对应的类方法,根元类的superclass
是指向NSObject
,故而两者相等- 而
[LGPerson class]
为LGPerson
,里面循环查找superclass
,没有与LGPerson
相等的类,故而返回NO
- 而
- 根据
isMemberOfClass
方法源码可以得出,self->ISA()
指向的是根元类
,而NSObject并不等于根元类
,所以返回NO
- 根据
isKindOfClass
实例方法可以得出,tcls == [self class]
,第一次
循环时直接相等,返回YES
- 根据
isMemberOfClass
实例方法得出,根据[self class]
返回的是self
,故而传入的与返回的相等 - 注:(如果
元类、父类、isa
之间的走位
不懂可以参考下面月月大神的流程图)
面试题类方法与实例方法存在位置
void lgInstanceMethod_classToMetaclass(Class pClass){
const char *className = class_getName(pClass);
Class metaClass = objc_getMetaClass(className);
// - (void)sayHello;
// + (void)sayHappy;
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));
LGLog(@"%s - %p-%p-%p-%p",__func__,method1,method2,method3,method4);
31b0-0x0-0x0-0x100003148
}
void lgClassMethod_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));
LGLog(@"%s-%p-%p-%p-%p",__func__,method1,method2,method3,method4);
}
void lgIMP_classToMetaclass(Class pClass){
const char *className = class_getName(pClass);
Class metaClass = objc_getMetaClass(className);
// - (void)sayHello;
// + (void)sayHappy;
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);
}
解析
* 1.class_getInstanceMethod
是否存在对应的实例方法
,根据打印结果可以看出,method1
与method4
有值,而method2、method3
没有值,故而得出,实例方法存在对应的类
中,而类方法是存在对应的元类
中。
* 2.class_getClassMethod
是否存在对应的类方法
,根据打印结果看出,method1、method2
没有值,method3、method4
有值,故而得出类方法可以在自身类与对应的元类中
找到
* 3.class_getMethodImplementation
查看实例方法与类方法
对应的imp
,根据结果可以看出imp2,imp3
是一致的,根据下面源码得出,如果没有对应的imp即sel方法
则会固定返回_objc_msgForward
,否则返回imp
。
IMP class_getMethodImplementation(Class cls, SEL sel)
{
IMP imp;
if (!cls || !sel) return nil;
imp = lookUpImpOrNil(nil, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER);
// Translate forwarding function to C-callable external version
if (!imp) {
return _objc_msgForward;
}
return imp;
}