iOS 基础面试题2018

1. 写一个setter方法用于完成@property (nonatomic,retain)NSString *name,写一个setter方法用于完成@property(nonatomic,copy)NSString *name.

若想令自己所写的对象具有拷贝功能,则需实现 NSCopying 协议。如果自定义的对象分为可变版本与不可变版本 , 那么就要同时实现 NSCopyiog 与NSMutableCopying 协议,不过一般没什么必要,实现 NSCopying 协议就够了

2.看下面的程序,三次NSLog会输出什么?为什么?

结果:-1、-1、-1  。-1代表没有引用计数或者引用计数非常大,因为str是字符串,字符串在常量区,没有引用计数。引用计数为-1,这可以理解为NSString实际上是一个字符串常量,是没有引用计数的(或者它的引用计数是一个很大的值(使用%lu可以打印查看),对它做引用计数操作没实质上的影响)

3. 关键字const什么含义?

const int a;  int const a;  const int * a;  int const * a;  int * const a;  int const * const a;

1> 前两个的作用是一样:a 是一个常整型数

2> 第三、四个意味着 a 是一个指向常整型数的指针(整型数是不可修改的,但指针可以)

3> 第五个的意思:a 是一个指向整型数的常指针(指针指向的整型数是可以修改的,但指针是不可修改的)

4> 最后一个意味着:a 是一个指向常整型数的常指针(指针指向的整型数是不可修改的,同时指针也是不可修改的)

define 和 const常量有什么区别?

define 在预处理阶段进行替换,const 常量在编译阶段使用

宏不做类型检查,仅仅进行替换,const 常量有数据类型,会执行类型检查

define 不能调试,const 常量可以调试

define 定义的常量在替换后运行过程中会不断地占用内存,而 const 定义的常量存储在数据段只有一份 copy,效率更高

define 可以定义一些简单的函数,const 不可以

4.  static的作用?

1>  static修饰的函数是一个内部函数,只能在本文件中调用,其他文件不能调用

2>  static修饰的全部变量是一个内部变量,只能在本文件中使用,其他文件不能使用

3>  static修饰的局部变量只会初始化一次,并且在程序退出时才会回收内存

5.响应链史上最详细的iOS之事件的传递和响应机制-原理篇

按照时间顺序,事件的生命周期是这样的:

事件的产生和传递(事件如何从父控件传递到子控件并寻找到最合适的view、寻找最合适的view的底层实现、拦截事件的处理)->找到最合适的view后事件的处理(touches方法的重写,也就是事件的响应)

其中重点和难点是:

1.如何寻找最合适的view

2.寻找最合适的view的底层实现(hitTest:withEvent:底层实现)

事件的传递和响应的区别:

事件的传递是从上到下(父控件到子控件),事件的响应是从下到上(顺着响应者链条向上传递:子控件到父控件。

6.控制器 View的生命周期及相关函数是什么?你在开发中是如何用的?

1.首先判断控制器是否有视图,如果没有就调用loadView方法创建:通过storyboard或者代码;

2.随后调用viewDidLoad,可以进行下一步的初始化操作;只会被调用一次;

3.在视图显示之前调用viewWillAppear;该函数可以多次调用;

4.视图viewDidAppear

5.在视图消失之前调用viewWillDisappear;该函数可以多次调用;

如需要);

6.在布局变化前后,调用viewWill/DidLayoutSubviews处理相关信息;

7.简单说一下 APP的启动过程,从 main文件开始说起

程序启动分为两类:1.有storyboard2.没有storyboard

有storyboard情况下:

1.main函数

2.UIApplicationMain

创建UIApplication对象

创建UIApplication的delegate对象

3.根据Info.plist获得最主要storyboard的文件名,加载最主要的

storyboard(有storyboard)

创建UIWindow

创建和设置UIWindow的rootViewController

显示窗口

没有storyboard情况下:

1.main函数

2.UIApplicationMain

创建UIApplication对象

创建UIApplication的delegate对象

3.delegate对象开始处理(监听)系统事件(没有storyboard)

程序启动完毕的时候, 就会调用代理的application:didFinishLaunchingWithOptions:方法在application:didFinishLaunchingWithOptions:中创建UIWindow创建和设置UIWindow的rootViewController

显示窗口

8.购物车实现Git源码

购物车的数据结构的分析

购物车的选中处理逻辑

购物车的数量变化与价格计算

购物车的商品删除与数据刷新

购物车的状态、价格刷新的公共方法

9.第3方库整理 分类

10.SDWebImage内部实现过程

1.入口setImageWithURL:placeholderImage:options: 会先把placeholderImage 显示,然后 SDWebImageManager 根据 URL 开始处理图片。

