iOS面试题
阅读原文时间:2021年04月20日阅读:1

描述一下MVC

MVC是一种架构模式,使用此架构模式的好处在于:业务逻辑、数据、视图的分离。做到高内聚低耦合

摘自《Cocoa Design Patterns》:

MVC的主要目的是解除模型子系统和视图之间的耦合,这样它们就可以独立变化。

摘自《设计模式》:

MVC通过建立一个”订购/通知”协议来分离视图和模型。视图必须保证它的显示正确地反映了模型的状态。一旦模型的数据发生变化,模型将通知有关的视图,每个模型的数据发生变化,模型将通知有关的视图,每个视图相应地得到刷新自己的机会。这种方法可以让你为一个模型提供不同的多个视图表现形式,也能够为一个模型创建新的视图而无须重写模型。

参考唐巧的技术博客-
《被误解的 MVC 和被神化的 MVVM》

  • M-Model-模型

  • V-View-视图

  • C-Controller-控制器

  • Controller与Model之间,Controller一侧是虚线,代表Controller可以直接访问Model;Model一侧是白色实线,代表不能直接访问Controller,但模型发生改变,Controller想要知道,这个时候可以采用观察者模式,间接地去通知Controller

  • Controller与View之间,Controller一侧是虚线,代表Controller可以直接访问View(如果有Storyboard,需要通过Outlet访问);View一侧是白色实线,代表不能直接访问Controller。如果View接收到用户交互时,可以通过目标选择器(Target、Selector/Action)进行回调Controller;如果View需要向Controller获取数据(cell个数、给你一个indexPath,你给我一个我要显示的Cell、TextField的文字发生变化,TextField接收到了一个”Return”按键)时,通过Delegate或DataSource向Controller回调,注:此图当中少了一个block!

  • View与Model之间,是一条黄色双实线,代表两者之间互相禁止访问或间接回调

典型案例:
Controller向Model当中取数据,显示到View(它的子类也可以,例如UIButton)上,button添加一个目标选择器,当button按下时,向Controller进行回调,Controller再去更新Model,当Model发生变化时,Controller会收到观察者消息或通知消息,再去更新View显示样式

延伸面试题:什么是高内聚低耦合?
延伸面试题:iOS中常用的设计模式?
延伸面试题:如果简历当中或自我介绍当中,或谈话过程当中,出现MVVM,会提问两者的区别。有些面试者会回答MVC架构模式当中的Controller层,会显得越来越臃肿,而MVVM则不会。此种回答会扣分,因为MVC架构模式只有当模块的职责划分不明确,才会造成Controller的臃肿,也就意味着:面试者的技术能力不足。所以MVVM这一概念一定要真的用过才能回答!
陷阱:MVC是一种架构模式,注意不是设计模式
陷阱:Notification和KVO有什么区别?在什么情况下使用Notification,在什么情况下使用KVO?

iOS如何使用多线程

回答思路:

  • NSThread
  • NSOperation
    • NSOperationQueue
    • NSInvocationOperation
    • NSBlockOperation
  • GCD
    • dispatch_async
    • dispatch_sync
    • dispatch_group
    • dispatch_semaphore
    • dispatch_after
    • dispatch_once
    • dispatch_barrier
    • dispatch_source

延伸面试题:NSOperation与GCD的区别

NSOperation与GCD的区别

  • GCD
    • 优点
      • 使用起来较为简单
    • 缺点
      • GCD任务只要被启动,理论上是不能够被停止的
      • GCD因为是使用block编写的,如果线程的控制情况比较复杂,block嵌套就会变多,所以这种情况下代码可读性就会变差
  • NSOperation
    • 优点
      • 可以像NSThread一样,发送cancel方法。内部根据当前线程的cancel状态做相应的处理
      • 可以根据NSOperationQueue的最大变发量,来控制线程的并发(异步)或串行(同步)
      • 相比GCD,代码可读性较好
    • 缺点
      • 因为NSOperation需要依赖于NSOperationQueue(任务队列),此任务队列对象需要额外的代码进行管理、分配、释放

延伸面试题:NSOperation被发送了cancel方法,会立即地停止吗?
延伸面试题:如果不会立即停止,那还有什么意义呢?

NSOperation被发送了cancel方法,会立即停止吗?如果会,那还有什么意义?

不会立即停止,只是向NSOperation对象发送了一个”我要取消你”的消息。NSOperation内部需要在合适的时机,判断自己是否接收到”取消”这种消息,做一些扫尾的操作,再自己把自己停止掉。

