一、内存管理-----autorelease原理
1.autorelease在什么时候会被释放(autorelease对象在什么时机会调用release)
1)如果直接被@autoreleasepool包住,release的时机就是大括号结束的时候,也就是调用pop方法的时候,
2)在项目里面,ios在主线程的RunLoop里面注册了两个Observer,第一个Observer监听了KCFRunLoopEntry事件,会调用objc_autoreleasePoolPush(),第二个Observer监听了KCFRunLoopBeforeWaiting事件,会调用objc_autoreleasePoolPop()、objc_autoreleasePoolPush(),监听了KCFRunLoopBeforeExit事件,会调用objc_autoreleasePoolPop()
//这个Person什么时候调用release,是有RunLoop来控制的
//可能是在所属的某次RunLoop循环中,RunLoop休眠前调用了release
// MJPerson *person = [[[MJPerson alloc]init] autorelease];
第一次创建是在启动RunLoop的时候,最后一次销毁是在RunLoop退出的时候,其他时候的创建和销毁:当RunLoop即将休眠时销毁之前的释放池,重新创建一个新的
1)autorelease是用来使对象的引用计数减1的,也可以使用release来减少他的引用计数,但是这样的话所有的代码都需要写到release之前
MJPerson *person = [[[MJPerson alloc]init] autorelease];
person.age = 10;
NSLog(@"age的值是:%d",person.age);
MJPerson *person1 = [[MJPerson alloc]init];
[person1 release];
使用release的话,person1的所有代码都需要写到release的前面,但是使用autorelease的话,后面仍然可以继续调用,直到autoreleasepool}结束
2)autorelease的本质如下
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
MJPerson *person = ((MJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)((MJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)((MJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("MJPerson"), sel_registerName("alloc")), sel_registerName("init")), sel_registerName("autorelease"));
}
return 0;
}
struct __AtAutoreleasePool {
//构造函数,在生成结构体变量的时候调用
__AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
//析构函数,在结构体销毁的时候调用
~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
void * atautoreleasepoolobj;
};
@autoreleasepool的本质就是一个结构体__AtAutoreleasePool
每次我们使用一次@autoreleasepool,就相当于调用一个这个结构体的构造函数和析构函数
@autoreleasepool {
MJPerson *person = [[[MJPerson alloc]init] autorelease];
}
他的本质就等价于
{
objc_autoreleasePoolPush()
MJPerson *person1 = [[[MJPerson alloc]init] autorelease] ;
objc_autoreleasePoolPop(atautoreleasepoolobj)
}
3)自动释放池的主要的底层数据结构是:__AtAutoreleasePool,AutoreleasePoolPage
4)AutoreleasePoolPage的结构
a.每个AutoreleasePoolPage对象占用4096字节内存,除了用来存放它内部的成员变量,剩下的空间用来存放autorelease对象的地址
b.所有的AutoreleasePoolPage对象通过双向链表的形式连接在一起
child指针会存储下一个AutoreleasePoolPage的地址,parent指针会存储上一个AutoreleasePoolPage的地址,最后一个AutoreleasePoolPage的child指针值为nil,第一个parent的指针值为nil
5)objc_autoreleasePoolPush()会把调用了autorelease方法的对象的内存地址会存放到AutoreleasePoolPage的这个内存里面去
调用push方法会将一个POOL_BOUNDARY入栈,并且返回其存放的内存地址,然后调用autorelease方法,把对象的内存地址存放到AutoreleasePoolPage的这个内存里面,然后调用pop方法,pop方法会一直调用release方法,直到释放遇到到POOL_BOUNDARY这个地址的时候,就release结束
6)next指针会指向下一个能存放autorelease对象地址的一个位置
7)下面的代码的底层逻辑
int main(int argc, const char * argv[]) {
@autoreleasepool {
// r1 = push()
MJPerson *person1 = [[[MJPerson alloc]init] autorelease];
MJPerson *person2 = [[[MJPerson alloc]init]autorelease];
@autoreleasepool {
//r2 = push();
MJPerson *person3 = [[[MJPerson alloc]init] autorelease];
MJPerson *person4 = [[[MJPerson alloc]init]autorelease];
@autoreleasepool {
//r3 = push();
MJPerson *person5 = [[[MJPerson alloc]init] autorelease];
MJPerson *person6 = [[[MJPerson alloc]init]autorelease];
//pop(r3);
}
//pop(r2)
}
//pop(r1)
}
8)可以通过以下私有函数来查看自动释放池的情况
extern void _objc_autoreleasePoolPrint(void)
hot修饰的page表示是当前页
#import <Foundation/Foundation.h>
#import "MJPerson.h"
extern void _objc_autoreleasePoolPrint(void);
在C语言中系统的私有函数或者是自定义的私有函数没有进行暴露的话,可以在其他类中使用extern修饰词来修饰这个函数,就可以在当前函数进行调用
9)如果在viewDidLoad里面的对象调用了autorelease,那这个对象释放是在viewDidLoad加载完毕和viewWillAppear执行完毕才会释放
2.方法里面有局部对象,出了方法会被立即释放掉吗?
如果这个局部对象他最终是通过autorelease的形式来去释放的话,意味着不会马上就释放,而是等他所处的那一次runLoop休眠前进行release,如果ARC生成release方法的话,会立即释放。
二、内存管理----- copy
1.copy的目的
拷贝的目的:产生一个副本对象,跟源对象互不影响,修改了源对象,不会影响副本对象,修改了副本对象,不会影响源对象
2.本身含有copy的类
NSString、NSArray、NSDictionary
NSString *str ;
NSString *str1 = [str copy];
直接可以调用copy来拷贝,copy是产生一个副本,如果对str1进行修改不会修改到str的内容,对str进行修改,不会修改到str1的内容
ios提供了两个拷贝方法,一个是copy不可变拷贝,产生不可变副本,一个是mutableCopy,可变拷贝,产生可变副本
NSString *str1 = [NSString stringWithFormat:@"test"];
NSString *str2 = [str1 copy];
NSString *str3 = [str1 mutableCopy];
NSLog(@"%@",str1);
NSLog(@"%@",str2);
NSLog(@"%@",str3);
copy返回的是NSString,mutableCopy返回的是 NSMutableString
无论是copy还是mutableCopy都是产生副本,对原字符串修改不会改变掉他复制完以后的值,对复制完以后的值进行修改,也不会改变掉他原来的值。copy始终复制出来的是不可变副本,mutableCopy始终复制出来的是可变副本,无论是对NSString类型的进行拷贝,还是对NSMutableString类型的进行拷贝。如果使用mutableCopy复制完以后,用NSString来接收的话接收到的就是不可变的字符串,得用NSMutableCopy来接收。appendString是可变字符串的方法。
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
NSString *str1 = [NSString stringWithFormat:@"test"];
NSString *str2 = [str1 copy];
NSMutableString *str3 = [str1 mutableCopy];
NSLog(@"str1的值是:%@",str1);
NSLog(@"str2的值是:%@",str2);
NSLog(@"str3的值是:%@",str3);
[str3 appendString:@"123"];
NSLog(@"str3的值是:%@",str3);
NSLog(@"str1的值是:%@",str1);
MJPerson *person;
MJPerson *person1 = [person copy];
NSMutableString *str4 = [NSMutableString stringWithFormat:@"test"];
NSString *str5 = [str4 copy];
NSMutableString *str6 = [str4 mutableCopy];
NSLog(@"str4的值是:%@",str4);
NSLog(@"str5的值是:%@",str5);
NSLog(@"str6的值是:%@",str6);
[str4 appendString:@"456"];
[str5 stringByAppendingString:@"123"];
[str6 appendString:@"123"];
NSLog(@"str4的值是:%@",str4);
NSLog(@"str5的值是:%@",str5);
NSLog(@"str6的值是:%@",str6);
}
return 0;
}
3.当调用alloc、new、copy、mutableCopy返回了一个对象,在不需要这个对象的时候,要调用release或者autorelease来释放他
NSString如果使用stringWithFormat初始化的话内部是调用了一次autorelease的,不需要在使用autorelease来进行释放,如果使用initWithFormat初始化的话就需要调用autorelease或者是release来释放
int main(int argc, const char * argv[]) {
@autoreleasepool {
//如果使用stringWithFormat调用的话内部是调用了一次autorelease的
NSString *str1 = [NSString stringWithFormat:@"test"];
NSString *str2 = [str1 copy];
NSMutableString *str3 = [str1 mutableCopy];
NSLog(@"%p ,%p ,%p",str1,str2,str3);
NSLog(@"%@, %@, %@",str1,str2,str3);
[str2 release];
[str3 release];
//如果使用initWithFormat的话得需要调用一次autorelease
// NSString *str1 = [[[NSString alloc]initWithFormat:@"test"] autorelease];
}
return 0;
}
可以看出copy出来的和原对象是一个地址,指向的是同一个对象,原因就是copy出来的是不可变的副本,NSString本身的内容也是不可变的,所以为了避免占用更多的内存,使用一个就可以,mutableCopy出来的是一个新的地址对象。
但是如果是对NSMutableString的对象进行拷贝的话,就是一个新的内容,因为原字符串是会变的
int main(int argc, const char * argv[]) {
@autoreleasepool {
//如果使用stringWithFormat调用的话内部是调用了一次autorelease的
NSString *str1 = [NSString stringWithFormat:@"test"];
NSString *str2 = [str1 copy];
NSMutableString *str3 = [str1 mutableCopy];
NSLog(@"%p ,%p ,%p",str1,str2,str3);
NSLog(@"%@, %@, %@",str1,str2,str3);
[str2 release];
[str3 release];
NSMutableString *str4 = [NSMutableString stringWithFormat:@"test"];
NSString *str5 = [str4 copy];
NSLog(@"str4的地址%p ,str5的地址%p",str4,str5);
//如果使用initWithFormat的话得需要调用一次autorelease
// NSString *str1 = [[[NSString alloc]initWithFormat:@"test"] autorelease];
}
return 0;
}
4.深拷贝和浅拷贝
深拷贝:内容拷贝,产生新的对象
浅拷贝:指针拷贝,没有产生新的对象
对NSMutableString对象进行拷贝,无论是使用copy还是mutableCopy都是深拷贝,对NSString对象进行拷贝,使用copy出来的是浅拷贝,使用mutableCopy出来的是深拷贝
5.对NSString对象进行拷贝,引用计数的计算
如果对NSString使用copy,相当于使用了一次retain,拷贝出来的是同一个对象,所以str1的引用计数为2 ,str2的引用计数为2,无论是对str1进行计数减1,还是对str2进行计数减1,都是对同一个对象进行计数减1 。
6.总结
7.copy作为属性修饰符
1)使用copy修饰NSArray
@property (copy, nonatomic) NSArray *data;
- (void)setData:(NSArray *)data {
if(_data !=data) {
[_data release];
_data = [data copy];
}
}
属性修饰符的不同是底层set方法的不同,assign方法就是直接赋值,retain操作就是把以前的值release掉,新传进来的值进行一次retain操作,copy作为属性修饰符的底层实现是如果传进来的值和原来的值不相等,就把原来的值release掉,然后对新传进来的值进行一次copy操作
2)使用copy修饰NSMutableArray
@property (copy, nonatomic) NSMutableArray *mutableArray;
- (void)setMutableArray:(NSMutableArray *)mutableArray {
if(_mutableArray != mutableArray) {
[_mutableArray release];
_mutableArray = [mutableArray copy];
}
}
person.mutableArray = [NSMutableArray array];
[person.mutableArray addObject:@"rose"];
如果NSMutableArray使用copy来进行修饰,底层是对传进来的新值进行了copy操作,最后变成了不可变的数组,所以不能使用可变数组的方法
8.给自己的类使用copy修饰符
1)让自己的类遵守<NSCopying>协议
2)实现- (id)copyWithZone:(struct _NSZone *)zone{}方法
类方法中是不能使用self.的
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
NSLog(@"Hello, World!");
MJPerson *person1 = [[MJPerson alloc]init];
person1.age = 20;
person1.weight = 50;
MJPerson *person2 = [person1 copy];
person2.age = 30;
NSLog(@"%@",person1);
NSLog(@"%@",person2);
}
return 0;
}
MJPeron.h文件
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface MJPerson : NSObject<NSCopying>
@property (nonatomic, assign) int age;
@property (nonatomic, assign) int weight;
@end
MJPerson.m文件
- (id)copyWithZone:(struct _NSZone *)zone {
MJPerson *person = [[MJPerson allocWithZone:zone]init];
person.age = self.age;
person.weight = self.weight;
return person;
}
- (NSString *)description {
return [NSString stringWithFormat:@"age = %d , weight = %d",self.age,self.weight];
}
copyWithZone方法中创建一个新的对象,让本身的属性值赋值给新的对象的属性,将对象进行返回,此时copy完之后的这个对象和copyWithZone这个是同一个对象,因为他是将对象进行了赋值,但是赋值前的对象和赋值以后的对象不是一个对象,属性可以进行部分属性值的修改。
三、内存管理----引用计数的存储
1.extra_rc:里面存储的值是引用计数器减1
2.has_sidetable_rc:引用计数器是否过大无法存储在isa指针中,如果为1,那么引用计数会存储在一个叫SideTable的类的属性中
3.在64bit中,引用计数可以直接存储在优化过的isa指针中,也可能存储在SideTable中
struct SideTable {
spinlock_t stock;
RefcountMap regents;
weak_table_t weak_table;
}
regents是一个存放着对象引用计数的散列表
objc_object::rootRetainCount()
{
if (isTaggedPointer()) return (uintptr_t)this;
sidetable_lock();
//优化过的isa
isa_t bits = __c11_atomic_load((_Atomic uintptr_t *)&isa.bits, __ATOMIC_RELAXED);
if (bits.nonpointer) {
uintptr_t rc = bits.extra_rc;
if (bits.has_sidetable_rc) {
//引用计数不是存储在isa中,而是存储在SideTable中
rc += sidetable_getExtraRC_nolock();
}
sidetable_unlock();
return rc;
}
sidetable_unlock();
return sidetable_retainCount();
}
四、内存管理------weak的底层原理实现
1.Dealloc的实现机制
1)在ARC的情况下不需要调用父类的dealloc方法,当对象销毁的时候就会自动调用dealloc方法
NSLog(@"111");
{
MJPerson *person = [[MJPerson alloc]init];
}
NSLog(@"222");
person出了{}这个作用域就销毁了,所以会调用他的dealloc方法
2)当调用dealloc的时候首先会调用_objc_rootDealloc方法
// Replaced by NSZombies
- (void)dealloc {
_objc_rootDealloc(self);
}
3)接下来会调用rootDealloc()
void
_objc_rootDealloc(id obj)
{
ASSERT(obj);
obj->rootDealloc();
}
4)此时会根据以下5个标准去判断是否可以被释放
isa.nonpointer 是否是优化过的isa指针
weakly_referenced是否有弱引用指向
has_assoc 是否有关联对象
has_cxx_dtor 是否有析构函数
has_sidetable_rc是否有sidetable另外一个数据结构来存储引用计数
inline void
objc_object::rootDealloc()
{
if (isTaggedPointer()) return; // fixme necessary?
if (fastpath(isa.nonpointer &&
!isa.weakly_referenced &&
!isa.has_assoc &&
#if ISA_HAS_CXX_DTOR_BIT
!isa.has_cxx_dtor &&
#else
!isa.getClass(false)->hasCxxDtor() &&
#endif
!isa.has_sidetable_rc))
{
assert(!sidetable_present());
free(this);
}
else {
object_dispose((id)this);
}
}
5)如果是isTaggerPointer,直接return,如果是优化过的指针并且没有关联对象,没有弱指针引用,没有析构函数,没有sidetable另外一个数据结构来存储引用计数的话就free,否则的话就调用object_dispose()方法,做下一步的处理
6)object_dispose方法,直接调用objc_destructInstance(obj)方法,然后free掉
id
object_dispose(id obj)
{
if (!obj) return nil;
objc_destructInstance(obj);
free(obj);
return nil;
}
7)objc_destructInstance方法会判断是否有析构函数,如果有C++内容,调用object_cxxDestruct(obj),清除掉成员变量,清除掉C++的内容,然后判断是否有关联对象,如果有关联对象,则销毁掉关联对象的一系列操作,然后调用clearDeallocating(),在clearDeallocating()方法里面将当前对象指向的弱引用指针置为nil
void *objc_destructInstance(id obj)
{
if (obj) {
// Read all of the flags at once for performance.
bool cxx = obj->hasCxxDtor();
bool assoc = obj->hasAssociatedObjects();
// This order is important.
if (cxx) object_cxxDestruct(obj);
if (assoc) _object_remove_assocations(obj, /*deallocating*/true);
obj->clearDeallocating();
}
return obj;
}
8)如果是没有优化过的指针,就执行 sidetable_clearDeallocating(),优化过的指针调用clearDeallocating_slow()
inline void
objc_object::clearDeallocating()
{
if (slowpath(!isa.nonpointer)) {
// Slow path for raw pointer isa.
sidetable_clearDeallocating();
}
else if (slowpath(isa.weakly_referenced || isa.has_sidetable_rc)) {
// Slow path for non-pointer isa with weak refs and/or side table data.
clearDeallocating_slow();
}
assert(!sidetable_present());
}
9)然后执行weak_clear_no_lock(&table.weak_table, (id)this)在这一个步骤中,会将指向该对象的弱引用指针置为nil,然后table.refcnts.erase(this),从引用计数表中擦除掉该对象的引用计数,dealloc结束
NEVER_INLINE void
objc_object::clearDeallocating_slow()
{
ASSERT(isa.nonpointer && (isa.weakly_referenced || isa.has_sidetable_rc));
SideTable& table = SideTables()[this];
table.lock();
if (isa.weakly_referenced) {
weak_clear_no_lock(&table.weak_table, (id)this);
}
if (isa.has_sidetable_rc) {
table.refcnts.erase(this);
}
table.unlock();
}
void
weak_clear_no_lock(weak_table_t *weak_table, id referent_id)
{
objc_object *referent = (objc_object *)referent_id;
weak_entry_t *entry = weak_entry_for_referent(weak_table, referent);
if (entry == nil) {
/// XXX shouldn't happen, but does with mismatched CF/objc
//printf("XXX no entry for clear deallocating %p\n", referent);
return;
}
// zero out references
weak_referrer_t *referrers;
size_t count;
if (entry->out_of_line()) {
referrers = entry->referrers;
count = TABLE_SIZE(entry);
}
else {
referrers = entry->inline_referrers;
count = WEAK_INLINE_COUNT;
}
for (size_t i = 0; i < count; ++i) {
objc_object **referrer = referrers[i];
if (referrer) {
if (*referrer == referent) {
*referrer = nil;
}
else if (*referrer) {
_objc_inform("__weak variable at %p holds %p instead of %p. "
"This is probably incorrect use of "
"objc_storeWeak() and objc_loadWeak(). "
"Break on objc_weak_error to debug.\n",
referrer, (void*)*referrer, (void*)referent);
objc_weak_error();
}
}
}
weak_entry_remove(weak_table, entry);
}