iOS OC 类原理一
2020-04-20 15:02
1266 查看
iOS OC 类原理一
- 3.1 类的结构是什么?
- 3.2 类的结构分析
- 通过对`类`结构的分析,得出:`成员变量`存在`ivars`中,`属性`存储在`baseProperties`中,`对象方法`存储在`类`里面,`类方法`存储在`元类`里。
1. 类
和元类
的创建时机
前面简单提到
类和
元类的创建时机是在编译器,今天我们通过一下两种方法来验证一下:
1.1 打印 类
和元类
的指针
首先看下面代码:
在
main函数之前打印断点,
通过
p/x打印
类指针,如果能获得
指针, 说明已经在内存中申请了内存空间
然后
x/4gx打印
类的内存结构,得到
类 的isa,然后
isa & 掩码 ISA_MASK获得
元类的
isa,如果这个过程中能正常打印出相应的指针,则能简单验证
类和
元类的创建是在编译期创建的,打印结果如下:
1.2 command + B
生成可执行文件,然后使用 MachoView
打开程序二进制可执行文件查看
由此,可以验证
类和
元类是在编译期创建的,在运行项目
alloc之前已经被创建出来了
2. 指针偏移
2.1 普通指针 值拷贝
int a = 10; // int b = 10; // LGNSLog(@"%d -- %p",a,&a); LGNSLog(@"%d -- %p",b,&b); // KC打印: 10 -- 0x7ffeefbff45c // KC打印: 10 -- 0x7ffeefbff458
2.2 指针拷贝
// 对象 - 指针拷贝 LGPerson *p1 = [LGPerson alloc]; LGPerson *p2 = [LGPerson alloc]; LGNSLog(@"%@ -- %p",p1,&p1); LGNSLog(@"%@ -- %p",p2,&p2); // KC打印: <LGPerson: 0x100753be0> -- 0x7ffeefbff450 // KC打印: <LGPerson: 0x10074e740> -- 0x7ffeefbff448
2.3 指针偏移
// 数组指针 int c[4] = {1,2,3,4}; int *d = c; NSLog(@"%p - %p - %p",&c,&c[0],&c[1]); NSLog(@"%p - %p - %p",d,d+1,d+2); for (int i = 0; i<4; i++) { // int value = c[i]; int value = *(d+i); LGNSLog(@"%d",value); } NSLog(@"指针 - 内存偏移"); // 0x7ffeefbff470 - 0x7ffeefbff470 - 0x7ffeefbff474 // 0x7ffeefbff470 - 0x7ffeefbff474 - 0x7ffeefbff478 // KC打印: 1 // KC打印: 2 // KC打印: 3 // KC打印: 4
首地址是
数组的第一个
元素的地址,
&c[0]和
&c[1],相差一个元素的大小,
指针d + 1,相当于偏移一个所占位数的元素的大小
3. 类的结构
3.1 类的结构是什么?
通过
clang查看看下面代码在
c++文件中的编译:
int main(int argc, const char * argv[]) { @autoreleasepool { LGPerson *person = [LGPerson alloc]; Class pClass = object_getClass(person); NSLog(@"%@ - %p",person,pClass); } return 0; }
int main(int argc, const char * argv[]) { /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; // id, SEL LGPerson *person = ((LGPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("LGPerson"), sel_registerName("alloc")); Class pClass = object_getClass(person); NSLog((NSString *)&__NSConstantStringImpl__var_folders_5s_4100t0cd5rn_d7gx0n5wqh8w0000gn_T_main_60f7a3_mi_9,person,pClass); } return 0; }
我们探究的
类的结构,就是
Class,在
cpp文件中不难发现
类结构是:
typedef struct objc_class *Class;
可以看出,
类是
objc_class类型的 结构体。
struct objc_object { Class _Nonnull isa __attribute__((deprecated)); };
我们知道万物皆对象,
objc_class继承自
objc_object,那么我们通过下图方法查看
objc_class的源码:
struct objc_class : objc_object { // Class ISA; // 8 Class superclass; // 8 cache_t cache; // 16 不是8 // formerly cache pointer and vtable class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags class_rw_t *data() { return bits.data(); } ··· // 方法和函数 }
源码中可以看到,有个隐藏的
Class isa(为什么有个隐藏的
Class isa?),
隐藏的属性必然是来自于
继承,
继承自
objc_object,看
objc_object源码:
object源码:
struct objc_object { Class _Nonnull isa OBJC_ISA_AVAILABILITY; };
那么
NSObject的定义是什么样的呢?
其实
NSObject的定义是
结构体的一种仿写:
@interface NSObject <NSObject> { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wobjc-interface-ivars" Class isa OBJC_ISA_AVAILABILITY; #pragma clang diagnostic pop }
问: 为什么
isa是
Class类型?
答:万物皆对象,
Clss本身继承自
object,用来接收
isa可以的,早期调用
isa就是为了返回
类,
后期优化了
nonpointer isa
问:
objc_class和
NSObject的关系?
objc_object和
NSObject的关系?
NSObject是一种
objc_class的类型,
NSObject也是一个
类class,底层也是
objc_class;
OC底层封装的
C,
objc_object是
NSObject底层编译的写法。
objc_object和
objc_class是底层的实现,对应当前
NSObject(Class)和
NSObject。
3.2 类的结构分析
通常我们会在
类中定义
属性、
成员变量和
方法,
@interface LGPerson : NSObject{ NSString *hobby; } @property (nonatomic, copy) NSString *nickName; - (void)sayHello; + (void)sayHappy; @end
那么在
类中是如何存储这些定义的
属性 成员变量 方法的呢?
接下来我们来研究一下:
int main(int argc, const char * argv[]) { @autoreleasepool { LGPerson *person = [LGPerson alloc]; Class pClass = object_getClass(person); NSLog(@"%@ - %p",person,pClass); } return 0; }
通过
x/4gx pClass打印
类结构,通过前面的查看源码得知如下图:
objc_class中
Class ISA和
Class superclass分别占
8字节,
cache_t cache占
16字节
struct cache_t { struct bucket_t *_buckets; // 8 mask_t _mask; // 4 uint32_t mask_t mask_t _occupied; // 4 public: // 下面是函数,函数不占内存 struct bucket_t *buckets(); // 方法 ··· };
因为
objc_class中
cache_t cache是
结构体,而不是
结构体指针占(结构体指针占
8字节), 所以
cache_t cache占内存
8 + 4 + 4 = 16字节。
猜测:
属性 成员变量存储在
class_data_bits_t bits中,通过指针偏移(偏移原理类比为数组),偏移
32字节获取
class_data_bits_t bits。
探索如下:
对
pClass首地址
0x100001278 + 32得到
0x100001298(16进制)即
bits,通过
bits.data()得到
class_rw_t *data(),打印如下:
而
class_rw_t的结构如下:
struct class_rw_t { // Be warned that Symbolication knows the layout of this structure. uint32_t flags; uint32_t version; const class_ro_t *ro; method_array_t methods; property_array_t properties; protocol_array_t protocols; Class firstSubclass; Class nextSiblingClass; char *demangledName; #if SUPPORT_INDEXED_ISA uint32_t index; #endif void setFlags(uint32_t set) { OSAtomicOr32Barrier(set, &flags); } void clearFlags(uint32_t clear) { OSAtomicXor32Barrier(clear, &flags); } // set and clear must not overlap void changeFlags(uint32_t set, uint32_t clear) { assert((set & clear) == 0); uint32_t oldf, newf; do { oldf = flags; newf = (oldf | set) & ~clear; } while (!OSAtomicCompareAndSwap32Barrier(oldf, newf, (volatile int32_t *)&flags)); } };
打印
*data():
通过命名,猜测
属性应该存储在
properties中,打印
properties,然后并打印其中
list:
同理打印
methods,一系列操作后如下:
由此我们探究出了
属性 方法的存储位置,那么
成员变量存储在什么地方呢?
通过查看
struct class_rw_t中的
const class_ro_t *ro,
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; method_list_t *baseMethods() const { return baseMethodList; } };
里面分别有
method_list_t * baseMethodList
property_list_t *baseProperties
const ivar_list_t * ivars,我们猜测
类的
方法
属性
成员变量分别存储在对应的变量中,打印
ro结果如下:
由此可以看出
LGPerson仅有的一个
成员变量 nickName存储在
bit.data()中的
ro中
baseProperties中,
那么为什么
bit.data()中
property_array_t properties也等打印出
成员变量呢?暂时先抛出个问题。
接下来我们用同样的方法分别打印
ivars
baseMethodList,如图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-s4iOVL2t-1586242928963)(https://user-gold-cdn.xitu.io/2019/12/28/16f4cc767683200b?w=745&h=355&f=png&s=43820)]
baseMethodList打印:
打印出
count = 2,分别打印
ivars中
成员变量,分别为
hobby
_nickName,再次验证了
@property生成的
属性,在系统底层会自动生成
_属性的
成员变量,并且会自动生成
setter
getter。
问题:从
baseMethodList中并未打印出
类方法 sayHappy,那么
类方法存储在什么地方呢?
猜测:
实例方法存在
类中,那么其实
类也是
元类创建出来的
类对象,
类的
类方法应该存在
元类中。
通过下面代码,分别在
类和
元类中打印
对象方法和
类方法:
void testInstanceMethod_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(@"%p-%p-%p-%p",method1,method2,method3,method4); NSLog(@"%s",__func__); } 打印结果 2019-12-29 12:28:17.714554+0800 LGTest[799:13098] 0x100002198-0x0-0x0-0x100002130 2019-12-29 12:28:17.715541+0800 LGTest[799:13098] testInstanceMethod_classToMetaclass
由打印结果看出,
对象方法存在于
类中,不存在于
元类中,
类方法存在于
元类中,不存在于
类中。
通过对类
结构的分析,得出:成员变量
存在ivars
中,属性
存储在baseProperties
中,对象方法
存储在类
里面,类方法
存储在元类
里。
- 点赞 1
- 收藏
- 分享
- 文章举报
相关文章推荐
- IOS OC详谈KVO 的原理以及使用
- iOS OC方法查找顺序~原理
- 编译原理 词法分析实验
- Java中数据库连接池原理机制的详细讲解
- ARM9&nbsp;2410移植之ARM中断原理,…
- XSS测试用例与原理讲解
- Java NIO使用及原理分析(三)
- JavaScript事件委托的技术原理
- RSTP原理详解及配置实例
- 计算机组成原理-1
- Shiro身份认证授权原理
- 对Socket CAN的理解(1)——【CAN总线原理】[转】
- PHP的socket通信原理及实现
- List集合代码原理分析
- Android P刘海屏适配及实现原理
- Java集合 LinkedList的原理及使用
- iptables原理及功能
- SQL防注入功能原理相关知识
- 《Spring技术内幕——深入解析Spring架构与设计原理》连载4
- 深入理解数据库原理系列(2)---并发控制原理(摘自老杨)