內(nèi)存管理(一)

面試題

1.使用CADisplayLink、NStimer有什么注意點(diǎn)?
  - 注意循環(huán)引用
  - 會(huì)造成時(shí)間不準(zhǔn)確的問題(NStimer依賴于RunLoop,如果RunLoop的任務(wù)過于繁重,可能會(huì)導(dǎo)致NSTimer不準(zhǔn)時(shí))
  - 要求比較準(zhǔn)時(shí)的時(shí)候,還是需要用GCD來實(shí)現(xiàn)定時(shí)器
2.介紹下內(nèi)存的幾大區(qū)域?
地址從低到高:保留區(qū)/代碼區(qū)/數(shù)據(jù)段/堆/棧/內(nèi)核區(qū)
3.講一下你對(duì)ios內(nèi)存管理的理解?
    TaggedPointer、NONPOINTER_ISA(64位系統(tǒng)下)、散列表(引用計(jì)數(shù)表、weak表)
4.ARC幫我們做了什么?
    LLVM+Runtime
    LLVM(編譯器在編譯的時(shí)候,會(huì)自動(dòng)幫我們?cè)诤线m的地方添加retain,release等操作)+runtime(在運(yùn)行時(shí)的比如弱指針會(huì)幫我們?nèi)ス芾砬宄?
    總的來說:是LLVM和Runtime共同作用的結(jié)果   
5.weak指針實(shí)現(xiàn)的原理?
    將弱引用存儲(chǔ)到一個(gè)哈希表里,當(dāng)對(duì)象要銷毀時(shí),就會(huì)取出當(dāng)前對(duì)象的弱引用表,將該表存儲(chǔ)的弱引用都給清除掉
【具體的實(shí)現(xiàn)原理可參考-- http://www.lxweimin.com/p/9c52b6f9d01c】
6.autorelease對(duì)象在什么時(shí)機(jī)會(huì)被調(diào)用release?
    如果有被autoreleasepool包裹的,是出大括號(hào)就開始被釋放(調(diào)用了pop的方法時(shí)候),如不是 :它可能是在某次RunLoop循環(huán)中,RunLoop休眠之前調(diào)用了release
7.方法里有局部對(duì)象,出了方法后會(huì)被立即釋放?
    MRC環(huán)境下:不一定,是在當(dāng)前runloop循環(huán)中,即將進(jìn)入休眠時(shí)釋放
  ARC環(huán)境下:馬上釋放,只要出了作用域就釋放

在下面我們會(huì)逐步來說明

Demo代碼可見MemoryManagement


分析下面的情況

-(void)test1{
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    for(int i=0;i<1000;i++){
        dispatch_async(queue, ^{
            self.name = [NSString stringWithFormat:@"abcdefghijk"];
        });
    }
}

================
-(void)test2{
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    for(int i=0;i<1000;i++){
        dispatch_async(queue, ^{
             self.name = [NSString stringWithFormat:@"abc"];
        });
    }
}
memoryManagerment_1.png

解決辦法:加鎖

@property (assign, nonatomic) os_unfair_lock lock;
@property (nonatomic,strong)NSString  *name;

-(void)test1{
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    for(int i=0;i<1000;i++){
        dispatch_async(queue, ^{
             os_unfair_lock_lock(&_lock);
             self.name = [NSString stringWithFormat:@"abcdefghijk"];
             os_unfair_lock_unlock(&_lock);
        });
    }
}

第二種:沒有崩潰(正常運(yùn)行)

  • 分析:taggedPointer

  • abc直接存儲(chǔ)在指針里面了

何為:taggedPointer 下面我們會(huì)具體的分析


CADisplayLink、NSTimer使用注意

CADisplayLink、NSTimer會(huì)對(duì)target產(chǎn)生強(qiáng)引用,如果target又對(duì)他們產(chǎn)生強(qiáng)引用,那么必然會(huì)造成循環(huán)強(qiáng)引用
如:
self.link = [CADisplayLink displayLinkWithTarget:self  selector:@selector(linkTest)];
self.timer = [NSTimer timerWithTimeInterval:1.0 target:self selector: @selector(timerTest) userInfo:nil repeats:YES]; 
  • 解決辦法
    • 使用block
    • 使用代理對(duì)象(NSProxy)
