【iOS-2】

209 阅读1小时+

原文

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类型对应于动态空间分配和静态空间分配。最常见的例子是数组和字典。
例如 NSArrayNSMutableArray,前者为静态数组,初始化后长度固定,不可以再动态添加新元素改变数组长度;
后者为动态数组,可以动态添加或者删除元素,动态申请新的空间或释放不需要的空间,伸缩数组长度。

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 的语义是如果你没有手动实现 settergetter 方法, 编译器会自动加上这两个方法
3. @dynamic 是告诉编译器. 属性的 settergetter 方法 由用户自己实现, 不自动生成,    (  对于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 这时就会调用这个方法. 给你把这个消息转发给其他对象的机会, 
   只要这个方法返回的不是nilself.  整个消息发送的过程就会被重启.  当然 发送的对象会变成你返回的那个对象. 
   否则. 就会继续 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 底层实现原理 )步骤如下:
      - 检查是否存在相应 keyset 方法,  存在就调用 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 是一个对象, 可以使用 copystrong  修饰, 延续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 的反射机制?

**

在动态运行下我们可以构建任何一个类,然后我们通过这个类知道这个类的所有的属性和方法,
并且如果我们创建一个对象,我们也可以通过对象找到这个类的任意一个方法,这就是反射机制。
比如NSClassFormStringNSStringFormSelectorNSSelectorFormString

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 的新对象,  然后释放旧对象, 新的对象 retain1, 与旧有对象引用计数 无关, 减少了对象对上下文的依赖. 
retain:  释放旧的对象, 将旧对象的值赋予输入给新对象,  再提高输入对象的索引计数 为 1,  新对象和旧对象的指针相同. 

4.9 readwrite readonly assign retain copy weak strong nonatomic 属性的作用? 即声明属性时可修饰属性特性的修饰词作用?

**

1.getter = getterName , setter  = setterName  设置 gettersetter的方法名
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 都可以实现对线程的管理. 
区别是 NSOperationNSOperationQueue 是多线程的 面向对象抽象.  
项目中使用 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 是在settergetter方法里会使用自旋锁 spinlock_t 来保证settergetter方法的线程安全, 可以看做是 getter方法获取到返回值之前不会执行setter方法里的赋值代码,
如果不加 atomic. 可能在getter方法读取的过程中, 在别的线程已经发生setter操作, 从而出现异常值, 
atomic 不能绝对的保证线程安全, 因为出了gettersetter方法后就不能继续保证线程安全

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. 架构模式相关

## 架构模式(Architectural Pattern)

架构模式(Architectural Pattern)是软件架构中在给定环境下,针对常遇到的问题的、通用且可重用的解决方案。它类似于软件设计模式但覆盖范围更广,致力于软件工程中不同问题,如计算机硬件性能限制、高可用性、业务风险极小化。一些架构模式会透过软件框架实现。

架构模式的作用是:

-   提高软件的可复用性:架构模式提供了通用的解决方案,可以减少开发者的重复劳动,提高软件的开发效率。
-   提高软件的可维护性:架构模式使软件的结构更加清晰,易于理解和维护。
-   提高软件的可扩展性:架构模式使软件更容易扩展,以满足新的需求。
-   提高软件的可靠性:架构模式可以帮助避免常见的软件架构问题,提高软件的可靠性。

## 常见的架构模式

以下是一些常见的架构模式:

-   **分层模式(Layered Architecture)** :将软件架构划分为多个层,每层都有自己的职责和接口。这种模式可以提高软件的可复用性和可维护性。
-   **MVC模式(Model-View-Controller)** :将软件架构划分为模型(Model)、视图(View)和控制器(Controller)三个层。这种模式可以提高软件的可维护性和可测试性。
-   **MVP模式(Model-View-Presenter)** :将软件架构划分为模型(Model)、视图(View)和演示者(Presenter)三个层。这种模式可以提高软件的可维护性和可测试性。
-   **微服务架构(Microservices Architecture)** :将软件架构划分为多个独立的服务,每个服务都有自己的职责和接口。这种模式可以提高软件的可扩展性和可部署性。

