01 iOS开发基础概念
1.1什么是应用瘦身?
在保证应用特性完整的前提下, 尽可能地压缩和减少应用程序安装包的体积.
也就是尽可能减少应用程序对用户设备内存的占用, 从而减小用户下载应用程序的负担,
可以通过减少本地大图的存放而采用打开应用后需要用到时下载的方式, 减少第三方库的依赖, 单一的功能尽量不使用第三方等
1.2 什么是响应者链?
响应者链用于确定事件响应者的一种机制, 其中的时间主要指 触摸事件.
响应触摸事件的都是屏幕上的界面元素, 而且必须是继承自 UIResponse 类的界面类. 才可以响应触摸事件.
一个事件响应者的完成 主要经过两个过程, hitTest 方法命中视图 和相应这链 确定相应者,
hitTest 方法首先从顶部 UIApplication 往下调用, ( 从父类到子类. ) 直到找到命中者, 然后从命中者视图 沿着 相应者链 往上传递寻找真正的相应者 ,
一个继承自 UIResponder 的视图 要想能响应事件, 需要满足以下条件:
1. 必须要有对应的视图控制器, 即响应函数的逻辑代码要写在控制器内, 另外,(对于不支持默认能响应事件的控件 如” UIImageView ) userInteractionEnabled 属性必须设置为 YES , 否则会忽视事件不响应.
2. hidden 属性必须为NO, 隐藏的视图不可以响应事件, alpha 透明度属性的值不能过低,. 低于 0.01 接近透明也会因影响响应.
3. 需要注意保证树形结构的正确性. 子节点的 frame 一定都要在 父节点的 frame 内
1.3 什么是 懒加载?
懒加载 也称为延迟加载, 核心思想是把对象的实例化尽量延迟,直到真正用到的时候才将其实例化,
这样做的好处是可以减轻大量对象在实例化时对资源的消耗,而不是在程序初始化的时候 就预先将对象实例化,
懒加载可以将对象的实例化代码从初始化方法中独立出来. 提高代码可读性,
1.4 什么是 Cocoa 和 Cocoa Touch?
相同之处: 两者都包含 OC 运行时的两个核心框架
- cocoa 包含 Foundation 和 AppKit 框架, 可用于开发 Mac OS X 系统的应用程序
- cocoa touch 包含 Foundation 和 UIKit 框架, 可用于开发IPhone OS 系统的应用程序,
- Cocoa 是 Mac OS X 的开发环境, Cocoa Touch 是 iPhoen OS 的开发环境.
1.5 什么是谓词?
简单的说 谓词就是一个过滤器, 符合条件的留下, 不符合条件的删除.
谓词本身就代表了一个逻辑条件, 计算谓词之后 返回的结果永远为 BOOL 类型的值.
而谓词最常用的功能就是对集合进行过滤,当程序使用谓词对集合元素进行过滤时, 程序会自动遍历其元素, 并根据集合元素来计算谓词的值,
当这个集合中的元素 计算谓词并返回YES时, 这个元素才会被保留下来. 并重新组合成一个集合返回 .
1.6 设计模式的工厂方法?
工厂模式是利用了 面向对象3大特性之一 ---> 多态.
父类指针指向子类对象这个特性,
父类定义方法, 子类实现, 是一种创建类模式,
在任何需要生产复杂对象的地方, 都可以使用工厂方法,
良好的封装性, 代码结构清晰. 屏蔽产品类, 调用者只需要关心产品的接口 而不关心内部的实现,
复杂对象比较适合工厂模式. 简单对象有时仅需要 new 创建就可以了.
工厂模式依赖抽象架构, 它把实例化任务交给实现类. 扩展性较好.
1.7 OC 的数组或字典中, 添加nil 对象会有什么问题?
数组或字典如果通过 addObject 函数添加 nil 会崩溃.
但初始化时 通过 initWithObjects方法里面的nil 会被编译器过滤去掉不会有影响,
另外如果使用语法糖初始化数组或字典也不可以有nil , 此时 nil 不会被过滤 也会崩溃.
1.8 Objective-C中的可变和不可变类型是什么?
Objective-C中的mutable和immutable类型对应于动态空间分配和静态空间分配。最常见的例子是数组和字典。
例如 NSArray和NSMutableArray,前者为静态数组,初始化后长度固定,不可以再动态添加新元素改变数组长度;
后者为动态数组,可以动态添加或者删除元素,动态申请新的空间或释放不需要的空间,伸缩数组长度。
1.9 iOS应用有哪几种状态?
- Not running 非运行状态. 应用程序没有运行或被系统终止.
- Inactive 前台非活动状态. 应用程序进入前台状态, 但是还不能接受事件处理,
- Active 前台活跃状态 应用程序进入前台状态, 能接受事件处理.
- Background 后台状态. 应用进入后台后, 依然能够执行代码. 如果有可执行的代码. 就会执行, 如果没有可执行的代码或者将可执行的代码执行完毕, 应用会马上进入挂起状态,
- Suspend 挂起状态, 被挂起的应用进入一种 “冷冻状态, ” 不能执行代码, 如果系统内存不够, 应用会被终止,
1.10 AppDelegate 中主要的几个回调方法?
- application (应用程序.) 应用启动并进入初始化时会调用该方法并发生通知, 这个阶段会实例化 根视图控制器,.
- applicationDidBecomeActice ( 变得活跃 ) 应用进入前台并处于活动状态 调用该方法并发生通知, 这个阶段可以恢复 UI 的状态,
- applicationWillResignActive (退出活动) 应用从活动状态 进入非活动状态时 调用该方法并发生通知, 这个阶段可以保存 UI 的状态,
- applicationDidEnterBackground ( 进入后台 ) 应用进入前台, 但是还没有处于活动状态时 调用该方法并发生通知, 这个阶段 可以恢复用户数据,
- applicationWillTerminate (应用将被终止 ) 应用被终止时调用该方法并发生通知, 但内存清除时 除外, 这个阶段释放一些资源, 也可以保存用户数据.
02 OC 语言基础
2.1 Swift 和 OC 的相比有哪些优点?
swift 的特点有:
- 快速, 现代. 安全, 互动, 而且明显优于 OC
- 可以使用现有的 Cocoa 和 Cocoa Touch 框架
- Swift 取消了 Objective-C 的指针 / 地址 等不安全访问的使用.
- 提供命名空间, 泛型, 运算对象重载,
- Swift 被简单的形容为 “ 没有C 的Objective-C”
- 为开发工具 Xcode 带来了 Xcode Playground 功能, 提供强大的互动效果, 能让 Swift 源代码在撰写的过程中实时显示出其运行结果,
- 基于C 和 OC. 却没有C 的一些兼容约束
- 采用了安全的编程模式
- 界面基于 Cocoa 和 Cocoa Touch 框架
- 舍弃 OC 早期应用 Smalltalk 的语法, 保留Smalltalk 的动态特性, 全面改为句点表示法.
- 对比OC 的动态绑定, 类型严谨.
2.2. View 视图的生命周期
- viewDidLoad 视图创建
- viewWillAppear 视图即将可见
- viewDidAppear 视图已经可见
- viewWillDisAppear 视图即将不可见
- viewDidDisappear 视图已经不可见
- 系统低内存时 调用: didReceiveMemoryWarning 和 viewDidUnLoad 方法, ( viewDidUnLoad 方法已经在 iOS 6 之后被废弃)
2.3 Foundation 和 Core Foundation 对象有什么区别
- Foundation 对象是 Objective-C 对象 使用 Objective-C 语言实现,
而 Core Foundation 对象是使用 C语言实现, 两者之间可以通过 __bridge. __bridge_transfer . __bridge __retained 等关键字转换 ( 桥接 )
- Foundation 对象 和 Core Foundation 更重要的区别是 ARC 下的内存管理问题.
在 MRC 下两者都需要开发者手动管理内存, 没有区别,
但是在 ARC 下, 系统只会自动管理 Foundation 对象的释放, 而 Core Foundation 对象的管理还是需要开发者手动管理.
因此在 ARC 下两者进行转换后, 必须要确定转换后的对象是由开发者手动管理, 还是由 ARC 系统继续管理. 否则可能会导致 内存泄漏问题.
2.4, 什么叫多态?
- 多态在面向对象语言中指同一个接口可以有多种不同的实现方式,
OC 中的多态 则是不同对象对同一消息的不同响应方式,
子类通过重写父类的方法来改变同一消息的实现. 体现为多态性,
2.5 Objective-C 的类 可以多重继承吗? 可以实现多个接口吗? 重写一个类的方式用继承好 还是 类别好? 为什么 ?
- Objective-C 的类 只支持单继承, 不可以多重继承
- Object 可以利用 protocol 代理协议实现多个接口, 通过实现多个接口完成类似C++ 的多重继承,
在OC 中 多态特性是通过协议 ( protocol ) 或者 类别 ( Category ) 来实现的. 协议定义的接口方法可以被多个类实现, 类别可以在不变动原来类的情况下进行方法重写或扩展.
- 重写一个类一般情况下 使用类别更好, 因为用类别去重写类的方法. 仅对本类别有效. 不会影响到其他类与原有类的关系.
2.6 关于 Objective-C 语言的动态性主要体现在哪?
Objective-C 语言的动态性主要体现为3个方面:
- 动态类型: 运行时确定对象的类型
- 动态绑定: 运行时确定对象的调用方法
- 动态加载: 运行时加载需要的资源或者可执行的代码
######扩展
NSString *string = [[ NSData alloc ] init ];
问题: string 在编译时和运行时分别是什么类型的对象??
答: 在编译时是 NSString 类型
在运行时是 NSData 类型
2.7. @synthesize 和 @dynamic分别有什么作用
1.以上是@property 对应的两个词 如果 @synthesize 和 @dynamic 都没写. 默认的就是 @synthesize var = _var;
2. @synthesize 的语义是如果你没有手动实现 setter 和 getter 方法, 编译器会自动加上这两个方法
3. @dynamic 是告诉编译器. 属性的 setter 和 getter 方法 由用户自己实现, 不自动生成, ( 对于readonly 的属性只需提供 getter 即可 )
假如: 一个属性被声明为 @dynamic var. 然后没有手动提供 @setter 和 @getter 方法, 编译的时候是没有问题的.
但是当程序运行到 instance.var = somevar 时 由于缺少 setter 方法 会导致程序崩溃;
或者 当运行到 someVar = var 时. 由于缺少 getter 方法 同样会导致崩溃.
编译时没问题. 运行时才执行相应的方法, 这就是所谓的动态绑定.
2.8. 类方法 load 和 initialize 的区别是什么?
- iOS 会在运行期提前自动调用这两个方法,
- 区别: load 是只要类所在文件被引用就会被调用, 而 initialize 是在类或者其子类的第一个方法被调用前调用,
所以如果类没有被引用进项目, 就不会有load调用, 但是即使类文件被引用, 没有使用, 那么initialize 也不会被调用.
- 相同点在于: 方法只会被调用一次.
方法调用的顺序: 父类 (SuperClass) 的方法优先于 子类 (SubClass) 的方法. 类中的方法优先于类别 (Catgory)中的方法
- 当类对象被引入项目时, runtime 会向每一个类对象发送load消息, load方法会在每一个类甚至分类被引入时仅调用一次,
调用的顺序是父类优先于子类, 子类优先于分类, 而且load方法不会被自动继承, 每一个类中的load方法都不需要像 viewDidLoad方法一样调用父类的方法.
2.9. id , instancetype 是什么? 区别是什么?
- id : 万能指针, 能作为参数 和 方法的返回类型
- instancetype : 只能作为方法的返回类型, 并且返回的类型是当前定义类的 类类型.
2.10. iOS 的系统架构分为哪几层?
iOS 的系统架构分为: 核心操作系统层, 核心服务层, 媒体层, Cocoa 界面服务层, 四个层次
03 OC 高级特性
3.1 什么叫多态?
- 不同对象以自己的方式响应相同的消息的能力叫做多态.
- 程序中的多态: 父类指针指向子类对象
- 在程序中的表现为: 父类指针指向不同子类对象的时候, 通过父类指针调用被重写的方法的时候,. 会执行该指针指向的那个对象的方法,
- 原理: 动态绑定: 动态类型能使程序直到执行时才确定对象的真是类型, 动态类型绑定能使程序直到程序执行时 才确定要对那个对象调用的方法.
- 多态的条件包括为: 有继承关系, 子类重写父类的方法, 父类指针指向子类对象.
3.2 重载 ( overload ) 和 重写 (override) 的区别?
- 重载: 同一个作用域内被申明的几个具有不同参数列表, ( 参数类型, 个数, 顺序不同,) 的同名函数, 根据参数列表确定调用哪个函数, 重载不关心函数 返回类型
- 重写: 覆盖 , 是指派生类中存在重新定义的函数, 其函数名, 参数列表, 都必须同基类中被重写的函数一致,
返回值类型 除了协变情况下 也必须和基类中被重写的函数一致, 只有函数体不同. ( 花括号内不同, )
- 严格来说 OC 是不支持 重载的, (支持参数个数不同的函数重载) swift 是支持重载的. OC 和 Swift 都是支持重写的.
3.3 什么是编译时 与 运行时?
- 编译时: 即编译器对语言的编译阶段, 编译时只是对语言进行最基本的检查报错, 包括词法分析, 语法分析等等,
将程序代码翻译成计算机能够识别的语言, 编译通过并不意味着程序就可以成功运行,
- 运行时: 即程序通过了编译之后, 将编译好的代码装载到内存中运行起来的阶段, 这个时候会具体对类型进行检查,
而不仅仅是对代码的简单扫描分析. 此时出错程序会崩溃.
3.4 派生. 重写 多态的概念?
一. 派生: 类的派生是由已存在的类 产生新类的过程, 已有的类叫基类, 产生的新类叫派生类,
其目的是扩展基类的功能或修改基类的功能.
派生类 包含了基类的所有特征和功能.
二. 重写 : 子类可以从父类继承方法, 但是有时候父类的方法不适合子类. 子类就可以写一个自己的同名方法来覆盖掉父类的同名方法, 称为重写.
三. 多态: 概念是某一类事务的多种形态. 在程序中的表现为: 不同的对象以自己的方式响应相同名称方法的能力称为多态,
3.5 什么时候会报unrecognized selector的异常 ?
- objc 在向一个对象发送消息时, runtime 库会根据对象的 isa指针 找到该对象实际的类. 然后在该类中的方法列表以及其父类的方法列表中寻找方法运行,
如果 在最顶层的父类中依然找不到相应的方法时, 程序在运行时 会挂掉 并抛出异常 unrecognized selector sent to XXX 但是在程序挂掉之前. Objc 的运行时机制 会给出三次拯救程序崩溃的机会
1. Method resolution
Objc 运行时会调用 resolveInstanceMethod: 或者 resolveClassMethod: 让你有机会提供一个函数实现,
如果你添加了函数, 那运行时系统就会重新启动一次消息发送的过程, 否则, 运行时就会移到下一步, 消息转发 ( Message Forwarding)
2. Fast forwarding
如果目标对象实现了 -forwardingTargetForSelector : Runtime 这时就会调用这个方法. 给你把这个消息转发给其他对象的机会,
只要这个方法返回的不是nil 和 self. 整个消息发送的过程就会被重启. 当然 发送的对象会变成你返回的那个对象.
否则. 就会继续 Normal Fowarding . 这里叫Fast 只是为了区别下一步的转发机制. 因为这一步不会创建任何新的对象. 但下一步转发会创建一个 NSInvocation 对象. 所以相对更快点.
3. Normal forwarding
这一步是Runtime 最后一次挽救程序崩溃的机会,
首先. 它会发送 - methodSignatureForSelector: 消息获得函数的参数和返回值类型,
如果 -ethodSignatureForSelector 返回nil . Runtime 则会发出 - doesNotRecognizeSelector 消息. 程序这时候也就挂掉了.
如果返回了一个函数签名. Runtime 就会创建一个 NSInvocation 对象并发送 -forwardInvocation 消息给目标对象.
小总结: 在OC项目中. 由于运行时机制, 在程序运行时才会去根据对象的isa指针找寻该对象的父类 , 直到顶层父类都找不到该对象时, 系统会进行三次的消息转发之后程序才会最终崩溃.
在这三次消息转发过程中, 可以实现其中任意一个方法, 进行crash的处理. 可以丢弃该事件 , 或在这个事件中实现相关的方法进行挽救. 保证程序不崩溃,
如果系统的三个消息转发函数都没有实现, 程序就会直接崩溃.
3.6. isEqual 和 isEqualToString 有什么区别?
- isEqual 是比较两个NSObject 的方法. 而 isEqualToString 是比较两个 NSString 的方法.
明显 isEqualToString只是专门用来比较字符串的. 是isEqual的衍生方法.
- isEqual 先是比较两个指针地址, 如果地址相同直接返回YES,
如果地址不同, 再看两个指针指向的对象是否为空以及对象类型是否相同,
如果有一个为空, 或者两者不是同类对象, 那么直接返回 NO.
如果都不为空且属于同类对象 而且对象的属性也相等, 那么返回YES.
3.7. isa 指针指向什么? 有什么作用?
- Objective-C 实例对象的isa指针是指向它的类对象的.
OC 中有3个层次的对象: 实例对象, 类对象, 和 元类.
Class 即自定义的类, 是实例对象的类对象, 而类对象又是其对应元类的实例对象.
isa 指针的作用是 通过它可以找到对应类对象 或 元类中的方法,
例如, 实例对象可以在 其类对象中 找到它的实例方法,
Class 对象可以从元类中找到它的类方法.
3.8.OC 的消息转发机制.
- _objc_msgForward 是一个函数指正, 用于消息转发, 当向一个对象发送一条消息. 但它并没有实现的时候,.
- _objc_msgForward 会尝试做消息转发. 可以使用 runtime 调用系统 methodSignatureForSelector 和 forwardInvocation 方法. 吞掉一个消息. 或者代理给其他对象都是没有问题的.
- 一旦调用 _objc_msgForward 将跳过查找 IMP 的过程, 直接触发 “消息转发”
- 如果调用了 _objc_msgForward 即使这个对象确实已经实现了这个方法, 你也会告诉 objc_msgSend “我没有在这个对象里找到这个方法的实现”
- 如果用不好会直接导致程序crash. 但是如果用得好. 可以做很多事情, 比如. JSPath. RAC.
3.9 . 类别 (category) 和分类 (extension) 的区别?
1. extension 是匿名的 category
2. extension 里声明的方法需要在 main implementation 中实现. category 不强制要求.
3. extension 可以添加属性 变量 category 不可以
4. Category 比原有的类的耦合 更低一些. 声明和实现都可以写在单独的文件里. 但是只能为已定义类 增加方法, 而不能加入 实例变量
5. extension 可以认为是一个私有的 Category
- Extension 耦合比较高, 声明可以单独写, 但是实现必须写在原有类的 @implement 中, 可以增加 Method 和实例变量.
Extension 的一个特性是可以重新声明 一个实例变量, 将之从 readonly 改为对内 readwrite.
- 使用 Extension 可以更好的封装类. 在 .h 文件中 能看到的都是对外的接口,. 其余的实例变量和对内的 @property 等都可以写在 Extension , 这样类的结构更加清晰.
3.10. 继承和类别的区别?
- 继承可以增加, 修改 或者 删除方法, 并且可以增加属性,添加新方法和父类方法一致, 但父类方法仍需要使用.
0. 类别:
1. 系统提供的一些类, 系统本身不提倡继承, 因为这些类的内部实现对继承有所限制.
2. 类别可以将自己构建的类中的方法进行分组, 对于大型的类, 提高可维护性,.
3. 分类的作用:
将类的实现分散到多个不同文件 或 多个不同框架中
创建对私有方法的前向引用
向对象添加非正式协议 ( 声明方法可以不实现, 不调用只警告. 不报错. ) 正式协议的优点: 可继承. 泛型约束.
4. 类别可以在不获悉 不改变原来代码的情况下往里面添加新的方法.
只能添加. 不能删除修改, 并且如果类别和原来类中的方法产生名称冲突, 则类别将覆盖原来的方法, 因为类别具有更高的优先级.
5. 分类的局限性:
无法向类中添加新的实例变量, 类别没有位置容纳实例变量
无法添加实例变量的局限可以使用字典对象解决
3.11. 在一个对象的方法里面. self.name = “ object ” 和 _name = “ object ” 有什么不同?
Self.name = “ object “ 会调用对象的 setName() 方法
_name = “ object ” 会直接把 object 赋值给当前对象的name 属性
3.12. 内存分区有哪些? 分别用来存放什么?
- 代码区: 存放函数二进制代码
- 数据区: 系统运行时申请内存并初始化, 系统退出时由系统释放, 存放全局变量. 静态变量. 常量
- 堆区: 通过 malloc 等函数 或 new 等动态申请到的, 需手动申请和释放
- 栈区: 函数模块内申请, 函数结束时由系统自动释放, 存放局部变量, 函数参数
3.13. 有哪些设计模式? 各自的区别有哪些?
KVO: 一对多, 观察者模式, 键值观察机制, 提供了观察某一属性变化的方法, 极大简化了代码
KVC: 键值编码, 一个对象在调用 setValue时做的事: ( 即KVC 底层实现原理 )步骤如下:
- 检查是否存在相应 key 的 set 方法, 存在就调用 set 方法
- set 方法不存在时, 就查找 _key 的成员变量 是否存在, 存在就直接赋值
- 如果 _ key 成员变量 没有找到, 就查找相同名称的 key. 存在就赋值
- 如果都没有找到则会调用 valueForUndefinedKey和 setValue: forUndefinedKey
delegate : 发送者和接收者的关系是直接 . 一对一的关系
Notification : 观察者模式, 发送者 和接收者的关系是间接, 多对多的关系.
区别:
1. Delegate 的 效率比 Notification 高
2. Delegate 比 Notification 更直接, 需要关注返回值, 常带有 should 关键词; Notification 不关心结果, 常带有 did 关键词
两个模块之间的联系如果不是很亲密, 就用 Notification 传值, 比如多线程之间的传值,
3. KVO 容易实现 两个对象的同步, 比如 Model 和 View 的同步
3.14. 字典的value可为空吗? OC中表示为空有几种? 区别是什么?
NULL : 是宏, 是对于 C 语言指针而使用的, 表示空指针
nil : 是宏. 是对于 OC 中的对象而使用的, 表示对象为空
Nil : 是宏, 是对于 OC 中的 类 而使用的, 表示 类 指向空
NSNull : 是类类型, 用于表示空的占位对象,
字典的value不可直接设置为空, 会carsh, 但是可以使用 NSNull 来占位. 例如:
NSDictionary *params = @{ @“arg1” : @“value1”, @“arg2” : value2.isEmpty ? [ NSNull null ] : value2 } ;
3.15. block的内存管理原理是什么?
无论 ARC 还是 MRC 只要 block 没有访问外部变量. block 始终在全局区.
MRC 下:
— block 如果访问外部变量, block 在栈区
— 不能对 block 使用 retain, 否则不能保存在堆区
— 只有使用 copy, 才能放到堆区
ARC 下:
— block 如果访问外部变量, block 在堆区.
— block 是一个对象, 可以使用 copy 或 strong 修饰, 延续MRC写法最好是使用copy修饰.
3.16. 简要说明 APP的启动过程, 从main 文件说起. Main 函数中有什么? 作用是什么?
打开程序 —> 执行main 函数. —-> UIApplicatonMain 函数. ——-> 初始化 UIApplicationMain 函数 (包括 设置代理, 开启事件循环 ) —— > 监听系统事件 —— > 程序启动结束
UIApplicationMain 函数作用:
1> 根据传入的第三个参数 创建 UIApplication 对象 或它的子类对象, 如果该参数为nil,. 直接使用该UIApplication对象的 代理 属性,
2> 根据传入的第四个参数 创建 AppDelegate 对象, 并将该对象赋值给第一步 创建的 UIApplication 对象的 delegate 属性.
3> 开启一个事件循环. 循环监控应用程序发生的事件. 每监听到对应的系统事件时, 就会通知 AppDelegate.
Main 函数作用:
1> 创建 UIApplication 对象,
2> 创建应用程序代理/
3> 开启时间循环,. 包括应用程序的循环运行, 并开始处理用户事件.
3.17. 数据存储 数据持久化方式有哪几种, 有什么区别?
iOS有4种数据持久化:
1.属性列表 ( plist ) .
2.对象归档 . Sqlite.
3.Core Data
4.NSUserDefaults 用于存储配置信息
keychain 存储用户的敏感信息, 如登录的token . 需要导入Security 框架
3.18. imageName 和 imageWithContextOfFile 的区别? 哪个性能更高?
* imageName的方式加载时, 图片使用完毕后缓存到内存中 内存消耗多. 加载速度快, 即使生成的对象被 内存管理池自动释放了. 这份缓存也不会被释放, 如果图像过大, 或者较多. 用这种方式会消耗很大的内存.
imageName 采用了缓存机制, 如果缓存中已加载了图片, 直接从缓存读取,不用每次读文件. 效率会更高.
* imageWithContentOfFile 加载 图片是不会缓存的,. 加载速度慢
大量使用 imageName方式会在不需要缓存的地方额外增加开销 CPU 的时间, 当应用程序需要加载一张比较大的图片并且只使用一次时. 是没有必要去缓存这个图片的.
用imageWithContentOfFile是最为经济的方式,. 这样不会因为UIImage元素较多情况下. CPU 会被逐个分散 在不必要的缓存上, 浪费过多时间.
3.19 静态链接是什么? 静态库和动态库有什么区别?
静态链接是指 将多个目标文件合并为一个可执行文件, 直观感觉就是将所有目标文件的段 合并,
静态库, 链接时完整的拷贝至可执行文件中, 被多次使用就又多份冗余拷贝
动态库: 链接时不复制, 程序运行时由系统动态加载到内存, 供程序调用, 系统只加载一次 多个程序公用. 节省内存
3.20 Static 和 Const 有什么区别?
const 是指声明一个常量
Static 修饰全局变量时, 表示此全局变量只在当前文件可见.
Static 修饰局部变量时, 表示每次调用的初始值 ,为上一次调用的值, 调用结束后存储空间不会释放.
3.21 深拷贝和浅拷贝的区别?
深拷贝: 对一个对象进行拷贝, 相当于对对象进行复制, 产生一个新的对象, 会存在两个指针分别指向两个对象, 当一个对象改变或者被销毁后, 深拷贝出来的新对象不会受到影响
浅拷贝: 对一个对象进行浅拷贝. 相当于对指向对象的指针进行复制, 产生一个新的指针指向这个对象, 存在两个指针指向同一个对象, 对象销毁后两个指针都应该被置空,
总结: 深拷贝 产生了新对象. 浅拷贝: 本质上并没有产生新对象.
3.22 说一下 OC 的反射机制?
在动态运行下我们可以构建任何一个类,然后我们通过这个类知道这个类的所有的属性和方法,
并且如果我们创建一个对象,我们也可以通过对象找到这个类的任意一个方法,这就是反射机制。
比如NSClassFormString,NSStringFormSelector,NSSelectorFormString
4.0 UI界面和数据类型相关
4.1. UIView 和 CALayer 的关系和区别有哪些?
UIView 是 iOS 中所有的界面元素都继承自它 每一个 UIView 内部都默认关联着一个layer 真正的绘图部分,
是由一个叫 CALayer 的类来管理的
UIView 有个 layer 属性 可以返回它的主 CALayer 实例,
UIView 有一个layerClass 方法, 返回主layer所使用的类.
UIView 的子类. 可以通过重载这个方法, 来让UIView 使用不同的CALayer 来显示,
** 区别:
UIView 继承自UIResponder, 能接收并响应事件, 负责显示内容的管理,
而CALayer继承自 NSObject, 不能响应事件, 负责显示内容的绘制,
UIView 侧重展示内容 ,CALayer 侧重于图形和界面的绘制,
当 view 展示的时候, View 是 layer 的CALayerDelegate, view 展示的内容是由 CALayer 进行 display 的.
View 内容的展示 依赖 CALayer 对内容的绘制, UIView 的frame 也是由内部的 CALayer 进行绘制.
对 UIView 的属性修改. 不会引起动画效果. 但是对于 CALayer 的属性修改, 是支持默认动画效果的, 在 view 执行动画的时候, view 是layer 的代理,
layer 通过 actionForLayer: forKey 向对应的代理 view 请求动画 action.
每个UIView 内部都有一个 CALayer 在背后提供内容的绘制和显示, 并且 UIView 的尺寸样式都由内部的 layer 所提供, layer 比 view 多了个 anchorPoint
一个 CALayer 的 frame 是由其 anchorPoint position bounds. Transfrom 共同决定的, 而 UIView 的 frame 只是简单的返回 CALayer 的frame,
4.2. layoutSubView 和 drawRects 区别?
layoutSubView 在以下情况会被调用:
1. init 初始化不会触发 layoutSubView
2. addSubView会触发layoutSubView
3. 设置 view 的Frame 会触发 layoutSubview . 当然前提是 frame 的值设置前后发生了变化.
4. 滚动一个 UIScrollView 会触发 layoutSubView
5. 旋转 Screen 会触发父View上的 layoutSubView事件.
6. 改变一个 UIView 大小的时候.也会触发父 UIView 上的 layoutSubView 事件
7. 直接调用 setLayoutSubView
drawRect 在以下情况会被调用:
1.如果 在 UIView 初始化时没有设置 rect 大小, 将导致 drawRect 不被自动调用,
2.drawRect 调用是在 Controller —-> loadView. Controller —-> viewDidLoad 两方法之后调用.
3.在调用 sizeToFit 后被调用. 所以可以先调用 sizeToFit 计算出 size . 然后系统自动调用 drawRect 方法.
4.通过设置 contentMode 属性值 为 UIViewContentModeRedraw. 那么每次设置 或更改 frame 的时候 自动调用 drawRect.
5.直接调用 setNeedsDisplay 或者 setNeedsDisPlayInRect 触发 drawRect. 但是有个前提条件是 rect 不能为0.
drawRect 方法使用注意点:
1> 若使用 UIView 绘图, 只能在 drawRect 方法中获取相应的contextRef 并绘图,
如果在其他方法中获取将获取到一个 invalidate 的 ref 并且不能用于画图, drawRect 方法不能手动显示调用,
必须通过 setNeedsDisplay 或者 setNeedsDisplayInRect ,. 让系统自动调用该方法.
2> 若使用 calayer 绘图, 只能在 drawInContext 中 绘制, 或者在 delegate 中的相应方法绘制, 同样也是调用 setNeedDisplay 等间接调用以上方法.
3> 若要实时画图, 不能使用 gestureRecognizer 只能使用 touchbegan 等方法来调用 setNeedsDisplay 实时刷新屏幕.
4.3 哪些操作会出发离屏渲染?
光栅化 layer. shouldRasterize = YES
遮罩. layer. mask
圆角. 同时设置, layer.maskToBounds = yes layer.cornerRadis 大于 0
阴影. layer.shadow 如果设置了 layer. shadowPath 就不会产生离屏渲染.
4.4 UI界面卡顿掉帧的原因?
iOS设备硬件时钟会发出 Vsync 垂直同步信号
然后 App 的 CPU 会去计算屏幕要显示的内容,
之后将计算好的内容提交到. GPU 去渲染,
随后 GPU 将渲染结果提交到 帧缓冲区. 等到下一个 VSync 到来时将缓冲区的帧显示到屏幕上.
也就是说. 一帧的显示是由CPU 和 GPU 共同决定的.
一般来说. 页面滑动流畅是 60 fps. 也就是 1s 有 60帧更新. 即每隔16.7ms 就要产生一帧画面,
而如果 CPU 和 GPU 加起来的处理时间超过 16.7ms 就会造成掉帧甚至卡顿. .
4.5 frame 和 bounds 有什么不同?
Frame 指的是 该view在父view坐标系统中的位置和大小, (参照点是父view的坐标系统)
Bounds 指的是 该view在本身系统中位置和大小, (参照点是本身坐标系统)
4.6 声明可变数组用copy 修饰会存在什么问题?
添加, 删除, 修改 数组内的元素的时候, 程序会因找不到对应的方法而崩溃,
因为 copy 就是将可变数组复制成一个 不可变的数组对象.
4.7 声明一个 NSString 或 NSArray 使用 Strong 修饰 可能会造成什么问题?
父类指针可以指向子类对象, 使用 Copy 修饰的目的是为了让该对象的属性不受外界影响,
使用Strong修饰可能使这个属性指向一个可变对象, 如果这个可变对象在外部被修改了. 那么会影响到该属性.
4.8 retain 和 copy 的区别?
copy 是建立了一个相同的对象, 而 retain 不是.
retain: 是指针拷贝, copy 是内容拷贝,
retain: 是创建一个指针, copy 是创建一个新的对象,
copy : 建立一个索引计数 为 1 的新对象, 然后释放旧对象, 新的对象 retain 为1, 与旧有对象引用计数 无关, 减少了对象对上下文的依赖.
retain: 释放旧的对象, 将旧对象的值赋予输入给新对象, 再提高输入对象的索引计数 为 1, 新对象和旧对象的指针相同.
4.9 readwrite readonly assign retain copy weak strong nonatomic 属性的作用? 即声明属性时可修饰属性特性的修饰词作用?
1.getter = getterName , setter = setterName 设置 getter 和 setter的方法名
2.readwrite, readonly : 设置属性可访问级别, readwrite 可读可写, readonly 可读不可写
3.assign: setter方法直接赋值, 不会进行任何retain操作, 用于非指针变量, 一般用于修饰基础数据类型 和 C 数据类型,
4.retain : setter 方法对参数进行release旧值在retain新值,
5.copy : setter 方法对参数进行copy 操作, 和 retain处理流程一样, 先旧值 release, 再 Copy 出新的对象, retainCount 为1.
6.nonatomic , atomic: 非原子操作 和 原子操作, 非原子操作是线程不安全的, 但在多线程并发访问时会提高性能, 原子操作是线程安全的, 但不是决定的, 性能比非原子操作低. 一般使用nonatomic
7.weak : 用于指针变量, 比 assign 多了一个功能, 当对象消失后自动把指针置为nil, 可以避免循环引用.
8.strong : 用于指针变量, setter方法对参数进行release 旧值再retain新值, 属于强引用
4.10. OC堆和栈的区别?
管理方式: 对于栈而言, 是由编译器自动管理, 无需手动控制,
对于堆而言, 释放工作需要程序猿控制, 容易造成内存泄漏.
分配方式: 堆都是动态分配的, 没有静态分配的堆,
栈有两种分配方式, 静态分配和动态分配, 静态分配是由编译器完成的, 比如 局部变量的分配, 动态分配由 alloca 函数进行分配, 但是栈的动态分配和堆是不同的, 他的动态分配是由编译器进行释放, 无需手动实现,
空间分配的区别:
栈: 由操作系统自动分配释放, 存放函数的参数值, 局部变量的值等, 使用的是一级缓存, 通常都是被调用时处于存储空间中, 调用完毕立即释放.
堆: 一般由程序猿手动释放, 若不释放, 程序结束时可能由OS 回收, 存放在二级缓存, 生命周期由虚拟机的垃圾回收算法来决定,
4.11 UIKit 和 CoreAnimation 和 CoreGraphics 的关系是什么?
绝大多数图形界面都由 UIKit 完成,UIKit 依赖于 Core Graphics 框架,也是基于 Core Graphics 框架实现的。某些更底层的功能,使用 Core Graphics 完成,是一组自由度更大的图形绘制和 动画 API。
UIKit 和 CoreGraphics 主要区别:
(1)Core Graphics 其实是一套基于 C 的 API 框架,使用了 Quartz 作为绘图引擎。这也就意
味着 Core Graphics 不是面向对象的。
(2) Core Graphics 需要一个图形上下文(Context) 使用 Core Graphics 来绘图,最简单的方法就是自定义一个类继承自 UIView,并重写子类的 drawRect 方法。在这个方法中绘制图形。
Core Graphics 绘图的步骤:
1.获取上下文(画布)
2.创建路径(自定义或者调用系统的 API)并添加到上下文中。
3.进行绘图内容的设置(画笔颜色、粗细、填充区域颜色、阴影、连接点形状等)
4.开始绘图(CGContextDrawPath)
5.释放路径(CGPathRelease)
(1)核心图形 Core Graphics 是用来实现用户界面视觉设计方案的重要技术框架
(2)核心动画 Core Animation 供了一套用于创建和渲染动态交互效果的简单易行的解决方案, 通过与 UIKit 的紧密配合,核心动画可以将界面交互对象与动画过渡效果进行完美地整合。
(3)UIkit 是用来打造 iOS 应用的最重要的图形技术框架,它供了用于构造触屏设备用户界面 的全部工具和资源,并在整个交互体验的塑造过程中扮演着至关重要的角色
4.12 Block 有哪几种形式?
分为 全局block . 栈 block. 堆 block.三种形式.
其中栈block 存储在栈区, 堆 block 存储在堆区, 全局block 存储在已初始化数据 data 区
4.13. 用assign 修饰对象会怎么样?
如果用assign修饰一个对象后,当对象被释放后,存在于栈上的指针还是存在的,
假如此时使用指针,它就是一个野指针了,就容易造成程序崩溃,如果是用copy修饰的对象,则不会产生上面的情况,
因为对象销毁的时候,系统会将指针置nil,也就不会产生野指针了。
所以: 修饰对象用 weak, 基础数据类型用 assgin
4.14 PerformSelector 的实现原理
1. 当调用 NSObject 的 performSelecter:afterDelay: 后,实际上其内部会创建一个 Timer 并添加到当前线程的 RunLoop 中。所以如果当前线程没有 RunLoop,则这个方法会失效。
2. 当调用 performSelector:onThread: 时,实际上其会创建一个 Timer 加到对应的线程去,同样的,如果对应线程没有 RunLoop 该方法也会失效。
4.15 @ property 会自动帮我们做的事有哪些?
1. 生成一个带下划线的成员变量
2. 声明一个 set 方法. 一个 get 方法
3. 生成 set 方法 和 get 方法的具体实现.
4.16 什么是垂悬指针? 什么是野指针?
垂悬指针: 指针指向的内存已经被释放了. 但是指针还在, 这就是一个垂悬指针, 或者说是迷途指针
野指针: 没有进行初始化的指针, 其实都是野指针.
4.17 实例方法和类方法有什么本质区别和联系?
类方法:
1.类方法是属于类对象的
2.类方法只能通过类对象调用
3.类方法中的self是类对象
4.类方法可以调用其他的类方法
5.类方法中不能访问成员变量
6.类方法中不能直接调用对象方法
实例方法:
1.实例方法是属于实例对象的
2.实例方法只能通过实例对象调用
3.实例方法中的self是实例对象
4.实例方法中可以访问成员变量
5.实例方法中直接调用实例方法
6.实例方法中也可以调用类方法(通过类名)
4.18 数据结构的存储一般常用的有哪几种? 各有哪些特点?
1.顺序存储方式: 在一块连续的存储区域, 一个接着一个的存放数据
2.链接存储方式: 不要求逻辑上相邻的结点在物理位置上相邻, 结点间的逻辑关系由附加的引用字段表示
3.索引存储方式: 采用附加索引表的方式存储结点信息
3.1 又可细分为:
- 稠密索引: 每个结点在索引表中都有一个索引项 , 其中索引项的地址指示结点所在的存储位置
- 稀疏索引: 一组结点在索引表中只对应一个索引项, 其中索引项的地址指示一组结点的起始存储位置.
4. 散列存储方式: 根据结点的关键字 直接计算出该结点的存储地址.
4.19 为什么一定要在主线程里面更新UI?
像UIKit这样大的框架上确保线程安全是一个重大的任务,会带来巨大的成本。
UIKit不是线程安全的,假如在两个线程中设置了同一张背景图片,很有可能就会由于背景图片被释放两次,使得程序崩溃。
或者某一个线程中遍历找寻某个subView,然而在另一个线程中删除了该subView,那么就会造成错乱。
apple有对大部分的绘图方法和诸如UIColor等类改写成线程安全可用,可还是建议将UI操作保证在主线程中
5.0 RunLoop / RunTime / 线程 相关
5.1. RunLoop 和线程的关系
1. 一一对应,主线程的runloop已经创建,子线程的必须手动创建
2. runloop在第一次获取时创建. 在线程结束时销毁
// 在runloop中有多个运行模式. 但是只能选择一种模式运行, mode 至少要由一个timer 或者是 source
Mode:
系统默认注册5个 Mode:
- KCFRunLoopDefaultMode : App默认 mode 通常主线程在这个 mode 下运行
- UITrackingRunLoopMode : 界面追踪 mode 用于 ScrollView 追踪触摸滑动, 保证滑动时不受其他 mode 影响
- KCFRunLoopCommonModes : 相当于 NSDefaultRunLoopMode + UITrackingRunLoopMode
- UIInitializationRunLoopMode : 刚启动 App 时进入的第一个 Mode 启动完成后不再使用
- GSEventReceiveRunLoopMode: 接受系统事件的内部mode. 通常用不到.
ps: 开发中常使用到的mode 类型为 RunLoopCommonModes.
5.2. 如何保证线程安全
@synchronized 加互斥锁.
需要注意 1. 锁必须是全局唯一. 2. 加锁的位置. 3. 加锁的前提条件 4, 加锁结果: 线程同步
优点: 防止多线程抢夺资源造成的数据安全问题,
缺点:消耗CPU性能
使用前提: 多线程使用同一块资源
线程同步: 多条线程在同一条线上按顺序的执行任务
5.3 什么情况下会出现线程不安全? 为什么 CFRunLoopRef 是线程安全的, 而 NSRunLoop基于 CFRunLoopRef 却是不安全的?
在同一个进程中运行多个线程本身不会导致问题, 问题在于多个线程访问了同一个资源, 进行了资源的改变操作.
比如写入删除等, 如果资源不发生变化, 多个线程同时读取相同的资源也是安全的,
CFRunLoopRef 基于 C . 线程安全, NSRunloop 基于 CFRunLoopRef 面向对象的 API 是不安全的.
5.4. 多线程的几种方式和生命周期
1> NSThread 手动管理线程生命周期 线程任务执行完毕之后被释放
2> GCD 自动管理线程生命周期. 大括号内的任务执行完毕之后被释放
3> NSOperation 基于 GCD的抽象基类 不需要手动管理线程的生命周期和同步
5.5. 线程间通信
1> NSThread 调用系统的 performSelectorOnMainThread 回到主线程处理UI或其他事件. performSelector:(SEL)aSelector 回到指定线程处理事件
2> GCD: 子线程与主线程通过系统方法 dispatch_async(dispath_get_main_queue( ) ) 进行线程间通信
3> NSOperation : 设置依赖关系 手动调用主线程[NSOperationQueue mainQueue] 进行线程间通信
5.6. 用过NSOperationQueue吗?如果用过或者了解的话,为什么要使用 NSOperationQueue?实现了什么?简述它和GCD的区别和类似的地方(提示:可以从两者的实现机制和适用范围来述)
使用NSOperationQueue 用来管理子类化的NSOperation 对象. 可以控制其线程并发数目.
GCD 和 NSOperation 都可以实现对线程的管理.
区别是 NSOperation 和 NSOperationQueue 是多线程的 面向对象抽象.
项目中使用 NSOperation的优点是对线程的高度抽象,会使得项目的程序结构更好,
子类化NSOperation是具有面向对象的优点, 复用 和 封装
使得实现是多线程支持, 接口简单, 适合在复杂的项目中使用,
GCD的优点是 本身非常简单, 易用, 对于不复杂的多线程操作, 会节省代码量, 而Block参数的使用, 使代码更为易读, 适合在简单项目中使用,
GCD是纯C语言的API. NSOperationQueue 是基于GCD的 OC 版本的封装
GCD只支持 FIFO 队列, 即先入先出, NSOperationQueue 可以调整执行顺序, 设置最大并发数量, 可以在Operation 间设置依赖关系,
而GCD需要写很多代码才能实现依赖.
NSOperationQueue 支持KVO 可以检测operation 是否正在执行, 是否结束, 是否取消,
GCD的执行速度比NSOperationQueue快. 任务间不太互相依赖,
5.7. NSOperation. GCD. NSThread 的区别?
NSOperation 与 GCD 的区别:
GCD:
GCD 纯C
GCD 是将任务添加到队列 *( 队列有 串行 并行 全局 主队列 ) 并且以 同步 或 异步的方式执行任务函数
GCD提供NSOperation 不具备的功能有以下:
一次性执行
延迟执行
调度组
GCD 是严格的队列. 先进先出. FIFO
NSOperation:
NSOperation 在 iOS 2.0 推出. 4.0 重写
NSOperation 将操作 ( 异步任务 )添加到队列 ( 并发队列 ) 就会执行指定的函数
NSOperation 提供的方便操作有以下:
最大并发数
队列暂停和继续
取消所有的操作
指定操作之间的依赖关系. 可以让异步任务同步执行
可以利用KVO监听一个任务是否完成
可以设置任务的优先级, 能使同一个并行队列中的任务区分先后地执行
对NSOperation 继承, 在这之上添加成员变量和成员方法, 提高代码的复用度
GCD 与 NSThread 的区别:
NSThread 使用 @selector 指定要执行的方法. 代码分散
GCD通过 block 指定执行的方法, 代码集中
GCD 不用管理线程的生命周期 ( 创建 销毁 复用 )
如果要开多个线程 NSThread 必须实例化多个线程对象
NSThread 通过 performSelector 方法实现线程间通信
为什么要取消和恢复队列?
一般内存警告后 取消队列中的操作
为保证ScrollView 在滚动时候的流畅, 通常在滚动开始时, 暂停队列中的所有操作, 滚动结束后, 恢复操作.
5.8 . 事件响应的过程?
苹果注册了一个 Source1 (基于 mach port 的) 用来接收系统事件,其回调函数为 __IOHIDEventSystemClientQueueCallback()。
当一个 触摸/锁屏/摇晃/等事件发生后. 首先是由 IOKit. Framework 生成一个 IOHIDEvent 事件 并由SpringBoard 接收.
这个过程的详细情况可以参考这里 SpringBoard 只接收按键 (锁屏 / 静音等) 触摸, 加速, 接近传感器等几种Event.
随后用 mach port 转发给需要的app 进程 随后苹果注册的 source1 就会触发回调, 并调用 _UIApplicationHandleEventQueue() 进行应用内部的分发.
_UIApplicationHandleEventQueue() 会把 IOHIDEvent 处理并包装成 UIEvent 进行处理或分发,
其中包括识别 UIGesture/处理屏幕旋转/发送给 UIWindow 等。通常事件比如 UIButton 点击、touchesBegin/Move/End/Cancel 事件都是在这个回调中完成的
5.9 什么是异步绘制?
异步绘制 就是可以在子线程把需要绘制的图形, 提前在子线程处理好,
将准备好的图像数据直接返给主线程使用, 这样可以降低主线程的压力.
5.10 能否向运行时创建的类中添加实例变量?
1. 编译好的类 不能添加实例变量
2. 运行时创建的类可以添加到实例变量, 但若已注册到内存中就不行.
原因:
1.编译好的实例变量存储的位置在 ro. 而 ro 是在编译时就已经确定了的.
2.编译完成, 内存结构就完全确定 无法修改
3.只能修改 rw 中的方法, 或者可以通过关联对象的方式来添加属性,
5.11 例举下你所知道的线程同步策略?
1. OSSpinLock 自旋锁, 已不再安全, 除这个锁, 下列锁在等待时, 都会进入线程休眠状态, 而非忙等.
2. os_unfair_lock atomic就是使用此锁来保证原子性的.
3. pthread_mutex_t 互斥锁. 并且支持递归实现和条件实现,
4. NSLock, NSRecursiveLock. 基本的互斥锁. 后者支持递归调用, 都是对 pthread_mutex_t 的封装.
5. NSCondition. NSConditionLock. 条件锁. 对pthread_mutex_t 的封装,
6. dispatch_semaphore_t 信号量.
7. @synchronized 也是对 pthread_mutex_t 的封装.
5.12 Runtime 如何实现 weak 变量的自动置为nil 功能?
Runtime 在注册和初始化一个类时. 当一个属性被修饰为 weak 时, 会将 weak 变量指向的地址作为 Value 放入一张 hash 表中.
将 weak变量的值 作为key. 这样形成一个 key - value 的键值对.
当引用计数变为 0 的时候. 系统通过 key - value 查找指向weak 变量的地址,. 将变量赋值为 nil.
5.13 atomic 的实现机制是怎样的? 为什么不能保证绝对的线程安全?
atomic 是在setter和getter方法里会使用自旋锁 spinlock_t 来保证setter 和 getter方法的线程安全, 可以看做是 getter方法获取到返回值之前不会执行setter方法里的赋值代码,
如果不加 atomic. 可能在getter方法读取的过程中, 在别的线程已经发生setter操作, 从而出现异常值,
atomic 不能绝对的保证线程安全, 因为出了getter和setter方法后就不能继续保证线程安全
5.14 RunLoop 的作用是什么?
字面意思是 消息循环, 运行循环, runloop内部实际上就是一个 do-while循环, 它在循环监听各种事件源, 消息, 对他们进行管理并分发给线程来执行. 主要作用有:
1.通知观察者将要进入运行循环, 线程和RunLoop之间是一一对应的.
2.通知观察者将要处理计时器
3.通知观察者任何非基于端口的输入源即将被触发.
4.触发任何准备触发的基于非端口的输入源
5.如果基于端口的输入源准备就绪并等待触发, 请立即处理该事件, 转到第9步
6.通知观察者线程即将睡眠
7.将线程置于睡眠状态, 直到发生以下事件之一:
- 事件到达基于端口的输入源
- 计时器运行
- 为运行循环设置的超时值到期
- 运行循环被明确唤醒
8. 通知观察者线程被唤醒
9. 处理待处理事件
- 如果触发了用户定义的计时器, 则主力计时器事件并重新启动循环, 转到第二步,
- 如果输入源被触发, 则传递事件,
- 如果运行循环被明确唤醒但尚未超时, 请重新启动循环. 转到第二步.
10. 通知观察者运行循环已退出.
6.0 底层原理相关
6.1 KVO原理
在使用KVC 命名约定时. 当你观察一个对象时, 一个新的类会被动态创建,
这个类 继承自该对象原本的类. 并重写了被观察属性的 setter方法. 重写的 setter 方法 会负责在调用 原setter 方法之前和之后, 通知所有观察对象 值的更改,
最后 通过 isa 混写 (isa-swizzling) 把这个对象的isa 指针 指向这个新创建的子类. 对象就神奇的变成了新创建的子类的实例.
键值观察通知依赖于 NSObject 的两个方法. willChangValueForKey 和 didChangvlueForkey .
在一个被观察属性发生改变之前, willChangValueForKey 一定会被调用, 继而. didChangvlueForkey 也会被调用,
6.2. SDWebImage 底层原理?
1.SDWebImageManager 根据URL 开始处理图片
2.先根据URL作为下标从缓存查找图片是否已经下载.
3.如果本地缓存图片中有图片, SDImageCacheDelegate 直接调用图片显示.
4.如果本地缓存中没有, 则开始通过URL为key 从本地磁盘查找图片是否已经被缓存至硬盘.
5.本地磁盘如果存在图片, 则通过 SDWebImageManagerDelegate 到 UIImageView+WebCache 展示图片
6.如果本地磁盘不存在图片, 说明所有缓存都不存在该图片, 需要下载,
7.生成下载器 SDWebImageDownloader 开始向服务器请求下载,
8.图片下载由 NSURLConnection 来做,实现相关 delegate 来判断图片下载中、下载完成和下载失败。
9.数据下载完成后交给 SDWebImageDecoder 做图片解码处理。
10.通知所有的 downloadDelegates 下载完成,回调给需要的地方展示图片。
11.将图片以URL为下标 . 保存到 SDImageCache 中,内存缓存和硬盘缓存同时保存。
6.3 SDImageCache 是怎么做数据管理的?
SDImageCache 分为两个部分:一个是内存层面的,一个是硬盘层面的。
内存层面的相当是个缓存器. 以Key-Value的形式存储图片。当内存不够的时候会清除所有缓存图片。
用搜索文件系统的方式做管理,文件替换方式是以时间为单位,剔除时间大于一周的图片文件。
当SDWebImageManager 向 SDImageCache要资源时,先搜索内存层面的数据,如果有直接返回,没有的话去访问磁盘,
将图片从磁盘读取出来,然后做Decoder,将图片对象放到内存层面做备份,再返回调用层。
7.0 项目或代码 优化相关
7.1 项目优化可以从哪几个方面着手??
CPU 和 GPU 优化
CPU优化:
尽量用轻量级的对象, 不需要处理事件的地方, 用CALayer 取代UIView.
尽量避免频繁调用 UIView 的属性, 如 frame bounds transform 等属性,
提前计算好布局, 在有需要时一次性调整对应的布局, 避免多次修改
Autolayout 会比直接设置 frame 消耗更多的 CPU 资源
图片的 size 最好和 UIImageView 的size保持一致
控制线程的最大并发数量
耗时的操作放在子线程
图片的处理. ( 解码 绘制 等)
GPU 优化:
尽量减少视图数量和层次.
尽量避免短时间内大量图片的显示, 将多张图片合成一张大图显示.
减少透明的视图, 不透明的设置 opaque 为yes
尽量避免离屏渲染
卡顿优化,
耗电优化,
app 启动优化.
安装包瘦身
7.2. UITableView 的优化
* 重用 cell
* 缓存行高
* 加载网络数据. 下载图片, 使用异步加载 并缓存,
从网络请求回来的图片先根据显示的图片大小切成合适大小的图, 每次只显示处理过大小的图片, 当查看大图时再显示大图,
图片数量过多时, 必要的时候需要准备好预览图和高清图, 需要时再加载高清图, 图片的懒加载方法, 当滚动速度很快时避免频繁请求服务器数据,
* 使用局部刷新, 尽量不要使用 reloadData 刷新某一个分组 某一行的局部刷新效率要高于全部刷新
* 渲染, 尽量少用或不使用透明图层 将 cell 的 opaque 值设为 yes, 背景色 和 子 view 不要使用 clearColor. 尽量不要使用阴影渐变, 透明度不要设置为 0
* 少用 addSubview 给cell 动态添加子view, 初始化时直接设置好, 通过 hidden 控制显示隐藏, 布局也在初始化时直接布局好, 避免cell 的重新布局,
* 如果 cell 内的显示的内容 来自 web, 使用异步加载, 缓存请求结果
* 按需加载cell cell滚动很快时, 只加载范围内的cell.
如果目标行与当前行相差超过指定行数, 只在目标滚动范围的前后指定 n 行加载, 滚动很快时, 只加载目标范围内的cell. 按需加载. 可以极大的提高流畅性,
* 类似于朋友圈涉及图文混排的复杂界面, 需要异步绘制, 异步绘制方法: 继承 UITableViewCell 给自定义 Cell 添加 draw 方法.
* 在方法中利用 GCD 异步绘制, 或者直接重写 drawRect 方法. 重写drawRect 不需要再使用GCD 异步线程, 因为 drawRect本身就是异步绘制.
7.3 如何对代码进行 性能优化?
1. 利用性能分析工具检测. 包括静态 Analyze 工具 , 以及运行时 profile 工具, 通过Xcode工具栏中 Product —> Profile 可以启动.
2. 测试程序的启动时间, 点击 Time Prefiler 应用程序开始运行后, 就能获取整个应用程序运行消耗时间分布 和 百分比,
为了保证数据分析在统一使用场景的真实. 要注意一定要使用真机. 模拟器此时是运行在 MAC 上. 而 Mac 上的 cpu 往往比 iOS 设备快.
3. 为防止一个应用占用过多的系统资源, 工程师设计了一个 “看门狗”机制. 在不同场景下, 看门狗 会检测应用的性能,
如果超出了该场景所规定的运行时间, 看门狗 就会强制终结这个应用的进程, 开发者在 crashlog 里面. 会看到如: 0x8badf00d 这样的错误代码.
7.4 如何有效降低 APP包的大小?
降低包大小 需要从两个方面着手
1. 可执行文件
包括:
- 编译器优化
- 利用 AppCode 检测未使用的代码: 菜单栏 -> Code -> Inspect Code
- 编写 LLVM 插件检测出重复代码. 未被调用的代码
2. 资源
包括: 图片. 音频. 视频 等.
- 优化的方式可以对资源进行无损压缩,
- 去除没有用到的资源
8.0 网络相关
8.1. TCP 和 UDP的区别
TCP: 传输控制协议, 提供的是面向连接 可靠的字节流服务. 客户端和服务端在交换数据之前, 必须先在双方建立一个 TCP 连接, 一个TCP 连接必须经过三次握手才能建立.
TCP 提供 超时重发, 丢弃重复数据, 检验数据, 流量控制等功能, 保证数据能从一端 传到另一端.
UDP: 用户数据报协议, 是面向数据报的运输层协议.
UDP 是面向 非连接的协议, 它不与对方建立连接, 而是只负责把数据包发送过去, 不提供可靠性, 可靠性不高,
由于不需要在客户端与服务端建立连接, 并且没有超时重发等机制, 因此传输速度 很快.
差别在于:
tcp —- 面向连接 udp —- 面向非连接
Tcp —- 传输可靠 udp —- 传输不可靠
tcp —- 传输大量数据 udp ——传输少量数据
tcp —— 速度慢 udp —— 速度快
8.2. Socket 和 http 连接的区别
socket 连接 和 http 连接的区别:
http 是基于socket 之上的, socket 是一套完整的 tcp udp 协议的接口
HTTP 协议: 简单对象访问协议, 对应于应用层, HTTP 协议是基于 TCP连接的.
TCP 协议: 对应于传输层
IP 协议: 对应于网络层
TCP/IP 是传输层协议, 主要解决数据如何在网络中传输, 而 HTTP 是应用层协议, 主要解决如何包装数据,
Socket 是对 TCP/IP协议的封装, Socket 本身并不是协议, 而是一个调用接口, 通过Socket 我们才能使用TCP/IP 协议.
HTTP 连接: 短连接, 即客户端向服务端发送一次请求, 服务端响应后连接, 请求结束后, 会主动释放连接.
Socket连接: 长连接, 理论上客户端和服务端一旦建立连接将不会主动断掉. 但由于各种因素可能会断开.
例如: 服务端或客户端主机挂掉了. 网络故障, 或两者之间长时间没有数据传输, 网络防火墙可能会断开该连接以释放网络资源,
所以当一个 Socket 连接中没有数据的传输, 为了 维持连接 需要发送 心跳包.
8.3. AFN 是如何实现断点续传的??
1.检查服务端文件信息
2.检查本地文件
3.如果本地文件比服务端文件小, 断点续传, 利用 HTTP 请求头的 Range 实现断点续传
4.如果比服务端文件大, 说明本地文件错误, 重新请求下载
5.如果和服务端文件一样, 下载完成
AFN 默认超时 时间 为 60s
8.4 项目中网络层如何做安全处理?
* 尽量使用 https
* 不要传输明文密码
* post 并不比 get 安全 事实上, post 和 get 一样不安全, 都是明文 参数放在 queryString 或者 body 没有任何安全上的差别,
在http的环境下. 使用post 或者 get 都需要做加密和签名处理.
* 不要使用301 跳转. 301 跳转很容易被 http 挟持攻击, 移动端 http 使用 301 比桌面端更危险,
* http请求都带上 MAC.
* http请求使用临时密钥
* AES 使用 CBC模式
8.5 HTTPS 协议 与 HTTP 协议有什么区别与联系?
HTTPS 协议是由 SSL + HTTP 协议构建的可进行加密传输身份认证的网络协议. 要比 http协议安全.
HTTPS 安全超文本传输协议 . 它是一个安全通信通道. 基于 HTTP 开发. 用于在 客户计算机 和服务器之间交换信息,.
它使用安全套接字层 ( SSL ) 进行信息交换, 简单来说 它是 HTTP 的安全版,
区别在于:
https协议需要用到 ca 申请证书, 一般需要交费.
http 是超文本传输协议, 信息是明文传输, https 则是具有安全性的 SSL 加密传输协议
http 和 https 使用的是完全不同的连接方式, 用的端口也不一样. http 是 80 . Https 是443
http 的连接很简单. 无状态.
8.6 NSURLConnection和NSURLSession的区别
1.异步请求不需要NSOperation 包装
2.支持后台运行的网络任务 ( 后台上传下载 )
3.根据每个Session做配置 ( httpHeader cache , Cookie 等 ) 不再在整个 APP 层面共享配置
4.支持网络操作的取消和断点续传, (继承系统类, 重写main 方法. )
5.改进了授权机制的处理.
8.7 APNS推送机制的大体能分为哪几个阶段?
第一阶段:应用程序的服务器端把要发送的消息、目的iPhone的标识打包,发给APNS。
第二阶段:APNS在自身的已注册Push服务的iPhone列表中,查找有相应标识的iPhone,并把消息发送到iPhone。
第三阶段:iPhone把发来的消息传递给相应的应用程序,并且按照设定弹出Push通知。
9.0 Swift 相关
9.1 Swift 内联函数是什么? 在什么情况下会不起作用?
内联的前提是确定调用的函数体内容, 将函数调用展开成函数体
以下情况函数不会被内联
1> 函数体比较长
2> 包含了递归调用.
3> 包含了动态派发.
9.2 举例说明Swift 里面有哪些类型是 OC 中没有的?
Swift 引入了在Object-C 中没有的一些高级数据类型,
例如:
1.tuples 元组. 可以创建和传递一组数值.
2.Optionals 可选项类型 用于处理变量值不存在的情况,
9.3 Swift 中如何阻止方法, 属性 下标 被子类改写?
在类的定义中使用 final 关键字声明类, 属性, 方法和下标,
final 声明的类不能被继承, final 声明的属性方法和下标 不能被重写.
如果只是限制一个方法或者属性不被重写, 只需要在该方法和该属性前加一个 final
如果需要限制整个类无法被继承, 那么可以在类名之前加一个 final
9.4 Swift 中 closure 和 OC 中的 Block 有什么区别?
1.closure 是匿名函数. block 是一个结构体对象
2.closure 通过逃逸闭包来在内部修改变量, block 通过 __block 修饰符
9.5 什么叫 逃逸闭包? 如何让一个 自动闭包可以”逃逸”?
逃逸闭包: 一个传入函数的闭包, 如果在函数执行结束之后才会被调用, 那么这个闭包就称为逃逸闭包.
如果想让自动闭包可以”逃逸”, 需要同时使用 @autoclosure 和 @escaping 进行修饰.
9.6 Swift 中 Class (类 )和 Struct ( 结构体 ) 的区别?
1. Class 是引用类型. Struct 是值类型.
2. 类可以被继承 , 结构体不可以继承
3. 值类型 struct 被赋予给一个变量, 常量 或者被传递给一个函数的时候, 其值会被拷贝,
4. 引用类型 class 在被赋予一个变量 常量 或者传递到一个函数时 , 其值不会被拷贝,
- 因此. 引用的是已存在的实例本身 而不是其拷贝.
9.7 Swift 是面向对象语言 还是面向过程的函数式编程语言?
Swift 既是面向对象的, 又是函数式的编程语言,
因为Swift 支持类的封装, 继承, 和多态 所以是面向对象的.
又因 Swift 支持 map. Reduce. filter, flatmap 这类去除中间状态.数学函数式的方法, 所以也支持面向过程编程.
9.8 什么是 POP 网络? 有了 Alamofire 封装网络URLSession. 为什么还要用 Moya?
POP网络: 面向协议编程的网络能够大大降低耦合度, 网络层下沉, 业务层上浮.
中间利用POP网络的Moya 隔开, 如果项目是RxSwift函数响应式的也没有关系,
10. 设计模式相关
10.1 MVVM 相比 MVC 有哪些优缺点?
优点:
MVVM 是在 MVC的基础上加入一个视图模型 ViewModel , 用于数据有效性的验证,
视图的展示逻辑, 网络数据请求及处理. 其他的数据处理逻辑,并定下相关接口和协议,
相比MVC, MVVM中 VC 的职责和复杂度更小, 对数据处理逻辑的测试更加方便,
对bug的原因排查更加方便, 代码可阅读性,重用性和可维护性更高, MVVM耦合性更低,
MVVM 不同层级的职责更加明确, 更有利于代码的编写和团队的写作,
*缺点:
相对比MVC代码量有所增加, MVVM相比MVC 在代码编写之前需要更清晰的模式思路.
10.2 对单例的理解?
在OC中. 实现一个单例, 需要完成以下4个步骤:
为单例对象实现一个静态实例. 并初始化, 然后设置为nil
实现一个实例构造方法检查上面声明的静态实例是否为nil, 如果是 则新建并返回一个本类的实例.
重写 allocWithZone 方法, 用来保证其他人直接使用alloc 和 init 试图获得一个新实例的时候 不产生新实例
适当实现 allocWithZone , copyWithZone. release 和 autorelease
10.3 看过哪些第三方框架的源码,它们是怎么设计的?
答案依据个人的感触
OC常用框架有: AFNetwork. SDWebImage. YYModel. YYKit. MJRefresh等
Swift常用框架: Alamofire Moya RxSwift. SwiftJson, SnapKit 等.
参考链接:iOS面试题 从底层到算法
参考链接:iOS进阶面试题总结
参考链接:iOS面试题及答案