iOS 技术栈

566 阅读24分钟

代码质量

1 代码质量

A 为了减少编译时间,从代码层考虑,如何做处理?(初级)

为了减少编译时间,.h文件中尽量少引入其他头文件,必要时可以考虑在.h文件里“向前声明”该类 @class QiShareClass;

B 如果自定义类继承某个超类或者要遵从某个协议的话,该如何处理 (中级)

如果自定义的类继承于某个超类,则必须引入定义那个超类的头文件,如果要遵从某个协议,尽量把该类遵循某协议的声明移到分类中。如果不行的话,就把协议单独放在一个头文件中,然后将其引入。

C 字面量语法和等价语法的声明区别 ,字面量语法相对于等价语法的好处(中级)

    //等价语法
  NSNumber *intNumber = [NSNumber numberWithInt:1];
   
   //字面量
    NSNumber *intNum = @3.14;

使用字面量的好处:假如创建一个含有3个对象的数组,第二个对象为nil,其他两个对象都有效,如果用字面量语法创建,则在运行时会抛出异常,可以更快找到错误。而如果用arrayWithObjects:方法,则不会抛出异常,但创建的数组会只包含第一个对象,因为该方法会依次处理各个参数,直到发现nil为止。这样会导致错误不容易被发现。

D 类型常量声明有什么注意事项 (中级)

答 变量一定要同时用static于const来声明。const修饰符可以保护变量不被修改。static修饰符则意味着仅在定义此变量的编译单元中可见。如果不加static,在另一个编译单元也声明了同名变量就会报错。这样创建的常量是不公开的。 如果需要对外公开某个常量,就需要常量放在全局符号表中,以便可以在定义该常量的编译单元之外

@interface xxx : xxx

// ...

@end

在.m文件里引入该类#import "QiShareClass.h" 

多用字面量语法,少用与之等价的语法

多用类型常量,少用#define预处理器指令

用枚举表示状态、选项、状态码

2 code review(代码评审)

A 问 代码规范的要点 (初级)

答 代码规范主要分为风格规范与设计规范两大类

B codeReview注意事项 (中级)

答:主要根据团队设定的代码规范,来review团队成员的代码,大致有以下几个方面:

1)代码有没有不符合代码规范的

比如:命名、注释

2)代码有没有(业务/算法)逻辑错误

比如:功能与需求有偏差;参数传递顺序出错;方法的边界条件有没有考虑等

3)代码有没有回归错误

比如:之前的功能回归测试不通过

4)代码有没有潜在性能问题

比如:考虑大数据量、大并发量下的性能下sql是否有问题?是否会有内存泄露?死锁等

5)代码有没有其他待改进的地方

比如可扩展性/过度设计

3 规范检测 - Swift lint (中级)

A 问Swift lint 是什么

答SwiftLint 是 realm 公司开发的一个插件,专门用于管理 Swift 代码的规范

SwiftLint 的工作原理是检查 Swift 代码编译过程中的 AST 和 SourceKit 环节,从而可以摆脱不同版本 Swift 语法变化的影响。

二 监控体系

1 crash (中级)

A 问 符号表的意义

答:1)概念:符号表就是指在Xcode项目编译后,在编译生成的.app的同级目录下生成的同名的.dSYM文件。

.dSYM文件其实是一个目录,在子目录中包含了一个16进制的保存函数地址映射信息的中转文件,所有Debug的symbols都在这个文件中(包括文件名、函数名、行号等),所以也称之为调试符号信息文件。

2)作用:符号表就是用来符号化 crash log(崩溃日志)。crash log中有一些方法16进制的内存地址等,通过符号表就能找到对应的能够直观看到的方法名之类。

3)获取途径:在Archive的时候会生成.xcarchive文件,然后显示包内容就能够在里面找到.dsYM文件和.app文件。

B 问Crash如何捕获

答:iOS端的crash分为两类,一类是NSException异常,另外一类是Signal信号异常。这两类异常我们都可以通过注册相关函数来捕获。www.jianshu.com/p/84b842ce9…

 

C 问 bugly 检测卡顿的原理

答 Runloop的两次source[kCFRunLoopBeforeSources 和 kCFRunLoopAfterWaiting]的监控  创建信号量的方式 

渲染界面的频率来监控帧率

 