## 如何选择架构模式

选择合适的架构模式取决于具体的软件项目。在选择架构模式时,需要考虑以下因素:

-   软件的规模和复杂性
-   软件的性能和可靠性要求
-   软件的可扩展性和可维护性要求
-   开发团队的经验和技能

## 架构模式的应用

架构模式可以应用于各种软件开发项目,包括 Web 应用、移动应用、桌面应用和嵌入式系统等。在实际应用中,可以根据需要选择合适的架构模式,并结合具体的项目情况进行调整和优化。

以下是一些架构模式的应用示例:

-   **分层模式**常用于大型企业级应用的开发,可以提高软件的可复用性和可维护性。
-   **MVC模式**常用于 Web 应用和移动应用的开发,可以提高软件的可维护性和可测试性。
-   **MVP模式**常用于具有复杂业务逻辑的应用开发,可以提高软件的可维护性和可测试性。
-   **微服务架构**常用于大型分布式应用的开发,可以提高软件的可扩展性和可部署性。

架构模式是软件架构设计的重要工具,可以帮助开发人员构建更易于理解、维护和扩展的软件系统。

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 等. 

作者:Tomboy_Anan
链接:www.jianshu.com/p/a2e31bfde…
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

11、设计模式

设计模式(Design Pattern)是软件设计中常见问题的通用解决方案。它们是经过反复实践和验证的最佳实践,可以帮助开发人员编写更易于理解、维护和扩展的代码。

设计模式通常分为以下三类:

  • 创建型模式(Creational Patterns):用于创建对象的模式。例如,工厂模式(Factory Pattern)、单例模式(Singleton Pattern)、抽象工厂模式(Abstract Factory Pattern)等。
  • 结构型模式(Structural Patterns):用于组织类的模式。例如,适配器模式(Adapter Pattern)、桥接模式(Bridge Pattern)、装饰器模式(Decorator Pattern)等。
  • 行为型模式(Behavioral Patterns):用于描述对象之间通信的模式。例如,观察者模式(Observer Pattern)、策略模式(Strategy Pattern)、模板方法模式(Template Method Pattern)等。

以下是几个常用的设计模式:

  • 工厂模式:用于创建对象的模式。工厂模式可以将对象的创建过程封装在一个类中,并提供一个创建对象的接口。这样,客户端代码无需了解对象的创建细节,只需调用工厂类的接口即可创建对象。
  • 单例模式:用于确保一个类只有一个实例的模式。单例模式通常用于创建全局共享的对象。例如,数据库连接池、日志记录器等。
  • 抽象工厂模式:用于创建一组相关或相互依赖的对象的模式。抽象工厂模式提供一个创建对象的接口,但具体的创建过程由子类实现。这样,客户端代码无需了解具体的创建过程,只需调用抽象工厂类的接口即可创建对象。
  • 适配器模式:用于将一个类的接口转换为另一个类兼容的接口的模式。适配器模式通常用于将不兼容的类或系统集成在一起。
  • 桥接模式:用于将对象的接口和实现解耦的模式。桥接模式可以使对象的可扩展性更好。
  • 装饰器模式:用于向一个对象添加新功能的模式。装饰器模式可以为对象动态添加功能,而无需修改对象的本身。
  • 观察者模式:用于定义一对多依赖关系的一种模式,让一个对象状态的变化可以通知所有依赖它的对象。
  • 策略模式:用于将一个类的行为封装成一个对象,从而使类的行为可以替换。策略模式可以使类的可扩展性更好。
  • 模板方法模式:用于定义一个算法骨架,并允许子类在不改变算法骨架的情况下添加或修改算法的某些步骤。模板方法模式可以使类的可扩展性更好。

