内存管理-ARC

理解引用计数机制

在引用计数的机制下,每个对象都有一个计数器,可递增递减,用以表示当前有多少事物想让该对象继续保留下去。NSObject 协议声明了三个方法用于操作计数器:

  • retain:递增计数器
  • release:递减计数器
  • autorelease : "autorelease pool" 清理时,再递减计数器。

retain 和 release

    NSNumber *number = @1;//number引用计数递增1
    [array addObject:number];////number引用计数递增1

对象A被创建出来时,会自动执行 retain ,引用计数递增。当对象B引用了对象A时,被引用者对象A递增。当对象A的引用计数为0时,对象会被系统回收。

NSMutableArray *array = [NSMutableArray array];//计数为1
NSNumber *number = @1;//计数为1
[array addObject:number];//计数为2
[number release];//计数为1
[array release];//0

如上,如果我们不需要对象了,就对它调用release,然而,此时number的计数仍然为1,number对象仍然存活。为了避免不经意间使用了无效指针,调用完release我们可以手动清空指针。(通常都是针对局部变量)

number = nil;

我们知道,程序在生命期间会创建很多对象,这些对象都相互联系着。所以,这些相互联系的对象就构成一个对象树,这个对象树的根节点是UIApplocation对象,它是程序启动时创建的单例。

autorelease

- (NSString *)temp{
    NSString *tmp = @"A";//计数为1
    return tmp;
}

如上,返回的temp对象的引用计数比我们期望的要多1,因为只有retain,没有对应的release操作。然而,我们又不能在方法中释放temp,否则这个方法就没有存在的意义了。
所以这里我们使用autorelease,它会在稍后释放对象,从而给调用者留下足够的时间,又不会因为没有释放而造成内存泄露。具体时间是当前线程的下一次时间循环。

- (NSString *)temp{
    NSString *tmp = @"A";//计数为1
    return [tmp autorelease];
}

ARC

为什么要使用ARC

使用 ARC 时,引用计数还是会执行的,只不过是 ARC 为你自动添加的,即上面所说的retain/release/ autorelease 方法。在对象被创建时 retain count +1,在对象被 release 时 retain count -1.当 retain count 为0 时,销毁对象。 程序中加入 autoreleasepool 的对象会由系统自动加上autorelease方法,如果该对象引用计数为0,则销毁。 因为ARC会自动执行这些方法,所以在ARC下调用这些方法是非法的,会产生编译错误。

那么ARC是为了解决什么问题诞生的呢?这个得追溯到MRC手动内存管理时代说起。
MRC下内存管理的缺点:
1.当我们要释放一个堆内存时,首先要确定指向这个堆空间的指针都被 release 了。(避免提前释放)
2.释放指针指向的堆空间,首先要确定哪些指针指向同一个堆,这些指针只能释放一次。(MRC下即谁创建,谁释放,避免重复释放)
3.模块化操作时,对象可能被多个模块创建和使用,不能确定最后由谁去释放。
4.多线程操作时,不确定哪个线程最后使用完毕。

内存管理语义

ARC下,我们创建一个属性,需要主动声明一下内存管理语义:

assign

适用于基本数据类型。赋值特性,不涉及引用计数,弱引用,仅仅是基本数据类型变量(scalar type,例如 CGFloat 或 NSlnteger 等) 在 setter 方法中的简单赋值操作。assign 其实也可以用来修饰对象,那么我们为什么不用它呢?因为用了就相当于回到 MRC 时代了:)。 被assign修饰的对象在释放之后,指针的地址还是存在的,也就是说指针并没有被置为nil。如果在后续的内存分配中,刚好分到了这块地址,程序就会崩溃掉。

strong

强引用,与MRC中retain类似,使用之后,计数器+1。

weak

弱引用 ,weak 修饰的对象在释放之后,指针地址会被置为nil,可以有效的避免野指针,其引用计数为1。在 ARC 中,在有可能出现循环引用的时候, 此时通过引用计数无法释放指针, 往往要通过让其中一端使用 weak 来解决,比如: delegate 代理属性。自身已经对它进行一次强引用,没有必要再强引用一次,此时也会使用 weak,自定义 IBOutlet 控件属性一般也使用 weak;当然,也可以使用strong。

weak 此特质表明该属性定义了一种“非拥有关系” (nonowning relationship)。为这种属性设置新值时,设置方法既不保留新值,也不释放旧值。此特质同assign类似, 然而在属性所指的对象遭到摧毁时,属性值也会清空(nil out)。 。