D 问 bugly检测崩溃的原理

答 一类是NSException异常,另外一类是Signal信号异常。这两类异常我们都可以通过注册相关函数来捕获

E 问符号化的概念

答:就是我们要获取到其他的堆栈信息的时候  我们就需要符号化数据了 通过 dSYM 文件提取地址和符号的对应关系,进行符号还原

 

2 卡顿监控 (高级)

 卡顿的监控和处理 : www.jianshu.com/p/399a661b3…

3 错误码监控 (中级)

A iOS开发常见错误码

答 AVFoundation 播放错误码—AVError  ,NSURLError 链接错误码 

   附:www.jianshu.com/p/c5438478c…

4 耗时监控 (推荐iOS性能监控工具 [QiLagMonitor]

(links.jianshu.com/go?to=https…)

A 问 什么是hook?(初级)

答 定义:hook是指在原有方法开始执行时,换成你指定的方法。或在原有方法的执行前后,添加执行你指定的方法。从而达到改变指定方法的目的。

例如:

使用runtime 的 Method Swizzle。

使用Facebook所开源的fishhook框架。

 

B 问 fishhook的大致实现思路是什么 (初级)

答 动态链接器dyld会根据Mach-O二进制可执行文件的符号表来绑定符号。而通过符号表及符号名就可以知道指针访问的地址,再通过更改指针访问的地址就能替换指定的方法实现了。

 

C 问为什么hook了objc_msgSend就可以掌握所有objc方法的耗时? (中级)

答 因为objc_msgSend是所有Objective-C方法调用的必经之路,所有的Objective-C方法都会调用到运行时底层的objc_msgSend方法。所以只要我们可以hook objc_msgSend,我们就可以掌握所有objc方法的耗时。

 

D 问如何分析iOS启动耗时 (中级)

答 启动耗时分析,一般我们会以main函数作为分割点,main之前和main之后main之前称为per-main 阶段。这个由dyld给你反馈应用的耗时。main之后由开发者自己检测。我们可以从main开始打点,到第一个页面显示为止。

各个函数的调用顺序如下:

启动页

main() UIApplicationMain()

willFinishLaunchingWithOptions()

didFinishLaunchingWithOptions()

loadView()

viewDidLoad()

applicationDidBecomeActive()

启动页是在main()函数调用之前出来的,main()是程序的入口,里面调用了UIApplicationMain()。当App从didFinishLaunchingWithOptions()返回的时候,实际的UI立刻开始加载,但是在applicationDidBecomeActive()这个回调完成之前,UI即使已经初始化,但仍旧被阻塞着。

总的启动时间T包括main()调用之前的pre-main timeT0,

加上从main()到applicationDidBecomeActive()的时间T1。

E iOS启动优化 (高级)

www.jianshu.com/p/e6b194ddd…

F iOS 异常处理 (高级)

I 奔溃处理  系统崩溃

对于系统崩溃而引起的程序异常退出,可以通过NSSetUncaughtExceptionHandler机制捕获,代码:实现一个用于处理异常的方法

II 热更新

常用热更新框架 JSPatch lua脚本 Weex React Native  Hybrid  DynamicCocoa

安全

1 沙盒/域*

A 问iOS沙盒机制介绍 (初级)

答 iOS中的沙盒机制是一种安全体系。为了保证系统安全,iOS每个应用程序在安装时,会创建属于自己的沙盒文件(存储空间)。应用程序只能访问自身的沙盒文件,不能访问其他应用程序的沙盒文件,当应用程序需要向外部请求或接收数据时,都需要经过权限认证,否则,无法获取到数据。所有的非代码文件都要保存在此,例如属性文件plist、文本文件、图像、图标、媒体资源等,其原理是通过重定向技术,把程序生成和修改的文件定向到自身文件夹中。

B 问iOS 什么数据保存在Cache(中级)

答:1.缓存数据应该保存在/Library/Caches目录下.
2.缓存数据在设备低存储空间时可能会被删除,iTunes或iCloud不会对其进行备份。
3.可以保存重新下载或生成的数据,而且没有这些数据也不会妨碍用户离线使用应用的功能。
4.当访问网络时系统自动会把访问的url,以数据库的方式存放在此目录下面.

2 网络安全

A 问网络安全的原则 (初级)

答 1)在网络上不允许传输用户隐私数据的明文。

