1. 什么是内存管理

  • 程序在运行的过程中通常通过以下行为,来增加程序的的内存占用

    • 创建一个OC对象
    • 定义一个变量
    • 调用一个函数或者方法
  • 而一个移动设备的内存是有限的,每个软件所能占用的内存也是有限的
  • 当程序所占用的内存较多时,系统就会发出内存警告,这时就得回收一些不需要再使用的内存空间。比如回收一些不需要使用的对象、变量等
  • 如果程序占用内存过大,系统可能会强制关闭程序,造成程序崩溃、闪退现象,影响用户体验

所以,我们需要对内存进行合理的分配内存、清除内存,回收那些不需要再使用的对象。从而保证程序的稳定性。

那么,那些对象才需要我们进行内存管理呢?

  • 任何继承了NSObject的对象需要进行内存管理
  • 而其他非对象类型(int、char、float、double、struct、enum等) 不需要进行内存管理

这是因为

  • 继承了NSObject的对象的存储在操作系统的里边。
  • 操作系统的:一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收,分配方式类似于链表
  • 非OC对象一般放在操作系统的里面
  • 操作系统的:由操作系统自动分配释放,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈(先进后出)
  • 示例:
    int main(int argc, const char * argv[])
    {
    @autoreleasepool {
    int a = ; // 栈
    int b = ; // 栈
    // p : 栈
    // Person对象(计数器==1) : 堆
    Person *p = [[Person alloc] init];
    }
    // 经过上面代码后, 栈里面的变量a、b、p 都会被回收
    // 但是堆里面的Person对象还会留在内存中,因为它是计数器依然是1
    return ;

2. 内存管理模型

提供给Objective-C程序员的基本内存管理模型有以下3种:

  • 自动垃圾收集(iOS运行环境不支持)
  • 手工引用计数和自动释放池(MRC)
  • 自动引用计数(ARC)

3.MRC 手动管理内存(Manual Reference Counting)

1. 引用计数器

系统是根据对象的引用计数器来判断什么时候需要回收一个对象所占用的内存

  • 引用计数器是一个整数
  • 从字面上, 可以理解为”对象被引用的次数”
  • 也可以理解为: 它表示有多少人正在用这个对象
  • 每个OC对象都有自己的引用计数器
  • 任何一个对象,刚创建的时候,初始的引用计数为1
    • 当使用alloc、new或者copy创建一个对象时,对象的引用计数器默认就是1
  • 当没有任何人使用这个对象时,系统才会回收这个对象, 也就是说
    • 当对象的引用计数器为0时,对象占用的内存就会被系统回收
    • 如果对象的计数器不为0,那么在整个程序运行过程,它占用的内存就不可能被回收(除非整个程序已经退出 )

2. 引用计数器操作

  • 为保证对象的存在,每当创建引用到对象需要给对象发送一条retain消息,可以使引用计数器值+1 ( retain 方法返回对象本身)
  • 当不再需要对象时,通过给对象发送一条release消息,可以使引用计数器值-1
  • 给对象发送retainCount消息,可以获得当前的引用计数器值
  • 当对象的引用计数为0时,系统就知道这个对象不再需要使用了,所以可以释放它的内存,通过给对象发送dealloc消息发起这个过程。
  • 需要注意的是:release并不代表销毁\回收对象,仅仅是计数器-1
int main(int argc, const char * argv[]) {
@autoreleasepool {
// 只要创建一个对象默认引用计数器的值就是1
Person *p = [[Person alloc] init];
NSLog(@"retainCount = %lu", [p retainCount]); // 1 // 只要给对象发送一个retain消息, 对象的引用计数器就会+1
[p retain]; NSLog(@"retainCount = %lu", [p retainCount]); // 2
// 通过指针变量p,给p指向的对象发送一条release消息
// 只要对象接收到release消息, 引用计数器就会-1
// 只要一个对象的引用计数器为0, 系统就会释放对象 [p release];
// 需要注意的是: release并不代表销毁\回收对象, 仅仅是计数器-1
NSLog(@"retainCount = %lu", [p retainCount]); // [p release]; //
NSLog(@"--------");
}
// [p setAge:20]; // 此时对象已经被释放
return ;
}

3. dealloc方法

  • 当一个对象的引用计数器值为0时,这个对象即将被销毁,其占用的内存被系统回收
  • 对象即将被销毁时系统会自动给对象发送一条dealloc消息(因此,从dealloc方法有没有被调用,就可以判断出对象是否被销毁)
  • dealloc方法的重写
    • 一般会重写dealloc方法,在这里释放相关资源,dealloc就是对象的遗言
    • 一旦重写了dealloc方法,就必须调用[super dealloc],并且放在最后面调用
- (void)dealloc
{
NSLog(@"Person dealloc");
// 注意:super dealloc一定要写到所有代码的最后
// 一定要写在dealloc方法的最后面
[super dealloc];
}
  • 使用注意

    • 不能直接调用dealloc方法
    • 一旦对象被回收了, 它占用的内存就不再可用,坚持使用会导致程序崩溃(野指针错误)

4. 野指针和空指针

  • 只要一个对象被释放了,我们就称这个对象为 "僵尸对象(不能再使用的对象)"
  • 当一个指针指向一个僵尸对象(不可用内存),我们就称这个指针为野指针
  • 只要给一个野指针发送消息就会报错(EXC_BAD_ACCESS错误)
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *p = [[Person alloc] init]; // 执行完引用计数为1 [p release]; // 执行完引用计数为0,实例对象被释放
[p release]; // 此时,p就变成了野指针,再给野指针p发送消息就会报错
[p release];
}
return ;
}
  • 为了避免给野指针发送消息会报错,一般情况下,当一个对象被释放后我们会将这个对象的指针设置为空指针
  • 空指针
    • 没有指向存储空间的指针(里面存的是nil, 也就是0)
    • 给空指针发消息是没有任何反应的
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *p = [[Person alloc] init]; // 执行完引用计数为1 [p release]; // 执行完引用计数为0,实例对象被释放
p = nil; // 此时,p变为了空指针
[p release]; // 再给空指针p发送消息就不会报错了
[p release];
}
return ;
}