timer解決:
方法一:     
__weak typeof(self) weakSelf = self;
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
        [weakSelf timerTest];
}];

- (void)dealloc{
    NSLog(@"%s", __func__);
    [self.timer invalidate];
}

方法二:
YDProxy.h
#import <Foundation/Foundation.h>
@interface YDProxy : NSObject
+ (instancetype)proxyWithTarget:(id)target;
@property (weak, nonatomic) id target;
@end

YDProxy.m
#import "YDProxy.h"

@implementation YDProxy
+ (instancetype)proxyWithTarget:(id)target{
    YDProxy *proxy = [[YDProxy alloc] init];
    proxy.target = target;
    return proxy;
}

- (id)forwardingTargetForSelector:(SEL)aSelector{
    return self.target;
}

-(void)dealloc{
    NSLog(@"%s", __func__);
}
@end

使用:
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:[YDProxy proxyWithTarget:self] selector:@selector(timerTest) userInfo:nil repeats:YES];

-(void)timerTest{
    NSLog(@"%s",__FUNCTION__);
}

-(void)dealloc{
    NSLog(@"%s", __func__);
    [self.timer invalidate];
}

=====================
CADisplayLink解決:
//保證調(diào)用頻率和屏幕的刷幀頻率一致,60FPS
self.link = [CADisplayLink displayLinkWithTarget:[MJProxy proxyWithTarget:self] selector:@selector(linkTest)];
[self.link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];

-(void)dealloc{
    [self.link invalidate];
}
memoryManagerment_2.png

NSProxy

  • 1.專門用來做消息轉(zhuǎn)發(fā)的(和NSObject平級(jí))
  • 2.和繼承自NSobject比較,效率高(本類方法沒有,不需要 從父類去找了,直接進(jìn)入消息轉(zhuǎn)發(fā))
YDProxy2.h
#import <UIKit/UIKit.h>
@interface YDProxy2 : NSProxy
+ (instancetype)proxyWithTarget:(id)target;
@property (weak, nonatomic) id target;
@end

YDProxy2.m
#import "YDProxy2.h"

@implementation YDProxy2
+ (instancetype)proxyWithTarget:(id)target{
    // NSProxy對(duì)象不需要調(diào)用init,因?yàn)樗緛砭蜎]有init方法
    YDProxy2 *proxy = [YDProxy2 alloc];
    proxy.target = target;
    return proxy;
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel{
    return [self.target methodSignatureForSelector:sel];
}

//重定向消息
- (void)forwardInvocation:(NSInvocation *)invocation{
    [invocation invokeWithTarget:self.target];
}
@end

ViewController_3.m
#import "ViewController_3.h"
#import "YDProxy2.h"

@interface ViewController_3()
@property (strong, nonatomic) NSTimer *timer;
@end

@implementation ViewController_3
-(void)viewDidLoad{
    [super viewDidLoad];
    self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:[YDProxy2 proxyWithTarget:self] selector:@selector(timerTest) userInfo:nil repeats:YES];
    
     //分析:
    //繼承了NSProxy ,那么大部分就會(huì)進(jìn)入消息轉(zhuǎn)發(fā)階段。
    //在調(diào)用isKindOfClass 的時(shí)候,進(jìn)入了消息轉(zhuǎn)發(fā)。進(jìn)入到 methodSignatureForSelector 和 forwardInvocation 這兩個(gè)方法。
    //調(diào)用順序就改成了 target 。也就是說先拿到[YDProxy2 proxyWithtarget: vc] 中的 vc
    //然后變成了[vc isKindOfClass:[ViewController class]];
    NSLog(@"%d",[[YDProxy2 proxyWithTarget:self] isKindOfClass:[ViewController_3 class]]);
}

- (void)timerTest{
    NSLog(@"%s", __func__);
}

- (void)dealloc{
    NSLog(@"%s", __func__);
    [self.timer invalidate];
}
@end

GCD定時(shí)器

memoryManagerment_3.png
#import "ViewController_4.h"
@interface ViewController_4()
@property (nonatomic,strong)dispatch_source_t timer;
@end

@implementation ViewController_4
-(void)viewDidLoad{
    [super viewDidLoad];
    
    [self test1];
}

