老铁们,大家好,相信还有很多朋友对于深入解析:ARC内存管理常见问题及解决方案和的相关问题不太懂,没关系,今天就由我来为大家分享分享深入解析:ARC内存管理常见问题及解决方案以及的问题,文章篇幅可能偏长,希望可以帮助到大家,下面一起来看看吧!
//第一个
NSString *name1=[NSString stringWithFormat:@"stringTestOne"];
__weak NSString *name2=name1;
NSLog(@"name1:%@", name1);
NSLog(@"name2:%@", name2);
名称1=零;
NSLog(@"name1:%@", name1);
NSLog(@"name2:%@", name2);
//第二个
NSString *a1=[[NSString alloc] initWithFormat:@"stringTestTwo"];
__weak NSString *a2=a1;
NSLog(@"a1:%@", a1);
NSLog(@"a2:%@", a2);
a1=零;
NSLog(@"a1:%@", a1);
NSLog(@"a2:%@", a2);
//第三个
NSString *b1=@"stringTestThree";
__weak NSString *b2=b1;
NSLog(@"b1:%@", b1);
NSLog(@"b2:%@", b2);
b1=零;
NSLog(@"b1:%@", b1);
NSLog(@"b2:%@", b2);
打印结果:
name1:stringTestOne
name2:stringTestOne
姓名1:(空)
name2:stringTestOne
a1:字符串测试二
a2:string测试二
a1:(空)
a2:(空)
b1:string测试三
b2:字符串测试三
b1:(空)
b2:stringTestThree 我们不要批评面试官问这里面有没有大x的成分,但是我确实觉得这个问题意义不大,而且有漏洞。
狗货
1.1、字符串的创建
NSString *string1=[NSString stringWithFormat:@"TestString1_BAXIANG"];
NSLog(@"%p",string1);
NSString *string2=[[NSString alloc] initWithFormat:@"TestString2_BAXIANG"];
NSLog(@"%p",string2);
NSString *string3=@"TestString3_BAXIANG";
NSLog(@"%p",string3);
打印结果:
0x7fb6e7d58a40
0x7fb6e7d1c3a0
0x10e6a7280(1) 关于stringWithFormat和initWithFormat的区别,如果你来自MRC开发人员,很容易理解这一点。然而ARC已经完全主导了今天的发展。引用计数的概念不需要这么严格地理解。 stringWithFormat 实际上创建的是一个添加到自动释放池中的对象。主要目的是延迟释放,而initWithFormat的对象需要遵循我们常说的内存管理黄金法则:谁创建,谁释放。这是MRC 中的发布。
使用po _objc_autoreleasePoolPrint() 打印当前自动释放池(Autorelease pools)中的对象。我们刚刚通过stringWithFormat 创建的字符串对象0x7fa65a50fdc0 就在当前的释放池中。
objc[24294]: ##############
objc[24294]: 线程0x10e6533c0 的自动释放池
objc[24294]: 798 版本待定。
objc[24294]: [0x7fa65b800000] ..页面(已满)(冷)
objc[24294]: [0x7fa65b800038] ################池0x7fa65b800038
objc[24294]: [0x7fa65b800040]0x7fa65a702820 __NSCFString
objc[24294]: [0x7fa65b800048] ################池0x7fa65b800048
…………
//
objc[24294]: [0x7fa65b010958]0x7fa65a50fdc0 __NSCFString 因此第一个字符name1=nil 只是删除指向当前0x7fa65a50fdc0 字符的指针。 name2仍然指向0x7fa65a50fdc0字符串,因此name2也可以打印出当前的字符数据。关于打印内存地址,你会发现字符串3(0x10e6a7280)会明显小于上面两个,因为它是在字符串常量区创建的,而我们的第一个和第二个字符串是在堆区创建的。所以b2仍然可以打印出字符串。但如果我们将第二个字符串内容修改为string 并在64 位Apple 设备上进行调试,打印的结果就变得和我们的第一、第二个字符串结果相同:
NSString *a1=[[NSString alloc] initWithFormat:@"string"];
__weak NSString *a2=a1;
NSLog(@"a1:%@", a1);
NSLog(@"a2:%@", a2);
a1=零;
NSLog(@"a1:%@", a1);
NSLog(@"a2:%@", a2);
打印结果:
a1:字符串
a2:字符串
a1:(空)
a2:string看到这个结果可能会觉得颠覆了我们上面的理论,但是当我们看到下面的截图时,你可能会更加惊讶:
字符内容为: stringTestTwo。字符内容为:字符串。我们只是缩短了字符串的长度,并且当前字符串的类别发生了变化。更奇怪的是,字符缩短后的对象是空的,没有isa。即当前字符串对象没有类。这涉及到苹果的Tagged Pointer。
1.2、字符串的isa
(1) NSTaggedPointerString
NSTaggedPointerString 使用指针地址的空闲位来存储当前变量值。如果对象指针的最低有效位为1(即奇数),则该指针是标记指针。这种指针并不是通过解引用isa来获取它所属的类,而是通过后面三位的类表的索引来获取它所属的类。该索引用于查找哪个类使用了标记指针。其余60 位存储数据。对于NSNumber,小于2^60-1 的整数使用标记指针存储。对于需要小于60位内存的字符串,它可以创建一个Tagged Pointer,因此NSTaggedPointerString是一个伪装的对象,它存储的不是指针地址而是字符串值,因此不需要真正对象的内存分配并且不需要间接值。同时,引用计数可以是空指令,这将显着提高性能,因为不需要释放内存。
(2)__NSCFConstantString
字符串常量是编译时常量。它的retainCount值非常大。控制台打印的值为18446744073709551615==2^64-1。测试证明,即使对其进行release操作,retainCount也不会产生任何变化。内容相同的__NSCFConstantString对象具有相同的地址,这意味着常量字符串对象是单例的。这种对象一般是通过字面量@"."、CFSTR(".")或者stringWithString:方法来传递的(需要注意的是,这个方法在iOS6 SDK中已经被称为多余了,使用这个方法会生成编译器警告。此方法相当于文字创建方法)。此类对象存储在字符串常量区中。
(3)__NSCFString
对象存储在堆上。 __NSCFString 对象是在运行时创建的NSString 子类。它不是字符串常量。因此,与其他对象一样,它在创建时的引用计数为1。通过NSString的stringWithFormat等方法创建的NSString对象一般都是这种类型。
二、拷贝(copy)
2.1、immutable对象的copy
向不可变对象发送复制消息肯定会产生新对象吗?在下面的测试演示中,复制消息被发送到不可变的NSString、NSArray、NSDictionary 和NSSet 对象,并获得新的不可变对象。但问题是:副本是深副本还是浅副本?
NSString *testStr=@"abc";
NSString *copyStr=[testStr 复制];
NSLog(@"testStr=%p", testStr);
NSLog(@"copyStr=%p", copyStr);
NSArray *testArray=@[@1, @2, @3];
NSArray *copyArray=[testArray 副本];
NSLog(@"testArray=%p", testArray);
NSLog(@"copyArray=%p", copyArray);
NSSet *testSet=[NSSet setWithObjects:@1,@2,@3,nil];
NSSet *copySet=[测试集副本];
NSLog(@"testSet=%p", testSet);
NSLog(@"copySet=%p", copySet);
NSDictionary *testDict=@{@"testKey":@"testValue"};
NSDictionary *copyDict=[testDict 副本];
NSLog(@"testDict=%p", testDict);
NSLog(@"testDict=%p", copyDict);打印结果:
测试Str=0x10442f220
复制Str=0x10442f220
测试数组=0x7f9e7b60c3a0
复制数组=0x7f9e7b60c3a0
测试集=0x7f9e7b515e00
副本集=0x7f9e7b515e00
测试字典=0x7f9e7b799140
testDict=0x7f9e7b799140 将复制消息发送到不可变对象。这些对象将直接返回它们自己,而不是返回新创建的对象。
- (id)复制{
返回[(id)self copyWithZone:nil];
}
- (id)mutableCopy {
返回[(id)self mutableCopyWithZone:nil];
}- (id)copyWithZone:(NSZone *)区域{
if (NSStringClass==Nil)
NSStringClass=[NSString 类];
返回保留(自我);
//返回[[NSStringClass allocWithZone:zone] initWithString:self];
}
/* NSMutableCopying 方法*/
- (id)mutableCopyWithZone:(NSZone*)区域{
返回[[NSMutableString allocWithZone:zone] initWithString:self];
2.2、mutable对象的copy
NSMutableArray *array=[NSMutableArray arrayWithObjects:@"bayanga",@"bayang1",@"12345",nil];
NSArray *copyArray=[数组复制];
NSMutableArray *mCopyArray=[数组mutableCopy];
NSLog(@"数组=%p", 数组);
NSLog(@"copyArray=%p", copyArray);
NSLog(@"mCopyArray=%p", mCopyArray); copyArray、mCopyArray和array的内存地址都不一样,说明copyArray和mCopyArray都是复制array的内容。
2.3、浅拷贝与深拷贝
复制对象有两种方式:浅复制(指针复制)和深复制(内容复制)。浅拷贝不复制对象内容,只复制指向对象的指针;深拷贝直接将整个对象内容复制到另一块内存中。复制不可变对象是浅复制,而mutableCopy是深复制;复制和可变复制可变对象都是深复制。表达如下:
[immutableObject copy] //浅拷贝
[immutableObject mutableCopy] //深拷贝
[mutableObject copy] //深拷贝
[mutableObject mutableCopy] //深拷贝
2.4 单层深拷贝
集合对象的深拷贝仅限于对象本身,对象元素仍然是浅拷贝。苹果称这种副本只能生成一层深的副本。真正的深拷贝是深拷贝。
NSArray* trueDeepCopyArray=[NSKeyedUnarchiver unarchiveObjectWithData:
[NSKeyedArchiver archivedDataWithRootObject:oldArray]];
三、 集合(Collections)
集合存储的是一个对象,它只是这个对象的引用。这就是我们所说的单层深拷贝,使得对象的retainCount+1。当对象从数组中移除时,则引用计数retainCount-1。
keepCount 不能在ARC 下使用。虽然这三种获取引用计数(保留计数)的方法不是很准确,但是仍然可以识别当前内存。
(1) 私有方法
OBJC_EXTERN int _objc_rootRetainCount(id);
NSLog(@"之前----%d",_objc_rootRetainCount(fo)); (2)使用KVC
NSLog(@"%@",[fo valueForKey:@"retainCount"]);(3) 核心基础
NSLog(@"%ld",CFGetRetainCount((__bridge CFTypeRef)(fo)));在iOS集合类中添加一个对象,会使该对象的引用计数加1,并由数组保存。但有时您不希望集合对象引用存储的对象。在这种情况下,您可以使用NSMapTable/NSHashTable/NSPointerArray。
Foo *fo=[[Foo alloc] init];
NSLog(@"之前----%d",_objc_rootRetainCount(fo));
NSMutableArray *array=[NSMutableArray 数组];
[数组addObject:fo];
NSLog(@"after----%d",_objc_rootRetainCount(fo));
//打印结果:
之前----1
after----2
3.1、NSMapTable
NSMapTable 类似于NSDictionary、NSDcictionary 或NSMutableDictionary。 key和value的内存管理就是复制key并对value进行强引用。
+ (instancetype)dictionaryWithObject:(ObjectType)object forKey:(KeyType)key;为了保证NSCitionary中key的内存管理是复制的,复制时需要考虑系统的负担,因此key应该是轻量级的。所以通常我们使用字符串和数字来进行索引,但这只能说是键到对象的映射,而不是对象到对象的映射。
- (instancetype)initWithKeyOptions:(NSPointerFunctionsOptions)keyOptions
valueOptions:(NSPointerFunctionsOptions)valueOptions
容量:(NSUInteger)initialCapacitystatic const NSPointerFunctionsOptions NSMapTableStrongMemory;
静态常量NSPointerFunctionsOptions NSMapTableZeroingWeakMemory;
静态常量NSPointerFunctionsOptions NSMapTableCopyIn;
静态常量NSPointerFunctionsOptions NSMapTableObjectPointerPersonality;
static const NSPointerFunctionsOptions NSMapTableWeakMemory;Person *p1=[[Person alloc] initWithName:@"jack"];
最喜欢的*f1=[[最喜欢的分配] initWithName:@"ObjC"];
Person *p2=[[Person alloc] initWithName:@"rose"];
最喜欢的*f2=[[最喜欢的alloc] initWithName:@"Swift"];
NSMapTable *MapTable=[NSMapTable mapTableWithKeyOptions:NSMapTableWeakMemory valueOptions:NSMapTableWeakMemory];
//设置对应关系表
//p1=f1;
//p2=f2
[MapTable setObject:f1 forKey:p1];
[MapTable setObject:f2 forKey:p2];
NSLog(@"%@ %@", p1, [MapTable objectForKey:p1]);
NSLog(@"%@ %@", p2, [MapTable objectForKey:p2]);
3.2、NSHashTable
NSHashTable 类似于NSSet 和NSMutableSet 的组合。 NSHashTable 是可变的,可以使用
NSHashTableWeakMemory,该选项使用弱存储对象,并在对象被销毁时自动将其从集合中删除。 NSHashTableObjectPointerPersonality:与NSPointerFunctionsObjectPointerPersonality 相同,该选项直接使用isEqual: 和hash 的指针来提高搜索效率。为了防止循环引用,创建了一个委托管理器,它操作NSHashTable。
- (NSHashTable *)代表
{
如果(!_代表){
_delegates=[NSHashTableweakObjectsHashTable];
}
返回_代表;
}
- (void)addDelegate:(id)委托
{
if ([self.delegates containsObject:delegate]) {
返回;
}
[self.delegates addObject:delegate];
}
- (void)removeDelegate:(id)委托
{
[self.delegatesremoveObject:delegate];
}
3.3、NSPointerArray
与NSArray类似,NSPointerArray默认是可变的,并且可以插入空值nil。我们可以设置存储对象是否被引用。
[NSPointerArray StrongObjectsPointerArray]; //strong引用
好了,文章到这里就结束啦,如果本次分享的深入解析:ARC内存管理常见问题及解决方案和问题对您有所帮助,还望关注下本站哦!
【深入解析:ARC内存管理常见问题及解决方案】相关文章:
用户评论
讲道理,我对 ARC 内存管理一直感觉很容易上手,但还真没深入考虑过会有哪些“容易忽略”的问题。这篇文章挺好的,希望能开阔一下我的视野。
有16位网友表示赞同!
想问问这篇文章具体会提到哪些常被忽视的问题?我之前开发项目的时候倒是觉得 ARC 用得蛮顺的。
有6位网友表示赞同!
ARC 这么久没听说过什么严重的内存泄漏案例啊,看来问题不大呗?
有13位网友表示赞同!
我觉得写代码的时候要始终记得去管理内存,无论用哪个机制。这样才能保证程序稳定运行。
有15位网友表示赞同!
虽然 ARC 简化了内存管理,但也不能掉以轻心啊,总归还是要掌握一些基本原则和技巧。
有7位网友表示赞同!
ARC 真的是开发效率的利器,解放了不少内存管理的精力。希望这篇文章能提醒我们一些容易忽略的地方,提高项目的质量。
有11位网友表示赞同!
我一直不太清楚 ARC 的具体机制是怎样的,这篇文章刚好可以补充一下我的知识面。
有17位网友表示赞同!
感觉最近很多新技术都在强调易用性,比如 ARC 等等,开发起来确实方便了许多。
有20位网友表示赞同!
对于那些没有使用过 ARC 的开发者,这篇文章简直就是福音了! 可以帮助他们快速了解这个机制,并避开一些常见问题。
有13位网友表示赞同!
还是得注意学习新技术,ARC 这个内存管理方式很不错,希望通过这篇文章能更全面地了解它,应用到项目中。
有20位网友表示赞同!
这篇文章的标题听起来就很有吸引力,看来是能够解答我很多疑惑的好文章!
有16位网友表示赞同!
写代码的路上总是遇到各种各样的坑,ARC 可能也有一些难以察觉的问题吧。 希望这篇文章能给我一些宝贵经验教训。
有18位网友表示赞同!
对于像我一样经验有限的小白来说,学习 ARC 确实需要花点心思和时间的积累,这篇文章能够帮我节省很多时间!
有10位网友表示赞同!
之前接触过一些内存泄漏问题,现在使用 ARC 也要时刻保持警惕才行啊!
有20位网友表示赞同!
觉得新技术的学习周期越来越短了,ARC 这种工具也应该跟着时代的变化不断改进完善吧!
有14位网友表示赞同!
分享一下你对这篇文章的看法吗? 我对 ARC 内存管理还是比较期待的。
有11位网友表示赞同!
文章内容很有深度,能够帮助我们更好地理解 ARC 的优缺点,以及在实际开发中需要注意的事项。
有20位网友表示赞同!
ARC 作为一种自动内存管理机制,能够简化代码开发过程,提高开发效率,这篇文章肯定能点明许多实用技巧!
有20位网友表示赞同!
我相信这种类型的文章会越来越受欢迎,因为越来越多的人开始使用 ARC 进行编程開発。
有6位网友表示赞同!