5. 内存管理规律

单个对象内存管理规律

  • 谁创建谁release :

    • 如果你通过alloc、new、copy或mutableCopy来创建一个对象,那么你必须调用release或autorelease
  • 谁retain谁release:
    • 只要你调用了retain,就必须调用一次release
  • 总结一下就是
    • 有加就有减
    • 曾经让对象的计数器+1,就必须在最后让对象计数器-1

多个对象内存管理规律

因为多个对象之间往往是联系的,所以管理起来比较复杂。这里用一个玩游戏例子来类比一下。

游戏可以提供给玩家(A类对象) 游戏房间(B类对象)来玩游戏。

  • 只要一个玩家想使用房间(进入房间),就需要对这个房间的引用计数器+1
  • 只要一个玩家不想再使用房间(离开房间),就需要对这个房间的引用计数器-1
  • 只要还有至少一个玩家在用某个房间,那么这个房间就不会被回收,引用计数至少为1
 

下面来定义两个类 玩家类:Person 和 房间类:Room

房间类:Room,房间类中有房间号

#import <Foundation/Foundation.h>

@interface Room : NSObject
@property int no; // 房间号
@end

玩家类:Person

#import <Foundation/Foundation.h>
#import "Room.h" @interface Person : NSObject
{
Room *_room;
} - (void)setRoom:(Room *)room; - (Room *)room;
@end

现在我们通过几个玩家使用房间的不同应用场景来逐步深入理解内存管理。

1. 玩家没有使用房间,玩家和房间之间没有联系的情况

int main(int argc, const char * argv[]) {
@autoreleasepool {
// 1.创建两个对象
Person *p = [[Person alloc] init]; // 玩家 p
Room *r = [[Room alloc] init]; // 房间 r
r.no = ; // 房间号赋值 [r release]; // 释放房间
[p release]; // 释放玩家
}
return ;
}

上述代码执行完前3行

        // 1.创建两个对象
Person *p = [[Person alloc] init]; // 玩家 p
Room *r = [[Room alloc] init]; // 房间 r
r.no = ; // 房间号赋值

之后在内存中的表现如下图所示:

可见,Room实例对象和Person实例对象之间没有相互联系,所以各自释放不会报错。执行完4、5行代码

        [r release];    // 释放房间
