腾讯 – 酷狗音乐 收集
一、直播相关技术
二、性能优化
性能优化,我将其分为三 方向:
- ① 操作流畅性(用户可感知)
- ② APP 大小瘦身
- ③ APP自身稳定健壮性(用户很少基本不感知)
1、操作追求流畅性(用户可感知优化:CPU、GPU入手)
-
启动时间优化: pre_main 和 main 后优化
- pre_main 优化主要由4部分组成:
-
dylib loading(动态库的加载) : 这个阶段 dylib 会分析应用依赖的 dylib。由此可知: 应用依赖的 dylib 越少越好。在这一步优化的宗旨是减少 dylib 数量:
- 移除不必要的 dylib ;
- 合并多个 dylib 成一个 dylib 。
-
rebase/binding : 这个阶段主要是注册 Objc 类。所以指针数量越少越好。可做的优化有:
- 清理项目中无用的类
- 删减没有被调用到或者已经废弃的方法
- 删减一些无用的静态变量
- 可以通过 AppCode 等工具实现项目中未使用的代码扫描
-
ObjeC setup : 这个阶段基本不用优化。若 rebase/binding 阶段优化很好,本阶段耗时也会很少 -
initializer :
- 在这个阶段,dylib 开始运行程序的初始化函数,调用每个
类和分类的 + load() 方法 ,调用 C/C++ 中的构造器函数 。 initializer 阶段执行结束后, dylib 开始调用 main() 函数。在这一步,检查 + load() 方法,尽量把事情推迟到 + initialize() 方法里执行 ;并且控制 category 数量,去掉不必要的 category。 - 在这里我们修改了部分原本代码中直接在 +load 函数初始化逻辑改为在 +initialize 中加载,也就是到使用时才加载。
Main() 后优化
didFinishLaunchingWithOptions 优化
- 业务分级(三级:启动必须注册、加载首页后注册、懒加载注册)。
- 目前 App 的 didFinishLaunchingWithOptions 方法里执行了多项项业务,有一大部分业务并不是一定要在这里执行的,如支付配置、客服配置、分享配置等。整理该方法里的业务,能延迟加载的就往后推迟,防止其影响启动时间。
- 可以新建一个类负责启动事件,新加的业务可以往这边添加。
- 不必要不开多线程
- 首页渲染优化 :
- 减少启动期间创建的 UIViewController 数量。
通过打符号断点-[UIViewController viewDidLoad] 发现,如果App 启动过程中创建了 12 个 UIViewController(包括闪屏),即在启动过程中创建了 12 个视图控制器,导致首页渲染时间较长。
- 延迟首页耗时操作。
如果 App 首页有个侧滑页面及侧滑手势,并且该页面是用 xib 构建的,将该 ViewController 改为代码构建,同时延迟该页面的创建时机,等首页显示后再创建该页面及侧滑手势,这个改动节省了 300-400ms。 - 去除启动时没必要及不合理的操作。
项目中使用了自定义的侧滑返回,在每次 push 的时候都会截图,启动的时候自定义导航栏会截取两张多余首页的图片,并且截图用的 API (renderInContext) 性能较差,耗时 800ms 左右,去掉启动截图的操作。 闪屏请求回调里写plist文件的操作放在主线程,导致启动时占用主线程,将文件读写移到子线程操作。 -
网络优化
- 减少、压缩网络数据。
- 如果多次请求的结果是相同的,尽量使用缓存。
- 使用断点续传,否则网络不稳定时可能多次传输相同的内容。
- 网络不可用时,不要尝试执行网络请求。
- 让用户可以取消长时间运行或者速度很慢的网络操作,设置合适的超时时间。
- 批量传输,比如,下载视频流时,不要传输很小的数据包,直接下载整个文件或者一大块一大块地下载。如果下载广告,一次性多下载一些,然后再慢慢展示。如果下载电子邮件,一次下载多封,不要一封一封地下载。
-
页面打开优化
- 跳转优化(跳转动画、跳转时间)
- 展示状态优化(请求数据中,请求成功、请求失败)
-
UI 卡顿优化
- 列表优化
- 复用机制
- 高级计算提前计算 + 高度缓存
- 善用 hidden,减少 cell 的注册。
- 按需加载
- 耗时操作尽量放入子线程
- 控制多线程并发量
- 图片异步加载(解码、绘制)
- 大图加载:考虑缓存多图合成
- 减少视图数量和层次 (subViews 的数量)
- 减少透明的视图(alpha<1),不透明的就设置opaque为YES
- 避免不需要动画
- 如有需要可使用异步进行 layer 渲染(AsyncDisplayKit 又叫 Texttrue)
避免离屏渲染 :需要创建新的缓冲区,需要在当屏和离屏直接切换。如下会导致离屏:
- 光栅化,layer.shouldRasterize = YES
- 遮罩,layer.mask
- 圆角,同时设置layer.masksToBounds = YES、layer.cornerRadius大于0
- 考虑通过CoreGraphics绘制裁剪圆角,或者叫美工提供圆角图片
- 阴影,layer.shadowXXX 、如果设置了layer.shadowPath就不会产生离屏渲染
友好的操作化提示、引导
2、App 瘦身优化(大小)
-
资源优化 :
-
删除无用图片 使用 LSUnusedResources 查找无用图片。注意 [UIImage imageNamed:[NSString stringWithFormat:“icon_%d.png”,index]]; 这种使用图片的方式,可能会被误删。 -
删除不使用重复资源:xib、Json、Plist、Extension 等 -
压缩图片资源
- 使用 ImageOptim 无损压缩图片。
- 使用 TinyPNG 有损压缩图片。使用的时候直接执行 tinypng *.png -k token 脚本即可。
-
其他技巧:
- 用 LaunchScreen.storyboard 替换启动图片。
- 本地大图片都使用 webp。
- 资源按需加载(苹果提供),非必要资源都等到使用时再从服务端拉取。
-
编译选项优化:
- Optimization Level 在 release 状态设置为 Fastest/Smallest。
- Strip Debug Symbols During Copy 在 release 状态设置为 YES。
- Strip Linked Product 在 release 状态设为 YES。
- Make String Read-Only 在 release 状态设为 YES。
- Dead Code Stripping 在 release 状态设为 YES。
- Deployment PostProcessing 在 release 状态设为 YES。
- Symbols hidden by default 在 release 状态设为 YES。
-
可执行文件优化: 使用 LinkMap 分析库的使用情况
-
三方库优化
- 删除不使用的三方库。
- 功能用的少但是体积大的三方库可以考虑自己重写。
- 合并功能重复的三方库。
-
代码分析:用 AppCode 进行代码扫描
- 去掉无用的类及文件
- 清理 import
- 去掉空方法
- 去掉无用的 log
- 去掉无用的变量
-
其他技巧(选用):
将业务打包成动态库。 如果动态库的加载时机不控制好,会影响 App 的启动速度,权衡使用。动态化 。将一部分 Native 界面用 RN/Weex 重写。去除 Swift 代码 ,Swift 的标准库是打包在安装包里的,一般都有 10M+。然后苹果官方说等到 Swift Runtime 稳定之后会合并到 iOS 系统里,那时候使用 Swift 就不会显著增加包大小了。- 在 target -> Build Settings -> Other Link Flags 里添加如下指令,会把 TEXT 字段的部分内容转移到 RODATA 字段,避免苹果对 TEXT 字段的审核限制。当然其实跟安装包瘦身好像没有什么关系,所以除非快不行了否则不建议操作。
-Wl,-rename_section,__TEXT,__cstring,__RODATA,__cstring -Wl,- rename_section,__TEXT,__gcc_except_tab,__RODATA,__gcc_except_tab -Wl,-rename_section,__TEXT,__const,__RODATA,__const -Wl,-rename_section,__TEXT,__objc_methname,__RODATA,__objc_methname -Wl,-rename_section,__TEXT,__objc_classname,__RODATA,__objc_classname -Wl,-rename_section,__TEXT,__objc_methtype,__RODATA,__objc_methtype
苹果官方的策略:
- 使用 xcasset 管理图片
- 开启 BitCode
3. APP自身稳定健壮性(用户很少基本不感知)
- 内存优化
- 耗电优化
Instruments 的 Energy Log 查看电量使用情况- 耗电分析:
- 地图定位:是否需要实时?定位精确度需求?定位完毕就关掉定位服务?后台定位设置?
- 网络传输:没有网络状态下不进行请求!
- 后台运行:是否需要后台运行?运行方案?保活多久?
- CPU 调度频率:多线程超量?离屏渲染操作?音视频编码使用了软编码?
- 定时器的使用
- 优化I/O操作:
- 尽量不要频繁的写入小数据,最好批量一次写入。
- 读写大量重要数据时,考虑用dispatch_io,其提供了基于GCD的异步操作文件I/O的的API。用dispatch_io系统会优化磁盘访问。
- 数据量比较大时,建议使用数据库(比如SQLite、CoreData)
- 奔溃相关预防处理
看第四点:如下
4. APP奔溃相关(属于第三点内容)
三、架构设计
点击跳转包含了架构设计
1、项目大局观:组件化选择
-
- Target-Action :
CTMediator 推荐使用,中间件模式,使用 target 、category 实现解耦 - Protocol-Class 注册 :
3、什么是设计模式?设计模式解决了什么问题?
在编写程序中,面临着耦合性、内聚性、可维护性、可扩展性、重用性、灵活性 等挑战。设计模式就是为了让程序拥有更好的:
- 代码重用性(相同功能,抽象剥离)
- 可读性(编程规范性,好的代码即使不注释都可以很好的阅读)
- 可扩展性(增加新功能的时候方便)
- 可靠性(增加新功能,不会对元功能造成影响)
- 实现高内聚,低耦合特性(public 暴露尽量的少)
4、设计模式遵循的 7 大原则?
- 单一职责原则:一个类只负责一个职责,一个方法函数只解决一个问题。
- 接口隔离原则:大接口改小接口。(外部不需要大接口那么多方法,更易控制)。
- 依赖反转原则:面向接口编程。尽量不要声明具体类,而是使用接口,实现解耦。
- 里氏置换原则:能出现父类的地方就一定可以用子类代替。即不要重写父类中已实现的方法。
- 开闭原则:面向扩展开放,面向修改关闭。不要修改一个已实现的,更不要修改内的方法,应当创建新类和新方法来解决。
- 迪米特原则:最少知道原则。即对外暴露的public 方法尽量少,实现高内聚。
- 合成复用原则:不要重复自己,不要copy 代码。应当选择抽象需要copy 的代码,实现多类复用。
5、常见的设计模式有哪些?使用场景?
6、项目稳定性?
- 开发流程规范化
-
代码规范
自测习惯 (review、sonar 代码检测)- XMind、PDMan、PostMan、Jenkins、Sonar 等工具使用。养成先计划,再动手习惯。
- Git 、Svn 、禅道、TAPD等使用规范。保证项目流程的流畅性。
- 性能检测:FPS(CADisplayLink)
- CPU 使用率:
-
**测试(风险把控)**
-
线上 bug 处理
- **监控:**异常监控、bug 流程跟踪、bug 保存、日志上传
- **修复:**JSPatch、RN、WEEX、Flutter、H5 跨平台
四、Socket、多线程、跨平台、OC/Swift、Flutter、数据库、Cocopods
1、Socket
定义:
网络上两个程序通过一个双向通信连接实现数据交互,这种双向通信的连接叫做Socket (套接字)。
本质:
网络模型中应用层与TCP/IP协议族通信 的中间软件抽象层 ,是它的一组编程接口(API) ,也即对TCP/IP的封装 。 TCP/IP 也要提供可供程序员做网络开发所用的接口,即Socket 编程接口。
要素:
Socket 是网络通信的基石 ,是支持TCP/IP协议 的网络通信的基本操作单元 ,包含进行网络通信的必须的五 种信息:
- 连接使用的协议
- 本地主机的IP地址
- 本地进程的协议端口
- 远程主机的IP地址
- 远程进程的协议端口
特性:
- Socket可以支持不同的传输协议(TCP或UDP),当使用TCP协议进行连接时,该Socket连接就是一个TCP连接;同理,当使用UDP协议进行连接时,该Socket连接就是一个UDP连接。
- 多个TCP连接或多个应用程序进程可能需要通过
同一个TCP协议端口 传输数据。为了区别不同的应用程序进程和连接,计算机操作系统为应用程序与TCP/IP协议交互提供了套接字(Socket)接口。应用层可以和传输层通过Socket接口,区分来自不同应用程序进程或网络连接的通信,实现数据传输的并发服务。
连接
建立Socket连接至少需要一对套接字 ,分别运行于服务端(ServerSocket)和客户端(ClientSocket)。套接字直接的连接过程氛围三个 步骤:
- 服务器监听:
服务端Socket始终处于等待连接状态,实时监听是否有客户端请求连接。 - 客户端请求
客户端Socket提出连接请求,指定服务端Socket的地址和端口号,这时就可以向对应的服务端提出Socket连接请求。 - 连接确认
当服务端Socket监听到客户端Socket提出的连接请求时作出响应,建立一个新的进程,把服务端Socket的描述发送给客户端,该描述得到客户端确认后就可建立起Socket连接。而服务端Socket则继续处于监听状态,继续接收其他客户端Socket的请求。
1. 数据粘包、半包问题解决方案?
- 以特殊字符串比如/r、/n作为数据的结尾,这样就可以区分数据包了。
- 发送请求包的时候只发送固定长度的数据包,这样在服务端接收数据也只接收固定长度的数据,这种方法效率太低,不太合适频繁的数据包请求。
- 在tcp协议的基础上
封装一层数据请求协议 ,既数据包 = 数据包长度 + 数据包内容 ,这样在服务端就可以知道每个数据包的长度,也就可以解决半包、粘包问题。
2. TCP协议如何来保证传输的数据的顺序性?
3、跨平台
- Flutter
- RN
- Weex
- H5 框架
5、数据库
因为是 iOS, 所以数据库方面没有储备,需要后续学习。
5、CocoaPods
线路清华线路、git 线路、国外线路、淘宝线路
自己生成 pod 库
创建库名称 # 创建库名称
pod lib create xxx
- 执行完上述命令之后,它会问你几个问题,按需求填写即可
What platform do you want to use?? [ iOS / macOS ]
> iOS
What language do you want to use?? [ Swift / ObjC ]
> ObjC
Would you like to include a demo application with your library? [ Yes / No ]
> Yes
Which testing frameworks will you use? [ Specta / Kiwi / None ]
> None
Would you like to do view based testing? [ Yes / No ]
> Yes
What is your class prefix?
> JC
添加库文件:功能撰写 修改 xxx.podspec 文件Pod::Spec.new do |s|
# 名称
s.name = 'xxx'
# 默认版本号
s.version = '0.1.0'
# 简短描述
s.summary = 'A short description of JCNetworking.'
# This description is used to generate tags and improve search results.
# * Think: What does it do? Why did you write it? What is the focus?
# * Try to keep it short, snappy and to the point.
# * Write the description between the DESC delimiters below.
# * Finally, don't worry about the indent, CocoaPods strips it!
s.description = <<-DESC
TODO: Add long description of the pod here.
DESC
# 主页
s.homepage = 'https://github.com/huqigu/JCNetworking'
# s.screenshots = 'www.example.com/screenshots_1', 'www.example.com/screenshots_2'
s.license = { :type => 'MIT', :file => 'LICENSE' }
s.author = { 'huqigu' => 'huqigu@163.com' }
# source路径
s.source = { :git => 'https://github.com/huqigu/JCNetworking.git', :tag => s.version.to_s }
# s.social_media_url = 'https://twitter.com/<TWITTER_USERNAME>'
# 最低支持 iOS 版本
s.ios.deployment_target = '8.0'
# 要封起来的文件路径
s.source_files = 'JCNetworking/Classes/**/*'
# s.resource_bundles = {
# 'JCNetworking' => ['JCNetworking/Assets/*.png']
# }
# s.public_header_files = 'Pod/Classes/**/*.h'
# s.frameworks = 'UIKit', 'MapKit'
#尝试引入第三方依赖库
s.dependency 'AFNetworking', '~> 2.3'
end
项目发布 :cd到项目路径下执行git命令,将项目发布到上一步创建的github里。yellow@jiangchongdeMacBook-Pro ~ cd /Users/yellow/Desktop/JCNetworking
// 添加github项目路径
yellow@jiangchongdeMacBook-Pro ~/Desktop/JCNetworking master ● git remote add origin https://github.com/huqigu/JCNetworking.git
// 添加文件
yellow@jiangchongdeMacBook-Pro ~/Desktop/JCNetworking master ● git add .
yellow@jiangchongdeMacBook-Pro ~/Desktop/JCNetworking master ? git commit -m "first commit"
# --allow-unrelated-histories
# git pull origin maste会失败 ,提示:fatal: refusing to merge unrelated histories
# 原因是远程仓库origin上的分支master和本地分支master被Git认为是不同的仓库,所以不能直接合并,需要添加 --allow-unrelated-histories
yellow@jiangchongdeMacBook-Pro ~/Desktop/JCNetworking master git pull origin master --allow-unrelated-histories
// 推送到master分支
? yellow@jiangchongdeMacBook-Pro ~/Desktop/JCNetworking master git push origin master
// 提交版本号并push
#注意这里的版本号要与.podspec中的版本号保持一致
yellow@jiangchongdeMacBook-Pro ~/Desktop/JCNetworking master git tag 0.1.0
yellow@jiangchongdeMacBook-Pro ~/Desktop/JCNetworking master git push origin 0.1.0
# 执行完上述命令之后可以去github上查看是否已经推送上去了。
尝试使用: 我们新建一个项目并引用我们的pod库 看看是否能成功pod下了。target 'TestSDK' do
# Uncomment the next line if you're using Swift or would like to use dynamic frameworks
# use_frameworks!
'jc',:inhibit_warnings => true
pod 'JCNetworking',:git =>"https://github.com/huqigu/JCNetworking.git"
# Pods for TestSDK
target 'TestSDKTests' do
inherit! :search_paths
# Pods for testing
end
target 'TestSDKUITests' do
inherit! :search_paths
# Pods for testing
end
end
更新:将podspec推送到cocoapods仓库 // 邮箱为github绑定的邮箱,会发送一封带有链接的邮件,打开链接即完成注册
yellow@jiangchongdeMacBook-Pro ~ pod trunk register huqigu@163.com 'mxObject-c' --description='regist trunk'
// 然后将仓库推送到cocoapods上
yellow@jiangchongdeMacBook-Pro ~/Desktop/JCNetworking master ● pod trunk push JCNetworking.podspec --allow-warnings
// 出现如下信息表示上传成功
🎉 Congrats
🚀 JCNetworking (0.1.0) successfully published
📅 February 27th, 21:23
🌎 https://cocoapods.org/pods/JCNetworking
👍 Tell your friends!
|