NSObject对象简介:
Class、objc_object、id的定义,都基于objc_class,一个NSObject对象的就是objc_object类型的结构体指针objc_object *
typedef struct objc_class *Class; //objc_class的结构体指针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; //重定义一个名字id
alloc
在ios中创建对象中创建一个NSObject对象基本上都会用到alloc语法,想研究它需要访问源码(以objc-750为例)
首先创建一个NSObject对象
LSPerson *person = [[LSPerson alloc] init]
当我们使用源码进行调试是,可以点击alloc进入NSObject.mm文件中,这个文件包含着创建一个NSObject对象的逻辑
+ (id)alloc {
return _objc_rootAlloc(self);
}
查看调用的实际是一个c方法,接着进入_objc_rootAlloc,发现调用callAlloc
id
_objc_rootAlloc(Class cls)
{
return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}
进入callAlloc发现实现逻辑如下,这里的ALWAYS_INLINE实际上是封装的一个inline宏,为LVVM的优化之一,可以优化c函数的调用(实际会看不到此函数的调用)
此方法里面检查了类的hasCustomAWZ方法,即检测是否重写allocWithZone方法,如果可以快速创建(已经申请了响应对象空间等),则直接initInstanceIsa,否则走class_createInstance,然后直接创建对象成功,返回对象,这里探究class_createInstance做了些什么 另外,参数中的allocWithZone默认为false,当重写了allocWithZone方法后才会调用最后的allocWithZone方法
static ALWAYS_INLINE id
callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
if (slowpath(checkNil && !cls)) return nil;
#if __OBJC2__
if (fastpath(!cls->ISA()->hasCustomAWZ())) {
//没有自定义的allocWithZone走这里
// No alloc/allocWithZone implementation. Go straight to the allocator.
// fixme store hasCustomAWZ in the non-meta class and
// add it to canAllocFast's summary
if (fastpath(cls->canAllocFast())) {
// No ctors, raw isa, etc. Go straight to the metal.
bool dtor = cls->hasCxxDtor();
id obj = (id)calloc(1, cls->bits.fastInstanceSize());
if (slowpath(!obj)) return callBadAllocHandler(cls);
obj->initInstanceIsa(cls, dtor);
return obj;
}
else {
// Has ctor or raw isa or something. Use the slower path.
id obj = class_createInstance(cls, 0);
if (slowpath(!obj)) return callBadAllocHandler(cls);
return obj;
}
}
#endif
// No shortcuts available.
if (allocWithZone) return [cls allocWithZone:nil];
return [cls alloc];
}
进入class_createInstance方法,发现调用了一个LVVM优化的函数_class_createInstanceFromZone,里面除了一些标识,主要方法有三个instanceSize、calloc、initInstanceIsa
id
class_createInstance(Class cls, size_t extraBytes)
{
return _class_createInstanceFromZone(cls, extraBytes, nil);
}
static __attribute__((always_inline))
id
_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone,
bool cxxConstruct = true,
size_t *outAllocatedSize = nil)
{
if (!cls) return nil;
assert(cls->isRealized());
// Read class's info bits all at once for performance
bool hasCxxCtor = cls->hasCxxCtor();
bool hasCxxDtor = cls->hasCxxDtor();
bool fast = cls->canAllocNonpointer();
size_t size = cls->instanceSize(extraBytes);
if (outAllocatedSize) *outAllocatedSize = size;
id obj;
if (!zone && fast) {
obj = (id)calloc(1, size);
if (!obj) return nil;
obj->initInstanceIsa(cls, hasCxxDtor);
}
else {
if (zone) {
obj = (id)malloc_zone_calloc ((malloc_zone_t *)zone, 1, size);
} else {
obj = (id)calloc(1, size);
}
if (!obj) return nil;
// Use raw pointer isa on the assumption that they might be
// doing something weird with the zone or RR.
obj->initIsa(cls);
}
if (cxxConstruct && hasCxxCtor) {
obj = _objc_constructOrFree(obj, cls);
}
return obj;
}
instanceSize
此方法是计算对象预申请的空间大小,其中一共主要调用了alignedInstanceSize、word_align、unalignedInstanceSize个方法 先通过调用了alignedInstanceSize简介调用unalignedInstanceSize和、word_align两个方法来获取大小,
unalignedInstanceSize获取了是获取了对象ro中计算好的对象大小(后面会介绍系统怎么计算的)
word_align是字节对齐,模拟“进一法”将预申请字节总大小定位到8的整数倍,可以自行测试思考一下,例如:(12 + 7 )& ~7 = 16
最后,通过instanceSize方法,通过前面计算的总字节数,如果申请的内存大小小于16,那么定位到最低16位
size_t instanceSize(size_t extraBytes) {
size_t size = alignedInstanceSize() + extraBytes;
// CF requires all objects be at least 16 bytes.
if (size < 16) size = 16;
return size;
}
uint32_t unalignedInstanceSize() {
assert(isRealized());
return data()->ro->instanceSize;
}
uint32_t alignedInstanceSize() {
return word_align(unalignedInstanceSize());
}
# define WORD_MASK 7UL //64位对齐到8字节的整数倍
static inline uint32_t word_align(uint32_t x) {
return (x + WORD_MASK) & ~WORD_MASK;
}
calloc
calloc为系统实际申请内存空间方法,实际上是对齐16字节的整数倍,通过malloc_size可以查看该对象申请空间大小
void *p = calloc(1, 24);
NSLog(@"%lu",malloc_size(p));
是系统代码实现如下,可以查看为对齐到16的整数倍,对齐实现方式和word_align有所不同,但殊途同归,这个和cpu硬件的单个内存长度有关
#define SHIFT_NANO_QUANTUM 4
#define NANO_REGIME_QUANTA_SIZE (1 << SHIFT_NANO_QUANTUM) // 16
static MALLOC_INLINE size_t
segregated_size_to_fit(nanozone_t *nanozone, size_t size, size_t *pKey)
{
size_t k, slot_bytes;
if (0 == size) {
size = NANO_REGIME_QUANTA_SIZE; // Historical behavior
}
k = (size + NANO_REGIME_QUANTA_SIZE - 1) >> SHIFT_NANO_QUANTUM; // round up and shift for number of quanta
slot_bytes = k << SHIFT_NANO_QUANTUM; // multiply by power of two quanta size
*pKey = k - 1; // Zero-based!
return slot_bytes;
}
initInstanceIsa
这一步直接更新isa,最终返回给最外层
inline void
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
assert(!cls->instancesRequireRawIsa());
assert(hasCxxDtor == cls->hasCxxDtor());
initIsa(cls, true, hasCxxDtor);
}
inline void
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor)
{
assert(!isTaggedPointer());
if (!nonpointer) {
isa.cls = cls;
} else {
assert(!DisableNonpointerIsa);
assert(!cls->instancesRequireRawIsa());
isa_t newisa(0);
newisa.bits = ISA_MAGIC_VALUE;
// isa.magic is part of ISA_MAGIC_VALUE
// isa.nonpointer is part of ISA_MAGIC_VALUE
newisa.has_cxx_dtor = hasCxxDtor;
newisa.shiftcls = (uintptr_t)cls >> 3;
isa = newisa;
}
}
ro->instanceSize的值怎么来的
前面了解到一个对象的创建过程,最终得到一个objc_object类型的对象,而objc_object对象里面存放的是一个objc_object(Class)类型的isa,可以测试isa实际大小为8字节
下面介绍一下系统分配规则
例如一个对象LSPerson,里面有三个属性,理论上创建对象大小应该为 isa + int + name + int = 8 + 4 + 8 + 4 = 24,字节对齐后,instanceSize为24,mallocSize大小为32
@interface LSPerson : NSObject
@property (nonatomic, assign) int height;
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) int age;
@end
扩展注意:
现在系统为了提高访问速度,对参数进行了优化,现在一块内存地位长度8字节
以上面的LSPerson为例
以前的储存方式: 实际占用字节为16
现在的储存方式:实际占用字节为24
那么系统创建的大小是不是计算错了么:instanceSize = 8 + 8 + 8 + 8 = 32 ?
实际上现在用的属性位置系统会从小到大进行合理调整,即看到的属性可能为height、age、name,因此空间大小还是24不会有浪费现象
init
介绍完毕alloc,开始介绍他的搭档init,话不多少,上源码
会发现,无论是类方法还是平时调用的对象方法init,实际返回的都是self本身,因此,init方法实际上什么也没做?
这其实苹果给的一种设计模式,可以让开发者在此重写方法初始参数等
+ (id)init {
return (id)self;
}
- (id)init {
return _objc_rootInit(self);
}
id
_objc_rootInit(id obj)
{
return obj;
}
new
介绍一下平时会看到有些人调用new函数,知道了alloc之后,其逻辑就更好理解了,直接调用了callAlloc参数来创建,最后调用init,相当于少了一步_objc_rootAlloc的c函数调用,如果没有重写allocWithZone,那么实现一模一样
+ (id)new {
return [callAlloc(self, false/*checkNil*/) init];
}
allocWithzone
最后看看一下allocWithzone,发现也是间接调用的class_createInstance
+ (id)allocWithZone:(struct _NSZone *)zone {
return _objc_rootAllocWithZone(self, (malloc_zone_t *)zone);
}
id
_objc_rootAllocWithZone(Class cls, malloc_zone_t *zone)
{
id obj;
(void)zone;
obj = class_createInstance(cls, 0);
if (slowpath(!obj)) obj = callBadAllocHandler(cls);
return obj;
}