1 OC基础
1.1 分类(category)
使用场景:
给现有类添加方法
分解大类,如按功能划分
声明私有方法
底层实现(Runtime):
1 | typedef struct category_t { |
tips:分类不能添加对象,但可使用关联对象间接实现
分类和拓展的区别:
分类为运行时决议,扩展为编译时决议
分类有声明和实现,而拓展只以声明形式存在,多数情况下寄生于宿主类.m中
可以为系统类添加分类,但不能为系统类添加拓展
1.2 扩展(Extension)
使用场景:
- 声明私有属性
- 声明私有方法
- 声明私有成员变量
特点:
- 编译时决议
- 只以声明形式存在,多数情况下寄生于宿主类.m中
- 不能为系统类添加扩展
1.3 代理
特点:
- 是一种软件设计模式
- iOS中以@protocol形式体现
- 传递方式一对一
1.4 通知
特点:
- 使用观察者模式来实现的用意跨层传递消息的机制
- 传递方式为一对多
1.5 KVC(Key-value coding)
使用字符串关联进行赋值
就俩方法:
1 | -(id)valueForKey:(NSString *)key |
1.6 KVO(Key-value Observing)
是观察者模式的又一个实现
使用isa混写(isa-swizzling)实现
触发方法:
使用setter方法改变值
使用KVC赋值
手动触发KVO则需添加如下方法:
1
2
3[self willChangeValueForKey:...];
一般在这里修改值
[self didChangeValueForKey:...];
1.7 属性关键字
1.7.1 读写权限相关
- readonly(只读)
- readwrite(读写)
1.7.2 原子性相关
- atomic(原子性)
- nonatomic(非原子性)
1.7.3 引用计数相关
- retain/strong
- assign/unsafe_unretained
- weak
- copy
1.8 Runtime
1.8.1 objc_object
数据结构包含:
1 | isa_t isa; |
1.8.2 objc_class(继承自objc_object)
数据结构包含:
1 | class superClass; |
1.8.3 isa相关
1.8.3.1 isa指针类型(共用体isa_t)
指针型isa(isa的值代表class的地址)
非指针型isa(isa的值的部分代表class的地址)
1.8.3.2 指向
- 对象的isa指向它的类对象
- 类对象的isa指向它的元类对象
1.8.4 cache_t
定义:
用于快速查找方法执行函数
是可增量扩展的哈希表结构
是局部性原理的最佳应用
1.8.5 class_data_bits_t
定义:
- 是对class_rw_t的封装
- class_rw_t代表了类相关的读写信息,是对class_ro_t的封装
- class_ro_t代表了类相关的只读信息
1.8.6 class_rw_t
数据结构包含:
1 | class_ro_t |
1.8.7 class_ro_t
数据结构包含:
1 | name |
1.8.8 method_t
数据结构包含:
1 | SEL name; // 名称 |
1.8.9 消息传递
1.8.9.1 objc_msgSend方法
1 | void obic_msgSend(void /*id self, SEL op, ...*/) |
1.8.9.2 objc_msgSendSuper方法
1 | void objc_msgSendSuper(void /*struct objc_super *super,SEL op, ...*/) |
面试可能问题:
1、在同一个类中调用[self class] 和 [super class]得到的结果分别是什么?
答案必须都是当前类喽,因为消息的接受者都是当前对象
1.8.10 消息转发
消息转发共有三个阶段:方法解析处理阶段、快速转发阶段、常规转发阶段
越早处理,所付出的代价越小
1.8.10.1 resolveClassMethod/resolveInstanceMethod
这个阶段,用于通过Runtime方式动态添加方法实现IMP
涉及方法:
1 | + (BOOL)resolveClassMethod:(SEL)sel // 类方法 |
例:
1 | +(BOOL)resolveInstanceMethod:(SEL)sel{ |
1.8.10.2 forwardingTargetForSelector
这个阶段用于将消息转发给另一个对象接受,该对象应该具备该消息的具体实现
涉及方法:
1 | -(id)forwardingTargetForSelector:(SEL)aSelector |
例:
1 | -(id)forwardingTargetForSelector:(SEL)aSelector{ |
1.8.10.3 methodSignatureForSelector和forwardInvocation
该阶段可以将消息转发给多个对象
涉及方法:
1 | -(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector |
例:
1 | -(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{ |
1.9 Runloop
1.9.1 数据结构
包含数据:
1 | pthread |
1.9.2 CFRunloopMode
数据结构包含:
1 | name 如:NSDefaultRunloopMode |
1.9.3 CFRunloopSource
1.9.3.1 source0
需要手动唤醒线程
1.9.3.2 source1
具备唤醒线程的能力
1.9.4 CFRunloopTimer
1.9.5 CFRunloopObserver
观测时间点:
- kCFRunloopEntry
- kCFRunloopBeforeTimers
- kCFRunloopBeforeSources
- kCFRunloopBeforeWaiting
- kCFRunloopAfterWaiting
- KcfRunloopExit
2 设计原则
2.1 单一职责原则
一个类只负责一件事
2.2 依赖倒置原则
抽象不应该依赖于具体实现,具体实现可以依赖于抽象
2.3 开闭原则
对修改关闭,对扩展开放
2.4 里氏替换原则
父类可以被子类无缝替换,且原有功能不受任何影响
2.5 接口隔离原则
使用多个专门的协议,而不是一个庞大臃肿的协议
2.6 迪米特法则
一个对象应当对其他对象有尽可能少的了解,高内聚、低耦合
3 设计模式
3.1 代理模式
3.2 工厂模式
3.3 观察者模式
3.4 单例模式
3.5 MVC
3.6 MVVM
3.7 MVP
4 算法
4.1 不用中间变量,交换A和B的值
1 | // 加法 |
4.2 求最大公约数
1 | // 遍历法 |
4.3 判断质数
质数指只能被1或本身整除的数
1 | int check(int a) { |
4.4 字符串反转
1 | void reserve(char *cha,int length) { |
4.5 选择排序
选出最小/最大的数,放在第一位,然后再在剩下的列表数据里再选出最小/最大数放在第二位,以此类推
1 | - (NSArray *)sort1:(NSArray *)array { |
4.6 冒泡排序
从首位起,与右侧数比较,大的话交换,直到最后一位。然后重新从首位开始同样进行比较及交换操作,直到最后第二位,以此类推
1 | - (NSArray *)sort2:(NSArray *)array { |
4.7 折半查找
数组必须是有序的
搜索过程从数组的中间元素开始,如果中间元素正好是要查找的元素,则搜索过程结束;如果某一特定元素大于或者小于中间元素,则在数组大于或小于中间元素的那一半中查找,而且跟开始一样从中间元素开始比较。如果在某一步骤数组为空,则代表找不到。这种搜索算法每一次比较都使搜索范围缩小一半
1 | int findKey(int *arr, int length, int key) { |
4.8 快速排序
通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归
进行,以此达到整个数据变成有序序列
1 | void quick_sort(int s[], int l, int r) |
5 性能优化
参考文章:https://www.jianshu.com/p/4e9c6a048f6f
5.1 卡顿优化
尽可能减少CPU、GPU资源消耗
按照60FPS的刷帧率,每隔16ms就会有一次VSync信号
尽量用轻量级的对象,比如用不到事件处理的地方,可以考虑使用CALayer取代UIView
不要频繁地调用UIView的相关属性,比如frame、bounds、transform等属性,尽量减少不必要的修改
尽量提前计算好布局,在有需要时一次性调整对应的属性,不要多次修改属性
Autolayout会比直接设置frame消耗更多的CPU资源
图片的size最好刚好跟UIImageView的size保持一致
控制一下线程的最大并发数量
尽量把耗时的操作放到子线程
文本处理(尺寸计算、绘制)
图片处理(解码、绘制)
尽量避免短时间内大量图片的显示,尽可能将多张图片合成一张进行显示
GPU能处理的最大纹理尺寸是4096x4096,一旦超过这个尺寸,就会占用CPU资源进行处理,所以纹理尽量不要超过这个尺寸
尽量减少视图数量和层次
减少透明的视图(alpha<1),不透明的就设置opaque为YES
尽量避免出现离屏渲染
离屏渲染消耗性能的原因:
- 需要创建新的缓冲区
- 离屏渲染的整个过程,需要多次切换上下文环境,先是从当前屏幕(On-Screen)切换到离屏(Off-Screen);等到离屏渲染结束以后,将离屏缓冲区的渲染结果显示到屏幕上,又需要将上下文环境从离屏切换到当前屏幕
离屏渲染的触发场景:
- 光栅化,layer.shouldRasterize = YES
- 遮罩,layer.mask
- 圆角,同时设置layer.masksToBounds = YES、layer.cornerRadius大于0
- 阴影,layer.shadowXXX
卡顿检测:
可以添加Observer到主线程RunLoop中,通过监听RunLoop状态切换的耗时,以达到监控卡顿的目的
5.2 耗电优化
5.2.1 尽可能降低CPU、GPU功耗
- 少用定时器
5.2.2 优化I/O操作
- 尽量不要频繁写入小数据,最好批量一次性写入
- 读写大量重要数据时,考虑用dispatch_io,其提供了基于GCD的异步操作文件I/O的API。用dispatch_io系统会优化磁盘访问
- 数据量比较大的,建议使用数据库(比如SQLite、CoreData)
5.3 网络优化
- 减少、压缩网络数据
- 如果多次请求的结果是相同的,尽量使用缓存
- 使用断点续传,否则网络不稳定时可能多次传输相同的内容
- 网络不可用时,不要尝试执行网络请求
- 让用户可以取消长时间运行或者速度很慢的网络操作,设置合适的超时时间
- 批量传输,比如,下载视频流时,不要传输很小的数据包,直接下载整个文件或者一大块一大块地下载。如果下载广告,一次性多下载一些,然后再慢慢展示。如果下载电子邮件,一次下载多封,不要一封一封地下载
5.4 定位优化
- 如果只是需要快速确定用户位置,最好用CLLocationManager的requestLocation方法。定位完成后,会自动让定位硬件断电
- 如果不是导航应用,尽量不要实时更新位置,定位完毕就关掉定位服务
- 尽量降低定位精度,比如尽量不要使用精度最高的kCLLocationAccuracyBest
- 需要后台定位时,尽量设置pausesLocationUpdatesAutomatically为YES,如果用户不太可能移动的时候系统会自动暂停位置更新
- 尽量不要使用startMonitoringSignificantLocationChanges,优先考虑startMonitoringForRegion:
5.5 硬件检测优化
用户移动、摇晃、倾斜设备时,会产生动作(motion)事件,这些事件由加速度计、陀螺仪、磁力计等硬件检测。在不需要检测的场合,应该及时关闭这些硬件
5.6 APP的启动优化
通过添加环境变量可以打印出APP的启动时间分析(Edit scheme -> Run -> Arguments)
DYLD_PRINT_STATISTICS设置为1
如果需要更详细的信息,那就将DYLD_PRINT_STATISTICS_DETAILS设置为1
APP的冷启动可以概括为3大阶段
- dyld
- runtime
- main
APP的启动 - dyld
dyld(dynamic link editor),Apple的动态链接器,可以用来装载Mach-O文件(可执行文件、动态库等)
- 启动APP时,dyld所做的事情有
- 装载APP的可执行文件,同时会递归加载所有依赖的动态库
- 当dyld把可执行文件、动态库都装载完毕后,会通知Runtime进行下一步的处理
APP的启动 - runtime
- 启动APP时,runtime所做的事情有
- 调用map_images进行可执行文件内容的解析和处理
- 在load_images中调用call_load_methods,调用所有Class和Category的+load方法
- 进行各种objc结构的初始化(注册Objc类 、初始化类对象等等)
- 调用C++静态初始化器和attribute((constructor))修饰的函数
- 到此为止,可执行文件和动态库中所有的符号(Class,Protocol,Selector,IMP,…)都已经按格式成功加载到内存中,被runtime 所管理
总结一下
- APP的启动由dyld主导,将可执行文件加载到内存,顺便加载所有依赖的动态库
- 并由runtime负责加载成objc定义的结构
- 所有初始化工作结束后,dyld就会调用main函数
- 接下来就是UIApplicationMain函数,AppDelegate的application:didFinishLaunchingWithOptions:方法
APP的启动优化
dyld阶段
- 减少动态库、合并一些动态库(定期清理不必要的动态库)
- 减少Objc类、分类的数量、减少Selector数量(定期清理不必要的类、分类)
- 减少C++虚函数数量
- Swift尽量使用struct
runtime阶段
- 用+initialize方法和dispatch_once取代所有的attribute((constructor))、C++静态构造器、ObjC的+load
main阶段
- 在不影响用户体验的前提下,尽可能将一些操作延迟,不要全部都放在finishLaunching方法中
- 按需加载
5.7 安装包瘦身
- 采取无损压缩
- 去除没有用到的资源
- 编译器优化
- Strip Linked Product、Make Strings Read-Only、Symbols Hidden by Default设置为YES
- 去掉异常支持,Enable C++ Exceptions、Enable Objective-C Exceptions设置为NO, Other C Flags添加-fno-exceptions
- 利用AppCode检测未使用的代码:菜单栏 -> Code -> Inspect Code
- 编写LLVM插件检测出重复代码、未被调用的代码