-(void)test1{
    // 隊(duì)列
    dispatch_queue_t queue = dispatch_queue_create("timer", DISPATCH_QUEUE_SERIAL);
    // 創(chuàng)建定時(shí)器
    dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
    // 設(shè)置時(shí)間
    uint64_t start = 2.0; // 2秒后開始執(zhí)行
    uint64_t interval = 1.0; // 每隔1秒執(zhí)行
    dispatch_source_set_timer(timer,
                              dispatch_time(DISPATCH_TIME_NOW, start * NSEC_PER_SEC),interval * NSEC_PER_SEC, 0);
    // 設(shè)置回調(diào)
    dispatch_source_set_event_handler(timer, ^{
        NSLog(@"1111");
    });
    
//    OR: dispatch_source_set_event_handler_f(timer, timerFire);
    
    // 啟動(dòng)定時(shí)器
    dispatch_resume(timer);
    self.timer = timer; //得使用強(qiáng)引用引用下
}

void timerFire(void *param){
    NSLog(@"2222 - %@", [NSThread currentThread]);
}

- (void)dealloc{
    NSLog(@"%s", __func__);
}
@end

接下來我們封裝下:

YDTimer.h
#import <Foundation/Foundation.h>
@interface YDTimer : NSObject
+ (NSString *)execTask:(void(^)(void))task
                 start:(NSTimeInterval)start
              interval:(NSTimeInterval)interval
               repeats:(BOOL)repeats
                 async:(BOOL)async;

+ (NSString *)execTask:(id)target
              selector:(SEL)selector
                 start:(NSTimeInterval)start
              interval:(NSTimeInterval)interval
               repeats:(BOOL)repeats
                 async:(BOOL)async;

+ (void)cancelTask:(NSString *)name;
@end

YDTimer.m
#import "YDTimer.h"

@implementation YDTimer
static NSMutableDictionary *timers_;
dispatch_semaphore_t semaphore_;
+ (void)initialize{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        timers_ = [NSMutableDictionary dictionary];
        semaphore_ = dispatch_semaphore_create(1);
    });
}

+ (NSString *)execTask:(void (^)(void))task start:(NSTimeInterval)start interval:(NSTimeInterval)interval repeats:(BOOL)repeats async:(BOOL)async{
    if (!task || start < 0 || (interval <= 0 && repeats)) return nil;
    
    // 隊(duì)列
    dispatch_queue_t queue = async ? dispatch_get_global_queue(0, 0) : dispatch_get_main_queue();
    
    // 創(chuàng)建定時(shí)器
    dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
    
    // 設(shè)置時(shí)間
    dispatch_source_set_timer(timer,
                              dispatch_time(DISPATCH_TIME_NOW, start * NSEC_PER_SEC),
                              interval * NSEC_PER_SEC, 0);
    
    
    dispatch_semaphore_wait(semaphore_, DISPATCH_TIME_FOREVER);
    // 定時(shí)器的唯一標(biāo)識(shí)
    NSString *name = [NSString stringWithFormat:@"%zd", timers_.count];
    // 存放到字典中
    timers_[name] = timer;
    dispatch_semaphore_signal(semaphore_);
    
    // 設(shè)置回調(diào)
    dispatch_source_set_event_handler(timer, ^{
        task();
        
        if (!repeats) { // 不重復(fù)的任務(wù)
            [self cancelTask:name];
        }
    });
    
    // 啟動(dòng)定時(shí)器
    dispatch_resume(timer);
    
    return name;
}

+ (NSString *)execTask:(id)target selector:(SEL)selector start:(NSTimeInterval)start interval:(NSTimeInterval)interval repeats:(BOOL)repeats async:(BOOL)async{
    if (!target || !selector) return nil;
    
    return [self execTask:^{
        if ([target respondsToSelector:selector]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
            [target performSelector:selector];
#pragma clang diagnostic pop
        }
    } start:start interval:interval repeats:repeats async:async];
}

+ (void)cancelTask:(NSString *)name{
    if (name.length == 0) return;
    
    dispatch_semaphore_wait(semaphore_, DISPATCH_TIME_FOREVER);
    
    dispatch_source_t timer = timers_[name];
    if (timer) {
        dispatch_source_cancel(timer);
        [timers_ removeObjectForKey:name];
    }
    
    dispatch_semaphore_signal(semaphore_);
}
@end