设计模式是软件设计中非常重要的概念,学习设计模式可以帮助开发人员编写更易于理解、维护和扩展的代码。

以下是一些学习设计模式的资源:

12、SOLID五大原则

image.png

www.jianshu.com/p/e95baecb3…

www.jianshu.com/p/a96e313ff…

在面向对象编程中,SOLID 是指五个基本原则,用于指导软件的设计和开发。这五个原则分别是:

  1. SRP(Single Responsibility Principle):单一职责原则

    SRP 原则规定,一个软件模块应该只做一件事,并且应该只有一个被更改的理由。这意味着一个模块应该只负责一个特定的功能,而不应该承担多个功能或职责。

    遵循 SRP 原则可以使代码更加易于理解、维护和扩展。如果一个模块只负责一个功能,那么它的代码就更容易理解和维护。此外,如果需要修改该模块的功能,那么只需要修改与该功能相关的代码即可,而不会影响其他功能。

  2. OCP(Open-Closed Principle):开闭原则

    OCP 原则规定,软件实体应该对扩展开放,对修改关闭。这意味着软件应该能够在不修改现有代码的情况下进行扩展。

    遵循 OCP 原则可以使代码更加灵活和可扩展。如果软件能够在不修改现有代码的情况下进行扩展,那么就更容易满足新的需求。此外,OCP 原则还可以使代码更加稳定,因为修改现有代码可能会引入错误。

  3. LSP(Liskov Substitution Principle):里氏替换原则

    LSP 原则规定,任何基类可以出现的地方,子类一定可以出现。这意味着子类应该能够完全替换其基类,并且不会产生任何意外的行为。

    遵循 LSP 原则可以使代码更加可靠和可预测。如果子类能够完全替换其基类,那么就可以在基类使用的地方使用子类,而不会产生任何意外的行为。此外,LSP 原则还可以使代码更加易于调试,因为如果出现错误,那么更容易找到错误的原因。

  4. ISP(Interface Segregation Principle):接口隔离原则

    ISP 原则规定,不应该强迫客户实现他们不使用的方法。这意味着一个接口应该只包含那些其所有客户都需要的功能。

    遵循 ISP 原则可以使代码更加易于使用和维护。如果一个接口只包含那些其所有客户都需要的功能,那么客户就只需要实现他们使用的功能即可。此外,ISP 原则还可以使代码更加灵活,因为可以根据需要创建新的接口。

  5. DIP(Dependency Inversion Principle):依赖反转原则

    DIP 原则规定,高层次的模块不应该依赖于低层次的模块,两者都应该依赖于抽象接口。抽象接口不应该依赖于具体实现,而具体实现则应该依赖于抽象接口。

    遵循 DIP 原则可以使代码更加灵活和可测试。如果高层次的模块和低层次的模块都依赖于抽象接口,那么就可以很容易地替换一个模块的具体实现。此外,DIP 原则还可以使代码更加易于测试,因为可以很容易地隔离和测试单个模块。

SOLID 原则是面向对象编程中非常重要的原则,遵循这些原则可以使代码更加易于理解、维护、扩展、灵活和可靠。

IM

blog.csdn.net/u012496940/…

IM 面试题

IM面试题涵盖的范围很广,具体涉及哪些问题取决于具体的职位和公司。但总的来说,IM面试题可以分为以下几类:

1. 基础知识

  • 了解 IM 系统的基本架构,包括客户端、服务器端、消息传递协议等。
  • 熟悉常见的 IM 协议,例如 TCP/IP、WebSocket、XMPP 等。
  • 了解 IM 系统的常见功能,例如消息发送、接收、存储、转发等。
  • 了解 IM 系统的性能和安全问题,例如如何保证消息的实时性和可靠性,如何防止恶意攻击等。

2. 技术能力

  • 能够使用编程语言开发 IM 客户端或服务器端程序。
  • 熟悉数据库技术,能够使用数据库存储和管理 IM 数据。
  • 熟悉网络编程技术,能够实现 IM 系统的网络通信。
  • 熟悉算法和数据结构,能够优化 IM 系统的性能。

