iOS 面试宝典

作者: Inlight先森 | 来源:发表于2017-11-09 19:15 被阅读83次

    tip1.可变集合类 和 不可变集合类的 copy 和 mutablecopy 有什么区别?

    1. 对于可变与不可变对象:区别在于是否需要在创建对象的时候确定并固定对象的内存地址的大小与位置。
    • 不可变对象在初始化之后不能改变自己所储存的内容大小,也就是不可修改自己的内存地址的大小与位置;
    • 而可变对象则可在初始化之后通过自己的方法修改自己的内存地址的大小和位置。
    1. 对于深拷贝浅拷贝:区别在于是否对对象拷贝。
    • 浅拷贝:仅仅是对指向对象的指针的拷贝,先后两个指针指向同一个对象;
    • 深拷贝:是对对象和指针的双重拷贝,新指针指向新对象,旧指针指向旧对象。
    1. 对于copy和mutableCopy方法:
    • copy:
      • 对于不可变对象,copy相当于做了一次浅拷贝,仅仅拷贝指针,不拷贝对象;
      • 对于可变对象,copy是深拷贝,但是拷贝出来的对象类型为相应的不可变对象。
    • mutableCopy
      • 不管是可变还是不可变对象,统统都是深拷贝,返回的统统都是可变对象。

    tip2.@synthesize和@dynamic分别有什么作用?

    1. @property有两个对应的词,一个是 @synthesize,一个是 @dynamic。如果 @synthesize和 @dynamic都没写,那么默认的就是@syntheszie var = _var;
    2. @synthesize 的语义是如果你没有手动实现 setter 方法和 getter 方法,那么编译器会自动为你加上这两个方法。
    3. @dynamic 告诉编译器:属性的 setter 与 getter 方法由用户自己实现,不自动生成。(当然对于 readonly 的属性只需提供 getter 即可)。假如一个属性被声明为 @dynamic var,然后你没有提供 @setter方法和 @getter 方法,编译的时候没问题,但是当程序运行到 instance.var = someVar,由于缺 setter 方法会导致程序崩溃;或者当运行到 someVar = var 时,由于缺 getter 方法同样会导致崩溃。编译时没问题,运行时才执行相应的方法,这就是所谓的动态绑定。

    tip3.实现description方法能取到什么效果?

    1. NSLog(@"%@", objectA);这会自动调用objectA的description方法来输出ObjectA的描述信息

    2. description方法默认返回对象的描述信息(默认实现是返回类名和对象的内存地址)

    3. description方法是基类NSObject 所带的方法,因为其默认实现是返回类名和对象的内存地址, 这样的话,使用NSLog输出OC对象,意义就不是很大,因为我们并不关心对象的内存地址,比较关心的是对象内部的一些成变量的值。因此,会经常重写description方法,覆盖description方法的默认实现

    4. 重写description方法有一些坑要注意

    • 千万不要在description方法中用以下方式使用%@和self,这样造成循坏调用,导致系统会发生运行时错误。
    - (NSString *)description {
        return [NSString stringWithFormat:@"%@", self];
    }
    
    • 当该方法使用NSLog("%@",self) 时候, 系统做了相关的优化,循坏调用几次后就会自动退出

    tip4.为什么把NSTimer对象以NSDefaultRunLoopMode(kCFRunLoopDefaultMode)添加到主运行循环以后,滑动scrollview的时候NSTimer却不动了?

    RunLoop只能运行在一种mode下,如果要换mode,当前的loop也需要停下重启成新的。利用这个机制,ScrollView滚动过程中NSDefaultRunLoopMode(kCFRunLoopDefaultMode)的mode会切换到UITrackingRunLoopMode来保证ScrollView的流畅滑动:只能在NSDefaultRunLoopMode模式下处理的事件会影响scrllView的滑动。

    如果我们把一个NSTimer对象以NSDefaultRunLoopMode(kCFRunLoopDefaultMode)添加到主运行循环中的时候, ScrollView滚动过程中会因为mode的切换,而导致NSTimer将不再被调度。

    同时因为mode还是可定制的,所以:Timer计时会被scrollView的滑动影响的问题可以通过将timer添加到NSRunLoopCommonModes(kCFRunLoopCommonModes)来解决。

    //将timer添加到NSDefaultRunLoopMode中
    [NSTimer scheduledTimerWithTimeInterval:1.0
         target:self
         selector:@selector(timerTick:)
         userInfo:nil
         repeats:YES];
    //然后再添加到NSRunLoopCommonModes里
    NSTimer *timer = [NSTimer timerWithTimeInterval:1.0
         target:self
         selector:@selector(timerTick:)
         userInfo:nil
         repeats:YES];
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
    

    tip5.苹果是如何实现autoreleasepool的?

    autoreleasepool以栈的数据结构实现,主要通过下列三个函数完成.

    • objc_autoreleasepoolPush(压入)
    • objc_autoreleasepoolPop(弹出)
    • objc_autorelease(释放内部)

    看函数名就可以知道,对autorelease分别执行push、pop操作。销毁对象时执行release操作

    tip6.Block内存管理分析

    1. 根据block在内存中的位置,block被分为三种类型:
    • NSGlobalBlock是位于全局区的block,它是设置在程序的数据区域(.data区)中。
    • NSStackBlock是位于栈区,超出变量作用域,栈上的Block以及 __block变量都被销毁。
    • NSMallocBlock是位于堆区,在变量作用域结束时不受影响。

    注意:在 ARC 开启的情况下,将只会有 NSConcreteGlobalBlock 和 NSConcreteMallocBlock 类型的 block。NSStackBlock会被系统自动copy一份到堆区。

    1. block什么时候在全局区,什么时候在栈上,什么时候又在堆上呢?
    • NSGlobalBlock
      • 定义全局变量的地方有block语法时
    void(^block)(void) = ^ { NSLog(@"Global Block");};
    int main() {
    }
    
    • block语法的表达式中没有使用应截获的自动变量时
    int(^block)(int count) = ^(int count) {
            return count;
        };
     block(2);
    

    总结:配置在全局区的block,从变量作用域外也可以通过指针安全地使用

    • NSStackBlock
    NSInteger i = 10; 
    block = ^{ 
         NSLog(@"%ld", i); 
    };
    

    总结:设置在栈上的block,如果其作用域结束,该block就被销毁。

    • NSMallocBlock
    - (void)viewDidLoad {
        [super viewDidLoad];  
        NSInteger i = 10;
        self.block = { 
            NSLog(@"%ld", i);
        };
    }
    

    总结:即使变量作用域结束,堆上的Block依然存在

    tip7.block里面使用self会造成循环引用吗?

    很显然答案不都是,有些情况下是可以直接使用self的:

    • block存在于静态方法中,虽然block对self强引用着,但是self却不持有这个静态方法,所以完全可以在block内部使用self
    [UIView animateWithDuration:0.3 animations:^{
            [self.tableview reloadData];
        }];
    
    • 当block不是self的属性时,self并不持有这个block,所以也不存在循环引用
    void(^block)(void) = ^() {
            NSLog(@"%@", self);
        };
    block();
    

    tip8.iOS程序中的内存分配

    • 栈区(stack)

      • 栈是向低地址扩展的数据结构,是一块连续的内存的区域
      • 由编译器自动分配并释放
      • 存放函数的参数值,局部变量等
      • 优点是快速高效,缺点时有限制,数据不灵活
      • 后进先出(last in first out)
      • 只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢出
    • 堆区(heap)

      • 堆是向高地址扩展的数据结构,是不连续的内存区域
      • 由程序员分配和释放,如果程序员不释放,程序结束时,可能会由操作系统回收 ,比如在ios 中 alloc 都是存放在堆中。
      • 优点是灵活方便,数据适应面广泛,但是效率有一定降低。
      • 申请方式:
        1. 首先应该知道操作系统有一个记录空闲内存地址的链表。
        2. 当系统收到程序的申请时,会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点的空间分配给程序。
        3. 由于找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的那部分重新放入空闲链表中
    • 全局区(静态区) (static)

      • 全局变量和静态变量的存储是放在一起的,初始化的全局变量和静态变量存放在一块区域(data区),未初始化的全局变量和静态变量在相邻的另一块区域(bss区)
      • 程序结束后由系统释放。
    • 文字常量区

      • 存放常量字符串,程序结束后由系统释放
    • 程序代码区

      • 存放函数的二进制代码
    #import "ViewController.h"
    
    int age = 24;//全局初始化区(data区)
    NSString *name;//全局未初始化区(BSS区)
    static NSString *sName = @"Dely";//全局(静态初始化)区
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
    
    int a = 10;  全局初始化区
      char *p;  全局未初始化区
    
     main{
        int b; 栈区
        char s[] = "abc" 栈
        char *p1; 栈 
        char *p2 = "123456";  123456\0在常量区,p2在栈上。
        NSString *number = @"abc"; //abc在堆区,number在栈上。
        NSMutableArray *array = [NSMutableArray arrayWithCapacity:1];//分配而来的8字节的区域就在堆中,array在栈中,指向堆区的地址
        NSInteger total = [self getTotalNumber:1 number2:1];
    }
    
    - (NSInteger)getTotalNumber:(NSInteger)number1 number2:(NSInteger)number2{
        return number1 + number2;//number1和number2 栈区
    }
    

    tip9.NSCache优于NSDictionary的几点?

    1. NSCache胜过NSDictionary之处在于,当系统资源将要耗尽时,它可以自动删减缓存。如果采用普通的字典,那么就要自己编写挂钩,在系统发出“低内存”通知时手工删减缓存。

    2. NSCache并不会“拷贝”键,而是会“保留”它。此行为用NSDictionary也可以实现,然而需要编写相当复杂的代码。NSCache对象不拷贝键的原因在于:很多时候,键都是不支持拷贝操作的对象来充当的。因此,NSCache不会自动拷贝键,所以说,在键不支持拷贝操作的情况下,该类用起来比字典更方便。

    3. NSCache是线程安全的,而NSDictionary不是。

    tip10.NStimer准吗?谈谈你的看法?如果不准该怎样实现一个精确的NSTimer?

    不准原因:

    1. NSTimer加在main runloop中,模式是NSDefaultRunLoopMode,main负责所有主线程事件,例如UI界面的操作,复杂的运算,这样在同一个runloop中timer就会产生阻塞。

    2. 模式的改变。主线程的 RunLoop 里有两个预置的 Mode:kCFRunLoopDefaultMode(是 App 平时所处的状态) 和 UITrackingRunLoopMode(追踪 ScrollView 滑动时的状态)。当你创建一个 Timer 并加到 DefaultMode 时,Timer 会得到重复回调,但此时滑动一个 ScrollView 时,RunLoop 会将 mode 切换为 TrackingRunLoopMode,这时 Timer 就不会被回调,并且也不会影响到滑动操作。所以也会影响到 NSTimer 不准。

    如何实现一个精确的 NSTimer:

    1. 在主线程中进行 NSTimer 操作,但是将 NSTimer 实例加到 main runloop 的 NSRunLoopCommonModes 中。避免被复杂运算操作或者UI界面刷新所干扰。
    self.timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(showTime) userInfo:nil repeats:YES];
    
    [[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
    
    1. 在子线程中进行NSTimer的操作,再在主线程中修改UI界面显示操作结果.
    __block TestViewController *blockSelf = self;
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        blockSelf->_timer=[NSTimer scheduledTimerWithTimeInterval:1.0
                                                target:blockSelf
                                            selector:@selector(caculateLeftTimeForTomorrow)
                                              userInfo:nil
                                               repeats:YES] ;
        [[NSRunLoop currentRunLoop] addTimer:blockSelf->_timer forMode:NSDefaultRunLoopMode];
        [[NSRunLoop currentRunLoop] run];
    });
    

    相关文章

      网友评论

        本文标题:iOS 面试宝典

        本文链接:https://www.haomeiwen.com/subject/agbbpxtx.html