内存管理

2021-04-13  本文已影响0人  bytebytebyte
1.理解NSProxy
ViewController *vc = [[ViewController alloc] init];
MJProxy:NSProxy
MJProxy *proxy1 = [MJProxy proxyWithTarget:vc];  
MJProxy1:NSObject
MJProxy1 *proxy2 = [MJProxy1 proxyWithTarget:vc];
NSLog(@"%d %d",[proxy1 isKindOfClass:[ViewController class]], [proxy2 isKindOfClass:[ViewController class]]); 1 0
解释:
//[proxy1 isKindOfClass:[ViewController class]]不继承NSObject不走消息发送,直接走了消息转发
调用对象时vc
//[proxy2 isKindOfClass:[ViewController class]])调用对象是NSObject

@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
@implementation MJProxy1
+ (instancetype)proxyWithTarget:(id)target{
    MJProxy1 *proxy = [[MJProxy1 alloc] init];
    proxy.target = target;
    return proxy;
}
- (id)forwardingTargetForSelector:(SEL)aSelector{
    return self.target;
}
@end

2.GCD不会产生循环引用,为什么?
diapatch_asyn^{ self }
因为只是产生了单方面的循环引用,GCD强引用了self,self没有强引用GCD。
Masonry的make同理不会。

3.如何解决NSTimer循环应用?
(1)使用GCDTimer,其跟系统内核有关,不会产生循环引用。
考虑 信号量,字典[@"1",timer]
(2)使用中间类 弱引用当前对象self和NSTimer
(3)使用NSProxy防止循环引用
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:[MJProxy proxyWithTarget:self] selector:@selector(timerTest) userInfo:nil repeats:YES];

4.CADisplayLink、NSTimer应注意什么?
会导致循环引用,都依赖NSRunloop,当CPU需要处理大量事件时会会不精准。

5.内存布局是什么样子的?栈堆正好高低 就记住了
        保留
低    代码段  编译后的代码
        数据段   字符串常量
                      已初始化数据:已初始化全局变量、静态变量
                      未初始化数据:未初始化全局变量、静态变量
         堆↓  alloc、malloc、calloc 分配内存空间越来越小  特点:程序员自己管理
高      栈↑  局部变量  分配内存空间越来越大 特点:系统管理
          内核区

6.TaggedPointer 技术:直接从指针提取数据,目的节省内存空间,使用效率的优化。
使用TaggedPointer 之前
number=0x10001  →  地址:0x10001 存储值:10
使用TaggedPointer 之后
number=0xb000a1  b和1是tag pointer中的a是值

当TaggedPointer技术存不下太大的值时又恢复成了number对象存值。
objc_msgSend能够识别Tagged Pointer,如NSNumber的IntValue方法,从中直接提取数据
NSNumber、NSDate、NSString用

7.ARC的本质?是转化成MRC
@property (copy) NSString *name;
- (void)setName:(NSString *)name {
            if (_name != name) {
                          [_name release];先干掉旧的 再赋值新的
                          _name = [name copy];//_name = [name retain]; strong修饰变成retain其他不变
             }
}

这两段代码会发生什么事?如何处理?有什么区别?
@property (strong, nonatomic) NSString *name;
//    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
//    for (int i = 0; i < 1000; i++) {
//        dispatch_async(queue, ^{
//            // 加锁
//5            self.name = [NSString stringWithFormat:@"abcdefghijk"];
//            // 解锁
//        });
//    }
发生坏内存访问,因为第5句代码self.name时OC对象,在多线程下 [_name release]执行了2次即_name被释放了2次,第2次已经释放了又去访问。应加锁。
    
//    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
//    for (int i = 0; i < 1000; i++) {
//        dispatch_async(queue, ^{
// 10           self.name = [NSString stringWithFormat:@"abc"];
//        });
//    }
不会发生坏内存访问。因为第10句self.name是taggedPointer不是OC对象,是指针赋值,没有调用setter方法,也不存在release。

8.理解MRC
4中情况 归根结底是第一种情况。
(1)一个alloc对应一个release或者autorelease,release在不用的时候release,怕忘记直接alloc后autorelease即可。
(2)一个@property (retain,nonatomic) NSMutableArray *data对应dealloc里的一个self.data = nil。
(3)一个self.data = [NSMutableArray array]和new对应dealloc里的一个self.data = nil,因为内部已经autorelease过了。
(4)非属性遵循(1)即可

+ (instencetype)array {
        return [[[self alloc]init] autorelease];
}
@property (retain,nonatomic) NSMutableArray *data;

viewdidload{
        self.data = [NSMutableArray array];
         Person *person = [[[Person alloc]init]autorelease];
}

dealloc {
          self.data = nil;
}

9.理解copy .一个copy相当于调用一个retain需要对应一个release,把copy后的对象当成一个全新的对象。
拷贝的目的:产生一个副本对象,跟源对象互不影响,修改了源对象,不会影响副本对象,修改了副本对象,不会影响源对象。

