iOS底层原理(五):内存管理

2020-07-29  本文已影响0人  冰风v落叶
前言
我们借助几道面试题,来探究一下iOS的内存管理
一、使用CADisplayLink、NSTimer有什么注意点?

需要注意两个地方:小心循环引用不准时

1. 小心循环引用

class ViewController: UIViewController {
    var displayLink: CADisplayLink!
    var timer: Timer!
    override func viewDidLoad() {
        super.viewDidLoad()
        displayLink = CADisplayLink(target: self, selector: #selector(testDisplayLink))
        displayLink.add(to: RunLoop.current, forMode: RunLoop.Mode.default)

        timer = Timer.init(timeInterval: 1, target: self, selector: #selector(testTimer), userInfo: nil, repeats: true)
        RunLoop.current.add(timer, forMode: RunLoop.Mode.default)
    }
}
class TFTProxy1: NSObject{
    weak var target: AnyObject?
    override init() {
        super.init()
    }
    convenience init(target: AnyObject?) {
        self.init()
        self.target = target
    }
    override func forwardingTarget(for aSelector: Selector!) -> Any? {
        return target
    }
}

使用的时候,这样写:
let proxy = TFTProxy1(target: self)
displayLink = CADisplayLink(target: proxy, selector: #selector(testDisplayLink))
displayLink.add(to: RunLoop.current, forMode: RunLoop.Mode.default)
@interface TFTProxy2 ()
@property (nonatomic,weak) id target;
@end

@implementation TFTProxy2
+ (instancetype)proxyWithTarget:(id)target{
    TFTProxy2 * proxy = [TFTProxy2 alloc];
    proxy.target = target;
    return proxy;
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel{
    return [self.target methodSignatureForSelector:sel];
}
- (void)forwardInvocation:(NSInvocation *)invocation{
    [invocation invokeWithTarget:self.target];
}
@end
let proxy = TFTProxy(target: self)
//let proxy = TFTProxy2(target: self)
timer1 = Timer.init(timeInterval: 1, target: proxy, selector: #selector(testTimer), userInfo: nil, repeats: true)
RunLoop.current.add(timer1, forMode: RunLoop.Mode.default)

timer2 = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true, block: { [weak self] (timer) in
     self?.testTimer()
})

2. 小心不准时

CADisplayLinkTimer都需要依赖RunLoop,如果RunLoop的任务过于繁重,那么就会造成不准时,所以想准时的话,我们应该使用GCD的定时器,如下所示,我们对GCD的定时器做了封装:

class TFTimer: NSObject{
    
    static var timers_ =  Dictionary<String,DispatchSourceTimer>()
    static let semaphore_ = DispatchSemaphore(value: 1)
    /*
     描述:执行某个定时任务
     参数:task-任务闭包
          start-任务开始时间,单位是秒
          intercval-定时器间隔,单位是秒
          isReapeat-是否重复执行任务
          isAsync-任务是否异步执行,true在全局队列中,false在主队列中
     */
    class func executeTask(task: @escaping () -> Void, start: DispatchTimeInterval, interval: DispatchTimeInterval, isRepeat: Bool, isAsync: Bool) -> String?{
        
        let queue = isAsync ? DispatchQueue.global() : DispatchQueue.main
        let timer = DispatchSource.makeTimerSource(queue: queue)
        
        timer.setEventHandler {
            task()
        }
        let deadline = DispatchTime.now() + start
        if isRepeat {
            timer.schedule(deadline: deadline, repeating: interval)
        }else{
            timer.schedule(deadline: deadline, repeating: .never)
        }
        timer.resume()
        
        semaphore_.wait()
        let taskID = "\(timers_.count)"
        timers_[taskID] = timer
        semaphore_.signal()
        return taskID
    }
    
    class func executeTask(target: NSObject?, selector: Selector?, start: DispatchTimeInterval, interval: DispatchTimeInterval, isRepeat: Bool, isAsync: Bool) -> String?{
        guard let target = target, let selector = selector else { return nil}
        let taskID = self.executeTask(task: {
            if target.responds(to: selector){
                target.perform(selector)
            }
        }, start: start, interval: interval, isRepeat: isRepeat, isAsync: isAsync)
        return taskID
    }
    
    /*
     取消某个定时任务
     */
    class func cancelTask(taskID: String?){
        guard let taskID = taskID , taskID.count > 0 else {
            return
        }
        semaphore_.wait()
        let timer = timers_[taskID]
        if let timer = timer{
            timer.cancel()
            timers_.removeValue(forKey: taskID)
        }
        semaphore_.signal()
    }
}
二、介绍下内存的几大区域?

iOS的虚拟内存地址由低至高,可分为:代码段、数据段、堆、栈、内核区,如下所示:

iOS内存的几大区域
三、讲一下你对iOS内存管理的理解
先声明此私有函数
extern void _objc_autoreleasePoolPrint(void);

然后就可以调用了,系统会自动帮我们找到此函数的实现
_objc_autoreleasePoolPrint();
四、ARC都帮我们做了什么?
ARC是LLVM编译器和RunLoop相互协作的一个结果,帮我们自动进行内存管理(如下所示),并且处理了弱引用(对象销毁时,指向这个对象的弱引用,都会被置为nil)
@property (nonatomic,assign) NSInteger age;

- (void)setAge:(NSInteger)age{
    _age = age;
}
- (NSInteger)age{
    return _age;
}
@property (nonatomic,retain) NSObject *status;

- (void)setStatus:(NSObject *)status{
    if (_status != status){
        //如果新对象和旧对象不相同,就先让旧对象的引用计数-1
        [_status release];
        //然后把新对象的引用计数+1,在赋值给_status
        _status = [status retain];
    }
}

- (NSObject *)status{
    return _status;
}
@property (nonatomic,copy) NSString *name;

- (void)setName:(NSString *)name{
    if (_name != name){
        //如果新对象和旧对象不相同,就先让旧对象的引用计数-1
        [_name release];
        //然后把新对象的引用计数+1,在赋值给_status
      此处会使用copy产生一个不可变的对象,这就是为什么NSMutableArray等可变类型不能使用copy的根本原因!!!
        _name = [name copy];
    }
}

- (NSString *)name{
    return _name;
}
五、思考下面两段代码能发生什么事情?有什么区别
Thread 3: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)
- (void)setName:(NSString *)name{
    if (_name != name){
        [_name release];
        _name = [name copy];
    }
}
let queue = DispatchQueue(label: "串行队列")
for i in 0..<10000{
    queue.async {
          self.name = "ajselfjalskfja;sfja;skjf;lasjfl;a\(i)"
    }
}
semaphore = DispatchSemaphore(value: 1)
for i in 0..<10000{
     DispatchQueue.global().async {
          self.semaphore.wait()
          self.name = "ajselfjalskfja;sfja;skjf;lasjfl;a\(i)"
          self.semaphore.signal()
     }
}
判断是否为Tagged Pointer
#if TARGET_OS_OSX &&__x86_64__
#   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;
}
六、weak指针的实现原理

为了处理weak指针Runtime专门维护了一个hash表用于存储指向某个对象的所有weak指针,这个hash表的Key是所指对象的地址,Value是指向这个对象的weak指针组成的数组;

当这个对象销毁时,就会从hash表中取出指向这个对象的弱引用置为nil,并且从hash表中删除

七、autorelease对象在什么时机会被调用release?

在RunLoop休眠之前或者离开的时候调用release

iOS在RunLoop注册了两个Observer观察者:

八、方法里有局部对象,出了方法以后会立即释放吗?

一般情况下ARC是通过release管理内存的,所以出了作用域会立即释放;

但是,如果ARC是通过autorelease管理内存的,就是在RunLoop休眠之前或者RunLoop退出的时候进行的释放

上一篇 下一篇

猜你喜欢

热点阅读