APP崩溃mysql
启动秒退git
在新 iOS 上正常的应用,到了老版本 iOS 上秒退最多见缘由是系统动态连接库或Framework没法找到。这种状况一般是因为 App 引用了一个新版操做系统里的动态库(或者某动态库的新版本)或只有新 iOS 支持的 Framework,而又没有对老系统进行测试,因而当 App 运行在老系统上时便因为找不到而秒退。解决办法是等开发人员发现这个问题后升级程序,或由用户自行升级其操做系统。程序员
还有一种常见的秒退是程序在升级时,修改了本地存储的数据结构,可是对用户既存的旧数据没有作好升级,结果致使初始化时由于没法正确读取用户数据而秒退。这类问题一般只需删除程序后从新安装一遍就能解决。但缺点是用户的既存数据会丢失——就算有备份可能也无济于事,由于备份下来的旧数据仍是没法被正确升级。github
还有一类秒退或是用到 App 里某个功能后必退的缘由,是开发时用到了只有新版操做系统才支持的某个方法,而又没有对该方法是否存在于老系统中作出判断。例如程序启动时用到了 Game Center,而没有判断用户的机器是否支持 Game Center,因而就秒退了。sql
访问的数据为空或者类型不对数据库
这类状况是比较常见的,后端传回了空数据,客户端没有作对应的判断继续执行下去了,这样就产生了crash。或者本身本地的某个数据为空数据而去使用了。还有就是访问的数据类型不是指望的数据类型而产生崩溃。好比,我指望服务端返回string类型,可是后台给我返回的是NSNumber类型,那么我操做时候用到的是string的方法。结果由于找不到对应的方法而崩溃。解决办法:一、服务端都加入默认值,不返回空内容或无key。或者是在客户端进行非空判断。二、对容易出错的地方提早进行类型判断。编程
点击事件方法处理不当后端
这类状况比较常见,好比个人点击事件须要对传入的参数作处理,可是点击时,我传入的对象类型或者传入为空,等到参数进行处理的时候,因为方法不当,产生crash。数组
数组越界缓存
当客户端尝试对数组中的数据进行操做的时候,数组为空或者所作的操做index 超过数组自己范围,就会引发崩溃。
下拉刷新时崩溃
使用下拉刷新时,若是下拉的距离长了就会崩溃。缘由是,在下拉刷新请求数据以前就将本地数组清空了。分析,下拉刷新的逻辑:一、下拉 二、下拉达到临界值时触发网络请求 三、等待数据加载到本地之后才更新datasource 四、tableview reloadData。 若是先清空数组再下拉请求,后果就是往下拉的距离超过一个 cell 的高度时,table view 的几个委托方法就会被调用,因为 data source 已经被清空,形成错误的内存访问(包括数组越界,访问已销毁的对象)致使 crash。
操做了不应操做的对象、野指针
iOS中有空指针和野指针两种概念。
空指针是没有存储任何内存地址的指针。如Student s1 = NULL;和Student s2 = nil;
而野指针是指指向一个已删除的对象("垃圾"内存既不可用内存)或未申请访问受限内存区域的指针。野指针是比较危险的。由于野指针指向的对象已经被释放了,不能用了,你再给被释放的对象发送消息就是违法的,因此会崩溃。
野指针访问已经释放的对象crash其实不是必现的,由于dealloc执行后只是告诉系统,这片内存我不用了,而系统并无就让这片内存不能访问。
因此野指针的奔溃是比较随机的,你在测试的时候可能没发生crash,可是用户在使用的时候就可能发生crash了。
注意:arc环境比非arc环境更少出现野指针。
一、对象释放后内存没被改动过,原来的内存保存无缺,可能不Crash或者出现逻辑错误(随机Crash)。
二、对象释放后内存没被改动过,可是它本身析构的时候已经删掉某些必要的东西,可能不Crash、Crash在访问依赖的对象好比类成员上、出现逻辑错误(随机Crash)。
三、对象释放后内存被改动过,写上了不可访问的数据,直接就出错了极可能Crash在objc_msgSend上面(必现Crash,常见)。
四、对象释放后内存被改动过,写上了能够访问的数据,可能不Crash、出现逻辑错误、间接访问到不可访问的数据(随机Crash)。
对象释放后内存被改动过,写上了能够访问的数据,可是再次访问的时候执行的代码把别的数据写坏了,遇到这种Crash只能哭了(随机Crash,难度大,几率低)!!
五、对象释放后再次release(几乎是必现Crash,但也有例外,很常见)
内存处理不当
用instruments排查内存泄露问题
主线程UI长时间卡死,被系统杀掉
主线程被卡住是很是常见的场景,具体表现就是程序不响应任何的UI交互。这时按下调试的暂停按钮,查看堆栈,就能够看到是究竟是死锁、死循环等,致使UI线程被卡住。
多线程之间切换访问引发的crash
多线程引发的崩溃大部分是由于使用数据库的时候多线程同时读写数据库而形成了crash。
内存紧张
这个如今不多遇到了。
ARC内存泄漏
block系列
在 ARC 下,当 block 获取到外部变量时,因为编译器没法预测获取到的变量什么时候会被忽然释放,为了保证程序可以正确运行,让 block 持有获取到的变量,向系统显明:我要用它,大家千万别把它回收了!然而,也正因 block 持有了变量,容易致使变量和 block 的循环引用,形成内存泄露!
对于 block 中的循环引用一般有两种解决方法:
一、将对象置为 nil ,消除引用,打破循环引用;
(这种作法有个很明显的缺点,即开发者必须保证 _networkFetecher = nil; 运行过。若不如此,就没法打破循环引用。
但这种作法的使用场景也很明显,因为 block 的内存必须等待持有它的对象被置为 nil 后才会释放。因此若是开发者但愿本身控制 block 对象的生命周期时,就能够使用这种方法。)
二、将强引用转换成弱引用,打破循环引用;
(__weak __typeof(self) weakSelf = self;若是想防止 weakSelf 被释放,能够再次强引用 __typeof(&*weakSelf) strongSelf = weakSelf;代码 __typeof(&*weakSelf) strongSelf 括号内为何要加 &* 呢?主要是为了兼容早期的 LLVM
block 的内存泄露问题包括自定义的 block,系统框架的 block 如 GCD 等,都须要注意循环引用的问题。
有个值得一提的细节是,在种类众多的 block 当中,方法名带有 usingBlock 的 Cocoa Framework 方法或 GCD 的 API ,如
1
2
|
- enumerateObjectsUsingBlock:
- sortUsingComparator:
|
这一类 API 一样会有循环引用的隐患,但缘由并不是编译器作了保留,而是 API 自己会对传入的 block 作一个复制的操做。
delegate系列
1
|
@property (nonatomic, weak) id delegate;
|
说白了就是循环使用的问题,假如咱们是写的strong,那么 两个类之间调用代理就是这样的啦
1
2
3
4
5
6
7
8
9
10
|
BViewController *bViewController = [[BViewController alloc] init];
bViewController.delegate = self;
//假设 self 是AViewController
[self.navigationController pushViewController:bViewController animated:YES];
/**
假如是 strong 的状况
bViewController.delegate ===> AViewController (也就是 A 的引用计数 + 1)
AViewController 自己又是引用了 ===> delegate 引用计数 + 1
致使: AViewController Delegate ,也就循环引用啦
*/
|
Delegate建立并强引用了 AViewController;(strong ==> A 强引用、weak ==> 引用计数不变)
因此用 strong的状况下,至关于 Delegate 和 A 两个互相引用啦,A 永远会有一个引用计数 1 不会被释放,因此形成了永远不能被内存释放,所以weak是必须的。
performSelector 系列
performSelector 顾名思义即在运行时执行一个 selector,最简单的方法以下
1
|
- (id)performSelector:(SEL)selector;
|
这种调用 selector 的方法和直接调用 selector 基本等效,执行效果相同
1
2
|
[object methodName];
[object performSelector:@selector(methodName)];
|
但 performSelector 相比直接调用更加灵活
1
2
3
4
5
6
7
8
9
|
SEL selector;
if
(
/* some condition */
) {
selector = @selector(newObject);
}
else
if
(
/* some other condition */
) {
selector = @selector(copy);
}
else
{
selector = @selector(someProperty);
}
id ret = [object performSelector:selector];
|
这段代码就至关于在动态之上再动态绑定。在 ARC 下编译这段代码,编译器会发出警告
1
|
warning: performSelector may cause a leak because its selector is unknow [-Warc-performSelector-leak]
|
正是因为动态,编译器不知道即将调用的 selector 是什么,不了解方法签名和返回值,甚至是否有返回值都不懂,因此编译器没法用 ARC 的内存管理规则来判断返回值是否应该释放。所以,ARC 采用了比较谨慎的作法,不添加释放操做,即在方法返回对象时就可能将其持有,从而可能致使内存泄露。
以本段代码为例,前两种状况(newObject, copy)都须要再次释放,而第三种状况不须要。这种泄露隐藏得如此之深,以致于使用 static analyzer 都很难检测到。若是把代码的最后一行改为
[object performSelector:selector];
不建立一个返回值变量测试分析,简直不可思议这里竟然会出现内存问题。因此若是你使用的 selector 有返回值,必定要处理掉。
还有一种状况就是performSelector的延时调用[self performSelector:@selector(method1:) withObject:self.myView afterDelay:5];,performSelector关于内存管理的执行原理是这样的,当执行[self performSelector:@selector(method1:) withObject:self.myView afterDelay:5];的时候,系统将myView的引用计数加1,执行完这个方法以后将myView的引用计数减1,而在延迟调用的过程当中极可能就会出现,这个方法被调用了,可是没有执行,此时myView的引用计数并无减小到0,也就致使了切换场景的dealloc方法没有被调用,这也就引发了内存泄漏。
NSTimer
NSTimer会形成循环引用,timer会强引用target即self,在加入runloop的操做中,又引用了timer,因此在timer被invalidate以前,self也就不会被释放。
因此咱们要注意,不只仅是把timer看成实例变量的时候会形成循环引用,只要申请了timer,加入了runloop,而且target是self,虽然不是循环引用,可是self却没有释放的时机。以下方式申请的定时器,self已经没法释放了。
1
2
|
NSTimer *timer = [NSTimer timerWithTimeInterval:5 target:self selector:@selector(commentAnimation) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
|
解决这种问题有几个实现方式,你们能够根据具体场景去选择:
增长startTimer和stopTimer方法,在合适的时机去调用,好比能够在viewDidDisappear时stopTimer,或者由这个类的调用者去设置。
每次任务结束时使用dispatch_after方法作延时操做。注意使用weakself,不然也会强引用self。
1
2
3
4
5
6
7
|
- (void)startAnimation
{
WS(weakSelf);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[weakSelf commentAnimation];
});
}
|
使用GCD的定时器,一样注意使用weakself。
1
2
3
4
5
6
7
|
WS(weakSelf);
timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC, 1 * NSEC_PER_SEC);
dispatch_source_set_event_handler(timer, ^{
[weakSelf commentAnimation];
});
dispatch_resume(timer);
|
内存泄漏的检测方式
1.静态分析
使用XCode分析功能,Product->Analyze
使用静态检测能够检查出一些明显的没有释放的内存,包括NSObject和CF开头的内存泄漏,最多见问题有2种,这些问题都不复杂,须要的是细心:
MRC的文件,常常遗漏release或者autorelease。
C方式申请的内存,忘记释放了。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
//1
static inline NSString* iphone_device_info(){
size_t size;
sysctlbyname(
"hw.machine"
, NULL, &size, NULL, 0);
char *machine = (char*)malloc(size);
sysctlbyname(
"hw.machine"
, machine, &size, NULL, 0);
NSString *platform = [NSString stringWithCString:machine encoding:NSASCIIStringEncoding];
...
}
//2
if
(alpha != 1) {
CGColorSpaceRef colorSpaceRef = CGColorSpaceCreateDeviceRGB();
CGColorRef color = CGColorCreate(colorSpaceRef, (CGFloat[]){255, 255, 255, 0.3});
[btn.layer setBorderColor:color];
}
|
不过在修改时应该注意:
这些场景是否真的泄漏了,以避免重复释放。
注意该文件是MRC仍是ARC,须要不一样的内存处理方式。
若是是C申请的内存,注意new delete, malloc free的配对处理。
好比咱们的代码中会遇到这样的问题。
1
2
3
4
|
if
([self.itemOutput hasNewPixelBufferForItemTime:currentTime]) {
[self displayPixelBuffer:[self.itemOutput copyPixelBufferForItemTime:currentTime itemTimeForDisplay:NULL]];
[_program useGlProgram];
}
|
进行静态检测时会报copyPixelBufferForItemTime内存泄漏,copy后的对象须要进行释放,可事实上,在“displayPixelBuffer”函数中已经对传入对内存进行了释放,咱们姑且不论这样对写法是否合理,只是切记在修改时注意结合上下文处理须要释放的内存。
2.动态检测 使用instruments
在Allocation中咱们主要关注的是Persistent和Persistent Bytes,分别表示当前时间段,申请了可是还没释放的内存数量和大小。
记住当前这两个值,而后进入某个新页面,退出该页面,观察这两个值是否增长。须要注意的是,因为有些图片调用自己是有缓存的,若是是用SDWebImage管理,则网络图片都会缓存在内存中。所以退出页面后内存有增长是正常的,并且还有些单例的内存也是不会释放的,咱们能够再次进入同一个页面,在图片都加载过的状况下,反复进入退出查看内存情况,若是持续增长,则说明有泄漏。
详细使用:
第三方工具MLeaksFinder
一、使用简单,不侵入业务逻辑代码,不用打开 Instrument
二、不须要额外的操做,你只需开发你的业务逻辑,在你运行调试时就能帮你检测
三、内存泄露发现及时,更改完代码后一运行即能发现(这点很重要,你立刻就能意识到哪里写错了)
四、精准,能准确地告诉你哪一个对象没被释放
具体特色,原理和集成方式能够参考以下博客的内容:
界面卡顿
1、离屏渲染
OpenGL中,GPU屏幕渲染有如下两种方式:
一.On-Screen Rendering
意为当前屏幕渲染,指的是GPU的渲染操做是在当前用于显示的屏幕缓冲区中进行。当前屏幕渲染是不须要额外建立额外的缓存,也不须要开启新的上下文,相较于离屏渲染,性能更好。
可是受当前屏幕渲染的局限因素限制(只有自身上下文,屏幕缓存有限等),不少图形渲染,当前屏幕渲染是解决不了的,这时必须使用到离屏渲染。
二.Off-Screen Rendering
离屏渲染,指的是GPU在当前屏幕缓冲区之外新开辟一个缓冲区进行渲染操做。
特殊的离屏渲染:CPU渲染
若是咱们重写了drawRect方法,而且使用任何Core Graphics的技术进行了绘制操做,就涉及到了CPU渲染,整个渲染过程有CPU在APP内同步的完成,渲染获得的bitmap(位图)最后再交由GPU用于显示。
注意:CoreGraphics一般是线程安全的,因此能够进行一步绘制,显示的时候再放回主线程,一个简单的异步绘制过程大体以下
1
2
3
4
5
6
7
8
9
10
11
|
-(void)display {
dispatch_async(backgroundQueue, ^{
CGContextRef ctx = CGBitmapContextCreate(...);
// draw in context...
CGImageRef img = CGBitmapContextCreateImage(ctx);
CFRelease(ctx);
dispatch_async(mainQueue, ^{
layer.contents = img;
});
});
}
|
普通的离屏渲染
相比于当前屏幕渲染,离屏渲染的代价是很高的,主要体如今两个方面:
1 建立新的缓冲区
想要进行离屏渲染,首先要建立一个新的缓冲区。
2 上下文切换
离屏渲染的整个过程,须要屡次切换上下文环境:先是从当前屏幕(On-Screen)切换到离屏(Off-Screen);等到离屏渲染结束之后,将离屏缓冲区的渲染结果显示到屏幕上有须要将上下文回家从离屏切换到当前屏幕。而上下文环境的切换时要付出很大代价的。
设置了如下属性时,都会触发离屏绘制:
1.shouldRasterize(光栅化)
2 masks(遮罩)
3 shadows (阴影)
4 edge antialiasing (抗锯齿)
5 group opacity (不透明)
其中shouldRasterize(光栅化)是比较特殊的一种:
光栅化概念:将图转化为一个个栅格组成的图像。
光栅化特色:每一个元素对应帧缓冲区中的一像素。
shouldRasterize = YES 在其余属性触发离屏渲染的同时, 会将光栅化后的内容缓存起来,若是对应的layer 及其sublayers没有发生改变,在下一帧的时候能够直接复用。shouldRasterize = YES, 这将隐式的建立一个位图,各类阴影遮罩等效果也会保存到位图中并缓存起来,从而减小渲染的频度(不是矢量图)。
至关于光栅化是把GPU的操做转到CPU上了,生成位图缓存,直接读取复用。
当你使用光栅化时,你能够开启"Color Hits Green And Misses Red"来检查该场景下光栅化操做是不是一个号的选择。绿色表示缓存被复用,红色表示缓存在被重复建立。
若是光栅化的层变红的太频繁那么光栅化对优化可能没有多少用处,位图缓存从内存中删除又从新建立的太过频繁,红色代表缓存重建的太迟。能够针对性的选择某个较小而深的层结构进行光栅化,来尝试减小渲染时间。
注意:
对于常常变更的内容, 这个时候不要开启,不然会形成性能的浪费。
例如咱们平常常常打交道的TableViewCell, 由于TableViewCell 的重绘是很频繁的(由于cell的复用),若是cell的内容不断变化,则cell须要不断重绘,若是此时设置了cell.layer可光栅化,则会形成大量的离屏渲染,下降图形性能。
为何使用离屏渲染
当使用圆角,阴影,遮罩的时候,图层属性的混合体被指定为在未预合成以前不能直接在屏幕中绘制,因此就须要屏幕外渲染被唤起。
屏幕外渲染并不意味着软件绘制,可是它意味着图层必须在被显示在一个屏幕外上下文中被渲染(不论死CPU仍是GPU)。
因此当使用离屏渲染的时候会很容易形成性能消耗,由于在OpenGL里离屏渲染会单独在内存中建立一个屏幕外缓冲区并进行渲染,而屏幕外缓冲区跟当前屏幕缓冲区上下文切换也是很耗性能的。
如何选择
摆在咱们面前的有三个选择:当前屏幕渲染、离屏渲染、CPU渲染。该使用哪一个呢?
尽可能使用当前屏幕渲染
鉴于离屏渲染、CPU渲染可能带来的性能问题,通常状况下,咱们要尽可能使用当前屏幕渲染。
离屏渲染、CPU渲染
因为GPU的浮点运算能力比CPU强,CPU渲染的效率可能不如离屏渲染;但若是仅仅是实现一个简单的效果,直接使用CPU渲染的效率又可能比离屏渲染好,毕竟普通的离屏渲染要涉及到缓冲区建立和上下文切换等耗时操做。普通的离屏绘制是发生在绘制服务(是独立的处理过程)而且同时经过GPU执行。当OpenGL的绘制程序在绘制每一个layer的时候,有可能由于包含多子层级关系而必须停下来把他们合成到一个单独的缓存里。你可能认为GPU应该老是比CPU牛逼一点儿,可是在这里咱们仍是须要慎重的考虑一下。由于对GPU来讲,当当前屏幕到离屏上下文环境的来回切换,代价是很是大的。由于对一些简单的绘制过程来讲,这个过程有可能用CoreGraphics,所有用CPU来完成反而会比GPU作的更好,因此若是你正在尝试处理一些复杂的层级,而且在犹豫到底用-[CALayer setShouldRasterize:]仍是经过CoreGraphics来绘制层级上的全部内容,惟一的方法就是测试而且进行权衡。
Instuments监测离屏渲染
Instruments的Core Animation工具中又几个和离屏渲染相关的检查选项:
Color offscreen-Rendered Yellow
开启后会把那些须要离屏渲染的图层高亮成黄色,这就意味着黄色图层可能存在性能问题。
Color Hits Green and Misses Red
若是shouldRasterize 被设置成YES,对应的渲染结果会被缓存,若是图层是绿色,就表示这些缓存被复用;若是是红色就表示缓存会被重复建立,这就表示该出存在性能问题了。
iOS 版本上的优化
iOS 9.0以前 UIImageView跟UIButton设置圆角都会触发离屏渲染
iOS 9.0 以后 UIButton设置圆角会触发离屏渲染,而UIImageView里png图片设置圆角不会再触发离屏渲染了,若是设置其余阴影效果之类的仍是会触发离屏渲染。
离屏渲染总结
1.尽可能使用当前屏幕渲染,能不适用离屏渲染则尽可能不用,你应当尽可能避免使用layer的border、corner、shadow、mask等技术。
2.必须离屏渲染时,相对简单的视图应该使用CPU渲染,相对复杂的视图则使用通常的离屏渲染。
2、tableview滑动卡顿
1."让出"主线程,让主线程减负。所谓"让出"主线程,指的是不要什么操做都放在主线程里。放在主线程中的通常都是视图相关的操做,好比添加子视图、更新子视图、删除子视图等。像其余的图片下载、数据请求这样的操做尽可能不要放在主线程中进行。
1
2
3
4
5
6
7
8
9
|
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//处理一些跟当前视图不要紧的事情
//...
//只用来操做与当前视图有关系的事情,好比:刷新tableView
dispatch_async(dispatch_get_main_queue(), ^{
[tableView reload];
});
});
|
2.正确重用cell。正确重用cell不只仅要重用cell视图,还须要好好重用cell的子视图。
1
2
3
4
5
6
|
static NSString *Identifier = @
"WeatherCell"
;
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:Identifier];
if
(!cell) {
cell = [[UITableViewCell alloc] initWithStyle:
reuseIdentifier:]
}
|
上面的代码在有cell可重用的时候,不会再建立新的cell,可是下面的一句话基本上会让重用粉身碎骨。
1
2
3
4
|
//清空子视图
[cell.contentView.subviews enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOLBOOL *stop) {
[obj removeFromSuperview];
}];
|
上面这段代码之因此会出现,缘由众所周知:cell重用时会出现重叠。"解决"了重叠问题,那么新问题来了,从新建立视图既消耗内存还占用时间,严重会出现滑动出现卡顿现象,并且都删除了重建还能叫重用么?
3、其余解决卡顿办法
imageView 尽可能设置为不透明
opaque尽可能设为YES,当imageView的opaque设置为YES的时候其alpha的属性就会无效,imageView的半透明取决于其图片半透明或者imageView自己的背景色的合成的图层view是半透明的。若是图片所有不是半透明就不会触发图层的blend操做,整个图层就回不透明,若是叠加的图片有出现半透明的,就会立马触发图层的blend操做,整个图层不透明。
opaque设为NO的话,当opaque为NO的时候,图层的半透明取决于图片和其自己合成的图层为结果。
背景色尽量设为alpha值为1
当某一块图层的alpha和其superView的背景色alpha不同的时候会触发alpha合成操做,这是一项看似简单可是却很是消耗CPU性能的操做。
UIView的背景色设置
UIview的背景色尽可能不要设置为clearColor,这样也会触发alpha叠加,在tableview滑动时候是很是消耗性能的。子视图的背景色尽量设置成其superView的背景色,这样图层合成的时候就不会触发blend操做了。
最好不适用带有alpha通道的图片,若是有alpha尽可能让美工取消alpha通道。
alpha通道 是透明的意思。
cell上layer尽可能避免使用圆角
在工做中关于滑动界面咱们会时常遇到cell行设置头像为圆角等需求,这时候咱们尽可能避免使用layer.masksToBounds,由于这会触发离屏渲染(上面有讲离屏渲染)。
优化UIImageView圆角方式 用贝塞尔曲线,不会触发离屏渲染:
1
2
3
4
5
6
7
8
9
10
|
UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
imageView.center = CGPointMake(200, 300);
UIImage *anotherImage = [UIImage imageNamed:@
"image"
];
UIGraphicsBeginImageContextWithOptions(imageView.bounds.size, NO, 1.0);
[[UIBezierPath bezierPathWithRoundedRect:imageView.bounds
cornerRadius:50] addClip];
[anotherImage drawInRect:imageView.bounds];
imageView.image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
[self.view addSubview:imageView];
|
优化图片的加载方式
1
2
|
UIImage Image = [UIImage imageNamed:@
"helloworld"
;
UIImage Image = [UIImage imageWithContentOfFile:@
"helloworld"
];
|
图片的两种加载方式:
第一种:当咱们常常须要这张图片而且仅仅是小图的时候,咱们能够使用此方式加载图片。这种方式是把图片缓存在图片缓冲区,当咱们使用的时候会经过图片的名字也就是经过key的方式去查找图片在缓存区的内存地址。
当咱们使用不少图片的时候系统就会开辟不少内存来存储图片。
第二种:当咱们使用工程里面的一张大图而且使用次数不多甚至为1次的时候,咱们优先会采用这种方式来加载图片,这种方式当使用完图片的时候会当即丢弃释放资源,因此对性能不会带来负担。
尽可能延迟图片的加载
当咱们在滑动页面的时候,尤为是对于那种布局特别复杂的cell,滑动的时候不要加载图片,当滑动中止的时候再进行图片的加载。
咱们都知道无论是tableview仍是scrollview在滚动的时候须要显示东西都是经过runloop去拿。当滚动的时候runloop会处于NSRunLoopTrackingMode的模式,咱们能够经过一个主线程队列dispatch_after或者selfPerformSeletor设置runloop的模式为NSDefaultRunLoopMode模式,就能够作到中止滚动再加载图片。注:其实严格意义上selfPerformSelector的事件就是在主线程队列中等待。
使用懒加载,即 须要时再加载。
最终要的是避免阻塞主线程。
让图片的绘制、图片的下载、对象的建立、文本的渲染等这些耗时的操做尽量采用子线程的方式去处理,对于layer以及UI的操做不得不在主线程里面,只能想办法优化(Facebook -> AsyncDisplayKit)
xib、storyBoard、纯代码
storyBoard能够为开发者节省大量的时间,提升开发效率,可是对于那种复杂的滑动界面,用storyBoard时很是耗资源的,对于那种重用性不强固定不怎么变化的界面仍是storyBoard省事儿。
不要重复建立没必要要的tableviewCell
UItableView只须要一屏幕的cell对象就能够了,由于tableview提供了cell的缓存机制,在不可见的时候,能够将其缓存起来,而在须要时继续使用便可。值得一提的是,cell被重用时,它内部绘制的内容并不会被自动清除,所以你可能须要调用setNeedsDisplayInRect:或setNeedsDisplay方法。
减小视图的数目
UITableViewCell包含了textLabel、detailTextLabel和imageView等view,而你还能够自定义一些视图放在它的contentView里,然而view是很大的对象,建立它会消耗较多的资源,而且也影响渲染性能。若是你的cell包含图片且数量较多,使用默认的cell会很是影响性能。最佳的解决办法是继承UITableViewCell,并在其drawRect:中自行绘制:
1
|
(void)drawRect:(CGRect)rect {
if
(image) { [image drawAtPoint:imagePoint]; self.image = nil; }
else
{ [placeHolder drawAtPoint:imagePoint]; } [text drawInRect:textRect withFont:font lineBreakMode:UILineBreakModeTailTruncation]; }
|
不过这样一来,你会发现选中一行后,这个cell就变蓝了,其中的内容就被挡住了。最简单的方法就是将cell的selectionStyle属性设为UITableViewCellSelectionStyleNone,这样就不会被高亮了。
此 外还能够建立CALayer,将内容绘制到layer上,而后对cell的contentView.layer调用addSublayer:方法。这个例 子中,layer并不会显著影响性能,但若是layer透明,或者有圆角、变形等效果,就会影响到绘制速度了。解决办法可参见后面的预渲染图像。
不要作多余的绘制工做。
在实现drawRect:的时候,它的rect参数就是须要绘制的区域,这个区域以外的不须要进行绘制。
例如上例中,就能够用CGRectIntersectsRect、CGRectIntersection或CGRectContainsRect判断是否须要绘制image和text,而后再调用绘制方法。
预渲染图像。
你会发现即便作到了上述几点,当新的图像出现时,仍然会有短暂的停顿现象。解决的办法就是在bitmap context里先将其画一遍,导出成UIImage对象,而后再绘制到屏幕
少用addView给cell动态添加view,能够初始化的时候就添加,而后经过hide控制是否显示。
提早计算并缓存好高度,由于heightForRow最频繁的调用。
善用hidden隐藏(显示)Subview
图片压缩
一 首先要知道 什么是压缩:
“压” 指文件体积变小,可是像素不变,长宽尺寸不变,那么质量可能降低
“缩” 指文件的尺寸变小,也就是像素数减小,而长宽尺寸变小,文件体积一样会减少。
二 图片的压处理
图片的压处理,咱们能够使用UIImageJPEGRepresentation或UIImagePNGRepresentation方法实现
如代码:
1
2
3
4
5
6
|
- (void)_imageCompression{
UIImage *image = [UIImage imageNamed:@
"HD"
];
//第一个参数是图片对象,第二个参数是压的系数,其值范围为0~1。
NSData * imageData = UIImageJPEGRepresentation(image, 0.7);
UIImage * newImage = [UIImage imageWithData:imageData];
}
|
关于PNG和JPEG格式压缩
UIImageJPEGRepresentation函数须要两个参数:图片的引用和压缩系数UIImagePNGRepresentation只须要图片引用做为参数。UIImagePNGRepresentation(UIImage image)要比UIImageJPEGRepresentation(UIImage image,1.0)返回的图片数据量大不少。一样的一张照片,使用UIImagePNGRepresentation(image)返回的数据量大小为200k,而UIImageJPEGRepresentation(image,1.0)返回的数据量大小只为150k,若是对图片的清晰度要求不是极高,建议使用UIImageJPEGRepresentation,能够大幅度下降图片数据量。注意:压缩系数不宜过低,一般是0.3~0.7,太小则可能会出现黑边等。
三 图片“缩”处理
经过[image drawInRect:CGRectMake(0,0,targetWidth,targetHeight)能够进行图片“缩”处理。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
|
/**
* 图片压缩到指定大小
* @param targetSize 目标图片的大小
* @param sourceImage 源图片
* @return 目标图片
*/
- (UIImage*)imageByScalingAndCroppingForSize:(CGSize)targetSize withSourceImage:(UIImage *)sourceImage
{
UIImage *newImage = nil;
CGSize imageSize = sourceImage.size;
CGFloat width = imageSize.width;
CGFloat height = imageSize.height;
CGFloat targetWidth = targetSize.width;
CGFloat targetHeight = targetSize.height;
CGFloat scaleFactor = 0.0;
CGFloat scaledWidth = targetWidth;
CGFloat scaledHeight = targetHeight;
CGPoint thumbnailPoint = CGPointMake(0.0,0.0);
if
(CGSizeEqualToSize(imageSize, targetSize) == NO)
{
CGFloat widthFactor = targetWidth / width;
CGFloat heightFactor = targetHeight / height;
if
(widthFactor > heightFactor)
scaleFactor = widthFactor;
// scale to fit height
else
scaleFactor = heightFactor;
// scale to fit width
scaledWidth= width * scaleFactor;
scaledHeight = height * scaleFactor;
// center the image
if
(widthFactor > heightFactor)
{
thumbnailPoint.y = (targetHeight - scaledHeight) * 0.5;
}
else
if
(widthFactor < heightFactor)
{
thumbnailPoint.x = (targetWidth - scaledWidth) * 0.5;
}
}
UIGraphicsBeginImageContext(targetSize);
// this will crop
CGRect thumbnailRect = CGRectZero;
thumbnailRect.origin = thumbnailPoint;
thumbnailRect.size.width= scaledWidth;
thumbnailRect.size.height = scaledHeight;
[sourceImage drawInRect:thumbnailRect];
newImage = UIGraphicsGetImageFromCurrentImageContext();
if
(newImage == nil)
NSLog(@
"could not scale image"
);
//pop the context to get back to the default
UIGraphicsEndImageContext();
return
newImage;
}
|
数据持久化
数据持久化是一种非易失性存储技术,在重启计算机或设备后也不会丢失数据,试讲内存中的数据模型转换为存储模型,以及将存储模型转换为内存中的数据模型的统称。数据模型能够是任何数据结构或对象模型,存储模型能够是关系模型、XML、二进制流等。持久化技术主要用于MVC模型中的model层。目前iOS平台上主要使用以下四种技术:
NSUserDefaults()
属性列表概念:属性列表是一种基于xml序列化的数据欧诺个就存储文件,又称plist文件,原理是将一些基本数据类型读写进plist文件(plist文件是XML格式文件,由于经常使用于存储配置信息,使用又称做plist格式文件)并以明文方式存储在设备中。许多OC 的基本数据类型(如NSArray、NSString 等)自己提供了向plist文件读写的方法,可是实际项目中咱们用到的更可能是NSUserDefaults,NSUserDefaults是苹果基于属性列表所封装的一个单例类,该类提供了基本数据类型的plist文件存储方法,由于其使用方便,代码易懂, NSUserDefaults成为了最经常使用的数据持久化方式之一。
NSUserDefaults经常使用方法
1
2
3
4
5
6
7
8
9
10
11
12
|
//从 NSUserDefaults 中取出 key 值所对应的 Value
id = [[NSUserDefaults standardUserDefaults] objectForKey:(NSString *)];
//将数据对象存储到 NSUserDefaults 中
[[NSUserDefaults standardUserDefaults] setObject:(id)
forKey:(NSString *)];
//将数据对象从 NSUserDefaults 中移除
[[NSUserDefaults standardUserDefaults] removeObjectForKey(NSString *)];
//同步更新到Plist文件,当修改了 NSUserDefaults 的数据后,必须进行此步操做
[[NSUserDefaults standardUserDefaults] synchronize];
|
NSUserDefaults特色
NSUserDefaults经常使用于存储OC基本数据类型,不适合存储自定义对象,NSUserDefaults支持的数据类型有:NSNumer(NSInteger,float,double)NSSstring,NSDate,NSArray,NSDictionary,BOOL.
自定义对象能够转化成基本类型NSData后再使用NSUserDefaults机型存储,但并不经常使用。
当plist文件存储的数据发生改变(写操做)时,须要调用syschronize方法同步,不然数据没法同步保存。
Key值应具备惟一性,重名时将覆盖先前key值。
实际开发中,NSUserDefaults经常使用语于存储配置信息,优势是简便,缺点是全部数据都以明文存储在plist文件中,容易被解读致使安全性不高。
NSUserDefautls将数据存储在什么地方了?
它存储在应用的一个plist文件中,在程序沙盒位置的/Library/Prefereces里面有个plist文件,存储的就是你的userDefaults,想要删掉的话,用removeObjectForKey或者删掉沙盒,也就是你的应用程序而后从新安装。
此外还能够自定义plist文件的位置进行存储数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
NSArray;
NSMutableArray;
NSDictionary;
NSMutableDictionary;
NSData;
NSMutableData;
NSString;
NSMutableString;
NSNumber;
NSDate;
1.得到文件路径
NSString *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
NSString *fileName = [path stringByAppendingPathComponent:@
"123.plist"
];
2.存储
NSArray *array = @[@
"123"
, @
"456"
, @
"789"
];
[array writeToFile:fileName atomically:YES];
3.读取
NSArray *result = [NSArray arrayWithContentsOfFile:fileName];
NSLog(@
"%@"
, result);
4.注意
只有以上列出的类型才能使用plist文件存储。
存储时使用writeToFile: atomically:方法。 其中atomically表示是否须要先写入一个辅助文件,再把辅助文件拷贝到目标文件地址。这是更安全的写入文件方法,通常都写YES。
读取时使用arrayWithContentsOfFile:方法。
|
对象归档(序列化)
和属性列表同样,对象归档也是将对象写入文件并保存在硬盘内,因此本质上是另外一种形式的序列化(存储模型不一样)。虽然说任何对象均可以被序列化,但只有某些特定的对象才能放置到某个集合类(例如:NSArray,NSMutableArray,NSDictionary,NSData等)中,并使用该集合类的方法在属性列表中读写,一旦将包含了自定义对象的数组写入属性列表,程序就会报错。归档与属性列表方式不一样,属性列表只有指定的一些对象才能进行持久化且明文存储,而归档时任何实现了NSCoding协议的对象均可以被持久化,且归档后的文件是加密的。对象归档涉及两个类: NSKeyedArchiver和NSKeyedUnarchiver,这两个类是NSCoder的子类,分别用于归档和解档。
对象归档
如今,咱们有一个自定义的Person类,该类有name,age,height三个属性,其.h文件以下
1
2
3
4
5
6
|
//Person.h
#import
@interface Person:NSObject
@property(nonatomic,copy)NSString *name;
@property(nonatomic,assign)int age;
@property(nonatomic,assign)double height;
|
在.m文件中,咱们要实现NSCoding中的两个协议方法,这两个方法分别在归档和解档时会被调用,Person类的.m文件以下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
//Person.m
#import"Person.h"
@implementation Person
/*
* 归档时调用该方法,该方法说明哪些数据须要储存,怎样储存
*/
- (void)encodeWithCoder:(NSCoder *)encoder
{
[encoder encodeObject:_name forKey:@
"name"
];
[encoder encodeInt:_age forKey:@
"age"
];
[encoder encodeDouble:_name forKey:@
"height"
];
}
/*
* 归档时调用该方法,该方法说明哪些数据须要解析,怎样解析
*/
-(id)initWithCoder:(NSCoder *)decode
{
if
(self = [
super
init]) {
_name = [decode decodeObjectForKey:@
"name"
];
_age = [decode decodeIntForKey:@
"age"
];
_height = [decode decodeDoubleForKey:@
"height"
];
}
return
self;
}
@end
|
这个Person类就具备了归档与解档能力,当你须要对一个Person类的实力对象进行储存或者解析时,在你本身的方法中只要键入以下代码便可,下面两个方法对应两个按钮的回调,点击按钮时分别执行person对象的归档和解档。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
//写操做
- (IBAction)Write {
Person *p = [[Person alloc]init];
p.name = @
"jin"
;
p.age = 10;
p.height = 176.0;
//设置归档后文件路径
NSString *path = @
"/Users/macbookair/Desktop/person.data"
;
//归档
[NSKeyedArchiver archiveRootObject:p toFile:path];
}
//读操做
- (IBAction)read {
//设置路径
NSString *path = @
"/Users/macbookair/Desktop/person.data"
;
//解档
Person *p = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
NSLog(@
"%@--%d---%f"
,p.name ,p.age ,p.height);
}
|
对象归档特色
能够将自定义对象写入文件或从文件中读出。
因为归档时进行了加密处理,所以安全性高于属性列表。
CoreData(集成化)
当你的应用程序须要在本地存储大量的关系型数据模型时,显然上述方法并不适合,由于不论对象归档仍是数据列表,一旦数据模型之间存在依赖关系,问题就将变得复杂。而此时iPhone自带的轻量级数据库Sqlite便成为咱们的首选,若是你熟悉数据库,那么恭喜,CoreData也将再也不神秘,你能够理解为它是苹果对Sqlite封装的一个框架,你能够在Xcode中进行Sqlite数据库的可视化操做
为何要使用CoreData?
CoreData脱离了Sql语句,集成化更高。实际上,一个成熟的工程中必定是对数据持久化进行了封装的,应该避免在业务逻辑中直接编写Sql语句。
CoreData对版本迁移支持的较好,App升级以后数据库字段或者表有更改会致使crash,CoreData的版本管理和数据迁移变得很是有用,手动写sql语句操做相对麻烦一些。
CoreData不光能操纵SQLite,CoreData和iCloud的结合也很好,若是有这方面需求的话优先考虑CoreData。
CoreData是支持多线程的,但须要thread confinement的方式实现,使用了多线程以后能够最大化的防止阻塞主线程。
Sqlite(灵活)
Sqlite是iPhone自带的的数据库管理系统。若是你对数据库和Sql语句不陌生,那么在介绍完CoreData后,你必定不知足CoreData,做为一个程序员,也许你更但愿可以直接操做数据库。既然苹果选择Sqlite做为数据库,天然也提供了一系列能够直接操做它的函数(C语言函数),利用这些函数你彻底可以本身封装一个sqlite数据库框架,但同时你必须熟悉sql语句以及C语言语法。SQLite数据库的几个特色:
基于C语言开发的轻型数据库
在iOS中须要使用C语言语法进行数据库操做、访问(没法使用ObjC直接访问,由于libqlite3框架基于C语言编写)
SQLite中采用的是动态数据类型,即便建立时定义了一种类型,在实际操做时也能够存储其余类型,可是推荐建库时使用合适的类型(特别是应用须要考虑跨平台的状况时)
创建链接后一般不须要关闭链接(尽管能够手动关闭)
#FMDB
FMDB框架中重要的框架类
FMDatabase
FMDatabase对象就表明一个单独的SQLite数据库,用来执行SQL语句
FMResultSet
使用FMDatabase执行查询后的结果集
FMDatabaseQueue
用于在多线程中执行多个查询或更新,它是线程安全的
网络传输协议
RunTime
总结的至关好 -----> iOS-RunTime,再也不只是据说
方法一 从APP服务器获取昵称和头像
昵称和头像的获取:当收到一条消息(群消息)时,获得发送者的用户ID,而后查找手机本地数据库是否有此用户ID的昵称和头像,如没有则调用APP服务器接口经过用户ID查询出昵称和头像,而后保存到本地数据库和缓存,下次此用户发来信息便可直接查询缓存或者本地数据库,不须要再次向APP服务器发起请求
昵称和头像的更新:当点击发送者头像时加载用户详情时从APP服务器查询此用户的具体信息而后更新本地数据库和缓存。当用户本身更新昵称或头像时,也能够发送一条透传消息到其余用户和用户所在的群,来更新该用户的昵称和头像。
方法二 从消息扩展中获取昵称和头像
昵称和头像的获取:把用户基本的昵称和头像的URL放到消息的扩展中,经过消息传递给接收方,当收到一条消息时,则能经过消息的扩展获得发送者的昵称和头像URL,而后保存到本地数据库和缓存。当显示昵称和头像时,请从本地或者缓存中读取,不要直接从消息中把赋值拿给界面(不然当用户昵称改变后,同一我的会显示不一样的昵称)。
昵称和头像的更新:当扩展消息中的昵称和头像URI与当前本地数据库和缓存中的相应数据不一样的时候,须要把新的昵称保存到本地数据库和缓存,并下载新的头像并保存到本地数据库和缓存。
能够参考http://www.jianshu.com/p/26b1294c71f6