readwrite

可读可写特性,需要生成getter方法和setter方法时使用。

readonly

只读特性,只会生成getter方法,不会生成setter方法,不希望属性在类外改变。

copy

表示拷贝特性,setter方法将传入对象复制一份,需要完全一份新的变量时。相当于strong,只不过在编译器生成的 setter 方法中会额外将传入对象复制一份。如果你自己实现了 setter 方法,不要忘了对变量进行 copy 操作,否则 copy 的声明就没有作用了。

nonatomic

非原子操作,不加同步,多线程访问可提高性能,但是线程不安全的。决定编译器生成的setter getter是否是原子操作。

atomic

原子操作,与nonatomic相反,系统会为setter方法加锁。 具体使用 @synchronized(self){//code } 。它是线程安全,需要消耗大量系统资源来为属性加锁 。(实际上并没有 atomic,只不过当没有 nonatomic 时就是 atomic,不过你要是写上去编译器也不会报错)。

内存泄露

即便有了ARC,也是有可能会内存泄露的。

两个类循环引用

//AViewController 对象
@interface AViewController : UIViewController
//aVC强引用了bVC
@property (nonatomic, strong) BViewController *bVC;
@end

/**************************************************/

//BViewController 对象
@interface BViewController : UIViewController
//bVC强引用了aVC
@property (nonatomic, strong) aViewController *aVC;
@end

如上的代码,A强引用了B,B也强引用了A,这样就导致了循环引用,ARC无法回收这两个对象,从而导致内存泄露。

那我们该怎么解决呢?我觉得两个对象最好不要互相引用,如果不得不互相引用,我们可以这么写:

@interface AViewController : UIViewController
//aVC强引用了bVC
@property (nonatomic, strong) BViewController *bVC;
@end

/**************************************************/

//BViewController 对象
@interface BViewController : UIViewController
//bVC弱引用了aVC
@property (nonatomic, weak) aViewController *aVC;
@end

delegate循环引用问题

delegate循环引用问题比较基础,原理和两个类循环引用一样,这里特地拿出来讲是因为 delegate 比较常用。具体可看下面的示例图。只需注意将代理属性修饰为weak即可。

@property (nonatomic, weak) id delegate;

Block

有这样一个场景:在网络工具类NetworkFetch中有一个网络请求的回调Block:

/**
 * 本例取自《Effective Objective-C 2.0》
 * NetworkFetecher 为自定义的网络获取器的类
 */ 
//EOCNetworkFetcher.h
#import <Foundation/Foundation.h>
typedef void (^EOCNetworkFetcherCompletionHandler)(NSData *data);
@interface EOCNetworkFetcher : NSObject
@property (nonatomic, strong, readonly) NSURL *url;
- (id)initWithURL:(NSURL *)url;
- (void)startWithCompletionHandler:(EOCNetworkFetcherCompletionHandler)completion;
@end;

//EOCNetworkFetcher.m
#import "EOCNetworkFetcher.h"
@interface EOCNetworkFetcher ()
@property (nonatomic, strong, readwrite) NSURL *url;
@property (nonatomic, copy) (EOCNetworkFetcherCompletionHandler)completionHandler;
@property (nonatomic, strong) NetworkFetecher *networkFetecher;
@end;
@implementation EOCNetworkFetcher
- (id)initWithURL:(NSURL *)url{
    if (self = [super init]) {
        _url = url;
    }
    return self;
}
- (void)startWithCompletionHandler:(EOCNetworkFetcherCompletionHandler)completion{
    self.completionHandler = completion;
}
- (void)p_requestCompleted{
    if (_completionHandler) {
        _completionHandler(_downloaderData);
    }
}

某个类中使用网络工具类发送请求并处理回调:

@implementation EOCClass {
    EOCNetworkFetcher *_networkFetcher;
    NSData *_fetcherData;
}
- (void)downloadData{
    NSURL *url = [NSURL alloc] initWithString:@"/* some url string */";
    _networkFetcher = [[EOCNetworkFetch alloc] initWithURL:url];
    [_networkFetcher startWithCompletionHandler:^(NSData *data) {
        NSLog(@"request url %@ finished.", _networkFetcher);
        _fetcherData = data;
    }]
}
@end;

很明显在使用block的过程中形成了循环引用:self 持有 networkFetecher;networkFetecher持有block;block持有 self。三者形成循环引用,内存泄露。

下面的例子也会造成内存泄露:

- (void)downloadData{
    NSURL *url = [[NSURL alloc] initWithString:@"/* some url string */"];
    NetworkFetecher *networkFetecher = [[NetworkFetecher alloc] initWithURL:url];
    [networkFetecher startWithCompletionHandler:^(NSData *data){
        NSLog(@"request url: %@", networkFetcher.url);
    }];
}

networkFetecher持有block,block持有networkFetecher,形成内存孤岛,无法释放。

解决方案有两种:

将对象置为nil,消除引用,打破循环引用。这种方法容易漏掉某个该置nil的属性。
代码中任意地方

_networkFetecher = nil;

将强引用转换成弱引用,打破循环引用。

__weak __typeof(self) weakSelf = self;

如果想防止 weakSelf 被释放,例如当 block 回调是在子线程,block 执行的时候,主线程可能在block 执行到一半的时候就将 self 置空,所以可以再次强引用一次:

// 将强引用转换成弱引用,打破循环引用
__weak __typeof(self) weakSelf = self;
NSURL *url = [[NSURL alloc] initWithString:@"g.cn"];
_networkFetecher = [[NetworkFetecher alloc] initWithURL:url];
[_networkFetecher startWithCompletionHandler:^(NSData *data){
    //如果想防止 weakSelf 被释放,可以再次强引用
    __typeof(&*weakSelf) strongSelf = weakSelf;
    if (strongSelf) {
        //do something with strongSelf
    }
}];

strongself是为了防止内存提前释放。具体原理可以假设A界面push到B界面,B界面执行Block如下:

self.title = @"B界面";
__weak typeof(self) weakSelf = self;
self.block = ^{
      dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
         NSLog(@"%@", weakSelf.title);
        });
};
self.block();