3. 项目经验

  • 有过 IM 系统开发或运维经验者优先。
  • 参与过开源 IM 项目者优先。
  • 有过互联网大厂 IM 项目经验者优先。

4. 软实力

  • 良好的沟通能力和团队合作能力。
  • 能够独立思考和解决问题。
  • 有学习新技术的能力。
  • 对 IM 行业有热情。

以下是一些常见的 IM 面试题:

  • 请简述 IM 系统的基本架构。
  • 请解释 TCP/IP 协议和 WebSocket 协议的区别。
  • 请描述 IM 系统的消息传递流程。
  • 如何保证 IM 系统的消息实时性和可靠性?
  • 如何防止 IM 系统遭受恶意攻击?
  • 请用 Java 或 Python 实现一个简单的 IM 客户端程序。
  • 请设计一个 IM 系统的数据库结构。
  • 如何优化 IM 系统的性能?
  • 请分享你对 IM 行业未来的看法。

在准备 IM 面试时,除了要熟悉上述知识和技能之外,还需要注意以下几点:

  • 了解目标公司的 IM 产品和技术。
  • 准备一些与 IM 相关的项目案例。
  • 练习一些常见的 IM 面试题。
  • 保持自信,积极沟通。

以下是一些额外的资源,可以帮助你准备 IM 面试:

希望这些信息对你有所帮助。

以下是一些针对特定 IM 系统的面试题:

微信面试题:

  • 请解释微信朋友圈的功能实现原理。
  • 如何优化微信朋友圈的加载速度?
  • 如何防止微信朋友圈刷屏?
  • 如何设计微信公众号的推荐算法?
  • 微信支付的安全机制有哪些?

钉钉面试题:

  • 请解释钉钉群聊的功能实现原理。
  • 如何优化钉钉群聊的性能?
  • 如何防止钉钉群聊中的垃圾信息?
  • 如何设计钉钉的办公协作功能?
  • 钉钉的安全机制有哪些?

企业微信面试题:

  • 请解释企业微信外部联系人的功能实现原理。
  • 如何优化企业微信外部联系人的搜索功能?
  • 如何防止企业微信外部联系人泄露公司信息?
  • 如何设计企业微信的客户关系管理功能?
  • 企划微信的安全机制有哪些?

希望这些信息对你有所帮助。




1. 请简述 IM 系统的基本架构。

IM 系统的基本架构通常包括以下几个组件:

  • ****客户端: 安装在用户设备上的应用程序,用于用户与 IM 系统进行交互。
  • ****服务器端: 负责存储用户数据、处理消息传递、管理连接等。
  • ****消息传递协议: 用于在客户端和服务器端之间传输消息的协议。
  • ****数据库: 用于存储用户数据、消息数据等。

2. 请解释 TCP/IP 协议和 WebSocket 协议的区别。

TCP/IP 协议是一种面向连接的协议,它在客户端和服务器端之间建立一个连接,然后在这个连接上进行数据传输。TCP/IP 协议具有可靠性高、传输速度快的优点,但它也存在头部开销大、资源消耗高的缺点。

WebSocket 协议是一种基于 TCP 的全双工协议,它允许客户端和服务器端在建立连接后进行持续的双向通信。WebSocket 协议具有头部开销小、资源消耗低的优点,但它也存在可靠性较低的缺点。

3. 请描述 IM 系统的消息传递流程。

IM 系统的消息传递流程通常如下:

  1. 用户在客户端输入消息并点击发送按钮。
  2. 客户端将消息封装成数据包,并通过消息传递协议发送到服务器端。
  3. 服务器端收到消息包后,解析消息并将其存储到数据库中。
  4. 服务器端根据消息的收件人,将消息转发到相应的客户端。
  5. 收件人客户端收到消息包后,解析消息并将其显示在界面上。