这种机制类似于用户双击了Home键,将App划出了屏幕,杀掉了这个App,但是App还是会在被杀掉之前接收到-(void)applicationWillTerminate消息,做数据的保存工作

C语言中auto,register,static,const,volatile的区别

参考zhige博客
C语言中auto,register,static,const,volatile的区别

iOS中常用的设计模式

工厂模式
单例模式
观察者模式
适配器模式(即delegate)
原型模式(即copy)
命令模式(即target-selector)

延伸面试题1:工厂模式与单例模式的区别
延伸面试题2:观察者模式与适配器模式的区别
延伸面试题3:KVC和KVO之间的联系
延伸面试题4:KVO和NSNotificationCenter的区别
延伸面试题5:单例模式怎样实现
陷阱:一些面试者会回答MVC、KVO、KVC、Notification,注意MVC是架构模式,KVO和Notification是观察者模式在iOS当中的实现方式,KVC是键值观察编码,也不是设计模式。另,设计模式是在所有的程序语言全部适用的,既然全部适用,KVO也会在Java当中可以使用吗?

单例模式怎么实现

方式1:

+ (instancetype)sharedInstance {
    static id sharedClient = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedClient = [[self alloc] init];
    });
    return sharedClient;
}

方式2:

+ (instancetype)sharedInstance {
    static id sharedClient = nil;
    @synchronized (self) {
        sharedClient = [[self alloc] init];
    }
    return sharedClient;
}

方式3:

+ (instancetype)sharedInstance {
    static id sharedClient = nil;
    if (sharedClient == nil) {
        sharedClient = [[self alloc] init];
    }
    return sharedClient;
}

什么是高内聚低耦合

摘自百度百科:

内聚就是一个模块内各个元素彼此结合的紧密程度,高内聚就是一个模块内各个元素彼此结合的紧密程度高。

所谓高内聚是指一个软件模块是由相关性很强的代码组成,只负责一项任务,也就是常说的单一责任原则。

耦合:一个软件结构内不同模块之间互连程度的度量(耦合性也叫块间联系。指软件系统结构中各模块间相互联系紧密程度的一种度量。模块之间联系越紧密,其耦合性就越强,模块的独立性则越差,模块间耦合的高低取决于模块间接口的复杂性,调用的方式以及传递的信息。)

对于低耦合,粗浅的理解是:一个完整的系统,模块与模块之间,尽可能的使其独立存在。也就是说,让每个模块,尽可能的独立完成某个特定的子功能。模块与模块之间的接口,尽量的少而简单。如果某两个模块间的关系比较复杂的话,最好首先考虑进一步的模块划分。这样有利于修改和组合。

说得比较文绉绉的,我们来接点地气
比如一个ViewController,它又要负责UITableView的delegate、dataSource,又要负责UITextField的delegate,又要负责网络请求,又要负责网络的响应及Model的更新….这种设计,就是内聚性较差的体现。高内聚这个概念,与”面向对象设计的五大原则”(SOLID)当中的单一职责原则(SRP)意义相近,即”一个类,只干一件事”

耦合又是什么?即是模块与模块之间的依赖。举之前的例子,在ViewController当中,又要依赖View,又要依赖AFNetworking,又要依赖Model…这种设计,就是耦合性较高的体现。低耦合这个概念,与”面向对象设计的五大原则”(SOLID)当中的依赖倒置原则(DIP)与接口隔离原则(SIP)意义相近。

NSNotificationCenter和KVO有什么区别?

两者的共同点:都是观察者模式
两者的不同点:KVO是键值观察,NSNotificationCenter是通知中心。在适用场景上,KVO必须有一个键,它才能观察值的变化。而NSNotificationCenter不需要

如果要观察的内容是跟模型相关的,使用KVO。反之,则使用NSNotificationCenter

陷阱:一些面试者会回答Notification是一对一,KVO是多对多

什么是线程?什么是进程?二者有什么区别和联系?

进程是一个“执行中的程序”,在iOS编程中,是不支持多进程设计的。比如在Mac系统下,登录了一个QQ号,再按下快捷键Command+N,又可以再登录一个QQ号,这时,QQ这个应用程序就开启了两个进程。但在iOS当中不能够双开。
在一个进程当中,可以有多个线程,但至少有一个主线程在一直运行,它们可以利用所在进程所拥有的资源。比如多个线程都可以去操作Model…
多进程设计中,一个进程crash,不会影响其他的进程。
多线程设计中,一个线程crash,会影响其他的线程。
如果进程crash了,它的所有线程也会crash。

