iOS 面试题

设计模式是什么? 你知道哪些设计模式,并简要叙述?

设计模式是一种编码经验,就是用比较成熟的逻辑去处理某一种类型的事情。

  • MVC模式:Model View Control,把模型 视图 控制器 层进行解耦合编写。
  • MVVM模式:Model View ViewModel 把模型 视图 业务逻辑 层进行解耦和编写。
  • 单例模式:通过static关键词,声明全局变量。在整个进程运行期间只会被赋值一次。
  • 观察者模式:KVO是典型的通知模式,观察某个属性的状态,状态发生变化时通知观察者。
  • 委托模式:代理+协议的组合。实现1对1的反向传值操作。
  • 工厂模式:通过一个类方法,批量的根据已有模板生产对象。

MVC 和 MVVM 的区别

  • MVVM是对胖模型进行的拆分,其本质是给控制器减负,将一些弱业务逻辑放到VM中去处理。
  • MVC是一切设计的基础,所有新的设计模式都是基于MVC进行的改进。

frame 和 bounds 有什么不同?

frame指的是:该view在父view坐标系统中的位置和大小。(参照点是父view的坐标系统)
bounds指的是:该view在本身坐标系统中的位置和大小。(参照点是本身坐标系统)

Objective-C 中创建线程的方法是什么?如果在主线程中执行代码,方法是什么?如果想延时执行代码、方法又是什么?

线程创建有三种方法:

  • 使用NSThread创建
  • 使用GCD的dispatch
  • 使用子类化的NSOperation,然后将其加入NSOperationQueue;在主线程执行代码,方法是performSelectorOnMainThread,如果想延时执行代码可以performSelector:onThread:withObject:waitUntilDone:

Category(类别)、 Extension(扩展)和继承的区别

区别:

  • 分类有名字,类扩展没有分类名字,是一种特殊的分类。
  • 分类只能扩展方法(属性仅仅是声明,并没真正实现),类扩展可以扩展属性、成员变量和方法。
  • 继承可以增加,修改或者删除方法,并且可以增加属性。

如何对iOS设备进行性能测试?

  • Profile-> Instruments ->Time Profiler

开发项目时你是怎么检查内存泄露?

  • 静态分析 analyze。
  • instruments工具里面有个leak可以动态分析。

什么是block?

闭包(block):闭包就是获取其它函数局部变量的匿名函数。

  • block反向传值
  • 在控制器间传值可以使用代理或者block,使用block相对来说简洁。

block的注意点

  • 在block内部使用外部指针且会造成循环引用情况下,需要用__week修饰外部指针: __weak typeof(self) weakSelf = self;
  • 在block内部如果调用了延时函数还使用弱指针会取不到该指针,因为已经被销毁了,需要在block内部再将弱指针重新强引用一下。 __strong typeof(self) strongSelf = weakSelf;
  • 如果需要在block内部改变外部栈区变量的话,需要在用__block修饰外部变量。

你一般是怎么用Instruments的?

  • Time Profiler: 性能分析
  • Zombies:检查是否访问了僵尸对象,但是这个工具只能从上往下检查,不智能。
  • Allocations:用来检查内存,写算法的那批人也用这个来检查。
  • Leaks:检查内存,看是否有内存泄露。

iOS中常用的数据存储方式有哪些?

数据存储有四种方案:NSUserDefault、KeyChain、file、DB。
其中File有三种方式:plist、Archive(归档)
DB包括:SQLite、FMDB、CoreData

GCD 与 NSOperation 的区别:

GCD 和 NSOperation 都是用于实现多线程
GCD 基于C语言的底层API,GCD主要与block结合使用,代码简洁高效。
NSOperation 属于Objective-C类,是基于GCD更高一层的封装。复杂任务一般用NSOperation实现。

写出使用GCD方式从子线程回到主线程的方法代码

dispatch_sync(dispatch_get_main_queue(), ^{ });

如何用GCD同步若干个异步调用?(如根据若干个url异步加载多张图片,然后在都下载完成后合成一张整图)