如果B界面10秒之后返回A界面会正常打印weakSelf.title为B界面
但如果B界面10秒之内返回A界面则会打印null,因为10秒之内返回,B界面执行dealloc销毁,内存提前销毁,B界面对应的self不存在,因此也不可能执行关于self的事项。

self.title = @"B界面";
__weak typeof(self) weakSelf = self;
self.block = ^{
      __strong typeof(self) strongSelf = weakSelf;
      dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
          NSLog(@"%@", strongSelf.title);
      });
};
self.block();

而如果具有strongSelf,会使B界面所对应的self引用计数+1,即使10秒内返回A界面, B界面也不会立刻释放。并且strongSelf属于局部变量,存在与栈中,会随着Block的执行而销毁。
总之strongSelf就是为了保证Block中的事件执行正确。

performSelector

performSelector 就是在运行时执行一个selector。

- (id)performSelector:(SEL)selector;
[object methodName];
[object performSelector:@selector(methodName)];

如果有以下的代码:

SEL selector;
if (/* some condition */) {
    selector = @selector(newObject);
} else if (/* some other condition */) {
    selector = @selector(copy);
} else {
    selector = @selector(someProperty);
}
id ret = [object performSelector:selector];

这段代码就相当于在动态之上再动态绑定。正是由于动态,编译器不知道即将调用的selector是什么,不了解方法签名和返回值,甚至是否有返回值都不懂,所以编译器无法用ARC的内存管理规则来判断返回值是否应该释放。因此,ARC采用了比较谨慎的做法,不添加释放操作,即在方法返回对象时就可能将其持有,从而可能导致内存泄露。

以本段代码为例,前两种情况(newObject, copy)都需要再次释放,而第三种情况不需要。这种泄露隐藏得如此之深,以至于使用static analyzer都很难检测到。如果把代码的最后一行改成:

[object performSelector:selector];

不创建一个返回值变量测试分析,简直难以想象这里居然会出现内存问题。所以如果你使用的selector有返回值,一定要处理掉。

NSNotificationcenter

这个比较常见,如果你在viewWillAppear 中监听了一个NSNotificationcenter,记得在 viewWillDisappear 中调用 removeObserver。同样的,如果你在viewDidLoad监听了一个NSNotificationcenter,记得在 dealloc 中调用 removeObserver。

不过,请注意,这个写法也是有存在隐患的,当你的类本来有内存泄漏的时候,你的类很可能不会调用dealloc 方法,所以你在 dealloc 中调用 removeObserver 可以说没什么卵用。

又或者,当你使用以下方法进行控制器的切换时:

+ (void)transitionWithView:(UIView *)view duration:(NSTimeInterval)duration options:(UIViewAnimationOptions)options animations:(void (^)(void))animations completion:(void (^)(BOOL finished))completion;