4. 如何保证 IM 系统的消息实时性和可靠性?

为了保证 IM 系统的消息实时性,可以使用以下方法:

  • 使用 WebSocket 协议进行消息传输。
  • 使用消息队列来缓冲消息。
  • 使用分布式服务器来分担负载。

为了保证 IM 系统的消息可靠性,可以使用以下方法:

  • 使用 TCP/IP 协议进行消息传输。
  • 为消息添加校验码。
  • 使用消息重传机制。

5. 如何防止 IM 系统遭受恶意攻击?

为了防止 IM 系统遭受恶意攻击,可以使用以下方法:

  • 对用户进行身份验证和授权。
  • 对消息进行加密和签名。
  • 使用防火墙和入侵检测系统进行防护。
  • 定期更新软件和系统漏洞。

7. 请设计一个 IM 系统的数据库结构。

IM 系统的数据库结构需要根据具体的需求进行设计,但一般会包含以下几个表:

  • ****用户表: 存储用户信息,例如用户 ID、用户名、密码等。

  • ****好友表: 存储用户的好友关系。

  • ****消息表: 存储消息数据,例如消息 ID、发送者 ID、接收者 ID、消息内容等。

  • ****群组表: 存储群组信息,例如群组 ID、群组名称、创建者 ID等。

  • ****群组成员表: 存储群组成员信息,例如群组 ID、用户 ID。

优化 IM 系统性能的方法

IM 系统的性能优化是一个复杂的问题,需要从多个方面入手。以下是一些常见的方法:

1. 使用缓存 :

  • 可以使用缓存来存储经常访问的数据,例如用户信息、好友关系、群组信息等。
  • 可以使用缓存来存储消息,例如最近的消息、离线消息等。
  • 可以使用缓存来存储文件,例如头像、表情等。

2. 使用消息队列 :

  • 可以使用消息队列来缓冲消息,以便在高峰期时能够平滑地处理消息。
  • 可以使用消息队列来异步处理消息,例如发送邮件、推送通知等。

3. 使用分布式服务器 :

  • 可以使用分布式服务器来分担负载,提高系统的处理能力。
  • 可以使用分布式数据库来存储数据,提高数据库的读写速度。

4. 使用压缩技术 :

  • 可以使用压缩技术来压缩消息、文件等,减少网络传输量。
  • 可以使用压缩技术来压缩数据库数据,减少存储空间。

5. 使用高效的算法和数据结构 :

  • 可以使用高效的算法来处理消息,例如消息路由、消息压缩等。
  • 可以使用高效的数据结构来存储数据,例如哈希表、B 树等。

6. 使用合理的网络协议 :

  • 可以使用 WebSocket 协议进行消息传输,减少网络开销。
  • 可以使用 CDN 来分发静态资源,减少网络延迟。

7. 进行性能测试和监控 :

  • 定期进行性能测试,发现系统性能瓶颈。
  • 对系统进行监控,及时发现和解决性能问题。

8. 优化客户端 :

  • 可以优化客户端的代码,减少 CPU 和内存的使用。
  • 可以优化客户端的界面,减少网络请求。

9. 保持软件和系统更新 :

  • 定期更新 IM 软件和系统,修复已知的漏洞和性能问题。

10. 选择合适的硬件和网络 :

  • 选择合适的硬件和网络,能够满足系统的性能需求。

IM 行业未来的看法

IM 行业是一个快速发展的行业,随着移动互联网的发展,IM 系统已经成为人们日常生活中不可或缺的一部分。未来,IM 行业将朝着以下方向发展:

  • ****更加智能化 : IM 系统将利用人工智能技术,提供更加智能化的服务,例如智能客服、智能推荐等。
  • ****更加社交化 : IM 系统将更加注重社交功能,例如直播、游戏等。
  • ****更加安全化 : IM 系统将更加注重安全性和隐私性,使用区块链等技术来保护用户数据。
  • ****更加融合化 : IM 系统将与其他应用更加融合,例如办公、购物等。