2.进入 SDWebImageManager-downloadWithURL:delegate:options:userInfo: 交给 SDImageCache 从缓存查找图片是否已经下载queryDiskCacheForKey:delegate:userInfo:

3.先从内存图片缓存查找是否有图片,如果内存中已经有图片缓存,SDImageCacheDelegate 回调 imageCache:didFindImage:forKey:userInfo: 到 SDWebImageManager。

4.SDWebImageManagerDelegate 回调 webImageManager:didFinishWithImage: 到UIImageView+WebCache 等前端展示图片。

5.如果内存缓存中没有,生成 NSInvocationOperation 添加到队列开始从硬盘查找图片是否已经缓存。

6.根据 URLKey 在硬盘缓存目录下尝试读取图片文件。这一步是在 NSOperation 进行的操作,所以回主线程进行结果回调notifyDelegate:

7.如果上一操作从硬盘读取到了图片,将图片添加到内存缓存中(如果空闲内存过小,会先清空内存缓存)。SDImageCacheDelegate 回调imageCache:didFindImage:forKey:userInfo: 进而回调展示图片。

8.如果从硬盘缓存目录读取不到图片,说明所有缓存都不存在该图片,需要下载图片,回调 imageCache:didNotFindImageForKey:userInfo:

9.共享或重新生成一个下载器 SDWebImageDownloader 开始下载图片。

10.图片下载由 NSURLConnection 来做,实现相关 delegate 来判断图片下载中、下载完成和下载失败。

11.connection:didReceiveData: 中利用 ImageIO 做了按图片下载进度加载效果。

12.connectionDidFinishLoading: 数据下载完成后交给 SDWebImageDecoder 做图片解码处理。

13.图片解码处理在一个 NSOperationQueue 完成,不会拖慢主线程 UI。如果有需要对下载的图片进行二次处理,最好也在这里完成,效率会好很多。

14.在主线程notifyDelegateOnMainThreadWithInfo: 宣告解码完成,imageDecoder:didFinishDecodingImage:userInfo: 回调给 SDWebImageDownloader。

15.imageDownloader:didFinishWithImage: 回调给 SDWebImageManager 告知图片下载完成。

16.通知所有的 downloadDelegates 下载完成,回调给需要的地方展示图片。

17.将图片保存到 SDImageCache 中,内存缓存和硬盘缓存同时保存。写文件到硬盘也在以单独 NSInvocationOperation 完成,避免拖慢主线程。

18.SDImageCache 在初始化的时候会注册一些消息通知,在内存警告或退到后台的时候清理内存图片缓存,应用结束的时候清理过期图片。

19.SDWebImage 也提供了UIButton+WebCache 和 MKAnnotationView+WebCache,方便使用。

20.SDWebImagePrefetcher 可以预先下载图片,方便后续使用。

SDWebImage原理图
SDWebImage原理图

11.堆和栈的区别

从管理方式来讲

对于栈来讲,是由编译器自动管理,无需我们手工控制;

对于堆来说,释放工作由程序员控制,容易产生内存泄露(memory leak)

从申请大小大小方面讲

栈空间比较小

堆控件比较大

从数据存储方面来讲

栈空间中一般存储基本类型,对象的地址

堆空间一般存放对象本身,block 的 copy 等

12.@property 的本质是什么?

@property = ivar(实例变量) + getter + setter;

@property 其实就是在编译阶段由编译器自动帮我们生成 ivar 成员变量,getter 方法,setter 方法

13.怎么用 copy 关键字?

NSString、NSArray、NSDictionary 等等经常使用 copy 关键字,是因为他们有对应的可变类型:NSMutableString、NSMutableArray、NSMutableDictionary,为确保对象中的属性值不会无意间变动,应该在设置新属性值时拷贝一份,保护其封装性

block 也经常使用 copy 关键字

block 使用 copy 是从 MRC 遗留下来的“传统”,在 MRC 中,方法内部的block 是在栈区的,使用 copy 可以把它放到堆区.

在 ARC 中写不写都行:对于 block 使用 copy 还是 strong 效果是一样的,但是建议写上 copy,因为这样显示告知调用者“编译器会自动对 block 进行了 copy 操作”

14.+(void)load; +(void)initialize;有什么用处?

+(void)load;

当类对象被引入项目时, runtime 会向每一个类对象发送 load 消息。

load 方法会在每一个类甚至分类被引入时仅调用一次,调用的顺序:父类优先于子类, 子类优先于分类。