2)在本地不允许保存用户隐私数据的明文。

 

B 问网络请求方法选择 (初级)

答 1.一定要使用POST请求提交用户的隐私数据。

2.GET请求的所有参数都直接暴露在URL中。

3.请求的URL一般会记录在服务器的访问日志中。

4.服务器的访问日志是黑客攻击的重点对象之一。

 

 

3数据加密

A 问数据加密算法有哪些 (初级)

答 编码方式                            Base64  Base58

哈希(散列)函数                 MD5(消息摘要算法)  SHA1  SHA256  SHA512

对称加密算法                    DES   AES

B 问MD5加密是否可逆 为什么 (中级)

答:MD5不可逆的原因是由于它是一种散列函数(也叫哈希函数,杂凑函数,他是一个单向密码体制,即从明文到密文的不可逆映射,只有加密过程没有解密过程)。

C 问如何进行签名校验 (高级)

www.jianshu.com/p/1554dc3e6…

4 抓包****

A 问抓包原理 (中级)

答:一般抓包都是通过代理服务来冒充你的服务器,客户端真正交互的是这个假冒的代理服务,这个假冒的服务再和我们真正的服务交互,这个代理就是一个中间者 ,我们所有的数据都会通过这个中间者,所以我们的数据就会被抓取。HTTPS 也同样会被这个中间者伪造的证书来获取我们加密的数据

B 问如何防止抓包 (高级)

juejin.cn/post/696401…

三.iOS 路由 组件化 Target-Action (中级)

1.iOS 路由 组件化 Target-Action 方案优缺点

Target-Action方案的优点

  •   Target-Action方案,只存在组件依赖Mediator中介的这一层依赖关系。

  •   在每个组件中创建Mediator的Category分类,针对维护Mediator的Category分类即可。

  •   每个组件的Category对应一个Target类,Categroy中的Action方法对应Target类中的Action场景。

  •   Target-Action方案统一了所有组件间调用入口。都是调用“performTarget: action: params: shouldCacheTarget:”方法。第三个参数是一个字典,这个字典里面可以传很多参数,只要Key-Value写好就可以了。

  •   Target-Action方案处理错误的方式也统一在一个地方了,Target没有,或者是Target无法响应的Action方法,都可以在Mediator这里进行统一出错处理。

  •   Target-Action方案也能有一定的安全保证,它对URL中的Native前缀进行安全验证。

  •   因此,Target-Action方案不管从维护性、可读性、扩展性来说,都是一个比较完美的方案。

  •   充分的利用Runtime的特性,实现了组件间服务的自动发现,无需注册即可实现组件间的调用。

Target-Action方案的缺点

  •   Target_Action在Category中将常规参数打包成字典,在Target处再把字典拆包成常规参数,这就造成了一部分的硬编码。

2. Target-Action路由方案的技术实现。(高级)

-  通过Target-Action的方式,创建一个Target类,Target类里面定义一些Action方法,这些方法的结果是返回一个Controller或其它Object对象。

-  在每个组件中,给Mediator中介类创建一个对应这个组件的分类,来保证Mediator中介类的代码纯净性。