我认为,IM 行业在未来将会有更大的发展空间,并将对人们的生活产生更加深远的影响。

以下是一些 IM 行业未来的趋势:

  • ****人工智能的应用 : 人工智能将被应用于 IM 系统的各个方面,例如智能客服、智能推荐、智能翻译等。
  • ****社交功能的增强 : IM 系统将更加注重社交功能,例如直播、游戏、社交电商等。
  • ****安全性和隐私性的提升 : IM 系统将更加注重安全性和隐私性,使用区块链等技术来保护用户数据。
  • ****与其他应用的融合 : IM 系统将与其他应用更加融合,例如办公、购物、金融等。

我相信, IM 行业在未来会变得更加智能化、社交化、安全化和融合化,并将为人们提供更加丰富、便捷的服务。

语音

在 iOS 中,可以使用 WebSockets 技术实现语音发送功能。WebSockets 是一种全双工的通信协议,允许客户端和服务器之间进行实时双向通信,非常适合传输实时语音数据。

以下是一般步骤:

1. 建立 WebSocket 连接:

  • 使用 NSURLSessionWebSocketTask 类来创建 WebSocket 连接。
  • 指定 WebSocket 服务器的 URL 地址和端口号。
  • 设置必要的协议头和参数。
  • 建立连接后,会触发 webSocketTask:didOpen 回调函数,指示连接已成功建立。

2. 准备语音数据:

  • 获取用户语音输入,例如使用 AVAudioRecorder 类录制麦克风声音。
  • 将语音数据转换为可传输的格式,例如 PCM 或 Opus。
  • 将语音数据分段压缩,以便有效传输。

3. 发送语音数据:

  • 使用 webSocketTask:sendData:completionHandler: 方法发送语音数据。
  • 每段语音数据都应作为单独的消息发送。
  • 可以使用 NSData 对象或 UInt8 数组来表示语音数据。

4. 接收语音数据:

  • 在 webSocketTask:didReceiveMessage: 回调函数中接收来自服务器的语音数据。
  • 语音数据通常以 NSData 对象或 UInt8 数组的形式表示。
  • 将接收到的语音数据解压缩并转换为可播放的格式。
  • 使用 AVAudioPlayer 类播放接收到的语音数据。

5. 处理错误和关闭连接:

  • 在 webSocketTask:didFailWithError: 回调函数中处理 WebSocket 连接错误。
  • 在不再需要发送或接收语音数据时关闭连接。
  • 使用 webSocketTask:close 方法关闭连接。

以下是一个简化的示例代码,演示如何在 iOS 中使用 WebSockets 发送语音数据:

Objective-C

#import <Foundation/Foundation.h>
#import <AudioToolbox/AudioToolbox.h>
#import <AVFoundation/AVFoundation.h>

@interface WebSocketViewController : UIViewController <NSURLSessionWebSocketTaskDelegate>

@property (nonatomic, strong) NSURLSessionWebSocketTask *webSocketTask;
@property (nonatomic, strong) AVAudioRecorder *audioRecorder;
@property (nonatomic, strong) AVAudioPlayer *audioPlayer;

- (IBAction)startRecordingButtonTapped:(id)sender;
- (IBAction)stopRecordingButtonTapped:(id)sender;

@end

@implementation WebSocketViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    // 建立 WebSocket 连接
    NSURL *url = [NSURL URLWithString:@"ws://localhost:8080"];
    NSURLSessionWebSocketTask *webSocketTask = [[NSURLSession sharedSession] webSocketTaskWithURL:url];
    webSocketTask.delegate = self;
    [webSocketTask connect];
    self.webSocketTask = webSocketTask;

    // 初始化录音器
    AVAudioSession *audioSession = [AVAudioSession sharedInstance];
    [audioSession setCategory:AVAudioSessionCategoryRecord error:nil];
    [audioSession setActive:YES error:nil];

    NSError *error;
    self.audioRecorder = [[AVAudioRecorder alloc] initWithURL:[NSURL fileURLWithPath:@"/tmp/audio.caf"] settings:[self audioSettings] error:&error];
    if (error) {
        NSLog(@"Error creating audio recorder: %@", error.localizedDescription);
        return;
    }

    // 初始化播放器
    self.audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:@"/tmp/audio.caf"] error:&error];
    if (error) {
        NSLog(@"Error creating audio player: %@", error.localizedDescription);
        return;
    }
}