viewWillDisappear 也不会调用,所以你想当然的想在viewWillDisappear 中调用 removeObserver 也没什么卵用。

所以正确的方式是使用上面说的方法 removeObserver 后,打 log 留意removeObserver 是否被调用到了。

NSTimer

在使用NSTimer addtarget时,为了防止target被释放而导致的程序异常,timer会持有self,所以这也是一处内存泄露的隐患。

看下面的例子:

#import "TestNSTimer.h"
 
@interface TestNSTimer ()
@property (nonatomic, strong) NSTimer *timer;
@end
@implementation TestNSTimer
 
- (instancetype)init {
    if (self = [super init]) {
        _timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(timeRefresh:) userInfo:nil repeats:YES];
    }
    return self;
}
 
- (void)timeRefresh:(NSTimer*)timer {
    NSLog(@"TimeRefresh...");
}
 
- (void)cleanTimer {
    [_timer invalidate];
    _timer = nil;
}
 
- (void)dealloc {
    [super dealloc];
    NSLog(@"销毁");
    [self cleanTimer];
}
 
@end

为了防止内存泄漏,我们在delloc方法中调用cleanTimer。然而这并没有什么作用,因为TestNSTimer对象并没有正常释放,所以 delloc 无法执行,定时器仍然在无限的执行下去。当前类销毁执行dealloc的前提是定时器需要停止并滞空,而定时器停止并滞空的时机在当前类调用dealloc方法时,这样就造成了互相等待的场景,从而内存一直无法释放。

所以,如何解决?

如上的解决方案为将cleanTimer方法外漏,在外部调用,如viewDidDisappearviewWillDisappear中清空计时器即可。

可是并不是特别优雅,要是其他开发者忘记调用cleanTimer,或者 viewDidDisappear或viewWillDisappear 没有调用到,这个类就会一直存在内存泄漏,然后定时器也不会停止。

你可以参考这个链接使用这种取巧的方法[https://blog.callmewhy.com/2015/07/06/weak-timer-in-ios/]

��所以我还是比较推荐用dispatch的timer,无须考虑这些事情:

@interface XXGCDTimer()
{
    dispatch_source_t _timer;
}
@end
    
@implementation XXGCDTimer
-(void) startGCDTimer{
    NSTimeInterval period = 1.0; //设置时间间隔
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
     _timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
    dispatch_source_set_timer(_timer, dispatch_walltime(NULL, 0), period * NSEC_PER_SEC, 0); //每秒执行
    dispatch_source_set_event_handler(_timer, ^{
        //在这里执行事件
        NSLog(@"每秒执行test");
    });
    
    dispatch_resume(_timer);
}
    
-(void) pauseTimer{
    if(_timer){
        dispatch_suspend(_timer);
    }
}
    
-(void) resumeTimer{
    if(_timer){
        dispatch_resume(_timer);
    }
}
    
-(void) stopTimer{
    if(_timer){
        dispatch_source_cancel(_timer);
        _timer = nil;
    }
}
@end

具体可以使用 YYKit 中封装的 YYTimer ,和上面使用的原理一样。

非OC对象内存处理

对于一些非OC对象,使用完毕后其内存仍需要我们手动释放。举个例子,比如常用的滤镜操作调节图片亮度:

CIImage *beginImage = [[CIImage alloc]initWithImage:[UIImage imageNamed:@"yourname.jpg"]];
CIFilter *filter = [CIFilter filterWithName:@"CIColorControls"];
[filter setValue:beginImage forKey:kCIInputImageKey];
[filter setValue:[NSNumber numberWithFloat:.5] forKey:@"inputBrightness"];//亮度-1~1
CIImage *outputImage = [filter outputImage];
//GPU优化
EAGLContext * eaglContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3];
eaglContext.multiThreaded = YES;
CIContext *context = [CIContext contextWithEAGLContext:eaglContext];
[EAGLContext setCurrentContext:eaglContext];
 
CGImageRef ref = [context createCGImage:outputImage fromRect:outputImage.extent];
UIImage *endImg = [UIImage imageWithCGImage:ref];
_imageView.image = endImg;
CGImageRelease(ref);//非OC对象需要手动内存释放

在如上代码中的CGImageRef类型变量非OC对象,其需要手动执行释放操作CGImageRelease(ref),否则会造成大量的内存泄漏导致程序崩溃。其他的对于CoreFoundation框架下的某些对象或变量需要手动释放、C语言代码中的malloc等需要对应free等都需要注意。