延伸面试题:在iOS当中怎样设计线程?
延伸面试题:线程同步问题

iOS持久化存储都有哪几种方式?

  • NSUserDefault
  • plist
  • 归档/解归档
  • 数据库
    • SQLite
      • FMDB
    • CoreData
      • MagicalRecord

延伸面试题:SQLite与CoreData的区别?
延伸面试题:CoreData与MagicalRecord的区别?
延伸面试题:如果在简历当中或项目描述当中体现了FMDB或SQLite,可能会问到多表查询的SQL语句。
延伸面试题:CoreData如何做表结构升级?
延伸面试题:CoreData使用的过程当中,有没有遇到过死锁,如何解决?
延伸面试题:使用数据库有何心得?
陷阱:如果涉及到多表查询的SQL,恰巧面试者不知道。会给面试官留下之前项目太简单的印象,进而对面试者的技术能力产生怀疑

SQLite与CoreData的区别?

CoreData即是对SQLite的封装,在CoreData当中,操作数据库的代码即是操作对象的代码

CoreData与MagicalRecord的区别

MagicalRecord是对CoreData的封装,使用起来比CoreData更简单。并且其内部考虑到了多线程操作数据库产生死锁的问题

使用数据库有何心得?有没有遇到过死锁?如何解决?

操作数据库不要过于频繁,一般情况下在程序启动时,将数据库当中的所有数据,读入到Model当中;程序关闭时,将Model当中的所有数据,存储到数据库当中。并且引入MagicalRecord

多线程同步和异步的区别?iOS中如何实现多线程同步?

多线程同步,即是排队效果,类似于交通路口的信号灯,后边的车要直行,但是前面的车在等待左转信号灯,后面的灯要等到前面的车走了之后才能继续走。不然的话就会继续等待
多线程异步,即是”并发执行”。多用于会造成阻塞的任务,如:网络请求如果使用同步,主线程会卡死,页面不再刷新,不再响应用户交互…如果使用异步,主线程继续处理页面刷新,响应用户交互,而另外一个线程则等待网络响应

多线程同步可采用以下方式:
dispatch_semaphore
原子操作OSAtomic
锁NSLock
事件NSCondition
http://blog.csdn.net/lifengzhong/article/details/7487505

延伸面试题:属性关键字当中的nonatomic与atomic有什么区别?

堆和栈的区别

堆和栈的概念请自行百度,一般情况下不会出现错误。在此仅说明它们之间的区别。
例如以下代码:

- (void)method {
    NSObject *obj1 = [[NSObject alloc] init];
    NSObject *obj2 = [[NSObject alloc] init];
    NSInteger inta = 0;
    NSInteger intb = 0;
}

堆区内容:
按照内存管理的法则,有alloc,就必须要有release。但此段代码没有做release并不是真的不需要release,而是ARC在函数结尾自动去release了。其中obj1和obj2两个对象被存放到了堆区,堆区的内容需要程序员自己手动去分配(alloc)和释放(release)

栈区内容:
由编译器自动分配释放,inta和intb两个整型数值肯定是要有一个内存区域去存储的,这一块内存不需要程序员去分配,也不需要程序员去释放

iOS有多重继承吗?没有的话用什么代替?

没有多重继承,OC只能够单继承。如果想要做多重继承的话,可以使用协议(protocol)代替
延伸面试题:因为涉及到多重继承,所以如果简历里如果体现出其他的编程语言,可能会问到(很多面试者会在简历里提到C++、Java,这时面试极有可能问到多重继承、虚函数、纯虚函数、抽象类之类的概念)

动态绑定、运行时、编译时

如有下列代码:
NSString *str = [[NSData alloc] init]; str = [str stringByAppendingString:@"abc"]
会不会顺利通过编译?如果编译通过,运行过程当中会发生什么?

答:会顺利通过编译,但编译器会报警告。在编译时,str是NSString类型,但在运行时,会创建一个NSData对象,并将str动态绑定成NSData,既然str实际上是一个NSData对象,而NSData对象又没有实现”stringByAppeningString”方法,所以会发生闪退。

#import、#include和@class有什么区别?@class代表着什么?如果没有#import关键字,你会怎么做?

#import和#include都是导入头文件,但#import在编译时能够保证”只导入一次”,但#include做不到。所以如果没有#import关键字,只能使用#include关键字,可以在被导入的头文件当中添加如下代码:

#ifndef _HEADER_H // 此处的_HEADER_H应该根据实际的头文件名取得
#define _HEADER_H
// 实际内容
#endif

@class 是为了防止交叉编译,一般优秀的设计,会尽量避免两个头文件相互导入,但有时确实避免不了,两个头文件相互导入,编译器就会报错。比如:

Teacher.h:

#import "Student.h"
@interface Teacher : NSObject
@property (strong, nonatomic) NSMutableArray<Student *> *students;
@end

Student.h:

#import "Teacher.h"
@interface Student : NSobject
@property (strong, nonatomic) NSMutableArray<Teacher *> *teachers;
@end

此时@class关键字就可以派上用场了,将头文件当中的#import剪切替换到.m文件当中,并在头文件当中的相同位置,添加@class关键字就可解决

property关键字中,assign, weak, retain, strong, copy, atomic, nonatomic, readonly, readwrite分别是什么意思?

如果是C语言基础类型,使用assign(CGFloat, long, int, short, NSInteger, NSUInteger)

如果是代理,使用weak(MRC使用assign)

如果强引用了,需要将一个strong改成weak;

如果对象需要保存一份副本,不想别人的修改了,自己也跟着修改,那么使用copy

剩下的,都使用strong(MRC当中使用retain)

原子操作:线程安全,相当于在getter和settger两个函数当中,添加了线程锁

nonatomic
非原子操作-非线程安全

atomic
原子操作-线程安全

self.imageView.money = money;

strong、retain
strong是在ARC当中用的,retain是在MRC当中用的
引用计数自动+1

- (void)setImage:(UIImage *)image {
    if (_image != image) {
        [image retain];
        [_image release];
        _image = image;
    }
}

- (void)dealloc {
    [_image release];
}

assign

- (void)setUnknown:(Unknown )var {
    _var = var;
}

weak
大体上跟assign一样,不一样的:如果在指向的对象被销毁了,那么指针会指向nil

copy

- (void)setString:(NSString *)string {
    if (_string != string) {
        [_string release]
        _string = [string copy];
    }
}

readonly
在别的类使用的过程当中,不能够调用setter方法
在自己的类中,如果要使用,可以在.m文件当中,自己再写一个匿名类别,写上与.h文中相同的property内容(删掉readonly关键字),就可以使用了

readwrite
unsafe
safe

属性二次定义

问:对于其他的类,属性是只读的,但对于自己的类,是可读可写的,如何做?
答:直接上代码

Template.h:

@inteface Template : NSObject
@property (strong, nonatomic, readonly) NSObject *prop;
@end`

Template.m:

@interface Template()
@property (strong, nonatomic) NSObject *prop;
@end

@implementation Template
// ...
@end

iOS捕捉屏幕

+[UIImage imageFromView:]

事件响应者链的概念

响应者链表示一系列的响应者对象。事件被交由第一响应者对象处理,如果第一响应者不处理,事件被沿着响应者链向上传递,交给下一个响应者(next responder)。一般来说,第一响应者是个视力对象或者其子类对象,当其被触摸后事件被交由它处理,如果它不处理,事件就会被传递给它的视力控制器对象(如果存在),然后是它的父视图(superview)对象(如果存在),以此类推,走到顶层视图。接下来会沿着顶层视图(top view)到窗口(UIWindow对象)再到程序(UIApplication对象)。如果整个过程都没有响应这个事件,该事件就被丢弃。一般情况下,在响应者链中只要由对象处理事件,事件就停止传递。但有时候可以在视图的响应方法中根据一些条件判断来判断是否需要继续传递事件。

const与指针

const int *cnp = 0;
int const *ncp = 0;
const * int cpn = 0;
int * const npc = 0;
int const * const ncpc = 0;
const int * const cnpc = 0;

以上6行分别是什么意思?

类别和类扩展有什么区别?

类扩展(Extension)是匿名的类别(Category),即括号当中没有任何字符

类别和继承有什么区别?

继承是以子类化的方式,对原有类进行扩展
类别是在原有类之上,对其进行扩展

如果想要让所有的UIView及其子类都支持一个方法。比如UIButton、UILabel、UIImagView都支持一个方法-(void)customMethod,应该使用类别还是继承?

答:类别

类别可以扩展属性吗?

理论上不可以,但实际上可以通过runtime方法添加。属性即是一个settger方法和一个getter方法。所以在头文件当中,直接添加属性声明就可以:

ExtensionObject.h:

#import <Foundation/Foundation.h>
@interface NSObject(ExtensionObject)
@property (retain, nonatomic) NSObject *extensionProp;
@end

但是在setter和getter方法中,必须要与一个成员变量相关联,因为类别不能够添加成员变量。所以只能曲线救国了:

ExtensionObject.m:

#import "ExtensionObject.h"
#import <objc/runtime.h>

static const void *extensionPropKey = &extensionPropKey;

@implementation NSObject(CustomAnimate)

- (NSObject *)extensionProp {
    return objc_getAssociatedObject(self, extensionPropKey);
}

- (void)setExtensionProp:(NSObject *)extensionProp{
    objc_setAssociatedObject(self, extensionPropKey, extensionProp, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

@end

写出一个ARC版本的工厂方法,和与之对应的MRC版本的工厂方法

// ARC版本
+ (instancetype)factory {
    id factory = [[self alloc] init];
    return factory;
}

// MRC版本
+ (instancetype)factory {
    id factory = [[self alloc] init];
    return [factory autorelease];
}

内存管理基本原则,关于内存管理,有什么自己的看法或心得?

基本原则:谁使用(alloc, retain, copy),谁释放(release、autorelease)。
心得:一律使用self.prop_name方式,不使用成员变量方式。可以避免掉绝大多数内存泄露问题。

深复制与浅复制的区别。要想获得一个对象的复制版本,代码需要怎么实现?

浅复制: 只复制对象本身,不对里面的属性复制。
深复制:不仅复制对象本身,对象持有的属性对象与做复制。

被复制的类,需要实现NSCopying协议

有如下两个类,分别写下深复制版本与浅复制版本

  • 深复制版本

Student.h:

#import <Foundation/Foundation.h>
@class Score;
@interface Student : NSObject<NSCopying>
@property (strong, nonatomic) Score *score;
@end

Student.m:

#import "Student.h"
#import "Score.h"

@implementation Student

- (id)copyWithZone:(NSZone *)zone {
    Student *result = [[Student allocWithZone:zone] init];
    result.score = [self.score copy]; // 属性也要复制
    return result;
}

@end

Score.h:

#import <Foundation/Foundation.h>
@interface Score : NSObject<NSCopying>
@property (copy, nonatomic) NSString *subjectName;
@property (assign, nonatomic) NSInteger score;
@end

Score.m:

#import "Score.h"
@implementation Score

- (id)copyWithZone:(NSZone *)zone {
    Score *result = [[Score allocWithZone:zone] init];
    result.subjectName = [self.subjectName copy];
    result.score = self.score;
    return result;
}
@end
  • 浅复制版本

Student.h:

#import <Foundation/Foundation.h>
@class Score;
@interface Student : NSObject<NSCopying>
@property (strong, nonatomic) Score *score;
@end

Student.m:

#import "Student.h"
#import "Score.h"

@implementation Student

- (id)copyWithZone:(NSZone *)zone {
    Student *result = [[Student allocWithZone:zone] init];
    result.score = self.score; // 此处不同
    return result;
}

@end

Score.h:

#import <Foundation/Foundation.h>
@interface Score : NSObject
@property (copy, nonatomic) NSString *subjectName;
@property (assign, nonatomic) NSInteger score;
@end

Score.m:

#import "Score.h"
@implementation Score
@end

ARC是否会存在内存泄露

会有内存泄露的,
1 如果不使用property自动生成的settger和getter方法,而使用成员变量。一样会出现内存泄露问题
2 如果涉及到对象复用(UITableViewCell或UICollectionViewCell),添加了观察者,就可能同时观察多个被观察者。当Model刷新时会错乱

描述复用机制

什么是自动释放池?

@synthesize与@dynamic的区别

什么是动态绑定?

延伸面试题:C++中想要做动态绑定,怎么做?

block与协议有什么区别?你比较喜欢用哪一种

在使用block时有什么注意事项?

阅读以下代码,说明区别

@interface Template()
@property (strong, nonatomic) NSObject *prop;
@end

@implementation Template
- (void)method {
    // 解释一下两行的区别
    self.prop = [[NSObject alloc] init];
    _prop = [[NSObject alloc] init];
}
@end

答:
self.prop = [[NSObject alloc] init];会调用编译器自动生成的setter方法,其内部会考虑到引用计数;
_prop = [[NSObject alloc] init];不会调用编译器自动 生成的setter方法,不会考虑到引用计数,所以有可能会产生内存泄露

手机扫一扫

移动阅读更方便

阿里云服务器
腾讯云服务器
七牛云服务器

你可能感兴趣的文章