調(diào)用:
#import "ViewController_5.h"
#import "YDTimer.h"
@interface ViewController_5()
@property (copy, nonatomic) NSString *task;
@end

@implementation ViewController_5
- (void)viewDidLoad {
    [super viewDidLoad];
    // 接口設(shè)計(jì)
    self.task = [YDTimer execTask:self
                         selector:@selector(doTask)
                            start:2.0
                         interval:1.0
                          repeats:YES
                            async:NO];
}

- (void)doTask{
    NSLog(@"doTask - %@", [NSThread currentThread]);
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    [YDTimer cancelTask:self.task];
}

- (void)dealloc{
    NSLog(@"%s", __func__);
    [YDTimer cancelTask:self.task];
}
@end

iOS程序的內(nèi)存布局

memoryManagerment_4.png
static int A = 10 ;
int B;

@implementation ViewController_6
-(void)viewDidLoad{
    [super viewDidLoad];
    
    //數(shù)據(jù)段
    NSLog(@"%p",&A);
    NSLog(@"%p",&B);
    
    NSString *str = @"字符串常量";
    NSLog(@"%p",str);
    
    //堆
    NSObject *obj1 = [[NSObject alloc]init];
    NSObject *obj2 = [[NSObject alloc]init];
    NSLog(@"%p",obj1);
    NSLog(@"%p",obj2);
    
    //棧
    int age=10;
    float height = 180.0f;
    NSLog(@"%p",&age);
    NSLog(@"%p",&height);
    
}

打印:
MemoryManagement[10795:942733] 0x105488190
MemoryManagement[10795:942733] 0x105488210
MemoryManagement[10795:942733] 0x105485450
MemoryManagement[10795:942733] 0x600002aab7e0
MemoryManagement[10795:942733] 0x600002aaba60
MemoryManagement[10795:942733] 0x7ffeea77c3c4
MemoryManagement[10795:942733] 0x7ffeea77c3c0
memoryManagerment_5.png

Tagged Pointer

memoryManagerment_6.png
#import "ViewController_7.h"
#import <objc/runtime.h>

@implementation ViewController_7
-(void)viewDidLoad{
    [super viewDidLoad];
    
    NSNumber *a = @(1);
    NSLog(@"%p",a);
    
    NSNumber *b  = @(2);
    NSLog(@"%p",b);
    
    NSNumber *c = @(3.1415926);
    NSLog(@"%p",c);
    
    NSLog(@"=======Start======");
    for(int i=0;i<10;i++){
        NSNumber *d  = @((int)(arc4random() % 520));
        NSLog(@"%p--a=%@",d,d);
    }
    NSLog(@"=======End======");
    
    NSString *str = @"abc"; // __NSCFConstantString(常量字符串)
    NSLog(@"%p -- %@",str,[str class]);
    
    NSString *str1 = [NSString stringWithFormat:@"%@",str]; // NSTaggedPointerString
    NSLog(@"%p -- %@",str1,[str1 class]);
    
    NSString *str2 = [str copy]; // __NSCFConstantString(常量字符串)
    NSLog(@"%p -- %@",str2,[str2 class]);

    NSMutableString *str3 = [str mutableCopy]; // __NSCFString(字符串)
    NSLog(@"%p -- %@",str3,[str3 class]);

}
@end

打印:
MemoryManagement[30107:1510547] 0x982ce8502b222809
MemoryManagement[30107:1510547] 0x982ce8502b222839
MemoryManagement[30107:1510547] 0x6000029efa00
MemoryManagement[30107:1510547] =======Start======
MemoryManagement[30107:1510547] 0x982ce8502b222d49--a=85
MemoryManagement[30107:1510547] 0x982ce8502b222009--a=129
MemoryManagement[30107:1510547] 0x982ce8502b222239--a=162
MemoryManagement[30107:1510547] 0x982ce8502b223959--a=276
MemoryManagement[30107:1510547] 0x982ce8502b222899--a=8
MemoryManagement[30107:1510547] 0x982ce8502b223f99--a=376
MemoryManagement[30107:1510547] 0x982ce8502b222ee9--a=111
MemoryManagement[30107:1510547] 0x982ce8502b223429--a=451
MemoryManagement[30107:1510547] 0x982ce8502b223969--a=279
MemoryManagement[30107:1510547] 0x982ce8502b222c29--a=67
MemoryManagement[30107:1510547] =======End======
MemoryManagement[30107:1510547] 0x10f0fe2d8 -- __NSCFConstantString
MemoryManagement[30107:1510547] 0x882ce8502d140e08 -- NSTaggedPointerString
MemoryManagement[30107:1510547] 0x10f0fe2d8 -- __NSCFConstantString
memoryManagerment_7.png

