将来跳槽用iOS面试总结

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

2019-06-10  本文已影响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的使用及原理

上一篇下一篇

猜你喜欢

热点阅读