代理未清空引起野指针

iOS 的某些API,delegate 声明为assign的,貌似是历史遗留问题。这样就会引起野指针的问题,可能会引起一些莫名其妙的crash。当一个对象被回收时,对应的delegate实体也就被回收,但是delegate的指针确没有被nil,从而就变成了游荡的野指针了。所以在delloc方法中要将对应的assign代理设置为nil,如:

- (void)dealloc{
    self.myTableView.delegate = nil;
    self.myTableView.dataSource = nil;
}

地图类处理

若项目中使用地图相关类,一定要检测内存情况,因为地图是比较耗费App内存的,因此在根据文档实现某地图相关功能的同时,我们需要注意内存的正确释放,大体需要注意的有需在使用完毕时将地图、代理等滞空为nil,注意地图中标注(大头针)的复用,并且在使用完毕时清空标注数组等。

- (void)clearMapView{
    self.mapView.delegate =nil;
    self.mapView.showsUserLocation = NO;
    [self.mapView removeAnnotations:self.annotations];
    [self.mapView removeOverlays:self.overlays];
    [self.mapView setCompassImage:nil];
}

大次数循环内存暴涨问题

for (int i = 0; i < 100000; i++) {
        NSString *string = @"Abc";
        string = [string lowercaseString];
        string = [string stringByAppendingString:@"xyz"];
        NSLog(@"%@", string);
}

该循环内产生大量的临时对象,直至循环结束才释放,可能导致内存泄漏,解决方法为在循环中创建自己的autoReleasePool,及时释放占用内存大的临时变量,减少内存占用峰值。

for (int i = 0; i < 100000; i++) {
        @autoreleasepool {
            NSString *string = @"Abc";
            string = [string lowercaseString];
            string = [string stringByAppendingString:@"xyz"];
            NSLog(@"%@", string);
        }
}

检测内存泄漏

借助Xcode自带的Instruments工具(选取真机测试)

重写dealloc方法

简单暴力的重写dealloc方法,加入断点或打印判断某类是否正常释放。

dealloc 调用方式如下: 如果 a 持有对象 b ,b 持有c, c持有 d, 假设 a 是一个vc(其实只要是个对象都是一样的) 这时候 a.navigationxxxx popviewcon...... 这时候如果a “本身”没有内存泄漏,dealloc 回正常执行,
但在执行dealloc的,a 会驱使b 释放,b如果没有泄漏会执行b 的delloc 然后b 在dealloc 执行完之前首先驱使 c 释放,c 如果没有泄漏,在c的dealloc 执行完之前会首先驱使d 释放。这是整个释放链。

我们现在假设a 确实没内存泄漏,但是b有,则b 的delloc 不会执行,这样c、d的也不会执行,你就会看到,a的delloc 执行了,但是 它所持有的b, b持有的c, c 持有的d 都没有释放。

因此 单纯以一个delloc 来确定我整个类释放了是不准确的,你要保证你这个对象所有所持有的对象(系统对象应该不与考虑,即使你考虑了 系统控件/对象造成的对象你也解决不了)都执行了delloc 方法,你才可以保证的说:没有内存泄漏了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 196,099评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,473评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,229评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,570评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,427评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,335评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,737评论 3 386
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,392评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,693评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,730评论 2 312
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,512评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,349评论 3 314
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,750评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,017评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,290评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,706评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,904评论 2 335

推荐阅读更多精彩内容

  • 内存管理 简述OC中内存管理机制。与retain配对使用的方法是dealloc还是release,为什么?需要与a...
    丶逐渐阅读 1,928评论 1 16
  • 29.理解引用计数 Objective-C语言使用引用计数来管理内存,也就是说,每个对象都有个可以递增或递减的计数...
    Code_Ninja阅读 1,460评论 1 3
  • 如需转载 务必加本文链接并注明出处 请尊重每一位作者!!!!!!!! 首先我们为什么要进行内存管理由于移动设备的内...
    Liwjing阅读 3,411评论 8 28
  • iOS内存管理 概述 什么是内存管理 应用程序内存管理是在程序运行时分配内存(比如创建一个对象,会增加内存占用)与...
    蚊香酱阅读 5,683评论 8 119
  • 最近迷上了百家讲坛,一方面也是因为老是盯着手机屏幕对眼睛不好,换成听节目很好地弥补了这一点,尤其是夜深人静的时候放...
    星飒说阅读 260评论 2 2