概述
这里有25条进行iOS应用性能优化的技巧,先列一下文档的主要知识点,分为基础,中级和高级是三个级别。
希望可以帮助到你。
原文地址放到最后了。
基础
1、使用ARC管理内存
2、使用reuseIdentifier进行复用
3、尽可能把视图设置为不透明
4、避免臃肿的XIB文件
5、不要阻塞主线程
6、调整图像视图中的图片尺寸
7、选择真确的集合
8、支持使用Gzip压缩
中级 ⚠️在遇到复杂的情况使用
9、使用懒加载和复用方式加载视图
10、使用缓存机制
11、对于绘图的使用要多加考虑
12、处理内存警告
13、针对占用内存大的对象要进行复用
14、使用精灵表
15、避免重复处理数据
16、选择正确的数据格式
17、适当的设置背景图片
18、减少网络占用
19、设置阴影路径
20、优化表视图
21、选择正确的数据存储类型
高级 ⚠️你觉得它们非常适用的时候使用
22、加速启动时间
23、使用自动释放池
24、缓存图片
25、尽可能避免使用日期格式器
iOS开发的25条建议和技巧 - 基础
1、使用ARC管理内存
在iOS5之后就推出了ARC技术,他是用来消除常见的内存泄漏的技术。ARC自动管理代码中retain/release循环这样就不用手动管理这个事情了。
以下代码是之前创建视图的代码
UIView *view = [[UIView alloc] init];
// ...
[self.view addSubview:view];
[view release];
这里经常忘记release释放,ARC会在后台自动帮你处理这个事情。
除了内存释放,ARC还能保证对象不在使用时立刻回收,从而提高APP的性能。
更多关于ARC的文档:
苹果官方文档
Matthijs Hollemans’s Beginning ARC in iOS Tutorial
Tony Dahbura’s How To Enable ARC in a Cocos2D 2.X Project
-
如果你不确信ARC的好处,可以看一下这个文章 eight myths about ARC 。
⚠️值得注意的是ARC不能消除所有的内存泄漏。这里依然有内存泄漏,这可能主要是由于blocks的循环引用,CoreFoundation管理对象的不善 或者是确实糟糕的代码。
这里有个好的博客文章来说明怎么解决blocks循环引用问题—解决blocks循环引用地址
2、使用reuseIdentifier进行复用
一个常见的错误就是没有给UITableViewCells
,UICollectionViewCells,
UITableViewHeaderFooterViews这些视图设置复用标识符。为最大优化性能,一个tableview的数据源一般应该复用UITableViewCell对象,当它在
tableView:cellForRowAtIndexPath:`方法给cells分配数据时。一个tableview维护了一个tableviewCell对象的队列,这些对象都已经被标记上复用标志。
如果不使用复用标志,tableview 每次会创建一个新的cell。这是非常耗时的操作并且会影响到App滚动的性能。
自动iOS6之后,你应该为header
footer
视图设置复用标志,就像UICollectionView’s cells
和补充视图一样。
当tableview提供一个新的cell时在这个方法中使用复用标志。
static NSString *CellIdentifier = @"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
如果队列中有一个复用标志的cell可以用,该方法会从队列中取出来进行复用。如果没有就会从类或者XIB文件创建一个新的cell来使用。如果你的cell没有使用复用标志或者没有从类或者xib文件注册过,将会返回nil对象。
3、尽可能把视图设置为不透明
如果没有透明度的视图你应该将他们的opaque属性为YES。这样系统会以最优的方式绘制你的视图。你可以在Interface Builder
或者代码中设置这个属性。
苹果官方文档也有对这个属性的说明
这个属性提供了一个暗示给系统,该怎么对待这个视图。如果设置为YES,系统在绘制这个视图的时候将变为弯曲不透明,这样会让系统优化一部分绘制操作并且提高APP性能。如果设置为NO,系统在绘制的时候会将这个视图和其他内容进行复合。这个属性默认为YES。
在相对静态的屏幕上设置opaque属性不会有太大的问题。但是如果你的视图嵌入scrollview或者复杂的动画,再不设置这个属性的情况下你的应用绝对会收到影响。
在模拟器情况下,你可以使用Debug\Color Blended Layers
选项来观察视图有没有设置不透明。你应该尽可能设置更多的不透明视图。
4、避免臃肿的XIB文件
在iOS5之后引入了故事板Storyboards
很快就代替了XIBs
。但是 XIBS
在一定情况下还是很有用的。如果你需要支持iOS5之前的设备或者你想自定义复用的视图,那么你不可能避免使用它。
⚠️当你加载一个XIB视图进入内存时,xib上面包含的所有内容包括图片在内都会加载到内存中去。
如果你有一个不立即使用的视图,那么你就浪费了宝贵的内存。⚠️这个事情不会发生在故事板上,因为一个故事只能在需要的时候实例化一个视图控制器。
当你加载一个XIB时,包括图片在内的所有文件都会被缓存下来,如果是开发OS X 那么音频文件也会被缓存下来。官方文档如下:
When you load a nib file that contains references to image or sound resources, the nib-loading code reads the actual image or sound file into memory and and caches it. In OS X, image and sound resources are stored in named caches so that you can access them later if needed. In iOS, only image resources are stored in named caches. To access images, you use the imageNamed: method of NSImage or UIImage, depending on your platform.
5、不要阻塞主线程
你永远不能在主线程处理任何繁重的工作。这是因为UIKit的所有工作都在主线程中进行,例如绘图,管理触摸和响应输出。
应用中所有的工作都在主线程中处理就会有导致主线程阻塞,应用反应迟钝的风险。
大部分阻塞主线程的情况发生在应用进行I/O操作。包括任何需要读写外部资源任务,比如磁盘读写和网络请求。
你使用下面的方法进行网络的异步请求或者AFNetwoking
第三方库请求
+ (void)sendAsynchronousRequest:(NSURLRequest *)request queue:(NSOperationQueue *)queue completionHandler:(void (^)(NSURLResponse*, NSData*, NSError*))handler
如果你需要做大量的开销操作,例如执行耗时的计算或者磁盘的读写,你可以使用GCD或者队列来操作。
GCD使用举例如下:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// switch to a background thread and perform your expensive operation
dispatch_async(dispatch_get_main_queue(), ^{
// switch back to the main thread to update your UI
//所有的UIKit的操作都需要在主线程处理,所有这里使用dispatch_async
});
});
6、调整图像视图中的图片尺寸
如果你使用UIImageView
加载应用中的图片,你要确保图片的大小和UIImageView
视图的大小一致。缩放图片是非常耗时的,特别是在UIImageView
被嵌套在UIScrollView
的时候。
如果图片是从服务器下载的,你没有办法控制图片大小或者你不能在下载之前缩放图片。这些情况,你可以在图片下载完成之后进行手动缩放一次,最好放到后台进程进行处理,然后在将适应的图片放到UIImageView
中。
7、选择真确的集合
学着选择最合适的类和对象,写出高效的代码。当使用集合collections时最恰当不过了
值得高兴的是,苹果开发者文档有详细的文档解释可用类之间的关系,还有各个类适用的情况。这个文档是每个人必须的文档。
这是一个常见的集合类型的快速简介
Arrays:有序值的列表,用index进行查找最快,使用值查找慢,插入和删除操作比较慢。
Dictionaries:保存 键/值对。使用key查找最快。
Sets:无序的值列表。快速查找使用值查找,插入和删除操作比较快。
8、支持使用Gzip压缩
应用中大部分数据都依赖于服务端或者外部接口。有时应用中需要下载XML,JSON,HTML或者其他数据。
问题是,当涉及到移动设备时,网络条件是不可依赖的。用户可以在一分钟的边缘网络,下一个3G网络。不管发生什么情况,您都不想让用户等待。
一个减小文件大小和加速下载网络资源的方法是在你的服务器端和客户端使用GZIP压缩。对于文本数据来说这种高比率压缩的方法非常有效。
好消息是iOS已经支持GZIP压缩,如果你在使用NSURLConnection
或者类似AFNetworking
的第三方库。而且一切云服务提供商比如Google App Engine
早已经发送压缩后的数据。
这篇文章介绍怎样在Apache或者IIS服务器上启动GZIP。
iOS开发的25条建议和技巧 - 中级
现在你已经了解了基础的方法来优化性能,但是问题的解决方法并不是那么显而易见,它由你的APP框架和代码的质量决定。下面看一下中级优化方案。
9、使用懒加载和复用方式加载视图
更多的视图意味着更多的绘制,最终意味着CPU和内存的开销。如果你的视图中包含很多UIScrollView
时,这会显得非常正确。
管理这样的视图的技巧是:不要一次性的创建所有的子视图,而是在需要的时候去创建,然后把它们放入到复用的队列中。
用这种方法,你只需要在视图需要的时候配置你的视图,避免了资源的分配开销。
视图创建的时机同样也适用于APP的其他地方。猜想一个按钮点击的时候需要呈现一个视图,这里至少有两种情况。
当屏幕上第一次载入的时候创建并隐藏视图。当你需要的时候去创建并展示出来。
当需要展示的情况下创建视图并展示它。
每种方式都有它的优缺点。
第一个方法,你占用了更多的内存,因为从创建开始到释放你都保持了他的内存。然而你在点击按钮的时候你的应用可以快速的改变状态,展示不同的视图。
第二个方法确实消耗了更少的内存,但是当点击按钮时响应并不像第一种方法那么迅速。
10、使用缓存机制
那些经常被访问但是不经常改变的东西在开发过程中应该被缓存下来。
你能缓存什么内容?缓存远程服务器的响应,图片或者计算值(UITableView的行高)。
NSURLConnection
可以根据HTTP头信息缓存资源到内存或者磁盘中去。你甚至可以创建一个NSURLRequest
值加载缓存过的值。
下面是你需要针对一个不会改变的图片创建的一个NSURLRequest
+ (NSMutableURLRequest *)imageRequestWithURL:(NSURL *)url {
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
request.cachePolicy = NSURLRequestReturnCacheDataElseLoad; // this will make sure the request always returns the cached image
request.HTTPShouldHandleCookies = NO;
request.HTTPShouldUsePipelining = YES;
[request addValue:@"image/*" forHTTPHeaderField:@"Accept"];
return request;
}
你可以在NSHiper上了解到更多关于HTTP caching,NSURLCache,NSURLConnection等内容。
NSURLCache
如果你需要缓不涉及到HTTP请求的其他东西,NSCache
是个很好的选择。
NSCache
和NSDictionary
比较像,但是当系统需要回收内存的时候,他会自动移除内容。
关于NSCache可以看一下这篇文章
关于HTTP caching 可以看一下这个文章
11、对于绘图的使用要多加考虑
在iOS中你可以制作很多好看的按钮。你可以直接使用图片或者使用CALayer甚至OpenGL进行绘制。
当然,每种方法都有不同的复杂级别和性能差别。这篇文章会有帮助 帮助了解对性能的权衡。
使用预渲染图片会更快,因为iOS不用创建一张图片和绘制图形到屏幕上。你需要把所有图片都放入到应用的bundle中,增加它的尺寸。为什么使用可调整尺寸的图片那么好:你通过移除不用的图片来节省空间。您不需要为不同的元素生成不同的图像。
然而,用图片你会失去代码调整你图片的能力,需要每次生成并把它们加入到应用中。这是个缓慢的过程。还有一点是,如果你使用动画或者多张图片,你需要加载大量的图片,这样会导致应用的大小。
总结一下,你需要考虑一下哪个更重要:绘制性能还是应用的大小。当然两个都很重要,两种方法你都要使用。
12、处理内存警告
当系统内存低时,系统会通知所有运行的应用。官方关于低内存的描述
如果你的应用接收到低内存的警告,它必须释放尽可能多的内存。最好的方式是移除那些缓存,图片和其他稍后要创建的对象的强引用。
幸运的是,UIKit提供了一些方法来接受处理低内存警告
- Implement the applicationDidReceiveMemoryWarning: method of your app delegate.
- Override didReceiveMemoryWarning in your custom UIViewController subclass.
- Register to receive the UIApplicationDidReceiveMemoryWarningNotification notification.
当上面任何一个接收到内存警告,你需要及时释放掉那些不必要的内存。
例如当前控制器上的视图不可见,控制器会清除这些视图。子类可以通过父类方法来清除额外的数据。一个应用的中任何没有在屏幕上的图片的缓存都会被释放掉。
及时的释放掉内存非常重要,否则应用就可能被系统杀死。
你需要小心的释放内存,因为你需要保证他们会在之后被重新创建。当你开发应用的时候,用你的模拟器去测试内存警告这种情况。
13、针对占用内存大的对象要进行复用
有些对象创建过程非常慢-NSDateFormatter
和NSCalendar
就是例子。
为了避免使用这些对象时的性能瓶颈,试着重用这些对象。你可以把它设置为一个类的属性或者一个静态变量。
⚠️如果你用第二种方法,这个对象会在APP运行时一直保持在内存中,像一个单例一样。
下面演示了一个NSDateFormatter
作为一个属性的懒加载方法,第一次调用创建,以后使用都使用创建的这一个对象。
// in your .h or inside a class extension
@property (nonatomic, strong) NSDateFormatter *formatter;
// inside the implementation (.m)
// When you need, just use self.formatter
- (NSDateFormatter *)formatter {
if (! _formatter) {
_formatter = [[NSDateFormatter alloc] init];
_formatter.dateFormat = @"EEE MMM dd HH:mm:ss Z yyyy"; // twitter date format
}
return _formatter;
}
如果在多个线程调用这个方法就会出现问题,所以针对这个多线程的问题我们使用下面的定义方法来定义NSDateFormatter
。
// no property is required anymore. The following code goes inside the implementation (.m)
- (NSDateFormatter *)formatter {
static NSDateFormatter *formatter;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_formatter = [[NSDateFormatter alloc] init];
_formatter.dateFormat = @"EEE MMM dd HH:mm:ss Z yyyy"; // twitter date format
});
return formatter;
}
设置一个NSDateFormatter
的日期格式几乎和创建一个新的一样慢。因此当你的应用中要处理多个日期格式时使用复用和初始化创建会更加有利。
14、使用精灵表 (游戏开发者⚠️)
如果你是游戏开发者。精灵表是你的好朋友之一。精灵表比标准屏幕绘制的速度更加快速。
下面有两个是哟哦那个精灵表的使用教程:
How To Use Animations and Sprite Sheets in Cocos2D
How to Create and Optimize Sprite Sheets in Cocos2D with Texture Packer and Pixel Formats
第二个教程覆盖了像素格式,他可能对游戏性能有一个可衡量的影响。
如果你不是很熟悉精灵表,这有个教程详细介绍了这个精灵表- SpriteSheets – The Movie, Part 1 and Part 2.
除了精灵表之外,之前说的内容也可以用到游戏开发上面。举例:如果你的游戏中有很多精灵,比如在标准的敌人或者炮弹射击游戏,你可以重用精灵表来代替每次创建新的精灵。
15、避免重复处理数据
许多APP调用函数来获取服务器数据。数据格式一般都是JSON或者XML格式。保持统一的数据格式对于请求和接收数据来说非常重要。
为什么?处理内存中的数据以适合您的数据结构可能是昂贵的。
例如:你需要在表格中展示数据。最好的请求和接收数据的格式时数组,以避免中间操作数据。
相似的,如果你的影哟ing依赖于访问特定的键值,你会更加希望返回的数据格式是一个字典格式。
通过一次性获取正确的数据格式,你会避免许多重复处理数据的工作。
16、选择正确的数据格式
这里有许多方式你可以从web服务器获取数据传递到你的应用中。到那时最常见的是JSON和XML格式,你要选择正确的格式来适应你的应用。
JSON比XML转化更加快,传输内容也比较小。自从iOS5之后,内置的JSON解析很简单也很实用。
然而,XML在你使用SAX parsing方法时是有优势的。你可以在传输过程中读取它,在数据量非常大时,你可以不必像JSON一样在下载完成后在去处理。
17、适当的设置背景图片
这里有两种加载替换背景图片的方法。
通过使用UIColor 的
colorWithPatternImage
方法设置视图的背景颜色-
在视图上添加一个
UIImageView
视图如果你有一个全尺寸的背景图片,你应该使用
UIImageView
视图。因为colorWithPatternImage
这个方法是重复创建小的模型图片。在这种情况下使用UIImageView
会节省很多内存。
// You could also achieve the same result in Interface Builder
UIImageView *backgroundView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"background"]];
[self.view addSubview:backgroundView];
如果你想使用小的图片作为背景,你可以使用colorWithPatternImage
这种哦方式,在这种情况下不会使用太多内存。
self.view.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:@"background"]];
18、减少网络占用
UIWebView是非常有用的。他可以轻松的展示网站内容,或者创建你的app窗体。这些都是UIKit很难做到的。
但是,你可能注意到UIWebView
组件并没有像苹果的safari应用那么快。这是因为WebKit引擎的限制,以JIT编译。
所以 获取更佳的体验你需要修改你的HTML。第一件事是尽可能的避免使用Javascript,包括避免使用大的框架例如jQuery。有时使用vanilla Javascript取代依赖的框架会更快
还要遵循在可能的情况下异步加载JavaScript文件,尤其是当它们不直接影响页面的行为时,比如分析脚本。
最后,要使用正确的图片尺寸。正如上面说到的使用精灵表的优势来节省内存。
And finally — always be aware of the images that you are using, and keep images right-sized for your purposes. As mentioned earlier in this tutorial, make use of sprite sheets wherever possible to conserve memory and improve speed.
更多信息可以看这个地址-> WWDC 2012 session #601 – Optimizing Web Content in UIWebViews and Websites on iOS.
19、设置阴影路径
你要给视图添加阴影怎么处理?
大多数开发者会使用QuartzCore
框架添加如下代码:
#import
// Somewhere later ...
UIView *view = [[UIView alloc] init];
// Setup the shadow ...
view.layer.shadowOffset = CGSizeMake(-1.0f, 1.0f);
view.layer.shadowRadius = 5.0f;
view.layer.shadowOpacity = 0.6;
看起来很简单,对吗?
不好的是这个方法有一个问题。Core Animation
-核心动画必须先做一个幕后画确定视图的形状之后才渲染阴影,这是一个相当费事而且昂贵的操作。
好消息是这个有个更简单的替换方法设置阴影路径:
view.layer.shadowPath = [[UIBezierPath bezierPathWithRect:view.bounds] CGPath];
设置阴影路径,iOS不需要一直计算如何绘制阴影。但是它会依赖你的视图格式,你的视图更加难以计算。还有一个问题是当视图坐标改变时你需要更新阴影路径。
你可以获取更多的关于这个内容的技巧-> post about shadowPath.
20、优化表视图
表格视图需要支持快速滚动,如果不行,用户会感觉太low了。
为了保持表格滚动的流畅,你可以使用以下建议:
使用
reuseIdentifier
-复用标志进行单元格的复用保证视图设置为不透明,包括单元格自己
避免渐变,图片缩放和屏幕外的绘制
当单元格高度不一致时缓存单元格高度
如果单元格展示的内容来自web,确保异步请求并缓存
使用
shadowPath
设置阴影减少子视图
cellForRowAtIndexPath
方法中尽可能少的做事情。如果必须要处理,尽量一次性处理并缓存结果使用适当的数据结构存储你要的信息。不同的结构对于不同的操作有不同的代价
设置
rowHeight
sectionFooterHeight
sectionHeaderHeight
为固定高度而不是访问代理方法
21、选择正确的数据存储类型
存储和读取数据的时候选择什么?有以下几种选择:
使用NSUserDefaults存储
使用XML,JSON或者plist格式的文件
使用NSCoding归档文件
使用SQLLite保存到数据库中
使用Core Data数据库存储
NSUserDefaults有什么问题?虽然NSUserDefaults很简单也很好用,但是它只适合存储少量的数据。如果存储大量的数据其他方式会更好。
保存在文件也可能有问题。一般来说在处理数据之前你需要加载整个文件到内存中,这个时非常耗时的操作。你可以使用SAX去处理XML文件,但是也是一个复杂的做法。加载全部对象到内存中,这也不是你想看到的。
那么NSCoding
怎样呢?很不幸运,他在读取和写入文件时也会出现同样的问题。
处理这个问题的最好方式是使用SQLLite或者Core Data 。使用这些技术,你可以执行特定的查询来加载需要的对象,避免了强力搜索方法来检索数据。SQLite和Core Data性能方面很接近。
SQLLite和Core Data最大的不同是他们的使用方法。CoreData呈现为一个模型,但是SQLite确是一个传统的DBMS 数据库管理系统。苹果建议使用Core Data,如果你有特殊原因你想避开CoreData,你可以使用哦更低级的SQLLite。
在APP中使用SQLLite,你要引入FMDB库。它允许你使用SQLLite而不用专研SQLLite的 C API。
iOS开发的25条建议和技巧 - 高级
下面这些方法会让你的应用更加的高效。
22、加速启动时间
快速启动对于应用来说非常重要,特别是当用户第一次启动应用的时候。
尽最大的努力保证APP启动更加快,尽可能执行异步任务,例如网络请求,数据库访问,数据解析。
避免使用臃肿的XIB,因为它们会在主线程中加载 。但是故事板不会有这个问题。
⚠️当Xcode运行调试时看门狗不会运行,所以一定要与你的设备断开Xcode而测试你的应用程序。
23、使用自动释放池
NSAutoreleasePool负责释放在block块中的自动释放的对象。一般情况,它是由UIKit自动调用。但是有些情景你需要手动创建NSAutoreleasePools
。
比如,你在代码中创建了大量的临时对象,你会注意到内存会一直增加直到这些对象被释放。这个问题在于内存释放只有在UIKit排空自动释放池的时候才会释放内存,这就意味着内存会被占用的时间超过了需要的时间。
问题是,这种是UIKit耗尽后自动释放池才发布,这意味着这种记忆保持更长的时间比必要的。
你可在autoreleasepool
中创建临时对象来避免这种情况,如下:
NSArray *urls = <# An array of file URLs #>;
for (NSURL *url in urls) {
@autoreleasepool {
NSError *error;
NSString *fileContents = [NSString stringWithContentsOfURL:url
encoding:NSUTF8StringEncoding error:&error];
/* Process the string, creating and autoreleasing more objects. */
}
}
在每次迭代之后会自动释放这些对象。
更多关于 *NSAutoreleasePool看这里- > Apple’s official documentation.
24、缓存图片
这里有两种从bundle加载图片到应用的方法。第一种是使用imageNamed
。第二种是使用imageWithContentsOfFile
方法。
为什么两种方法处理同一个事情呢,哪个更高效呢?
imageNamed
在载入图片之后会将图片缓存到内存中。官方文档解释如下:
这个方法看起来在系统缓存一个图片并指定一个名字,如果存在则返回一个对象。如果不存在会从指定文件中加载这个图片并缓存它,然后返回结果对象。
imageWithContentsOfFile
只是简单的加载图片并不缓存。
两种方法的使用方法如下:
UIImage *img = [UIImage imageNamed:@"myImage"]; // caching
// or
UIImage *img = [UIImage imageWithContentsOfFile:@"myImage"]; // no caching
在什么情况使用哪个方法?
如果你加载一张大的图片但是只加载一次的时候,并且不需要缓存图片。这种情况下使用imageWithContentsOfFile
。这样就不会浪费内存来缓存图片了。
然而imageNamed
对于图片加载来说会是更好的选择。这种方法会节省从磁盘加载图片的时间。
25、尽可能避免使用日期格式器
如果你有大量的数据需要转化为日期格式,这时你就要注意了。之前提过,尽可能复用NSDateFormatters
。
然而,你需要更加快速,你可以使用C
来代替NSDateFormatter
来解析数据。这篇文章-> blog post about this topic说明了如何使用代码来解析ISO-8601数据格式的字符串。你可以根据你的需求快速地更改demo代码。
这个听起来很棒,但是你相信有更好的办法吗?
如果你能控制你要处理的日期数据格式,尽可能选用 Unix timestamps。Unix时间戳可以简单的整数代表从某个起点到现在的秒杀。这个起点默认是1970年1月1日 UTC 00:00:00
你可以很容易把时间戳转化为NSDATE,如下:
- (NSDate*)dateFromUnixTimestamp:(NSTimeInterval)timestamp {
return [NSDate dateWithTimeIntervalSince1970:timestamp];
}
这个甚至比C函数还要快。
⚠️很多web APIs返回的都是毫秒级别的时间戳,因为对于Javascript
最终来使用或者处理数据非常快常见。只要记住将这个时间戳处以1000在传递给dateFromUnixTimestamp
方就可以了。
原文地址—raywenderlich地址
IOS开发的25条建议和技巧-基础篇
IOS开发的25条建议和技巧 – 中级篇
IOS开发的25条建议和技巧 – 高级篇
IOS开发的25条建议和技巧-总结