OC的对象都是指针形式表示,对于对像内存管理从四个方面来说(ARC和MRC,区别大部分是在于编译器帮我们添加retain和release,可以归到一起总结)
- 使用了TaggedPointer技术的对象指针,由于把数据存在了指针里,在函数栈调用结束时,就会自动释放,不涉及retain,release操作,即使ARC环境下,系统也不用加release
- 普通对象,使用了strong,copy策略声明的属性,编译器会在函数栈结束或者类调用dealloc的时候,加上release,释放对象指针,及内存。
- 类方法(array,[NSString stringWithFormat:]等)创建的对象,框架内部会把他加入到自动释放池,这种方法创建的对象和MRC下autorelease对像都是加到自动释放池,而自动释放池设计到内存分页管理,见下文。
- weak类型指针对象,被指向的对象,有一张哈希表,里面存着所有执向他的指针,在类dealloc时,会去释放这些指针。
iOS程序内存分为6个区:
- 保留区
- 代码区(__TEXT):保存编译之后的代码
- 数据段(__DATA):字符串常量:NSString *str = @"String";已初始化的全局变量,静态变量等;未初始化的全局变量,静态变量。
- 堆区(heap):函数调用开销,比如局部变量。分配的内存空间地址越来越小。
- 栈区(stack):通过alloc,malloc,calloc等动态分配的空间,分配的内存空间地址越来越大
- 内核区
数据对象调用copy和mutableCopy所发生的拷贝情况表格
总结:不可变对象调用copy是浅拷贝,调用mutableCopy是深拷贝;可变对象调用copy和mutableCopy都是深拷贝。
Tagged Pointer 技术
从64bit开始,iOS引入Tagged Pointer技术用于优化NSNumber,NSdate,NSString等小对象的存储。
在没有使用Tagged Point对象之前, NSNumber等对象需要动态分配内存,维护引用计数等,NSNumber指针存储的是堆中NSNumber对象的地址。
使用Tagged Pointer技术后,就变成了Tag + Data,也就是将数据直接存在了指针中, 但是如果指针存不下实再按原方法存到堆中。
当使用Tagged pointer计数后的对象时 调用
objc_msgSend()时,会直接去指针取之而不是去找什么isa,找对象.
把使用了Tagged pointer技术的对象赋值给一个对象,相当于把地址赋值给新对象。
判断是否是tagged pointer指针
iOS 平台:address & (1<<63):指针的最高有效位是否是1
Mac OSX 平台:address & 1:指针最低的有效位是否是1
MRC相关:
内存管理
- 在iOS中,使用引用计数来管理OC对象的内存
- 一个新建的OC对象引用计数默认是1,当引用计数为0,OC对象就会自动销毁释放其占用的内存。
- 调用retain会让OC对象的引用计数+1,调用release会让OC对象的引用计数-1
- 可以通过以下私有函数来查看自动释放池的情况
-objc_autoreleasePoolPrint(void);
平常声明属性时我们写的assig,retain,copy的含义相当于在MRC下是,如果我们调用set方法的属性没有对应的assig,retain,copy,就会报方法找不到。
//assign
-(void)setProperty:(id)Property
{
_property = property;
}
//retain
-(void)setProperty:(id)Property
{
if(_property!=property)
[_property release];
_property = [property retain];
}
//copy
-(void)setProperty:(id)Property
{
if(_property!=property)
[_property release];
_property =[property copy];
}
调用类方法创建对象:[NSString stringWithFormat:@"123"];相当于在MRC下如下代码,不用我们去给对象写release。
[[NSString stringWithFormat:@"123"]autorelease];
自定义类对象实现copy
需要实现copyWithZone:(NSZone*)zone方法,因为copy也是调用这个方法实现,然后在copyWithZone里把想复制值的对象属性赋值一遍。
- (id)copyWithZone:(NSZone*)zone
{
SomeClass* instance = [[SomeClass allocWithZone:zone]init];
instance.property1 = self.property1;
instance.property2 = self.property2;
instance.property3 = self.property3;
return instance;
}
引用计数的存储
在64位优化过后,isa指针是isa_t共用体类型,里面的extra_rc这个19位就是存储引用计数,当这19位装不下时,isa_t 共用体里的has_sidetable_rc就变成1,引用计数就存到一个SideTable的类的属性中refcnts
union isa_t
{
class cls;
struct {
uintptr_t nonpointer :1;//0代表普通指针,存Class,Meta-Class对象的内存地址
uintptr_t has_assoc. :1;//代表优化过,使用位域存储更多的信息
uintptr_t has_cxx_dtor :1;//是否有C++的析构函数,如果没有释放更快
uintptr_t shiftcls :33;//存着Class,Meta-Class对象的内存地址信息
uintptr_t magic. :6;//调试时分辩对象是否未完成初始化
uintptr_t weakly_referenced :1;//是否有被弱引用执行过,没有则释放更快
uintptr_t deallocating :1; //对象是否正在释放
uintptr_t has_sidetable_rc :1;//extra_rc存不下的时候变为1,引用计数存到一个SideTable类的属性中
uintptr_t extra_rc :19; //存引用计数
}
}
//存引用计数的结构体
struct SideTable
{
spinlock_t slock;
RefcountMap refcnts;//存着引用计数,是个散列表
weak_table_t weak_table;
}
weak指针
weak 和unsafe_unretained区别
weak在所指向的对象销毁时,系统会将指针置nil,而unsafe_unretained不会自动清空的。
实现原理:
当一个对象要释放时,会自动调用dealloc,这个对象有张哈希表SideTable,这里存着指向这个对象的弱引都清掉,调用轨迹。
- dealloc
- _objc_rootDealloc
- rootDealloc
- 如果是tagged指针,直接return
- 如果这个对象的isa指针是个普通指针,并且没有弱引用,没有关联对象,没有C++析构函数没有弱引用列表就直接释放,
- 不然就调用object_dispose
- object_dispose
- 调用objc_destructInstance
- free(objc)
- objc_destructInstance
- 查看C++析构器返回是否是1,调用object_cxxDestruct清除成员变量
- 查看是否有关联对象,调用_object_remove_assocations(obj);清楚关联对象
- 调用clearDeallocating();//将指向当前对象的弱指针置为nil;
- free
autorelease
工程build Seting -> 搜索Automatic reference Counting设置NO 启用MRC模式。
在main.m的文件中加几句代码
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person = [[[Person alloc]init]autorelease];
NSLog(@"autorelease will end");
}
NSLog(@"autorelease end");
return 0;
}
用clang编译器重写下main文件成main.cpp,看下原理。可以在重写后的main.cpp看到如下源码
int main(int argc, const char * argv[]) {
/* @autoreleasepool */
{
__AtAutoreleasePool __autoreleasepool;
Person *person = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("alloc")), sel_registerName("init")), sel_registerName("autorelease"));
}
return 0;
}
除去Person对象声明,可以看到这么一段东西
{
__AtAutoreleasePool __autoreleasepool;
}
这个就是我们写的
@autoreleasepool {}
相当于声明了个__AtAutoreleasePool对象,找到这个类的代码
struct __AtAutoreleasePool {
__AtAutoreleasePool() { atautoreleasepoolobj=objc_autoreleasePoolPush();
}
~__AtAutoreleasePool() {
objc_autoreleasePoolPop(atautoreleasepoolobj);
}
void * atautoreleasepoolobj;
};
在创建__AtAutoreleasePool对象的时候会调用构造函数,{...}作用域结束后,回收栈空间__AtAutoreleasePool对象会调用析构所以我们写的@autoreslease{}就相当于
{
void *atautireleasepooloobj = objc_autoreleasePoolPush();
//中间调用autorelease的对象会被加入autorelease释放池
[someNSObject autorelease];
objc_autoreleasePoolPop(atautireleasepooloobj);//释放所有对象
}
关键的就是上面这三句代码
- objc_autoreleasePoolPush();
- [someNSObject autorelease];
- objc_autoreleasePoolPop(atautireleasepooloobj)
1. objc_autoreleasePoolPush();
转到objc源码里,在源码里正好能找到同名的函数,这个函数objc_autoreleasePoolPush()调用就相当于调用AutoreleasePoolPage的push方法,这个方法在源码里如下:
void *
objc_autoreleasePoolPush(void)
{
return AutoreleasePoolPage::push();
}
//先忽略inline关键字,这是内联函数,表示下面的函数直接转成代码,在汇编里而不是跳到一个函数地址
static inline void *push() {
id *dest;
//判断是否有AutoreleasePoolPage对象,如果没有创建个新的,有的话,把POOL_BOUNDARY压进栈
if (DebugPoolAllocation) {
dest = autoreleaseNewPage(POOL_BOUNDARY);
} else {
dest = autoreleaseFast(POOL_BOUNDARY);
}
assert(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);
return dest;
}
上面的代码可以看出来会先去判断是否有autoreleaseNewPage,DebugPoolAllocation是个全局的,如果我们@autorelease循环嵌套的话并且当前的AutoreleasePage没满的话,第二个@autorelease只会把一个边界标记压入AutoreleasePoolPage栈里,如果当前的page满了才会创建下一页(注意:不是每个@autorelease都创建AutoreleasePoolPage对象,当前page只有一个,不同@autorelease靠bundary来区别)。
2. autorelease()
如下所示,
static inline id autorelease(id obj)
{
assert(obj);
assert(!obj->isTaggedPointer());
id *dest __unused = autoreleaseFast(obj);
assert(!dest || dest == EMPTY_POOL_PLACEHOLDER || *dest == obj);
return obj;
}
static inline id *autoreleaseFast(id obj)
{
AutoreleasePoolPage *page = hotPage();
if (page && !page->full()) {
return page->add(obj);
} else if (page) {
return autoreleaseFullPage(obj, page);
} else {
return autoreleaseNoPage(obj);
}
}
一个对象调用autorelease方法时,存在三种情况
- 当前有AutoreleasePoolPage对象并且page对象没有放满,这样就把对象加入到page里
- page对象存在,但是满了,autoreleaseFullPage(obj, page);会新建个page,新的page里会指向旧的page,
- 当前没有page对象,autoreleaseNoPage(obj)里会创建page,并把对象加入到page里。
objc_autoreleasePoolPop(atautireleasepooloobj
接收objc_autoreleasePoolPush();返回的对象。从最后一页page开始,一个个把page里的对象release,直到遇到一个POOL_BOUNDARY,代表这个autorelease对象释放完毕。
多个AutoreleasePoolPage采用双向链表的形式存储。
autoreleasePool和runloop的关系,
iOS在主线程的Runloop注册了2个Observer,
- 第一个Observer
监听了kCFRunloopEntry:
- 调用objc_autoreleasePoolPush
- 第二个Observer
监听了kCFRunloopBeforeWaiting事件:
- 调用objc_autoreleasePoolpop ,objc_autoreleasePoolPush
监听了kCFRunloopBeforeExit事件 - 调用objc_autoreleasePoolPop()
可以看到是个结构体,里面有两个
autorelesase对象什么时候释放,是在所属Runloop循环中休眠之前调用pop释放
所以 :
我们的对象如果调用了autorelease 加入了自动释放池,则是在Runloop休眠或者退出时,被释放,而不是在所处作用域(函数或者{})结束时立即被释放。
我们的普通局部对象,则是因为编译器编译时会自动加上release,所以出了作用域,要是没有别的地方强引用着,则释放掉。
可以通过_objc_autoreleasePoolPrint(void)查看自动释放池情况
必须得先声明,extern void _objc_autoreleasePoolPrint(void);但是不用实现。