定时器
CADisplayLink 定时器
1,调用频率和屏幕的刷帧频率一致,60FPS 2,监控屏幕是否掉帧。(cpu计算,gpu绘制,如果处理不了就会掉帧)
NSTimer
初始化方法
scheduledTimerWithTimeInterval 直接放入到了runloop 中, timerWithTimeInterval 只初始化没有让如到runloop 中
timer 存在的循环引用
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timerTest) userInfo:nil repeats:YES];
1,self 拥有了 timer ,timer又拥有了self,导致循环引用
timer 循环引用的解决(错误方法)
错误方法,__weak 修饰self, 原因1 __weak 修饰block 内的self 时,block对内部的self弱引用,打破了循环,但是这个不是block. 原因2 用__weak 修饰self,timer内部可能是用强引用持有的self,还是没有打破循环引用。
timer 循环引用的解决(错误方法)
1,用带block的timer,__weak修饰self (即可打破循环引用)
__weak typeof(self) weakSelf = self;
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
[weakSelf timerTest];
}];
timer 循环引用的解决(加入中间层打破循环引用)
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:[MJProxy proxyWithTarget:self] selector:@selector(timerTest) userInfo:nil repeats:YES];
1,timer强持有中间层,中间层弱持有控制器(此处为代指) 2,中间层没有处理事件的能力(timerTest),消息发送的本质解决问题 交给传入的弱持有对象,即可。
@implementation MJProxy
+ (instancetype)proxyWithTarget:(id)target
{
MJProxy *proxy = [[MJProxy alloc] init];
proxy.target = target;
return proxy;
}
- (id)forwardingTargetForSelector:(SEL)aSelector
{
return self.target;
}
@end
timer 循环引用的解决(加入NSProxy中间层打破循环引用)
1,NSProxy 和NSObject平级,专门用来做消息转发的(不会去父类中寻找,有没有实现此方法) 2,因此它的效率更高
@implementation MJProxy
+ (instancetype)proxyWithTarget:(id)target
{
// NSProxy对象不需要调用init,因为它本来就没有init方法
MJProxy *proxy = [MJProxy alloc];
proxy.target = target;
return proxy;
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel
{
return [self.target methodSignatureForSelector:sel];
}
- (void)forwardInvocation:(NSInvocation *)invocation
{
[invocation invokeWithTarget:self.target];
}
@end
GCD定时器 (NSTimer依赖于RunLoop,如果RunLoop的任务过于繁重,可能会导致NSTimer不准时)
而GCD的定时器会更加准时
GCD定时器的使用

gcd 定时器的封装
1,设计接口,外界如何调用。 2,考虑多线程,(当多个线程都创建定时器时)
信号量为1,同一时间只执行一个线程里面的任务
dispatch_semaphore_wait(semaphore_, DISPATCH_TIME_FOREVER);
任务
dispatch_semaphore_wait(semaphore_, DISPATCH_TIME_FOREVER);
iOS中的内存布局(代码段,数据段(先常量后变量),堆,栈,内核)
1,堆空间是越来越大的。 2,栈空间是越来越小的。

int a = 10;
int b;
int main(int argc, char * argv[]) {
@autoreleasepool {
static int c = 20;
static int d;
int e;
int f = 20;
NSString *str = @"123";
NSObject *obj = [[NSObject alloc] init];
NSLog(@"\n&a=%p\n&b=%p\n&c=%p\n&d=%p\n&e=%p\n&f=%p\nstr=%p\nobj=%p\n",
&a, &b, &c, &d, &e, &f, str, obj);
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
/*
字符串常量
str=0x10dfa0068
已初始化的全局变量、静态变量
&a =0x10dfa0db8
&c =0x10dfa0dbc
未初始化的全局变量、静态变量
&d =0x10dfa0e80
&b =0x10dfa0e84
堆
obj=0x608000012210
栈
&f =0x7ffee1c60fe0
&e =0x7ffee1c60fe4
*/
Tagged Pointer (将小数据直接存到指针里面)
1,从64bit开始,iOS引入了Tagged Pointer技术,用于优化NSNumber、NSDate、NSString等小对象的存储
2,在没有使用Tagged Pointer之前, NSNumber等对象需要动态分配内存、维护引用计数等,NSNumber指针存储的是堆中NSNumber对象的地址值
3,使用Tagged Pointer之后,NSNumber指针里面存储的数据变成了:Tag + Data,也就是将数据直接存储在了指针中
4,当指针不够存储数据时,才会使用动态分配内存的方式来存储数据
5,objc_msgSend能识别Tagged Pointer,比如NSNumber的intValue方法,直接从指针提取数据,节省了以前的调用开销
6,如何判断一个指针是否为Tagged Pointer? iOS平台,最高有效位是1(第64bit) Mac平台,最低有效位是1 这里是判断的源码


Tagged Pointer (不是OC对象)

1,第一段代码会崩溃,报坏内存访问。
因为线程不安全。ser方法时,name可能已经被释放了
set方法的本质
- (void)setName:(NSString *)name
{
if (_name != name) {
[_name release];
_name = [name retain];
}
}
2,第二段代码不会崩溃
本质上就不会走set方法。
MRC
改成mrc 模式
Automatic Reference congting 改为No
引用计数控制对象的生命周期
一个对象的引用计数为0时销毁内存,防止内存泄漏
MRC 手动管理内存,当有alloc 时就得有release
当只有一个对象时,
一个alloc 对应一个release
当一个对象还持有另外一对象时
要让当前对象强只有另一个对象,让另一个对象的引用计数+1。
防止另一个对象被销毁了,这个对象还要调用。
对象持有另一个对象的set方法
- 如果传入的新对象和旧对象一样,就什么都不做(为什么什么都不做,因为释放的时候也是释放一次,这样管理更方便,如果愿意增加几次释放几次也可以,但是太不简洁了)
- 如果传入的不一样,先释放旧的(对于旧对象,持有的时候+1,现在不用了-1)
- 传的新的对象,管理其引用计数。开始接管其生命周期。
- (void)setDog:(MJDog *)dog
{
if (_dog != dog) {
[_dog release];
_dog = [dog retain];
}
}
内存管理原则,谁创建谁销毁
1,一个对象持有另一个对象时,另一个对象引用计数+1,此对象也要负责另外一个对象的释放。(一般本身销毁的时候,释放自己管理的对象。)
2,当一个对象变更属性对象时,先释放旧对象,在接管新对象。
编译器带来的优化
.h文件中声明的属性只是申明的set和get方法,但是没有方法的实现和_成员变量。
@synthesize 关键字可以完成生成成员变量和属性的setter、getter实现。 但是现在,这个关键字变成默认的了,不用主动声明。
类方法创建对象
类方法创建的对象,都放入自动释放池了。
+ (instancetype)person
{
return [[[self alloc] init] autorelease];
}
ARC 都帮我们做了什么?
LLVM + Runtime
- LLVM (加上,release,retain,autoRlease 操作)
- Runtime (运行时,清空弱引用的对象)
copy
为什么要有copy (产生一个副本对象,跟源对象互不影响)
修改了源对象,不会影响副本对象
修改了副本对象,不会影响源对象

两种copy 方法 (copy 一定会产生副本对象,为了和源对象互不影响,但是不一定产生新的对象)
通过打印地址来看有没有生成新的对象。
copy
copy,不可变拷贝,产生不可变副本(可能不会产生新对象) 如果源对象本身不可变,copy之后产生不可变副本,此时只需要产生一个新的指针指向源地址就可以了。(都是不可变对象,没必要新开辟内存了)
mutableCopy (只有用于Foundation中的对象)
mutableCopy,可变拷贝,产生可变副本(一定会产生新对象) 它只生成新对象,才会产生可变副本。
如果mutableCopy 可变数组也可能浅copy(可变集合中元素是自定义(自定义对象要准守协议)对象或者二维集合)
两种copy结果(深copy和浅copy)
深拷贝
浅拷贝
1.深拷贝:内容拷贝,产生新的对象 2.浅拷贝:指针拷贝,没有产生新的对象
为什么用copy修饰字符串
1,如果用storng修饰的话,源字符串修改。新的字符串也会修改。 2,UITextField的text是用copy修饰的,防止拿着这个属性去拼接别的字符串,让这个text只接受生成好的字符串。
自定义对象的copy
1,准守 协议 2,重写copyWithZone 方法
- (id)copyWithZone:(NSZone *)zone
{
MJPerson *person = [[MJPerson allocWithZone:zone] init];
person.age = self.age;
// person.weight = self.weight;
return person;
}
weak 内部实现(重点)
自动释放池 autoreleasepool 的原理
autoreleasepool 转成C++代码 1,构造函数(创建函数时)(push 调用push方法会将一个POOL_BOUNDARY入栈,并且返回其存放的内存地址 2,析构函数(销毁函数时)(调用pop方法时传入一个POOL_BOUNDARY的内存地址,会从最后一个入栈的对象开始发送release消息,直到遇到这个POOL_BOUNDARY)
struct __AtAutoreleasePool {
__AtAutoreleasePool() { // 构造函数,在创建结构体的时候调用
atautoreleasepoolobj = objc_autoreleasePoolPush();
}
~__AtAutoreleasePool() { // 析构函数,在结构体销毁的时候调用
objc_autoreleasePoolPop(atautoreleasepoolobj);
}
void * atautoreleasepoolobj;
};
自动释放池的主要底层数据结构 :__AtAutoreleasePool、AutoreleasePoolPage

AutoreleasePoolPage的结构
- 每个AutoreleasePoolPage对象占用4096字节内存,除了用来存放它内部的成员变量,剩下的空间用来存放autorelease对象的地址(即放入自动释放池的对象)
- 所有的AutoreleasePoolPage对象通过双向链表的形式连接在一起

-
调用push方法会将一个POOL_BOUNDARY入栈,并且返回其存放的内存地址
-
调用pop方法时传入一个POOL_BOUNDARY的内存地址,会从最后一个入栈的对象开始发送release消息,直到遇到这个POOL_BOUNDARY
-
id *next指向了下一个能存放autorelease对象地址的区域

autoreleasepool 什么时候释放
1,肯定不是被main函数外边的autorele释放的(当然,main也不是在程序退出的时候才释放)
autoreleasepool 和runloop
这个对象什么时候调用release,是由RunLoop来控制的。 它可能是在某次RunLoop循环中,RunLoop休眠之前调用了release
原理 iOS在主线程的Runloop中注册了2个Observer 第1个Observer监听了kCFRunLoopEntry事件,会调用objc_autoreleasePoolPush() 第2个Observer 监听了kCFRunLoopBeforeWaiting事件,会调用objc_autoreleasePoolPop()、objc_autoreleasePoolPush() 监听了kCFRunLoopBeforeExit事件,会调用objc_autoreleasePoolPop()
方法里有局部对象, 出了方法后会立即释放吗
如果这个对象是通过autoreleasepool的方法,不会立即释放。 如果是arc 自动管理的话会立即释放。
可以通过以下私有函数来查看自动释放池的情况
c语言语法,声明一下可以直接调用
extern void _objc_autoreleasePoolPrint(void);
什么时候手动创建自动释放池
for 循环创建大量零时变量的时候。此时自动释放会有延时。需要手动创建
性能优化
CPU和GPU
CPU(Central Processing Unit,中央处理器) 对象的创建和销毁、对象属性的调整、布局计算、文本的计算和排版、图片的格式转换和解码、图像的绘制(Core Graphics)
GPU(Graphics Processing Unit,图形处理器) 纹理的渲染

卡顿产生的原因
CPU、GPU资源消耗过大
卡顿问题的解决
卡顿解决的主要思路 尽可能减少CPU、GPU资源消耗
按照60FPS的刷帧率,每隔16ms就会有一次VSync信号
CPU 的优化
-
尽量用轻量级的对象,比如用不到事件处理的地方,可以考虑使用CALayer取代UIView
-
不要频繁地调用UIView的相关属性,比如frame、bounds、transform等属性,尽量减少不必要的修改
-
尽量提前计算好布局,在有需要时一次性调整对应的属性,不要多次修改属性
-
Autolayout会比直接设置frame消耗更多的CPU资源
-
图片的size最好刚好跟UIImageView的size保持一致
-
控制一下线程的最大并发数量
-
尽量把耗时的操作放到子线程 文本处理(尺寸计算、绘制) 图片处理(解码、绘制)
GPU的优化
-
尽量避免短时间内大量图片的显示,尽可能将多张图片合成一张进行显示
-
GPU能处理的最大纹理尺寸是4096x4096,一旦超过这个尺寸,就会占用CPU资源进行处理,所以纹理尽量不要超过这个尺寸
-
尽量减少视图数量和层次
-
减少透明的视图(alpha<1),不透明的就设置opaque为YES
-
尽量避免出现离屏渲染
卡顿优化
耗电的主要来源
CPU处理,Processing
网络,Networking
定位,Location
图像,Graphics
耗电优化
-
尽可能降低CPU、GPU功耗
-
少用定时器
-
优化I/O操作 尽量不要频繁写入小数据,最好批量一次性写入 读写大量重要数据时,考虑用dispatch_io,其提供了基于GCD的异步操作文件I/O的API。用dispatch_io系统会优化磁盘访问 数据量比较大的,建议使用数据库(比如SQLite、CoreData)
-
网络优化 减少、压缩网络数据 如果多次请求的结果是相同的,尽量使用缓存 使用断点续传,否则网络不稳定时可能多次传输相同的内容 网络不可用时,不要尝试执行网络请求 让用户可以取消长时间运行或者速度很慢的网络操作,设置合适的超时时间 批量传输,比如,下载视频流时,不要传输很小的数据包,直接下载整个文件或者一大块一大块地下载。如果下载广告,一次性多下载一些,然后再慢慢展示。如果下载电子邮件,一次下载多封,不要一封一封地下载
-
定位优化 如果只是需要快速确定用户位置,最好用CLLocationManager的requestLocation方法。定位完成后,会自动让定位硬件断电 如果不是导航应用,尽量不要实时更新位置,定位完毕就关掉定位服务 尽量降低定位精度,比如尽量不要使用精度最高的kCLLocationAccuracyBest 需要后台定位时,尽量设置pausesLocationUpdatesAutomatically为YES,如果用户不太可能移动的时候系统会自动暂停位置更新 尽量不要使用startMonitoringSignificantLocationChanges,优先考虑startMonitoringForRegion:
-
硬件检测优化 用户移动、摇晃、倾斜设备时,会产生动作(motion)事件,这些事件由加速度计、陀螺仪、磁力计等硬件检测。在不需要检测的场合,应该及时关闭这些硬件
APP的启动
APP的启动可以分为2种 冷启动(Cold Launch):从零开始启动APP 热启动(Warm Launch):APP已经在内存中,在后台存活着,再次点击图标启动APP
APP启动时间的优化,主要是针对冷启动进行优化
通过添加环境变量可以打印出APP的启动时间分析(Edit scheme -> Run -> Arguments) DYLD_PRINT_STATISTICS设置为1 如果需要更详细的信息,那就将DYLD_PRINT_STATISTICS_DETAILS设置为1
APP的启动阶段
APP的冷启动可以概括为3大阶段 dyld runtime main

APP的启动 - dyld
-
dyld(dynamic link editor),Apple的动态链接器,可以用来装载Mach-O文件(可执行文件、动态库等)
-
启动APP时,dyld所做的事情有 装载APP的可执行文件,同时会递归加载所有依赖的动态库 当dyld把可执行文件、动态库都装载完毕后,会通知Runtime进行下一步的处理
APP的启动 - runtime
1.启动APP时,runtime所做的事情有 调用map_images进行可执行文件内容的解析和处理 在load_images中调用call_load_methods,调用所有Class和Category的+load方法 进行各种objc结构的初始化(注册Objc类 、初始化类对象等等) 调用C++静态初始化器和__attribute__((constructor))修饰的函数
- 到此为止,可执行文件和动态库中所有的符号(Class,Protocol,Selector,IMP,…)都已经按格式成功加载到内存中,被runtime 所管理
APP的启动 - main
总结一下
-
APP的启动由dyld主导,将可执行文件加载到内存,顺便加载所有依赖的动态库
-
并由runtime负责加载成objc定义的结构
-
所有初始化工作结束后,dyld就会调用main函数
-
接下来就是UIApplicationMain函数,AppDelegate的application:didFinishLaunchingWithOptions:方法

安装包优化
安装包(IPA)主要由可执行文件、资源组成
-
资源(图片、音频、视频等) 采取无损压缩 去除没有用到的资源: github.com/tinymind/LS…
-
可执行文件瘦身 编译器优化 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
安装包(IPA)主要由可执行文件、资源组成
资源(图片、音频、视频等) 采取无损压缩 去除没有用到的资源: github.com/tinymind/LS…
可执行文件瘦身 编译器优化
-
生成LinkMap文件,可以查看可执行文件的具体组成
-
可借助第三方工具解析LinkMap文件: github.com/huanxsd/Lin…


架构模式
MVC
优点:View、Model可以重复利用,可以独立使用 缺点:Controller的代码过于臃肿
MVC变种
优点:对Controller进行瘦身,将View内部的细节封装起来了,外界不知道View内部的具体实现 缺点:View依赖于Model
MVP
Model-View-Presenter
MVVM
Model-View-ViewModel
封层架构

设计模式
什么是设计模式
是一套被反复使用、代码设计经验的总结 使用设计模式的好处是:可重用代码、让代码更容易被他人理解、保证代码可靠性 一般与编程语言无关,是一套比较成熟的编程思想
设计模式分类
创建型模式:对象实例化的模式,用于解耦对象的实例化过程 单例模式、工厂方法模式,等等
结构型模式:把类或对象结合在一起形成一个更大的结构 代理模式、适配器模式、组合模式、装饰模式,等等
行为型模式:类或对象之间如何交互,及划分责任和算法 观察者模式、命令模式、责任链模式,等等
小结
定时器的类型
界面刷新,timer(永不用加入到runloop中)
定时器的循环引用的解决方案。
1,__weak 修饰timer中的block 对象(只使用带block的timer) 2,添加中间层打破循环引用,再利用消息发送机制,让中间层处理timer要执行的方法。
内存布局(内存地址由低到高)
1,代码段:编译后的代码
2,常量
3,变量
4,堆
5,栈
Tagged Pointer (将小数据直接存到指针里面)
如何判断不是Tagged Pointer
打印地址的最后一位,转成二进制后判断 iOS平台,最高有效位是1(第64bit) Mac平台,最低有效位是1
MRC
创建和释放要一一对应。
谁持有谁释放(set方法的三步)
1,判断有没有变更对象,2释放旧的对象,3接管新的对象。
copy
copy 和mutableCopy 是操作方法
1.copy,不可变拷贝,产生不可变副本 2.mutableCopy,可变拷贝,产生可变副本
深copy 和浅copy是操作结果
1.深拷贝:内容拷贝,产生新的对象 2.浅拷贝:指针拷贝,没有产生新的对象
weak的内部实现结构
自动释放池 autoreleasepool (实现原理)
自动释放池 autoreleasepool 和 runloop (依赖关系)
性能优化
App 三大启动阶段
-
APP的启动由dyld主导,将可执行文件加载到内存,顺便加载所有依赖的动态库
-
并由runtime负责加载成objc定义的结构
-
所有初始化工作结束后,dyld就会调用main函数
GPU CPU
安装包优化
- 资源(图片、音频、视频等)
- 可执行文件瘦身
架构模式
MVC,MVP,MVVM