美文网首页将来跳槽用iOS面试总结
iOS面试题总结 (2019年6月)(3)

iOS面试题总结 (2019年6月)(3)

作者: 爱恨的潮汐 | 来源:发表于2019-06-10 20:12 被阅读30次
    1、属性默认关键词是那些,NSString常用什么修饰,为什么?

    对应基本数据类型默认关键字是:atomic, readwrite, assign。
    对于普通的 Objective-C 对象默认关键字是:atomic, readwrite, strong。

    NSString用copy修饰。为了安全,一般情况下,我们都不希望字串的值跟着字符串变化,所以我们一般用copy来设置string的属性。

    @property (strong,nonatomic)  NSString *rStr;
    @property (copy, nonatomic)  NSString *cStr;
    
    - (void)test{
        NSMutableString *mStr = [NSMutableString stringWithFormat:@"abc"];
        self.rStr = mStr;
        self.cStr = mStr;
        NSLog(@"mStr:%p,%p",  mStr,&mStr);
        NSLog(@"strongStr:%p,%p", _rStr, &_rStr);
        NSLog(@"copyStr:%p,%p",   _cStr, &_cStr);       
    }
    

    假如,mStr对象的地址为0x11,也就是0x11是@“abc”的首地址,mStr变量自身在内存中的地址为0x123;
    当把mStr赋值给strong的rStr时,rStr对象的地址为0x11,rStr变量自身在内存中的地址为0x124;rStr与mStr指向同样的地址,他们指向的是同一个对象@“abc”,这个对象的地址为0x11,所以他们的值是一样的。
    当把mStr赋值给copy的cStr时,cStr对象的地址为0x22,cStr变量自身在内存中的地址0x125;cStr与mStr指向的地址是不一样的,他们指向的是不同的对象,所以copy是深复制,一个新的对象,这个对象的地址为0x22,值为@“abc”。

    如果现在改变mStr的值:
    //注意mStr如果这里是不可变字符串,那么这里无法改变,是浅拷贝。会崩溃

       [mStr appendString:@"de"];        
       NSLog(@"strongStr:%@",  _rStr);        
       NSLog(@"copyStr:%@",    _cStr);
    

    结果:
    使用strong的字串rStr的值:@"abcde",
    而使用copy的字串cStr的值:@"abc",

    如果是不可变字符串

      NSString * str = @"abc";
        self.rStr = str;
        self.cStr = str;
        str = @"66666666";
        NSLog(@"%@===%@",self.rStr,self.cStr);//打印结果都是abc
    
    2、nullable、nonnull 、NS_ASSUME_NONNULL_BEGIN 和 NS_ASSUME_NONNULL_END 的含义和用途?

    __nullable指代对象可以为NULL或者为NIL
    __nonnull指代对象不能为null
    当我们不遵循这一规则时,编译器就会给出警告。
    事实上,在任何可以使用const关键字的地方都可以使用__nullable和__nonnull,不过这两个关键字仅限于使用在指针类型上。而在方法的声明中,我们还可以使用不带下划线的nullable和nonnull,如下所示:

    - (nullable id)itemWithName:(NSString * nonnull)name
    

    在属性声明中,也增加了两个相应的特性,因此上例中的items属性可以如下声明:

    @property (nonatomic, copy, nonnull) NSArray * items;
    

    当然也可以用以下这种方式:

    @property (nonatomic, copy) NSArray * __nonnull items;
    

    推荐使用nonnull这种方式,这样可以让属性声明看起来更清晰。

    如果需要每个属性或每个方法都去指定nonnull和nullable,是一件非常繁琐的事。苹果为了减轻我们的工作量,专门提供了两个宏:NS_ASSUME_NONNULL_BEGIN和NS_ASSUME_NONNULL_END。在这两个宏之间的代码,所有简单指针对象都被假定为nonnull,因此我们只需要去指定那些nullable的指针。如下代码所示:

    NS_ASSUME_NONNULL_BEGIN
    @interface TestNullabilityClass ()
    @property (nonatomic, copy) NSArray * items;
    - (id)itemWithName:(nullable NSString *)name;
    @end
    NS_ASSUME_NONNULL_END
    

    本题参考

    3、BAD_ACCESS在什么情况下出现?

    访问了悬垂指针,比如对一个已经释放的对象执行了release、访问已经释放对象的成员变量或者发消息。 死循环。

    4、以下代码输出什么?

    - (void)deadLockCase1 {
        NSLog(@"1"); // 任务1
        dispatch_sync(dispatch_get_main_queue(), ^{
            NSLog(@"2"); // 任务2
        });
        NSLog(@"3"); // 任务3
    }
    
    控制台输出:1 ,后面就崩溃了。
    

    5、isKindOfClass和isMemberOfClass的区别?selector的作用?

    相同点:
    都是NSObject的比较Class的方法.
    不同点:
    isKindOfClass:确定一个对象是否是一个类的成员,或者是派生自该类的成员。或者是继承自某类。
    isMemberOfClass:确定一个对象是否是当前类的成员.

    selector:通过方法名,获取在内存中的函数的入口地址。

    6、使用NSOperationQueue的addOperationWithBlock要考虑循环引用吗,为什么?
    7、为什么标准头文件都有类似以下的结构?
    #ifndef __INCvxWorksh 
    #define __INCvxWorksh  
    #ifdef __cplusplus 
    
    extern"C"{ 
    #endif  
    /*...*/
    #ifdef __cplusplus 
    } 
    #endif  
    #endif /* __INCvxWorksh */
    

    答案:

    #ifndef __INCvxWorksh
    #define __INCvxWorksh
    #endif 
    这一段是用来防止头文件重复引用,vs可以使用#pragma once。但是推荐使用宏定义来防止重复包含,其跨平台,兼容性好。 
    

    如果是C++文件,以C的方式编译,并执行{}内的指令。其目的是为了兼容C代码,常出现在动态链接库的代码中。

    #ifdef _cplusplus  //这句表示如果是c++文件
    extern"C"{  //用extern "C"把一段代码包起来
    #endif
    #ifdef _cplusplus
    }
    #endif
    

    8、HTTP七层协议

    TCP协议对应于传输层
    网络七层协议由下往上分别为物理层、数据链路层、网络层、传输层、会话层、表示层和应用层。其中物理层、数据链路层和网络层通常被称作媒体层,是网络工程师所研究的对象;传输层、会话层、表示层和应用层则被称作主机层,是用户所面向和关心的内容。

    HTTP协议对应于应用层,TCP协议对应于传输层,IP协议对应于网络层,HTTP协议是基于TCP连接的,三者本质上没有可比性。 TCP/IP是传输层协议,主要解决数据如何在网络中传输;而HTTP是应用层协议,主要解决如何包装数据。Socket是应用层与TCP/IP协议族通信的中间软件抽象层,是它的一组接口。

    本题参考

    9、Objective-C 消息发送与转发机制原理

    消息发送和转发流程可以概括为:
    消息发送(Messaging)是 Runtime 通过 selector 快速查找 IMP 的过程,有了函数指针就可以执行对应的方法实现;

    另外:(Objective-C动态性的根源在方法的调用是通过message来实现的,一次发送message的过程就是一次方法的调用过程。发送message只需要指定对象和SEL,Runtime的objc_msgSend会根据在信息在对象isa指针指向的Class中寻找该SEL对应的IMP,从而完成方法的调用。)

    消息转发(Message Forwarding)是在查找 IMP 失败后执行一系列转发流程的慢速通道,如果不作转发处理,则会打日志和抛出异常。

    消息查找过程IMP
    (1)缓存查找
    (2)继续在类的继承体系中查找

    10、底层解析weak的实现原理?

    weak 实现原理的概括
    Runtime维护了一个weak表,用于存储指向某个对象的所有weak指针。weak表其实是一个hash(哈希)表,Key是所指对象的地址,Value是weak指针的地址(这个地址的值是所指对象指针的地址)数组。
    weak 的实现原理可以概括一下三步:
    1、初始化时:runtime会调用objc_initWeak函数,初始化一个新的weak指针指向对象的地址。
    2、添加引用时:objc_initWeak函数会调用 objc_storeWeak() 函数, objc_storeWeak() 的作用是更新指针指向,创建对应的弱引用表。
    3、释放时,调用clearDeallocating函数。clearDeallocating函数首先根据对象地址获取所有weak指针地址的数组,然后遍历这个数组把其中的数据设为nil,最后把这个entry从weak表中删除,最后清理对象的记录。

    参考

    11、Block 底层原理总结,有几种类型的Block,Block的循环引用原理?

    (1)Block可以简单总结:
    block本质上也是一个OC对象,它内部也有个isa指针;
    block是封装了函数调用以及函数调用环境的OC对象.

    (2)Block 有三种类型:
    NSGlobalBlock 全局区的Block
    NSStackBlock 栈区的Block
    NSMallocBlock 堆区的Block
    (3)Block的循环引用原理?
    对象、变量、Block的相互持有

    循环引用原理

    那么如何解决这个问题呢?
    通常我们ARC环境下面的解决办法是通过__weak指针来解决这个问题,通过上面讲的Block里面的变量是通过访问的外部变量是否是strong或weak指针来进行内部对象进行相应修饰的,所以如果访问的外部对象是weak指针时,他们的引用关系就会如下图:

    解决

    参考

    12、YTCar *car 实现对象car的手动内存管理模式下的set方法

    //是一个不断去掉旧值赋新值的过程
    - (void)setCar:(YTCar *)car{   
        if (_car != car) {  //判断新旧值是否相等
            //release掉旧值
            [_car release]; 
            //retain新值
            _car = [car retain];  
        }   
    }
    

    13、NSThread、 GCD、NSOperation的区别?

    1)NSThread

    优点:NSThread 比其他两个轻量级。
    缺点:需要自己管理线程的生命周期,线程同步。
    线程同步对数据的加锁会有一定的系统开销。

    3)GCD

    替代NSThread等线程,自动管理生命周期。

    2)NSOperation

    优点:不需要关心线程管理, 数据同步的事情,可以把精力放在自己需要执行的操作上。
    基于GCD底层,使用起来更加面向对象。比GCD多了一些简单实用的功能。

    多线程

    14、autorelease 自动释放池的释放时机,autoreleasepool的实现原理?

    (1)runloop就是iOS中的消息循环机制,当一个runloop结束时系统才会一次性清理掉被autorelease处理过的对象,其实本质上说是在本次runloop迭代结束时清理掉被本次迭代期间被放到autorelease pool中的对象的。至于何时runloop结束并没有固定的duration!
    (2)当一个autorelease pool被drain 的时候,会对pool里的每一个对象发送一个release消息;
    (3)每一个线程(包括主线程)都有一个AutoreleasePool栈。当一个新的池子被创建的时候,push进栈,当池子被释放内存时,pop出栈。对象调用autorelease方法进入栈顶池子中。当线程结束的时候,会自动地销毁所有跟它有关联的池子。

    使用容器的block版本的枚举器时,内部会自动添加一个AutoreleasePool:

    [array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
    // 这里被一个局部@autoreleasepool包围着
    }];
    

    在普通for循环和for in 循环中没有,当for循环中便利产生大量autorelease变量时,就需要手动加局部AutoreleasePool。

    autoreleasepool的实现原理?
    AutoreleasePool(自动释放池)是OC中的一种内存自动回收机制,它可以延迟加入AutoreleasePool中的变量release的时机。在正常情况下,创建的变量会在超出其作用域的时候release,但是如果将变量加入AutoreleasePool,那么release将延迟执行。

    AutoreleasePool底层实现原理
    autoreleasepool的实现原理

    15、OC中如何判断两个对象完全相同?

    (1)isEqual和hash

    - (BOOL)isEqual:(id)object {
      if (self == object) {
          return YES;
      }
      if (![self class] == [object class]) {
          return NO;
      }
    //此处还需判断对象中的各个属性是否相同
    ...
    //若所有属性都相同则返回
      return YES;
    }
    

    首先判断两个指针是否相等,若相等,则均指向同一对象,所以受测的对象也必定相等。接下来判断两对象所属的类,若属于同一类。

    hash:比较得时候最好是先计算其哈希码,再进行比较。

    参考:OC中 判断2个对象相等(isEqual和hash)

    (2)==
    比较的是两个对象的指针本身,有时候返回的结果并不是我们想要的结果。
    (3)isEqualToString
    用于判断两个字符串是否相等的方法,当然还有isEqualToArray: isEqualToDictionary:

    (4)NSSet中可变类的等同性比较【数组去重】
    OC 判断两个对象是否相等

    16、retain一个NSTime类型成员变量会有什么问题?

    使用NSTimer可能会碰到循环引用的问题。特别是当类具有NSTimer类型的成员变量,并且需要反复执行计时任务时。

    _timer = [NSTimer scheduledTimerWithTimeInterval:5.0
                                              target:self
                                            selector:@selector(startCounting) userInfo:nil
                                             repeats:YES];
    

    类有一个成员变量_timer,给_timer设置的target为这个类本身。这样类保留_timer,_timer又保留了这个类,就会出现循环引用的问题,最后导致类无法正确释放。

    解决这个问题的方式也很简单,当类的使用者能够确定不需要使用这个计时器时,就调用

    [_timer invalidate];
    _timer = nil;
    

    17、Category的使用及原理?与Runtime有关吗?

    Category的使用及原理

    相关文章

      网友评论

        本文标题:iOS面试题总结 (2019年6月)(3)

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