[p release]; // 释放玩家

后,将房间对象和玩家对象各自释放掉,在内存中的表现如下图所示:

最后各自实例对象的内存就会被系统回收

2. 一个玩家使用一个游戏房间,玩家和房间之间相关联的情况

int main(int argc, const char * argv[]) {
@autoreleasepool {
// 1.创建两个对象
Person *p = [[Person alloc] init]; // 玩家 p
Room *r = [[Room alloc] init]; // 房间 r
r.no = ; // 房间号赋值 // 将房间赋值给玩家,表示玩家在使用房间
// 玩家需要使用这间房,只要玩家在,房间就一定要在
p.room = r; // [p setRoom:r] [r release]; // 释放房间 // 在这行代码之前,玩家都没有被释放,但是因为玩家还在,那么房间就不能销毁
NSLog(@"-----"); [p release]; // 释放玩家
}
return ;
}

上边代码执行完前3行的时候和之前在内存中的表现一样,如图

当执行完第4行代码p.room = r;时,因为调用了setter方法,将Room实例对象赋值给了Person的成员变量,不做其他设置的话,在内存中的表现如下图(做法不对):

 

在调用setter方法的时候,因为Room实例对象多了一个Person对象引用,所以应将Room实例对象的引用计数+1才对,即setter方法应该像下边一样,对room进行一次retain操作。

- (void)setRoom:(Room *)room // room = r
{
// 对房间的引用计数器+1
[room retain];
_room = room;
}

那么执行完第4行代码p.room = r;,在内存中的表现为:

继续执行第5行代码[r release];,释放房间,Room实例对象引用计数-1,在内存中的表现如下图所示:
 

然后执行第6行代码[p release];,释放玩家。这时候因为玩家不在房间里了,房间也没有用了,所以在释放玩家的时候,要把房间也释放掉,也就是在delloc里边对房间再进行一次release操作。

这样对房间对象来说,每一次retain/alloc操作都对应一次release操作。

- (void)dealloc
{
// 人释放了, 那么房间也需要释放
[_room release];
NSLog(@"%s", __func__); [super dealloc];
}

那么在内存中的表现最终如下图所示:

最后实例对象的内存就会被系统回收

3. 一个玩家使用一个游戏房间r后,换到另一个游戏房间r2,玩家和房间相关联的情况

int main(int argc, const char * argv[]) {
@autoreleasepool {
// 1.创建两个对象
Person *p = [[Person alloc] init]; // 玩家 p
Room *r = [[Room alloc] init]; // 房间 r
r.no = ; // 房间号赋值 // 2.将房间赋值给玩家,表示玩家在使用房间
p.room = r; // [p setRoom:r]
[r release]; // 释放房间 r // 3. 换房
Room *r2 = [[Room alloc] init];
r2.no = ;
p.room = r2;
[r2 release]; // 释放房间 r2 [p release]; // 释放玩家 p
}
return ;
}

执行下边几行代码

        // 1.创建两个对象
Person *p = [[Person alloc] init]; // 玩家 p
Room *r = [[Room alloc] init]; // 房间 r
r.no = ; // 房间号赋值 // 2.将房间赋值给玩家,表示玩家在使用房间
p.room = r; // [p setRoom:r]
[r release]; // 释放房间 r

之后的内存表现为:

 

接着执行换房操作而不进行其他操作的话,

        // 3. 换房
Room *r2 = [[Room alloc] init];
r2.no = ;
p.room = r2;

内存的表现为:

 

最后执行完

        [r2 release];    // 释放房间 r2
[p release]; // 释放玩家 p

内存的表现为:

 

可以看出房间 r 并没有被释放,这是因为在进行换房的时候,并没有对房间 r 进行释放。所以应在调用setter方法的时候,对之前的变量进行一次release操作。具体setter方法代码如下:

- (void)setRoom:(Room *)room // room = r
{
// 将以前的房间释放掉 -1
[_room release]; // 对房间的引用计数器+1
[room retain]; _room = room;
}
}

这样在执行完p.room = r2;之后就会将 房间 r 释放掉,最终内存表现为:

 