由于 load 方法会在类被 import 时调用一次,而这时往往是改变类的行为的最佳时机,在这里可以使用例如 method swizlling 来修改原有的方法。

load 方法不会被类自动继承。

+(void)initialize;

也是在第一次使用这个类的时候会调用这个方法,也就是说 initialize 也是懒加载

总结:

在 Objective-C 中,runtime 会自动调用每个类的这两个方法

1.+load 会在类初始加载时调用

2.+initialize 会在第一次调用类的类方法或实例方法之前被调用

这两个方法是可选的,且只有在实现了它们时才会被调用

两者的共同点:两个方法都只会被调用一次

15.KVO内部实现原理?

KVO 是基于 runtime 机制实现的

当某个类的属性对象第一次被观察时,系统就会在运行期动态地创建该类的一个派生类,在这个派生类中重写基类中任何被观察属性的 setter 方法。派生类在被重写的 setter 方法内实现真正的通知机制

如果原类为 Person,那么生成的派生类名为NSKVONotifying_Person每个类对象中都有一个 isa 指针指向当前类,当一个类对象的第一次被观察,那么系统会偷偷将 isa 指针指向动态生成的派生类,从而在给被监控属性赋值时执行的是派生类的 setter 方法键值观察通知依赖于NSObject的两个方法 : willChangeValueForKey:    和 didChangevlueForKey:;在一个被观察属性发生改变之前, willChangeValueForKey:一定会被调用,这就 会记录旧的值。而当改变发生后,didChangeValueForKey: 会被调用,继而 observeValueForKey:ofObject:change:context: 也会被调用。

补充:KVO 的这套实现机制中苹果还偷偷重写了 class 方法,让我们误认为还是使用的当前类 ,从而达到隐藏生成的派生类。

KVO实现原理图

16.UIView和 CALayer是什么关系?

UIView 显示在屏幕上归功于 CALayer,通过调用 drawRect 方法来渲染自身的内容,调节 CALayer 属性可以调整 UIView 的外观,UIView 继承自 UIResponder,比起CALayer 可以响应用户事件,Xcode6 之后可以方便的通过视图调试功能查看图层之间的关系。

UIView 是 iOS 系统中界面元素的基础,所有的界面元素都继承自它。它内部是由Core Animation 来实现的,它真正的绘图部分,是由一个叫 CALayer(Core Animation Layer)的类来管理。UIView 本身,更像是一个 CALayer 的管理器,访问它的跟绘图和坐标有关的属性,如 frame,bounds 等,实际上内部都是访问它所在 CALayer 的相关属性。

UIView 有个 layer 属性,可以返回它的主 CALayer 实例,UIView 有一个layerClass方法,返回主 layer所使用的类,UIView 的子类,可以通过重载这个方法,来让 UIView 使用不同的 CALayer 来显示,如:

- (class) layerClass {// 使某个 UIView的子类使用 GL来进行绘制return([CAEAGLLayerclass]);}

UIView 的 CALayer 类似 UIView 的子 View 树形结构,也可以向它的 layer 上添加子layer,来完成某些特殊的显示。例如下面的代码会在目标 View 上敷上一层黑色的透明薄膜。

grayCover = [[CALayeralloc]init];grayCover.backgroudColor    =  [[UIColorblackColor]colorWithAlphaComponent:0.2].CGColor;[self.layer addSubLayer:grayCover];

补充部分:这部分有深度了,大致了解一下吧,UIView 的 layer 树形在系统内部被系统维护着三份 copy

1.逻辑树,就是代码里可以操纵的,例如更改 layer 的属性等等就在这一份

2.动画树,这是一个中间层,系统正是在这一层上更改属性,进行各种渲染操作。

3.显示树,这棵树的内容是当前正被显示在屏幕上的内容。这三棵树的逻辑结构都是一样的,区别只有各自的属性

17.沙盒目录结构是怎样的?各自用于那些场景?

Application:存放程序源文件,上架前经过数字签名,上架后不可修改

Documents:常用目录,iCloud 备份目录,存放数据

Library

1.Caches:存放体积大又不需要备份的数据

2.Preference:设置目录,iCloud 会备份设置信息

tmp:存放临时文件,不会被备份,而且这个文件下的数据有可能随时被清除的可能

18.使用 drawRect有什么影响?

drawRect 方法依赖 Core Graphics 框架来进行自定义的绘制

缺点:它处理 touch 事件时每次按钮被点击后,都会用 setNeddsDisplay 进行强制重绘;而且不止一次,每次单点事件触发两次执行。这样的话从性能的角度来说,对 CPU 和内存来说都是欠佳的。特别是如果在我们的界面上有多个这样的UIButton 实例,那就会很糟糕了。这个方法的调用机制也是非常特别. 当你调用 setNeedsDisplay 方法时, UIKit 将会把当前图层标记为 dirty,但还是会显示原来的内容,直到下一次的视图渲染周期,才会将标记为 dirty 的图层重新建立 Core Graphics 上下文,然后将内存中的数据恢复出来, 再使用 CGContextRef 进行绘制

19.iOS动画篇_CoreAnimation(超详细解析核心动画)

20.runloop的 mode作用是什么?

用来控制一些特殊操作只能在指定模式下运行,一般可以通过指定操作的运行。

mode 来控制执行时机,以提高用户体验

系统默认注册了 5 个 Mode

1.kCFRunLoopDefaultMode:App 的默认 Mode,通常主线程是在这个 Mode下运行,对应 OC 中的:NSDefaultRunLoopMode

2.UITrackingRunLoopMode:界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响

3.kCFRunLoopCommonModes:这是一个标记 Mode,不是一种真正的 Mode,事件可 以 运 行 在 所 有 标 有 common modes 标 记 的 模 式 中 , 对 应 OC 中 的NSRunLoopCommonModes , 带 有    common  modes  标 记 的 模 式 有 :UITrackingRunLoopMode 和 kCFRunLoopDefaultMode

4.UIInitializationRunLoopMode:在启动 App 时进入的第一个 Mode,启动完成后就不再使用

5.GSEventReceiveRunLoopMode:接受系统事件的内部 Mode,通常用不到

21.以+scheduledTimerWithTimeInterval...的方式触发的 timer,在滑动页面上的列表时,timer会暂定回调,为什么?如何解决?

这里强调一点:在主线程中以+scheduledTimerWithTimeInterval...的方式触发的timer 默认是运行在 NSDefaultRunLoopMode模式下的,当滑动页面上的列表时,进入了 UITrackingRunLoopMode模式,这时候 timer 就会停止。可以修改 timer 的运行模式为 NSRunLoopCommonModes,这样定时器就可以一直运行了。

以下是我的补充:

在子线程中通过 scheduledTimerWithTimeInterval:...方法来构建

NSTimer方法内部已经创建NSTimer 对象 ,并加入到 RunLoop 中 , 运行模式为NSDefaultRunLoopMode。

由于 Mode 有 timer 对象,所以 RunLoop 就开始监听定时器事件了,从而开始进入运行循环。

这个方法仅仅是创建 RunLoop 对象,并不会主动启动 RunLoop,需要再调用 run方法来启动

如果在主线程中通过 scheduledTimerWithTimeInterval:...方法来构

建 NSTimer,就不需要主动启动 RunLoop 对象,因为主线程的 RunLoop 对象在程序运行起来就已经被启动了

// userInfo 参数:用来给 NSTimer 的userInfo属性赋值,userInfo是只读的,只能在构建NSTimer对象时赋值[NSTimerscheduledTimerWithTimeInterval:1.0target:selfselector:@selector(run:)    userInfo:@"ya了个hoo"repeats:YES];//scheduledTimer...方法创建出来 NSTimer虽然已经指定了默认模式,但是【允许你修改模式】[[NSRunLoopcurrentRunLoop] addTimer:timerforMode:NSRunLoopCommonModes];// 【仅在子线程】需要手动启动 RunLoop对象,进入运行循环[[NSRunLoopcurrentRunLoop] run];

22.lldb(gdb)常用的调试命令?

po:打印对象,会调用对象 description 方法。是 print-object 的简写

expr:可以在调试时动态执行指定表达式,并将结果打印出来,很有用的命令

print:也是打印命令,需要指定类型

bt:打印调用堆栈,是 thread backtrace 的简写,加 all 可打印所有 thread 的堆栈

brl:是 breakpoint list 的简写

23.什么时候会报 unrecognized selector的异常?

当调用该对象上某个方法,而该对象上没有实现这个方法的时候, 可以通过“消息转发”进行解决,如果还是不行就会报 unrecognized selector 异常

objc 是 动 态 语 言 , 每 个 方 法 在 运 行 时 会 被 动 态 转 为 消 息 发 送 , 即 :

objc_msgSend(receiver, selector),整个过程介绍如下:

objc 在向一个对象发送消息时,runtime 库会根据对象的 isa 指针找到该对象实际所属的类

然后在该类中的方法列表以及其父类方法列表中寻找方法运行

如果,在最顶层的父类中依然找不到相应的方法时,程序在运行时会挂掉并抛出异常 unrecognized selector sent to XXX 。但是在这之前,objc 的运行时会给出三次拯救程序崩溃的机会

三次拯救程序崩溃的机会

Method resolution

objc运行时会调用+resolveInstanceMethod: 或者+resolveClassMethod:,让你有机会提供一个函数实现。

如果你添加了函数并返回 YES,那运行时系统就会重新启动一次消息发送的过程

如果 resolve 方法返回 NO ,运行时就会移到下一步,消息转发

Fast forwarding

如果目标对象实现了-forwardingTargetForSelector:,Runtime 这时就会调用这个方法,给你把这个消息转发给其他对象的机会

只要这个方法返回的不是 nil和 self,整个消息发送的过程就会被重启,当然发送的对象会变成你返回的那个对象。否则,就会继续 Normal Fowarding。这里叫 Fast,只是为了区别下一步的转发机制。因为这一步不会创建任何新的对象,但 Normal forwarding 转发会创建一个 NSInvocation 对象,相对 Normal forwarding 转发更快点,所以这里叫 Fast forwarding

Normal forwarding

这一步是 Runtime 最后一次给你挽救的机会。

首先它会发送-methodSignatureForSelector:消息获得函数的参数和返回值类型。

如果  -methodSignatureForSelector:返回nil,Runtime则会发出

-doesNotRecognizeSelector:消息,程序这时也就挂掉了。

如果返回了一个函数签名,Runtime 就会创建一个 NSInvocation 对象并发送-forwardInvocation:消息给目标对象

24.HTTP协议中 POST方法和 GET方法有那些区别?

.先说一个不正确的理解:GET 用于向服务器请求数据,POST 用于提交数据,这样说是不对的,GET也可以提交数据,POST也可以请求数据。

你轻轻松松的给出了一个“标准答案”:

GET在浏览器回退时是无害的,而POST会再次提交请求。

GET产生的URL地址可以被Bookmark,而POST不可以。

GET请求会被浏览器主动cache,而POST不会,除非手动设置。

GET请求只能进行url编码,而POST支持多种编码方式。

GET请求参数会被完整保留在浏览器历史记录里,而POST中的参数不会被保留。

GET请求在URL中传送的参数是有长度限制的,而POST么有。

对参数的数据类型,GET只接受ASCII字符,而POST没有限制。

GET比POST更不安全,因为参数直接暴露在URL上,所以不能用来传递敏感信息。

GET参数通过URL传递,POST放在Request body中。

(本标准答案参考自w3schools)

“很遗憾,这不是我们要的回答!”

如果我告诉你GET和POST本质上没有区别你信吗?

让我们扒下GET和POST的外衣,坦诚相见吧!

GET和POST是什么?HTTP协议中的两种发送请求的方法。

HTTP是什么?HTTP是基于TCP/IP的关于数据如何在万维网中如何通信的协议。

HTTP的底层是TCP/IP。所以GET和POST的底层也是TCP/IP,也就是说,GET/POST都是TCP链接。GET和POST能做的事情是一样一样的。你要给GET加上request body,给POST带上url参数,技术上是完全行的通的。

HTTP只是个行为准则,而TCP才是GET和POST怎么实现的基本

GET和POST本质上就是TCP链接,并无差别。但是由于HTTP的规定和浏览器/服务器的限制,导致他们在应用过程中体现出一些不同

GET和POST还有一个重大区别

简单的说:

GET产生一个TCP数据包;POST产生两个TCP数据包

长的说:

对于GET方式的请求,浏览器会把http header和data一并发送出去,服务器响应200(返回数据);

而对于POST,浏览器先发送header,服务器响应100 continue,浏览器再发送data,服务器响应200 ok(返回数据)。

注意:并不是所有浏览器都会在POST中发送两次包,Firefox就只发送一次。

25.算法相关

26.TCP 和 UDP 有什么区别?

TCP 是面向连接的,建立连接需要经历三次握手,保证数据正确性和数据顺序

UDP 是非连接的协议,传送数据受生成速度,传输带宽等限制,可能造成丢包

UDP 一台服务端可以同时向多个客户端传输信息

TCP 报头体积更大,对系统资源要求更多

27.TCP 的三次握手

第一次握手:客户端发送 syn 包到服务器,并进入 syn_send状态,等待服务器进行确认;

第二次握手:服务器收到客户端的 syn 包,必须确认客户的SYN,同时自己也发送一个 SYN 包,即 SYN + ACK 包,此时服务器进入 SYN_RECV 状态;

第三次握手:客户收到服务器发送的 SYN+ACK 包之后,向服务器发送确认包, 此包发送完毕,客户端和服务器进入ESTABLISHED 状态,完成第三次握手。

28.面试总结篇链接

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