iOS分享世界

iOS内存管理注意点

2019-05-29  本文已影响34人  天明天

1. 定时器使用CADisplayLinkNSTimer

CADisplayLink是一个能让我们以和屏幕刷新率相同的频率将内容画到屏幕上的定时器。我们在应用中创建一个新的 CADisplayLink 对象,把它添加到一个RunLoop中,并给它提供一个 targetselector 在屏幕刷新的时候调用。

使用方式:


@property (strong, nonatomic) CADisplayLink *link;
// 保证调用频率和屏幕的刷帧频率一致,60FPS
 self.link = [CADisplayLink displayLinkWithTarget:self selector:@selector(linkTest)];
    [self.link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];

每秒钟调用60次linkTest方法。此时的self.linktarget(self) 产生强引用,而控制器本身对link已经有引用,所以此处会产生循环引用。 调用 [self.link invalidate]方法定时器无法取消, 自身self也无法得到释放。同样的 NSTimer也存在此方法

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

    [[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];

也同样会产生循环引用。
解决方案:

(1) 更换定时器方法:使用Block来代替具体的方法实现。
//    __weak typeof(self) weakSelf = self;
//    self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
//        [weakSelf timerTest];
//    }];

(2)使用中间对象弱引用Target,打破原来的强引用循环。 屏幕快照 2019-05-29 下午12.02.49.png

ViewController 引用着timertimer引用着objectobject引用targettarget弱引用ViewController,打破循环引用圈。具体的做法为:

#import <Foundation/Foundation.h>

@interface MJProxy : NSProxy
+ (instancetype)proxyWithTarget:(id)target;
@property (weak, nonatomic) id target;
@end

.m
#import "MJProxy.h"

@implementation MJProxy

+ (instancetype)proxyWithTarget:(id)target
{
    // NSProxy对象不需要调用init,因为它本来就没有init方法
    MJProxy *proxy = [MJProxy alloc];
    proxy.target = target;
    return proxy;
}

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

- (void)forwardInvocation:(NSInvocation *)invocation
{
    [invocation invokeWithTarget:self.target];
}
@end

然后调用中间对象

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

// link 的解决方案:
self.link = [CADisplayLink displayLinkWithTarget:[MJProxy proxyWithTarget:self] selector:@selector(linkTest)];
    [self.link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];

类:NSproxy 也是基类 跟 NSobject 相同级别的,让你的 中间类继承自 NSproxy ,作为中间类,NSproxy 没有 init 方法。NSproxy 存在的意义 就是用于中间消息转发的。继承自NSporxy 的类 调用方法 回先搜索自身的消息,没有的话则 直接进入到 消息转发阶段 调用methodSignatureForSelector: 转发消息,注意跟继承自NSobject类的区别。 此时消息转发会更加方便。

2. NSTimer的缺点以及GCD定时器封装。

NSTimer是依赖于RunLoop的,而有的时候RunLoop需要处理繁重的任务,会导致定时器不准时。RunLoop 循环一圈会计算时间,如果单圈比较耗时则有可能导致超时,所有NSTimer并不准时。

解决方法:使用GCD创建定时器,它不依赖于RunLoop
@property (strong, nonatomic) dispatch_source_t timer;
//最后一个参数 决定该定时器是在哪个线程调用
self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());

NSTimeInterval start = 2.0f;
    NSTimeInterval invaler = 1.0f;
    //设置时间,第一个参数:几秒后开始,第二个参数:间隔,第三个参数:误差
    dispatch_source_set_timer(timer,dispatch_time(DISPATCH_TIME_NOW, start * NSEC_PER_SEC) , invaler * NSEC_PER_SEC, 0);
 // 设置回调
    dispatch_source_set_event_handler(timer, ^{
        
        NSLog(@"调用定时器");
    });
// 启动定时器
    dispatch_resume(timer);
//取消定时器方法:dispatch_source_cancel(timer)

注意 这里的`dispatch_source_create` 并不需要释放

最后进行封装,我把疯转的GCD定时器放在这里了,使用超级简单:

/*
start:开始时间,interval:间隔,repeats: 是否重复,async : 同步还是异步
返回一个 定时器标识
*/
+ (NSString *)execTask:(void(^)(void))task
           start:(NSTimeInterval)start
        interval:(NSTimeInterval)interval
         repeats:(BOOL)repeats
           async:(BOOL)async;
//取消定时器,参数是:执行方法返回的标识
+ (void)cancelTask:(NSString *)name;

3. Tagged Pointer 技术

如何判断一个指针是否为Tagged Pointer?
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"];
            // 解锁
        });
    }
    
    dispatch_queue_t queue1 = dispatch_get_global_queue(0, 0);

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

解答:1. 第一个 会发生 crash ,因为同时有多条线程 同时访问 [ _name release]; 方法,会形成坏内存访问。把属性修饰符改成 atomic 线程安全。或者加线程锁。

@property (assign, nonatomic) os_unfair_lock moneyLock;
// 初始化锁
self.ticketLock = OS_UNFAIR_LOCK_INIT;

    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    for (int i = 0; i < 1000; i++) {
        dispatch_async(queue, ^{
         os_unfair_lock_lock(&_ticketLock);
            // 加锁
            self.name = [NSString stringWithFormat:@"abcdefghijk"];
            // 解锁
       os_unfair_lock_unlock(&_ticketLock);
        });
    }

第二个2 : 程序不会崩溃,原因: Tagged Pointer 的存在,str 使用Tagged Pointer的形式存储。意味着 self.name 不会调用 OC对象,不会调用OC对象的 setter 方法,也不会调用release 方法所以不会crash。

可以通过这种方法来 看它的类型
NSString *str1 = [NSString stringWithFormat:@"abcdefghijk"];
NSString *str2 = [NSString stringWithFormat:@"123abc"];
NSLog(@"%@ %@", [str1 class], [str2 class]);

4. MRC 技术

ios 采用引用计数的方式来管理OC对象的内存。

内存管理的经验总结
MRC下的setter与getter方法
- (void)setDog:(MJDog *)dog
{
    if (_dog != dog) {
        [_dog release];
        _dog = [dog retain];
    }
}

- (MJDog *)dog
{
    return _dog;
}

ARC 下 属性修饰符为assign生成的getter 跟 setter 方法为:

//- (void)setAge:(int)age
//{
//    _age = age;
//}
//
//- (int)age
//{
//    return _age;
//}

这个需要知道。

5. Copy 操作

Copy : 不可变 拷贝,产生不可变副本
MutableCopy: 产生可变副本。注意: 不管你拷贝的是可变的还是不可变的对象,只要是调用copy那么返回的就是不可变的对象,只要是multableCopy 返回的 就是 不可变的对象。

深拷贝与浅拷贝 总结

深拷贝 :内容拷贝
浅拷贝:指针拷贝

深浅拷贝.png
属性修饰符 Copy

MRC 下的实现是:

- (void)setDog:(MJDog *)dog
{
    if (_dog != dog) {
        [_dog release];
        _dog = [dog copy];//这里是copy 操作
    }
}

- (MJDog *)dog
{
    return _dog;
}

注意:@property (copy,nonatomic)NSMutableArray *dataArr;// 不推荐这样写,因为,对 [dataArr copy] 以后会生成不可变的数组,无法在修改dataArr内容。所有这里要使用 strong 修饰!字符串 都一般 copy 修饰:保证字符串是不可变的,不论外面传入是 可变 还是 不可变的,都保证自己的是 不可变的。

自定义 Copy 协议

OC 自定义类 实现 copy 操作:
(1)遵守 协议 <NSCopying> 协议
(2) 实现 -(id) copyWithZone: 方法

- (id)copyWithZone:(NSZone *)zone
{
    MyPerson *person = [[MyPerson allocWithZone:zone] init];
    person.age = self.age;//添加原有属性
    person.weight = self.weight;
    return person;
}

那么 这个自定义的类就可以 使用copy 操作了
Myperson *per = [Myperson alloc] init];
Myperson *per2 = [per copy];

6. weak底层的实现原理

https://www.jianshu.com/p/f331bd5ce8f8

7.AutoreleasePool的原理和实现

https://www.jianshu.com/p/1b66c4d47cd7

上一篇 下一篇

猜你喜欢

热点阅读