mian {
NSString *str1 = [[NSString alloc] initWithFormat:@"test11111111111111111"];
NSString *str2 = [str1 copy]; //相当于调用retain 浅拷贝,指针拷贝,没有产生新对象。
NSString *str3 = [str1 mutableCopy]; //深拷贝:内容拷贝,有产生新对象。
[str3 release];
[str2 release];
[str1 release];
}
alloc、new、copy、mutableCopy返回了一个对象,对应一个release和autorelease.

mutableCopy拷贝出来的一定是可变对象。
copy拷贝出来的一定是不可变对象。
mutableCopy一定是深拷贝。
copy原对象是不可变对象是浅拷贝,copy原对象是可变对象是深拷贝。

10.copy的实现原理?
@property (nonatomic,copy) NSArray *array; //copy->右边一定是不可变
//@property (nonatomic,copy) NSMutableArray *array //是错误的 会崩溃
- (void)setData:(NSArray *)data {
           if (_data != data) {
    `                [ _data release];
                     _data = [data copy];
            }
}

NSString为什么要用copy?可以从UI层面解释。
(copy) text
NSMutableString *str = [NSMutableString @'123'];
textField.text = str;
防止修改str的值textField.text 的值会发生变化。

一般情况下:
@property (nonatomic,strong) NSArray *array;
@property (nonatomic,copy) NSString *str;

11.自定义copy 实现原理
遵循NSCopying协议,实现- (id)copyWithZone:(nullable NSZone *)zone 方法。

.h
@interface Person : NSObject<NSCopying>
.m
- (id)copyWithZone:(nullable NSZone *)zone {
    Person *person = [[Person alloc]init];
    person.age = self.age;
    person.weight = self.weight;
    return person;
}

12.ARC帮我们做了什么?是llvm编译器和Runtime系统协作的结果。
__strong MJPerson *person1;
    __weak MJPerson *person2;
    __unsafe_unretained MJPerson *person3;
    NSLog(@"111");
    {
        MJPerson *person = [[MJPerson alloc] init];
        person3 = person;
    }
    NSLog(@"222 - %@", person3);

大括号结束llvm帮我们生成release代码。
程序运行时大括号结束person对象释放,Runtime清除当前对象person的弱引用指针。

13.weak指针实现原理?
当大口号结束时,person对象释放,会调用dealloc->rootdealloc系列调用,根据当前对象指针找到其在弱引用表中的索引,再将指向当前对象的所有弱引用指针全部清除。

14.extern如何使用?在类外部声明私有变量、方法,即可访问。
extern void person_test(void);
main() {
        person_test();
}

Person.h  person_test不在这里声明
Person.m
void person_test(){
    NSLog(@"person - test");
}

15.autoreleasepool实现原理?从结构、push、autorelease、pop四方面去回答
底层数据结构:_AtAutoreleasePool、AutoreleasePoolPage
AutoreleasePoolPage管理autorelease对象
class AutoreleasePoolPage {
      pthread_t const thread;
      id *next; //指向下一个能存放autorelease对象的地址的内存区域
      AutoreleasePoolPage *const parent;
      AutoreleasePoolPage *child;
}

调用push方法会将一个POOL_BOUNDARY入栈,并且返回其存放的内存地址。
调用autorelease的对象都会入栈;
调用pop方法时传入一个POOL_BOUNDARY的内存地址,会从最后一个入栈的对象开始发送release消息,直到遇到这个POOL_BOUNDARY

16.autorelease对象在什么时机会被调用release?分2种情况。大括号结束,休眠前,退出runloop时候。
第1种情况
是由runloop来控制的,,它可能是在某次runloop循环中runloop休眠之前调用的。
viewdidload {
      Person *person = [[[Person alloc] init] autorelease];
}

第2种情况
viewdidload {
      @autoreleasepool{
                  Person *person = [[[Person alloc] init] autorelease];
        }//在这里释放
}

iOS在主线程的Runloop中注册了2个Observer
第1个Observer监听了kCFRunLoopEntry事件,会调用objc_autoreleasePoolPush()
第2个Observer
监听了kCFRunLoopBeforeWaiting事件,会调用objc_autoreleasePoolPop()、objc_autoreleasePoolPush()
监听了kCFRunLoopBeforeExit事件,会调用objc_autoreleasePoolPop()

14.方法里有局部对象,出了方法后会立即释放吗?2中情况。由编译器生成的release、autorelease决定。
viewdidload {
      Person *person = [[Person alloc] init];
}
(1)如果编译器 Person *person = [[Person alloc] init];在这里立刻生成了autorelease方法的话,可能是在某次runloop循环中runloop休眠之前释放的。
(2)如果是在方法结束之前生成了[person release]则是在方法结束后
























上一篇下一篇

猜你喜欢

热点阅读