前言
面向对象的语言那么万物皆对象,那么到底OC中什么是对象呢?那么今天带着这个问题来讨论一下什么是对象。
之前我们提到过,所有的对象都是通过
+alloc
方法初始化的。
什么才是OC对象。
通过clang 分析我们的对象
下面这段代码是将OC
代码编译成c++
源码:clang -rewrite-objc main.m -o main.cpp
1、然后新建一个CDPerson对象.并且声明两个属性。
@interface CDPerson : NSObject
@property (nonatomic, copy) NSString *cd_name;
@property (nonatomic, assign) NSInteger cd_age;
@end
@implementation CDPerson
@end
2、然后通过clang
编译成c++
源码。
clang -rewrite-objc CDPerson.m -o CDPerson.cpp
3、然后在c++
源码里面我们发现我们的CDPerson
变成了下面这个;
#ifndef _REWRITER_typedef_CDPerson
#define _REWRITER_typedef_CDPerson
typedef struct objc_object CDPerson;
typedef struct {} _objc_exc_CDPerson;
#endif
extern "C" unsigned long OBJC_IVAR_$_CDPerson$_cd_name;
extern "C" unsigned long OBJC_IVAR_$_CDPerson$_cd_age;
struct CDPerson_IMPL {
struct NSObject_IMPL NSObject_IVARS;
NSString * _Nonnull _cd_name;
NSInteger _cd_age;
};
.....
里面还有些属性的setter/getter。等等
4、结论:通过这个源码我们不难看出,对象在底层被变异成了一个结构体 objc_object
那么我们再来看看 objc_object
到底是什么东西。
在objc 源码我们可以找到下面这个
/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;
/// Represents an instance of a class.
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
/// A pointer to an instance of a class.
typedef struct objc_object *id;
重源码里面不难发现objc_object
里面只有一个isa
(结构体指针);
通过上前面的+alloc方法我们可以找到下面的方法。在这里面有一个方法 _class_createInstanceFromZone
下面有一个obj->initInstanceIsa(cls, hasCxxDtor);
这样的方法。这个方法就是将申请的内存空间和当前类进行了绑定。
1、看看下面两个:
只是申请的内存空间
2、当进行了类信息绑定后:
绑定后的实列对象
3、在看看 obj->initInstanceIsa(cls, hasCxxDtor)
里面到底发生了些什么事情;里面调用了 objc_object::initIsa
这个方法。
inline void
objc_object::initIsa(Class cls, bool nonpointer, UNUSED_WITHOUT_INDEXED_ISA_AND_DTOR_BIT bool hasCxxDtor)
{
ASSERT(!isTaggedPointer());
isa_t newisa(0);
//
if (!nonpointer) {
newisa.setClass(cls, this);
} else {
ASSERT(!DisableNonpointerIsa);
ASSERT(!cls->instancesRequireRawIsa());
newisa.bits = ISA_MAGIC_VALUE;
// isa.magic is part of ISA_MAGIC_VALUE
// isa.nonpointer is part of ISA_MAGIC_VALUE
# if ISA_HAS_CXX_DTOR_BIT
newisa.has_cxx_dtor = hasCxxDtor;
# endif
newisa.setClass(cls, this);
newisa.extra_rc = 1;
}
isa = newisa;
}
4、从这个源码里面我们可以知道,对象下面最终为isa_t
那现在我们来分析一下这个isa_t
;下图为is a_t
的结构信息。
名词解释:typedef unsigned long uintptr_t;
里面有一个宏定义:ISA_BITFIELD
具体定义为:
# define ISA_MASK 0x0000000ffffffff8ULL
# define ISA_MAGIC_MASK 0x000003f000000001ULL
# define ISA_MAGIC_VALUE 0x000001a000000001ULL
# define ISA_HAS_CXX_DTOR_BIT 1
# define ISA_BITFIELD \
uintptr_t nonpointer : 1; \
uintptr_t has_assoc : 1; \
uintptr_t has_cxx_dtor : 1; \
uintptr_t shiftcls : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/ \
uintptr_t magic : 6; \
uintptr_t weakly_referenced : 1; \
uintptr_t unused : 1; \
uintptr_t has_sidetable_rc : 1; \
uintptr_t extra_rc : 19
//
通过这个宏我们发现
第一部分(nonpointer ):表示是否对 isa 指针开启指针优化 0:纯isa指针,1:不止是类对象地址,isa 中包含了类信息、对象的引用计数等;
第二部分(has_assoc):关联对象标志位,0没有,1存在;
第三部分(has_cxx_dtor):该对象是否有 C++ 或者 Objc 的析构器,如果有析构函数,则需要做析构逻辑, 如果没有,则可以更快的释放对象;
第四部分(shiftcls):存储类指针的值。开启指针优化的情况下,在 arm64 架构中有 33 位用来存储类指针;
第五部分(magic):用于调试器判断当前对象是真的对象还是没有初始化的空间;
第六部分(weakly_referenced):志对象是否被指向或者曾经指向一个 ARC 的弱变量,
没有弱引用的对象可以更快释放;
第七部分(has_sidetable_rc):当对象引用技术大于 10 时,则需要借用该变量存储进位;
第八部分(extra_rc):当表示该对象的引用计数值,实际上是引用计数值减 1, 例如,如果对象的引用计数为 10,那么 extra_rc 为 9。如果引用计数大于 10, 则需要使用到下面的 has_sidetable_rc;
5、验证一下:
5.1还是上面那个CDPerson,通过下面打印获取到 class,用ISA_MASK 获取isa信息
ISA_MASK
5.2查看二进制这个首地址的二进制文件
二进制
在为CDPerson创建一个分类,实现一个关联对象;
@interface CDPerson (Test)
@property (nonatomic, copy) NSString *nick_name;
@end
@implementation CDPerson (Test)
- (NSString *)nick_name {
return objc_getAssociatedObject(self, @selector(nickname));
}
- (void)setNick_name:(NSString *)nick_name {
objc_setAssociatedObject(self, @selector(nickname), nick_name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
@end
关联对象
通过结果我们可以发现:如果有关联对象那么 has_assoc 确实为1;
5.3引用计数的值( extra_rc 和 has_sidetable_rc )
小引用数的时候 大引用数的时候
补充
位域
有些数据在存储时并不需要占用一个完整的字节,只需要占用一个或几个二进制位即可。例如开关只有通电和断电两种状态,用 0 和 1 表示足以,也就是用一个二进位。正是基于这种考虑,C语言又提供了一种叫做位域的数据结构。
在结构体定义时,我们可以指定某个成员变量所占用的二进制位数(Bit),这就是位域。请看下面的例子:
struct CDStruct1 {
BOOL a;
BOOL b;
BOOL c;
BOOL d;
};
struct CDStruct2 {
BOOL a:1;
BOOL b:1;
BOOL c:1;
BOOL d:1;
};
按照之前我们讲的结构体的内存大小问题一问,我们可以指知道 CDStruct1 的大小为 4字节;那么结构体 CDStruct2 呢?
位域
通过打印结果可以发现:这种情况可以极大的优化内存。
联合体
结构体和共用体的区别在于:结构体的各个成员会占用不同的内存,互相之间没有影响;而共用体的所有成员占用同一段内存,修改一个成员会影响其余所有成员。
结构体占用的内存大于等于所有成员占用的内存的总和(成员之间可能会存在缝隙),共用体占用的内存等于最长的成员占用的内存。共用体使用了内存覆盖技术,同一时刻只能保存一个成员的值,如果对新的成员赋值,就会把原来成员的值覆盖掉。
struct CDStruct {
char *name;
double a;
int age;
};
union CDUnion {
char *name;
double a;
int age;
};
联合体
网友评论