- (IBAction)startRecordingButtonTapped:(id)sender {
    [self.audioRecorder record];
}

- (IBAction)stopRecordingButtonTapped:(id)sender {
    [self.audioRecorder stop];
    [self.audioRecorder prepareToPlay];

    // 将录音数据发送到服务器
    NSData *audioData = [NSData dataWithContentsOfURL:[NSURL fileURLWithPath:@"/tmp/audio.caf"]];
    [self.webSocketTask sendData:audioData completionHandler:^(NSError *error) {
        if (error) {
            NSLog(@"Error sending audio data: %@", error.localizedDescription);
        }
    }];
}

- (void)webSocketTask:(NSURLSessionWebSocketTask *)webSocketTask didReceiveMessage:(id)message {
    // 接收来自服务器的语音数据
    NSData *audioData = (NSData *)message;

    

音频编码:AAC 和 Opus 的比较

AAC(Advanced Audio Coding)  和 Opus 都是常用的音频编码格式,用于将原始音频数据压缩成更小文件大小的格式,以便存储、传输或流媒体播放。两种格式都具有较高的音质和压缩率,但它们在一些关键方面存在差异:

支持的采样率和比特率:

  • AAC 支持的采样率范围为 8 kHz 到 96 kHz,比特率范围为 32 kbit/s 到 1.5 Mbit/s。
  • Opus 支持的采样率范围为 6 kHz 到 48 kHz,比特率范围为 6 kbit/s 到 256 kbit/s。

音质:

  • AAC 在较高的比特率下通常提供比 Opus 更好的音质,尤其是在高频部分。
  • Opus 在较低的比特率下通常提供与 AAC 相同或更好的音质。

延迟:

  • AAC 的延迟通常比 Opus 更高,尤其是在低比特率下。
  • Opus 的延迟更低,使其更适合实时语音通信和网络游戏等应用。

复杂度:

  • AAC 的编码和解码复杂度通常高于 Opus。
  • Opus 的编码和解码复杂度更低,使其更适合资源受限的设备。

专利:

  • AAC 受专利保护,需要支付许可费才能使用。
  • Opus 是免版税的,可以免费使用。

应用场景:

  • AAC 常用于音乐流媒体、数字广播和视频压缩。
  • Opus 常用于实时语音通信、网络游戏和 VoIP。

以下表格总结了 AAC 和 Opus 的主要区别:

特性AACOpus
支持的采样率8 kHz - 96 kHz6 kHz - 48 kHz
支持的比特率32 kbit/s - 1.5 Mbit/s6 kbit/s - 256 kbit/s
音质 (高比特率)更好相同或更好
音质 (低比特率)相同或更好更好
延迟较高较低
复杂度较高较低
专利受专利保护免版税
应用场景音乐流媒体、数字广播、视频压缩实时语音通信、网络游戏、VoIP

drive_spreadsheet导出到 Google 表格

总体而言,AAC 和 Opus 都是优秀的音频编码格式,各有优缺点。选择哪种格式取决于您的具体需求和应用场景。

以下是一些具体的建议:

  • 如果您需要在高比特率下获得最佳音质,请选择 AAC。
  • 如果您需要在低比特率下获得良好的音质,或者您需要低延迟,请选择 Opus。
  • 如果您需要免版税的音频编码格式,请选择 Opus。
  • 如果您需要兼容旧设备,请选择 AAC。