發(fā)現(xiàn):變量a和變量b不是一個(gè)對(duì)象(偽對(duì)象)、而變量c是一個(gè)真正的oc對(duì)象

(如果:是一個(gè)比較長的數(shù)字,或者小數(shù)等(占位較大的),就是一個(gè)真正的oc對(duì)象)

memoryManagerment_8.png

發(fā)現(xiàn):變量str1不是一個(gè)對(duì)象(偽對(duì)象,isa的指向0x0),而變量str,str2,str3是oc對(duì)象

(如果:是一個(gè)比較長的字符串,那么就是一個(gè)真正的OC對(duì)象)

我們?cè)趤砜纯瓷厦娲蛴〉膬?nèi)存地址:

對(duì)于NSNumber:是taggerPointer的末尾 是一樣的(現(xiàn)在的重新運(yùn)行時(shí)9)(再次運(yùn)行可能結(jié)果不一樣)

并且:他們的前面的幾位都有一樣的

對(duì)于string的 是NSTaggedPointerString的,他的地址和真正的字符串地址是有很大不一樣的

具體的:可以參考Objective-C 的 Tagged Pointer 實(shí)現(xiàn)Objective-C對(duì)象的TaggedPointer特性

【這里只是簡單的瞅瞅】


判斷是否為Tagged Pointer

objc4-750里面
objc_internal.h

#if (TARGET_OS_OSX || TARGET_OS_IOSMAC) && __x86_64__
    // 64-bit Mac - tag bit is LSB
#   define OBJC_MSB_TAGGED_POINTERS 0
#else
    // Everything else - tag bit is MSB
#   define OBJC_MSB_TAGGED_POINTERS 1
#endif

#if 
#   define _OBJC_TAG_MASK (1UL<<63)
#else
#   define _OBJC_TAG_MASK 1UL
#endif

static inline bool 
_objc_isTaggedPointer(const void * _Nullable ptr){
    return ((uintptr_t)ptr & _OBJC_TAG_MASK) == _OBJC_TAG_MASK;
}

即:(需要講16進(jìn)制的地址轉(zhuǎn)為2進(jìn)制的)
ios平臺(tái)(64bit):最高有效位為1
Mac平臺(tái):最低有效位是1

我們用上面的例子:

對(duì)于:NSNumber *a = @(1); a的地址是:0x982ce8502b222809 轉(zhuǎn)為二進(jìn)制

1001/1000/0010/1100/1110/1000/0101/0000/0010/1011/0010/0010/0010/1000/0000/1001

發(fā)現(xiàn):最高位為1(是Tagged Pointer)

對(duì)于:NSNumber *c = @(3.1415926); c的地址是:0x6000029efa00轉(zhuǎn)為二進(jìn)制

0000/0000/0000/0000/0110/0000/0000/0000/0000/0010/1001/1110/1111/1010/0000/0000

發(fā)現(xiàn):最高位為0(不是Tagged Pointer)

這里我們可以發(fā)現(xiàn):

dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
for(int i=0;i<1000;i++){
   dispatch_async(queue, ^{
       self.name = [NSString stringWithFormat:@"abc"];
   });
}

沒有崩潰:不會(huì)出現(xiàn)資源爭奪的問題(它的值是直接存儲(chǔ)在指針地址里面的,不是一個(gè)真正的oc對(duì)象)


OC對(duì)象的內(nèi)存管理

memoryManagerment_9.png
MRC:
Person *person1 = [[[Person alloc] init] autorelease];
在自動(dòng)釋放池里面調(diào)用的autorelease會(huì)被釋放掉