// 使用Dispatch Group追加block到Global Group Queue,这些block如果全部执行完毕,就会执行Main Dispatch Queue中的结束处理的block。
 // 创建队列组 dispatch_group_t group = dispatch_group_create();
 // 获取全局并发队列 
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); 
dispatch_group_async(group, queue, ^{ /*加载图片1 */ }); 
dispatch_group_async(group, queue, ^{ /*加载图片2 */ }); 
dispatch_group_async(group, queue, ^{ /*加载图片3 */ });
// 当并发队列组中的任务执行完毕后才会执行这里的代码
dispatch_group_notify(group, dispatch_get_main_queue(), ^{         // 合并图片 });

什么是 RunLoop

从字面上讲就是运行循环,它内部就是do-while循环,在这个循环内部不断地处理各种任务。
一个线程对应一个RunLoop,基本作用就是保持程序的持续运行,处理app中的各种事件。通过runloop,有事运行,没事就休息,可以节省cpu资源,提高程序性能。

什么是 Runtime

Runtime又叫运行时,是一套底层的C语言API,其为iOS内部的核心之一,我们平时编写的OC代码,底层都是基于它来实现的。

Runtime实现的机制是什么,怎么用,一般用于干嘛?

  • 使用时需要导入的头文件
  • Runtime 运行时机制,它是一套C语言库。
  • 实际上我们编写的所有OC代码,最终都是转成了runtime库的东西。
    比如:

类转成了 Runtime 库里面的结构体等数据类型
方法转成了 Runtime 库里面的C语言函数
平时调方法都是转成了 objc_msgSend 函数(所以说OC有个消息发送机制)
OC是动态语言,每个方法在运行时会被动态转为消息发送,即:objc_msgSend(receiver, selector)。
[stu show]; 在objc动态编译时,会被转意为:objc_msgSend(stu, @selector(show));

  • 因此,可以说 Runtime 是OC的底层实现,是OC的幕后执行者。

有了Runtime库,能做什么事情呢?

Runtime库里面包含了跟类、成员变量、方法相关的API。
比如:

  • 获取类里面的所有成员变量。
  • 为类动态添加成员变量。
  • 动态改变类的方法实现。
  • 为类动态添加新的方法等。

因此,有了Runtime,想怎么改就怎么改。

什么是 TCP / UDP ?

  • TCP:传输控制协议。
    TCP 是面向连接的,建立连接需要经历三次握手,是可靠的传输层协议。
  • UDP:用户数据协议。
    UDP 是面向无连接的,数据传输是不可靠的,它只管发,不管收不收得到。 简单的说,TCP注重数据安全,而UDP数据传输快点,但安全性一般。

tableView的重用机制?

UITableView 通过重用单元格来达到节省内存的目的: 通过为每个单元格指定一个重用标识符,即指定了单元格的种类,当屏幕上的单元格滑出屏幕时,系统会把这个单元格添加到重用队列中,等待被重用,当有新单元格从屏幕外滑入屏幕内时,从重用队列中找看有没有可以重用的单元格,如果有,就拿过来用,如果没有就创建一个来使用。

你是怎么封装一个view的

  • 可以通过纯代码或者xib的方式来封装子控件
  • 建立一个跟view相关的模型,然后将模型数据传给view,通过模型上的数据给view的子控件赋值
/**  *  纯代码初始化控件时一定会走这个方法  */ 
- (instancetype)initWithFrame:(CGRect)frame
 {    
   if(self = [super initWithFrame:frame]) 
    {         
      [self setupUI];    
    }    
     return self; 
}  
/**  *  通过xib初始化控件时一定会走这个方法  */ 
- (id)initWithCoder:(NSCoder *)aDecoder {   
  if(self = [super initWithCoder:aDecoder]) {
         [self setupUI];    
   }   
  return self; 
}  
- (void)setupUI { 
    // 初始化代码 
}

第三方框架

AFNetworking 底层原理分析

AFNetworking主要是对NSURLSession和NSURLConnection(iOS9.0废弃)的封装,其中主要有以下类:

  • AFHTTPRequestOperationManager:内部封装的是 NSURLConnection, 负责发送网络请求, 使用最多的一个类。(3.0废弃)
  • AFHTTPSessionManager:内部封装是 NSURLSession, 负责发送网络请求,使用最多的一个类。
  • AFNetworkReachabilityManager:实时监测网络状态的工具类。当前的网络环境发生改变之后,这个工具类就可以检测到
  • AFSecurityPolicy:网络安全的工具类, 主要是针对 HTTPS 服务。
  • AFURLRequestSerialization:序列化工具类,基类。上传的数据转换成JSON格式 (AFJSONRequestSerializer).使用不多。
  • AFURLResponseSerialization:反序列化工具类;基类.使用比较多
  • AFJSONResponseSerializer; JSON解析器,默认的解析器.
  • AFHTTPResponseSerializer; 万能解析器; JSON和XML之外的数据类型,直接返回二进 制数据.对服务器返回的数据不做任何处理.
  • AFXMLParserResponseSerializer; XML解析器;

描述下SDWebImage里面给UIImageView加载图片的逻辑

SDWebImage 中为 UIImageView 提供了UIImageView+WebCache.h,
这个分类中有一个最常用的接口
sd_setImageWithURL:placeholderImage:会在真实图片出现前会先显示占位图片,当真实图片被加载出来后再替换占位图片。
加载图片的过程大致如下:

  • 首先会在 SDWebImageCache 中寻找图片是否有对应的缓存, 它会以url 作为数据的索引先在内存中寻找是否有对应的缓存
  • 如果缓存未找到就会利用通过MD5处理过的key来继续在磁盘中查询对应的数据, 如果找到了, 就会把磁盘中的数据加载到内存中,并将图片显示出来
  • 如果在内存和磁盘缓存中都没有找到,就会向远程服务器发送请求,开始下载图片
  • 下载后的图片会加入缓存中,并写入磁盘中
  • 整个获取图片的过程都是在子线程中执行,获取到图片后回到主线程将图片显示出来

SDWebImage原理: 调用类别的方法: 1. 从内存(字典)中找图片(当这个图片在本次使用程序的过程中已经被加载过),找到直接使用。 2. 从沙盒中找(当这个图片在之前使用程序的过程中被加载过),找到使用,缓存到内存中。 3. 从网络上获取,使用,缓存到内存,缓存到沙盒。

编码格式(优化细节)

在 Objective-C 中,enum 建议使用 NS_ENUM 和 NS_OPTIONS 宏来定义枚举类型。

//定义一个枚举(比较严密)
typedef NS_ENUM(NSInteger, BRUserGender) { 
    BRUserGenderUnknown,    // 未知     
    BRUserGenderMale,       // 男性 
    BRUserGenderFemale,      // 女性    
    BRUserGenderNeuter      // 无性 
};
@interface BRUser : NSObject 
@property (nonatomic, readonly, copy) NSString *name; 
@property (nonatomic, readonly, assign) NSUInteger age; 
@property (nonatomic, readonly, assign) BRUserGender gender; 
 - (instancetype)initWithName:(NSString *)name age:(NSUInteger)age gender:(BRUserGender)gender;  
@end  

避免使用C语言中的基本数据类型,建议使用 Foundation 数据类型

对应关系如下:
int -> NSInteger unsigned -> NSUInteger float -> CGFloat 动画时间 -> NSTimeInterval

如何实行cell的动态的行高

如果希望每条数据显示自身的行高,必须设置两个属性,

  • 预估行高
    设置预估行高 tableView.estimatedRowHeight = 200。
  • 自定义行高
    设置定义行高 tableView.estimatedRowHeight = UITableViewAutomaticDimension。

如果要让自定义行高有效,必须让容器视图有一个自下而上的约束。

用StoryBoard开发界面有什么弊端?如何避免?

使用简单逻辑页面的跳转是可以使用sb的,开发比较块。但是SB对于逻辑项目比较复杂的时候,开发起来比较慢。
不适合多人合作开发;也不利于版本的梗系和后期的维护。
使用sb在项目变异编译的时候,也都会直接加载到内存中,造成内存的浪费。 可以使用xib来代替,编辑复杂逻辑界面时候可以使用纯码编写。

Swift

类(class)和结构体(struct)有什么区别?

Swift 中,类是引用类型,结构体是值类型。值类型在传递和赋值时将进行复制,而引用类型则只会使用引用对象的一个"指向"。所以他们两者之间的区别就是两个类型的区别。

请说明并比较以下关键词:Open, Public, Internal, File-private, Private

Swift 有五个级别的访问控制权限,从高到底依次为比如 Open, Public, Internal, File-private, Private。
基本原则:高级别的变量不允许被定义为低级别变量的成员变量。
比如一个 private 的 class 中不能含有 public 的 String。反之,低级别的变量却可以定义在高级别的变量中。比如 public 的 class 中可以含有 private 的 Int。

  • Open 具备最高的访问权限。其修饰的类和方法可以在任意 Module 中被访问和重写;它是 Swift 3 中新添加的访问权限。
  • Public 的权限仅次于 Open。与 Open 唯一的区别在于它修饰的对象可以在任意 Module 中被访问,但不能重写。
  • Internal 是默认的权限。它表示只能在当前定义的 Module 中访问和重写,它可以被一个 Module 中的多个文件访问,但不可以被其他的 Module 中被访问。
  • File-private 也是 Swift 3 新添加的权限。其被修饰的对象只能在当前文件中被使用。例如它可以被一个文件中的 class,extension,struct 共同使用。
  • Private 是最低的访问权限。它的对象只能在定义的作用域内使用。离开了这个作用域,即使是同一个文件中的其他作用域,也无法访问。

请说明并比较以下关键词:strong, weak, unowned

Swift 的内存管理机制与 Objective-C一样为 ARC(Automatic Reference Counting)。
它的基本原理是,一个对象在没有任何强引用指向它时,其占用的内存会被回收。反之,只要有任何一个强引用指向该对象,它就会一直存在于内存中。

  • strong 代表着强引用,是默认属性。
    当一个对象被声明为 strong 时,就表示父层级对该对象有一个强引用的指向。此时该对象的引用计数会增加1。
  • weak 代表着弱引用。
    当对象被声明为 weak 时,父层级对此对象没有指向,该对象的引用计数不会增加1。它在对象释放后弱引用也随即消失。继续访问该对象,程序会得到 nil,不亏崩溃
  • unowned 与弱引用本质上一样。
    唯一不同的是,对象在释放后,依然有一个无效的引用指向对象,它不是 Optional 也不指向 nil。如果继续访问该对象,程序就会崩溃。
    加分回答
    weak 和 unowned 的引入是为了解决由 strong 带来的循环引用问题。
    简单来说,就是当两个对象互相有一个强指向去指向对方,这样导致两个对象在内存中无法释放。

weak 和 unowned 的使用场景有如下差别:

  • 当访问对象时该对象可能已经被释放了,则用 weak。比如 delegate 的修饰。
  • 当访问对象确定不可能被释放,则用 unowned。比如 self 的引用。
  • 实际上为了安全起见,很多公司规定任何时候都使用 weak 去修饰。

在Swift和Objective-C的混编项目中,如何在Swift文件中调用Objective-C文件中已经定义的方法?如何在Objective-C文件中调用Swift文件中定义的方法?

  • Swift中若要使用Objective-C代码,可以在ProjectName-Bridging-Header.h里添加Objective-C的头文件名称,Swift文件中即可调用相应的Objective-C代码。
    一般情况Xcode会在Swift项目中第一次创建Objective-C文件时自动创建ProjectName-Bridging-Header.h文件。
  • Objective-C中若要调用Swift代码,可以导入Swift生成的头函数ProjectName-Swift.h来实现。
  • Swift文件中若要规定固定的方法或属性暴露给Objective-C使用,可以在方法或属性前加上@objc来声明。如果该类是NSObject子类,那么Swift会在非private的方法或属性前自动加上@objc。

实现一个函数。求一个整型二维数组中所有元素之和

func sumPairs(_ nums: [[Int]]) -> Int {
  return nums.flatMap { $0 }.reduce(0) { $0 + $1 }
}

Swift 有函数式编程的思想。其中 flatMap, map, reduce, filter 是其代表的方法。
本题中考察了 flatMap 的降维思路,以及 reduce 的基本使用。相比于一般的 for 循环,这样的写法要更加得简洁漂亮。

项目

  • 有已经上线的项目么?
  • 项目里哪个部分是你完成的?(找一个亮点问一下如何实现的)
  • 开发过程中遇到过什么困难,是如何解决的?

学习

  • 遇到一个问题完全不能理解的时候,是如何帮助自己理解的?举个例子?
  • 有看书的习惯么?最近看的一本是什么书?有什么心得?
  • 有没有使用一些笔记软件?会在多平台同步以及多渠道采集么?(如果没有,问一下是如何复习知识的)
  • 有没有使用清单类,日历类的软件?(如果没有,问一下是如何安排,计划任务的)
  • 平常看博客么?有没有自己写过?(如果写,有哪些收获?如果没有写,问一下不写的原因)

你可能感兴趣的:(iOS 面试题)