4. 一个玩家使用一个游戏房间,不再使用游戏房间,将游戏房间释放掉之后,再次使用该游戏房间的情况

int main(int argc, const char * argv[]) {
@autoreleasepool {
// 1.创建两个对象
Person *p = [[Person alloc] init];
Room *r = [[Room alloc] init];
r.no = ; // 2.将房间赋值给人
p.room = r; // [p setRoom:r]
[r release]; // 释放房间 r // 3.再次使用房间 r
p.room = r;
[r release]; // 释放房间 r
[p release]; // 释放玩家 p
}
return ;
}

执行下面代码

        // 1.创建两个对象
Person *p = [[Person alloc] init];
Room *r = [[Room alloc] init];
r.no = ; // 2.将房间赋值给人
p.room = r; // [p setRoom:r]
[r release]; // 释放房间 r

之后的内存表现为:

 

然后再执行p.room = r;,因为setter方法会将之前的Room实例对象先release掉,此时内存表现为:

 

此时_room、r 已经变成了一个野指针。之后再对野指针 r 发出retain消息,程序就会崩溃。所以我们在进行setter方法的时候,要先判断一下是否是重复赋值,如果是同一个实例对象,就不需要重复进行release和retain。换句话说,如果我们使用的还是之前的房间,那换房的时候就不需要对这个房间再进行release和retain。则setter方法具体代码如下:

- (void)setRoom:(Room *)room // room = r
{
// 只有房间不同才需用release和retain
if (_room != room) { // 0ffe1 != 0ffe1
// 将以前的房间释放掉 -1
[_room release]; // 对房间的引用计数器+1
[room retain]; _room = room;
}
}
因为retain不仅仅会对引用计数器+1, 而且还会返回当前对象,所以上述代码可最终简化成:
- (void)setRoom:(Room *)room // room = r
{
// 只有房间不同才需用release和retain
if (_room != room) { // 0ffe1 != 0ffe1
// 将以前的房间释放掉 -1
[_room release]; _room = [room retain];
}
}

以上就是setter方法的最终形式。

6. @property参数

  • 在成员变量前加上@property,系统就会自动帮我们生成基本的setter/getter方法
@property (nonatomic) int val;
  • 如果在property后边加上retain,系统就会自动帮我们生成getter/setter方法内存管理的代码,但是仍需要我们自己重写dealloc方法
 @property(nonatomic, retain) Room *room;
  • 如果在property后边加上assign,系统就不会帮我们生成set方法内存管理的代码,仅仅只会生成普通的getter/setter方法,默认什么都不写就是assign
@property(nonatomic, assign) int val;

7. 自动释放池

当我们不再使用一个对象的时候应该将其空间释放,但是有时候我们不知道何时应该将其释放。为了解决这个问题,Objective-C提供了autorelease方法。

  • autorelease是一种支持引用计数的内存管理方式,只要给对象发送一条autorelease消息,会将对象放到一个自动释放池中,当自动释放池被销毁时,会对池子里面的所有对象做一次release操作

    注意,这里只是发送release消息,如果当时的引用计数(reference-counted)依然不为0,则该对象依然不会被释放。

  • autorelease方法会返回对象本身,且调用完autorelease方法后,对象的计数器不变
Person *p = [Person new];
p = [p autorelease];
NSLog(@"count = %lu", [p retainCount]); // 计数还为1

1. 使用autorelease有什么好处呢

  • 不用再关心对象释放的时间
  • 不用再关心什么时候调用release

2. autorelease的原理实质上是什么?

autorelease实际上只是把对release的调用延迟了,对于每一个autorelease,系统只是把该对象放入了当前的autorelease pool中,当该pool被释放时,该pool中的所有对象会被调用release。

3. autorelease的创建方法

  1. 使用NSAutoreleasePool来创建
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; // 创建自动释放池
[pool release]; // [pool drain]; 销毁自动释放池
  1. 使用@autoreleasepool创建