-  在每个组件Mediator的分类中,定义组件外部可调用的接口方法。接口方法内部,通过统一调用Mediator中介类的“performTarget: action: params: shouldCacheTarget:”方法,实现组件间解耦。(即调用者即使不导入其它组件的头文件,也能调用其它组件。

-  在Mediator中介类的“performTarget: action: params: shouldCacheTarget:”方法中,主要通过Runtime中的NSClassFromString获取Target类,通过NSSelectorFromString获取Target类中的Action方法名。

-  路由方案最终实现是通过获取到的Target类和Target类中的Action方法名,去匹配已经创建好的Target类,由Target类中的Action方法,真正的实现创建需要的控制器或对象。将这些对象作为方法的返回值,传递给调用者。(即在Target类中的Action方法中,才能真正调用当前组件控制器的功能。)

3. main()和runApp()函数在flutter的作用分别是什么?有什么关系吗?(flutter)(初级)

  •       main函数是类似于java语言的程序运行入口函数

  •       runApp函数是渲染根widget树的函数

  •       一般情况下runApp函数会在main函数里执行

  4. 什么是widget?  在flutter里有几种类型的widget?分别有什么区别?能分别说一下生命周期吗?   (flutter)    (初级)

  •       widget在flutter里基本是一些UI组件

  •       有两种类型的widget,分别是statefulWidget 和 statelessWidget两种

  •       statelessWidget不会自己重新构建自己,但是statefulWidget会 

  5. Hot Restart 和 Hot Reload 有什么区别吗?(flutter)(初级)

  •       Hot Reload比Hot Restart快,Hot Reload会编译我们文件里新加的代码并发送给dart虚拟机,dart会更新widgets来改变UI,而Hot Restart会让dart 虚拟机重新编译应用。另一方面也是因为这样, Hot Reload会保留之前的state,而Hot Restart回你重置所有的state回到初始值。

  6.WKWebView优势缺点:(中级)

  优势

  •   运行速度更快,占用内存低,大概是UIwebView 的四分之一到三分之一

  •   多进程,在APP的主进程之外执行;为空webView为多进程组件,他会从APP内存中分离内存到单独的进程中。当内存超过了系统分配给为空webView的内存时候,会导致为空webView浏览器崩溃白屏,但是APP不会crash。(APP会收到系统通知,并且尝试去重新加载页面)

  •   相反的UIwebView 和APP同一个进程,内存不够用时就会crash ,从而导致APP crash

  •   wkwebview 使用和手机Safari浏览器一样的nitro JavaScript引擎,相比于UIwebView的javaScript 引擎有非常大的性能提升

  •   wkwebview 是异步处理APP原生代码与JavaScript之间的通信,因此普遍上执行速度会更快

  •   消除了触摸延迟

 缺点:

  •   不能截屏

  •   不支持记录webkit 的请求

  •   APP退出会清湖HTML5的本地存储数据

  •   不支持accept cookies 的设置

7.提高小程序的应用速度的方法(小程序)(中级)

  •   减少默认data的大小

  •   组件化方案,公用的如弹框等写个自定义的组件,然后调用

8.生命周期函数(小程序)(初级)

  •   onLoad——页面加载,调一次

  •   onShow——页面显示,每次打开页面都调用

  •   onReady——初次渲染完成,调一次

  •   onHide——页面隐藏,当navigateTo或底部tab切换时调用

  •   onUnload——页面卸载,当redirectTo或navigateBack时调用

9.MVVM和MVC的区别 (中级)

www.jianshu.com/p/d0bc12a63…

10. iOS 三种工厂模式(简单工厂模式、工厂模式、抽象工厂模式) (中级)

www.jianshu.com/p/847af218b…

11 .设计模式的基本原则 (中级)

设计原则和规矩一样,是用来规范也是用来打破的。并非所有的时候我们都需要完全准守设计原则,准守设计原则可以减少遇到不必要的麻烦,但是有时候如果完全准守原则,你可能寸步难行。灵活变通,才是王道。

单一职责原则:

也就是我们常说的一个类负责一个职责,虽然这个对于程序猿们是一个常识,但是事件经常会发展到我们无法控制的地步。

  比如刚开始的时候类T只负责一个职责P。后来由于某种原因,也许是需求变更了,需要将职责P细分为粒度更细的职责P1,P2,这时如果要使程序遵循单一职责原则,需要将类T也分解为两个类T1和T2,分别负责P1、P2两个职责。但是在程序已经写好的情况下,这样做简直太费时间了。所以,简单的修改类T,用它来负责两个职责是一个比较不错的选择,虽然这样做有悖于单一职责原则。(这样做的风险在于职责扩散的不确定性,因为我们不会想到这个职责P,在未来可能会扩散为P1,P2,P3,P4……Pn。所以记住,在职责扩散到我们无法控制的程度之前,立刻对代码进行重构。)

里氏替换原则:

这项原则最早是在1988年,由麻省理工学院的Barbara Liskov女士提出来的。

  定义1:如果对每一个类型为 T1的对象 o1,都有类型为 T2 的对象o2,使得以 T1定义的所有程序 P 在所有的对象 o1 都代换成 o2 时,程序 P 的行为没有发生变化,那么类型 T2 是类型 T1 的子类型。

  定义2:所有引用基类的地方必须能透明地使用其子类的对象。

  看完定义大致也知道这个是个和继承相关的定义。其实就是我们通常使用继承时子类尽量不要去修改父类的功能。这也是为什么我们在继承父类方法进行扩展的时候通常都会代用super的原因。

依赖倒置原则:

定义:高层模块不应该依赖低层模块,二者都应该依赖其抽象;抽象不应该依赖细节;细节应该依赖抽象。(抽象指的是接口或者抽象类,细节就是具体的实现。)

  在面向过程的开发,上层调用下层,上层依赖于下层,当下层剧烈变动时上层也要跟着变动,这就会导致模块的复用性降低而且大大提高了开发的成本。

  而我们iOS是面向对象的开发,很好的解决了这个问题,一般情况下抽象的变化概率很小,让用户程序依赖于抽象,实现的细节也依赖于抽象。即使实现细节不断变动,只要抽象不变,客户程序就不需要变化。这大大降低了客户程序与实现细节的耦合度。

  简而言之就是某些可能改变的参数不要在底层的模块进行实现,而是通过传递参数,block块等方式进行传递。

接口隔离原则:

定义:客户端不应该依赖它不需要的接口;一个类对另一个类的依赖应该建立在最小的接口上。

  还是以苹果的UITableViewDelegate为例,在设计时定义了部分必须实现的基本方法(最小接口)。那么其他的接口只需要在必要时才实现。而不是需要实现所有接口才能使用某个功能。

###  迪米特法则:

定义:一个对象应该对其他对象保持最少的了解。

  问题由来:类与类之间的关系越密切,耦合度越大,当一个类发生改变时,对另一个类的影响也越大。

  解决方案:尽量降低类与类之间的耦合。

  软件编程的总的原则:低耦合,高内聚。无论是面向过程编程还是面向对象编程,只有使各个模块之间的耦合尽量的低,才能提高代码的复用率。低耦合的优点不言而喻,但是怎么样编程才能做到低耦合呢?那正是迪米特法则要去完成的。

  迪米特法则又叫最少知道原则,最早是在1987年由美国Northeastern University的Ian Holland提出。通俗的来讲,就是一个类对自己依赖的类知道的越少越好。也就是说,对于被依赖的类来说,无论逻辑多么复杂,都尽量地的将逻辑封装在类的内部,对外除了提供的public方法,不对外泄漏任何信息。迪米特法则还有一个更简单的定义:只与直接的朋友通信。首先来解释一下什么是直接的朋友:每个对象都会与其他对象有耦合关系,只要两个对象之间有耦合关系,我们就说这两个对象之间是朋友关系。耦合的方式很多,依赖、关联、组合、聚合等。其中,我们称出现成员变量、方法参数、方法返回值中的类为直接的朋友,而出现在局部变量中的类则不是直接的朋友。也就是说,陌生的类最好不要作为局部变量的形式出现在类的内部。

开闭原则:

开闭原则中“开”,是指对于组件功能的扩展是开放的,是允许对其进行功能扩展的;开闭原则中“闭”,是指对于原有代码的修改是封闭的,即修改原有的代码对外部的使用是透明的。

12.iOS 单例实现方式与优缺点(中级)

www.jianshu.com/p/d1856ad6b…

13.组件化有什么好处?(中级)

  •   业务分层、解耦,使代码变得可维护;

  •   有效的拆分、组织日益庞大的工程代码,使工程目录变得可维护;

  •   便于各业务功能拆分、抽离,实现真正的功能复用;

  •   业务隔离,跨团队开发代码控制和版本风险控制的实现;

  •   模块化对代码的封装性、合理性都有一定的要求,提升开发同学的设计能力;

  •   在维护好各级组件的情况下,随意组合满足不同客户需求;(只需要将之前的多个业务组件模块在新的主App中进行组装即可快速迭代出下一个全新App)

14.你是如何组件化解耦的?(高级)

  •   分层基础功能组件:按功能分库,不涉及产品业务需求,跟库Library类似,通过良好的接口拱上层业务组件调用;不写入产品定制逻辑,通过扩展接口完成定制;基础UI组件:各个业务模块依赖使用,但需要保持好定制扩展的设计业务组件:业务功能间相对独立,相互间没有Model共享的依赖;业务之间的页面调用只能通过UIBus进行跳转;业务之间的逻辑Action调用只能通过服务提供;

  •   中间件:target-action,url-block,protocol-class

15.为什么CTMediator方案优于基于Router的方案?(高级)

Router的缺点:

  •   在组件化的实施过程中,注册URL并不是充分必要条件。组件是不需要向组件管理器注册URL的,注册了URL之后,会造成不必要的内存常驻。注册URL的目的其实是一个服务发现的过程,在iOS领域中,服务发现的方式是不需要通过主动注册的,使用runtime就可以了。另外,注册部分的代码的维护是一个相对麻烦的事情,每一次支持新调用时,都要去维护一次注册列表。如果有调用被弃用了,是经常会忘记删项目的。runtime由于不存在注册过程,那就也不会产生维护的操作,维护成本就降低了。 由于通过runtime做到了服务的自动发现,拓展调用接口的任务就仅在于各自的模块,任何一次新接口添加,新业务添加,都不必去主工程做操作,十分透明。

  •   在iOS领域里,一定是组件化的中间件为openURL提供服务,而不是openURL方式为组件化提供服务。如果在给App实施组件化方案的过程中是基于openURL的方案的话,有一个致命缺陷:非常规对象(不能被字符串化到URL中的对象,例如UIImage)无法参与本地组件间调度。 在本地调用中使用URL的方式其实是不必要的,如果业务工程师在本地间调度时需要给出URL,那么就不可避免要提供params,在调用时要提供哪些params是业务工程师很容易懵逼的地方。

  •   为了支持传递非常规参数,蘑菇街的方案采用了protocol,这个会侵入业务。由于业务中的某个对象需要被调用,因此必须要符合某个可被调用的protocol,然而这个protocol又不存在于当前业务领域,于是当前业务就不得不依赖public Protocol。这对于将来的业务迁移是有非常大的影响的。

CTMediator的优点:

  •   调用时,区分了本地应用调用和远程应用调用。本地应用调用为远程应用调用提供服务。

  •   组件仅通过Action暴露可调用接口,模块与模块之间的接口被固化在了Target-Action这一层,避免了实施组件化的改造过程中,对Business的侵入,同时也提高了组件化接口的可维护性。

  •   方便传递各种类型的参数。

##  16.基于CTMediator的组件化方案,有哪些核心组成?

  •   CTMediator中间件:集成就可以了

  •   模块Target_%@:模块的实现及提供对外的方法调用Action_methodName,需要传参数时,都统一以NSDictionary*的形式传入。

  •   CTMediator+%@扩展:扩展里声明了模块业务的对外接口,参数明确,这样外部调用者可以很容易理解如何调用接口。

##  17.iOS中的函数响应式编程思想

响应式编程是一种和事件流有关的编程模式,关注导致状态值改变的行为事件,一系列事件组成了事件流。一系列事件是导致属性值发生变化的原因。FRP非常类似于设计模式里的观察者模式。如果使用FRP,c的值将会随着b的值改变而改变,所以叫做「响应式编程」。比较直观的例子就是Excel,当改变某一个单元格的内容时,该单元格相关的计算结果也会随之改变。 FRP提供了一种信号机制来实现这样的效果,通过信号来记录值的变化。信号可以被叠加、分割或合并。通过对信号的组合,就不需要去监听某个值或事件。

18.iOS皮肤切换方案 换肤的实现(中级)

www.jianshu.com/p/676bfde0c…

19.iOS编译速度优化(中级)

swift 类型检查耗时

对于 swift 来说,编译耗时的主要就是类型检查 在 xcode => build settings => Other Swift Flags 添加下面设置,可以看到 swift 的表达式和函数的 类型检查的时长。并给出警告。

项目设置优化

使用 New Xcode Build System

dSYM

可以设置在 debug 不生成 dSYM,只在 release 生成。

模块化

可以把 pod 等第三方库先打包成 .a 文件,然后再放入项目里面,这样 pod 的第三方库就编译时间就会减少。但是这样一来 调试就不方便了,所以这是个取舍问题。

代码优化

  •   闭包和 lazy不推荐

20. iOS 国际化都涉及到哪些,怎样实现(中级)

一、项目内的语言国际化

二、app名字国际化设置

blog.csdn.net/shanghaibao…