============
// 自動(dòng)生成成員變量和屬性的setter、getter實(shí)現(xiàn)
@synthesize age = _age;


@property (nonatomic, retain) Dog *dog;
@synthesize dog= _dog;

/*
- (void)setDog:(Dog *)dog{
    if (_dog != dog) {
        [_dog release];
        _dog = [dog retain];
    }
}

- (Dog *)dog{
    return _dog;
}
*/

- (void)dealloc{
    self.dog = nil;
    [super dealloc];
}

==========
@property (retain, nonatomic) NSMutableArray *data;

1.
self.data = [[[NSMutableArray alloc] init] autorelease];
    
2.
self.data = [[NSMutableArray alloc] init];
[self.data release];
    
3.
NSMutableArray *data = [[NSMutableArray alloc] init];
self.data = data;
[data release];
 
4.self.data = [NSMutableArray array];
- (void)dealloc {
     self.data = nil;    
     [super dealloc];
}

copy

拷貝的目的:產(chǎn)生一個(gè)副本對(duì)象,跟源對(duì)象互不影響
修改了源對(duì)象,不會(huì)影響副本對(duì)象
修改了副本對(duì)象,不會(huì)影響源對(duì)象

iOS提供了2個(gè)拷貝方法
1.copy,不可變拷貝,產(chǎn)生不可變副本
 
2.mutableCopy,可變拷貝,產(chǎn)生可變副本

深拷貝和淺拷貝
1.深拷貝:內(nèi)容拷貝,產(chǎn)生新的對(duì)象
2.淺拷貝:指針拷貝,沒有產(chǎn)生新的對(duì)象

NSString *str1 = [NSString stringWithFormat:@"test"];
NSLog(@"%p -- %@",str1,[str1 class]);//0xf7f20831888a5de3 -- NSTaggedPointerString
    
NSString *str2 = [str1 copy];
NSLog(@"%p -- %@",str2,[str2 class]);//0xf7f20831888a5de3 -- NSTaggedPointerString
    
NSMutableString *str3 = [str1 mutableCopy];
NSLog(@"%p -- %@",str3,[str3 class]);//0x600002a13480 -- __NSCFString
    
[str3 release];
[str2 release];
[str1 release];

打印:
MemoryManagement[30966:1563960] 0x8bbfbc3f19412f76 -- NSTaggedPointerString
MemoryManagement[30966:1563960] 0x8bbfbc3f19412f76 -- NSTaggedPointerString
MemoryManagement[30966:1563960] 0x6000016a03c0 -- __NSCFString

發(fā)現(xiàn):str1和str2指向同一個(gè)地方,而str3不是

memoryManagerment_10.png
NSMutableString *str1 = [[NSMutableString alloc]initWithString:@"test"];
NSLog(@"%p -- %@",str1,[str1 class]);//0x600002d16dc0 -- __NSCFString
    
NSString *str2 = [str1 copy];
NSLog(@"%p -- %@",str2,[str2 class]);//0xef964e52ba42afec -- NSTaggedPointerString

NSString *str3 = [str2 mutableCopy];
NSLog(@"%p -- %@",str3,[str3 class]);//0x600002d16cd0 -- __NSCFString
    
[str3 release];
[str2 release];
[str1 release];

打印:
MemoryManagement[31096:1571416] 0x600002d16dc0 -- __NSCFString
MemoryManagement[31096:1571416] 0xef964e52ba42afec -- NSTaggedPointerString
MemoryManagement[31096:1571416] 0x600002d16cd0 -- __NSCFString
memoryManagerment_11.png

copy和mutableCopy

memoryManagerment_12.png

@property (copy, nonatomic) NSMutableArray *data; //有問題,講一個(gè)可變的數(shù)組copy成了不可變的了


友情鏈接:

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,460評(píng)論 6 538
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,067評(píng)論 3 423
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,467評(píng)論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,468評(píng)論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,184評(píng)論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,582評(píng)論 1 325
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,616評(píng)論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,794評(píng)論 0 289
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,343評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,096評(píng)論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,291評(píng)論 1 371
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,863評(píng)論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,513評(píng)論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,941評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,190評(píng)論 1 291
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,026評(píng)論 3 396
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,253評(píng)論 2 375