@autoreleasepool
{ //开始代表创建自动释放池 } //结束代表销毁自动释放池

4. autorelease的使用方法

NSAutoreleasePool *autoreleasePool = [[NSAutoreleasePool alloc] init];
Person *p = [[[Person alloc] init] autorelease];
[autoreleasePool drain];
@autoreleasepool
{ // 创建一个自动释放池
Person *p = [[Person new] autorelease];
// 将代码写到这里就放入了自动释放池
} // 销毁自动释放池(会给池子中所有对象发送一条release消息)

5. autorelease的注意事项

  • 并不是放到自动释放池代码中,都会自动加入到自动释放池
@autoreleasepool {
// 因为没有调用 autorelease 方法,所以对象没有加入到自动释放池
Person *p = [[Person alloc] init];
[p run];
}
  • 在自动释放池的外部发送autorelease 不会被加入到自动释放池中

    • autorelease是一个方法,只有在自动释 放池中调用才有效。
@autoreleasepool {
}
// 没有与之对应的自动释放池, 只有在自动释放池中调用autorelease才会放到释放池
Person *p = [[[Person alloc] init] autorelease];
[p run]; // 正确写法
@autoreleasepool {
Person *p = [[[Person alloc] init] autorelease];
} // 正确写法
Person *p = [[Person alloc] init];
@autoreleasepool {
[p autorelease];
}

6. 自动释放池的嵌套使用

  • 自动释放池是以栈的形式存在
  • 由于栈只有一个入口, 所以调用autorelease会将对象放到栈顶的自动释放池

栈顶就是离调用autorelease方法最近的自动释放池

@autoreleasepool { // 栈底自动释放池
@autoreleasepool {
@autoreleasepool { // 栈顶自动释放池
Person *p = [[[Person alloc] init] autorelease];
}
Person *p = [[[Person alloc] init] autorelease];
}
}
  • 自动释放池中不适宜放占用内存比较大的对象

    • 尽量避免对大内存使用该方法,对于这种延迟释放机制,还是尽量少用
    • 不要把大量循环操作放到同一个 @autoreleasepool 之间,这样会造成内存峰值的上升
// 内存暴涨
@autoreleasepool {
for (int i = ; i < ; ++i) {
Person *p = [[[Person alloc] init] autorelease];
}
}
// 内存不会暴涨
for (int i = ; i < ; ++i) {
@autoreleasepool {
Person *p = [[[Person alloc] init] autorelease];
}
}

7. autorelease错误用法

  • 不要连续调用autorelease
@autoreleasepool {
// 错误写法, 过度释放
Person *p = [[[[Person alloc] init] autorelease] autorelease];
}
调用autorelease后又调用release(错误)
@autoreleasepool {
Person *p = [[[Person alloc] init] autorelease];
[p release]; // 错误写法, 过度释放
}

8. MRC中避免循环retain

定义两个类Person类和Dog类

  • Person类:
#import <Foundation/Foundation.h>
@class Dog; @interface Person : NSObject
@property(nonatomic, retain)Dog *dog;
@end
  • Dog类:
#import <Foundation/Foundation.h>
@class Person; @interface Dog : NSObject
@property(nonatomic, retain)Person *owner;
@end

执行以下代码:

int main(int argc, const char * argv[]) {
Person *p = [Person new];
Dog *d = [Dog new]; p.dog = d; // retain
d.owner = p; // retain assign [p release];
[d release]; return ;
}

就会出现A对象要拥有B对象,而B对应又要拥有A对象,此时会形成循环retain,导致A对象和B对象永远无法释放

那么如何解决这个问题呢?

  • 不要让A retain B,B retain A
  • 让其中一方不要做retain操作即可
  • 当两端互相引用时,应该一端用retain,一端用assign

4.ARC 自动管理内存(Automatic Reference Counting)

  • Automatic Reference Counting,自动引用计数,即ARC,WWDC2011和iOS5所引入的最大的变革和最激动人心的变化。ARC是新的LLVM 3.0编译器的一项特性,使用ARC,可以说一 举解决了广大iOS开发者所憎恨的手动内存管理的麻烦。
  • 使用ARC后,系统会检测出何时需要保持对象,何时需要自动释放对象,何时需要释放对象,编译器会管理好对象的内存,会在何时的地方插入retain, release和autorelease,通过生成正确的代码去自动释放或者保持对象。我们完全不用担心编译器会出错

1. ARC的判断原则

ARC判断一个对象是否需要释放不是通过引用计数来进行判断的,而是通过强指针来进行判断的。那么什么是强指针?

  • 强指针

    • 默认所有对象的指针变量都是强指针
    • 被__strong修饰的指针
 Person *p1 = [[Person alloc] init];
__strong Person *p2 = [[Person alloc] init];
  • 弱指针

    • 被__weak修饰的指针
__weak  Person *p = [[Person alloc] init];

ARC如何通过强指针来判断?

  • 只要还有一个强指针变量指向对象,对象就会保持在内存中

2. ARC的使用

int main(int argc, const char * argv[]) {
// 不用写release, main函数执行完毕后p会被自动释放
Person *p = [[Person alloc] init];
return ;
}

3. ARC的注意点

  • 不允许调用对象的 release方法
  • 不允许调用 autorelease方法
  • 重写父类的dealloc方法时,不能再调用 [super dealloc];

4. ARC下单对象内存管理

  • 局部变量释放对象随之被释放

    int main(int argc, const char * argv[]) {
    @autoreleasepool {
    Person *p = [[Person alloc] init];
    } // 执行到这一行局部变量p释放
    // 由于没有强指针指向对象, 所以对象也释放
    return ;
    }
  • 清空指针对象随之被释放

    int main(int argc, const char * argv[]) {
    @autoreleasepool {
    Person *p = [[Person alloc] init];
    p = nil; // 执行到这一行, 由于没有强指针指向对象, 所以对象被释放
    }
    return ;
    }
  • 默认清空所有指针都是强指针

    int main(int argc, const char * argv[]) {
    @autoreleasepool {
    // p1和p2都是强指针
    Person *p1 = [[Person alloc] init];
    __strong Person *p2 = [[Person alloc] init];
    }
    return ;
    }
  • 弱指针需要明确说明

    • 注意: 千万不要使用弱指针保存新创建的对象

      int main(int argc, const char * argv[]) {
      @autoreleasepool {
      // p是弱指针, 对象会被立即释放
      __weak Person *p1 = [[Person alloc] init];
      }
      return ;
      }

5. ARC下多对象内存管理

  • ARC和MRC一样, 想拥有某个对象必须用强指针保存对象, 但是不需要在dealloc方法中release

    @interface Person : NSObject
    // MRC写法
    //@property (nonatomic, retain) Dog *dog; // ARC写法
    @property (nonatomic, strong) Dog *dog;
    @end

6. ARC下@property参数

  • strong : 用于OC对象,相当于MRC中的retain
  • weak : 用于OC对象,相当于MRC中的assign
  • assign : 用于基本数据类型,跟MRC中的assign一样

6. ARC下循环引用问题

  • ARC和MRC一样,如果A拥有B,B也拥有A,那么必须一方使用弱指针
@interface Person : NSObject
@property (nonatomic, strong) Dog *dog;
@end @interface Dog : NSObject
// 错误写法, 循环引用会导致内存泄露
//@property (nonatomic, strong) Person *owner; // 正确写法, 当如果保存对象建议使用weak
@property (nonatomic, weak) Person *owner;
@end

作者:行走少年郎
链接:https://www.jianshu.com/p/48665652e4e4
來源:简书

OC 知识:彻底理解 iOS 内存管理(MRC、ARC)的更多相关文章

  1. iOS 内存管理,ARC

    iOS 对象内存释放时机:当对象的引用计数为0时对象被释放. 所以如下代码: __weak NSObject * a = [[NSObject alloc] init]; 这个对象在创建完赋完值后会被 ...

  2. iOS内存管理 ARC与MRC

    想驾驭一门语言,首先要掌握它的内存管理特性.iOS开发经历了MRC到ARC的过程,下面就记录一下本人对iOS内存管理方面的一些理解. 说到iOS开发,肯定离不开objective-c语言(以下简称OC ...

  3. 【Bugly干货分享】iOS内存管理:从MRC到ARC实践

    Bugly 技术干货系列内容主要涉及移动开发方向,是由Bugly邀请腾讯内部各位技术大咖,通过日常工作经验的总结以及感悟撰写而成,内容均属原创,转载请标明出处. 对于iOS程序员来说,内存管理是入门的 ...

  4. iOS 内存管理:从 MRC 到 ARC 实践

    对于 iOS 程序员来说,内存管理是入门的必修课.引用计数.自动释放等概念,都是与 C 语言完全不同的. iOS 内存管理的核心是引用计数. 接触 MRC 时遇到最头疼的问题就是:为什么那么多 rel ...

  5. iOS内存管理机制解析之MRC手动引用计数机制

    前言: iOS的内存管理机制ARC和MRC是程序猿參加面试基本必问的问题,也是考察一个iOS基本功是 否扎实的关键,这样深入理解内存管理机制的重要性就不言而喻了. iOS内存管理机制发展史 iOS 5 ...

  6. OC内存管理(MRC)

    首先说明一下几块存储区域: 栈区(局部变量.函数参数值) 堆区(对象.手动申请/释放内存) BSS区(未初始化的全局变量.未初始化的静态数据) 常量区(字符串常量以及初始化后的全局变量.初始化后的静态 ...

  7. iOS内存管理(一)

    最近有时间,正好把iOS相关的基础知识好好的梳理了一下,记录一下内存相关方面的知识. 在理解内存管理之前我觉得先对堆区和栈区有一定的了解是非常有必要的. 栈区:就是由编译器自动管理内存分配,释放过程的 ...

  8. iOS-旧项目中手动内存管理(MRC)转ARC

    在ARC之前,iOS内存管理无论对资深级还是菜鸟级开发者来说都是一件很头疼的事.我参 加过几个使用手动内存管理的项目,印象最深刻的是一个地图类应用,由于应用本身就非常耗内存,当时为了解决内存泄露问题, ...

  9. OC的内存管理(二)ARC

    指针: 指向内存的地址指针变量 存放地址的变量指针变量值 变量中存放的值(地址值)指针变量指向的内存单元值 内存地址指向的值1):强指针:默认的情况下,所有的指针都是强指针,关键字strong ):弱 ...

随机推荐

  1. atitit。流程图的设计与制作 attilax 总结

    atitit.流程图的设计与制作 attilax 总结 1. 流程图的规范1 2. 绘图语言2 2.1. atitit.CSDN-markdown编辑器2 2.2. js-sequence-diagr ...

  2. scp远程传输文件和ssh远程连接

    ssh使用方法 如果从一台linux服务器通过ssh远程登录到另一台Linux机器, 这种情况通常会在多台服务器的时候用到. 如用root帐号连接一个IP为192.168.1.102的机器,输入:“  ...

  3. 对《SQL Server中tempdb的management》的一些更正和补充

    对<SQL Server中tempdb的management>的一些更正和补充 前几天看了这篇文章:SQL Server中tempdb的management 发现里面有些内容不是很准确 文 ...

  4. Oracle 处理坏块

    本文主要介绍如何去处理在Oracle数据库中出现坏块的问题,对于坏块产生在不同的对象上,处理的方法会有所不同,本文将大致对这些方法做一些介绍.因为数据库运行时间长了,由于硬件设备的老化,出现坏块的几率 ...

  5. Java简单方法批量修改Windows文件夹下的文件名(简单IO使用)

    package test.tttt; import java.io.File; import java.util.ArrayList; import java.util.List; public cl ...

  6. ORACLE RAC clusterware/GI 启动诊断流程图11.2+

  7. android 常用adb 及linux 命令

    一.ADB相关 adb shell:进入连接的USB调试模式设备shell命令行下 adb tcpip 5555:将USB连接的调试及的连接方式改为网络远程模式进行调试 这里端口为5555(adb 默 ...

  8. AFNetworking 2.5.x 网络请求的封装

    AFNetworking 2.5.x 网络请求的封装 源码地址 https://github.com/YouXianMing/Networking 说明 1. 将block形式的请求转换成用代理来处理 ...

  9. 剑指offer 09变态跳台阶

    一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级.求该青蛙跳上一个n级的台阶总共有多少种跳法. java版本: public class Solution { public stati ...

  10. Laravel 实践之路: 数据库迁移与数据填充

    数据库迁移实际上就是对数据库库表的结构变化做版本控制,之前对数据库库表结构做修改的方式比较原始,比如说对某张库表新增了一个字段,都是直接在库表中执行alter table xxx add .. 的方式 ...