目录结构
- Foundation
- NSDateFormatter
- NSFileManager
- NSObjectRuntime
- UIKit
- UIImage
- UIView
- QuartzCore
- CALayer
一 Foundation
1.1 NSDateFormatter
NSDateFormatter
不是唯一一个创建的开销就很昂贵的类,但是它却是常用的、开销大到 Apple 会特别建议应该缓存和重复使用实例的一个。
一种通用的缓存 NSDateFormatter 的方法是使用 -[NSThread threadDictionary](因为 NSDateFormatter 不是线程安全的):
@implementation DateTool
+ (NSDateFormatter *)cachedDateFormatter {
NSMutableDictionary *threadDictionary = [[NSThread currentThread] threadDictionary];
NSDateFormatter *dateFormatter = [threadDictionary objectForKey:@"cachedDateFormatter"];
if (dateFormatter == nil) {
dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setLocale:[NSLocale currentLocale]];
[dateFormatter setDateFormat: @"YYYY-MM-dd HH:mm:ss"];
[threadDictionary setObject:dateFormatter forKey:@"cachedDateFormatter"];
}
return dateFormatter;
}
@end
// 使用
- (void)getCurTime {
NSDateFormatter *dateFormatter = [DateTool cachedDateFormatter];
NSDate* now = [NSDate date];
NSString* dateString = [dateFormatter stringFromDate:now];
NSLog(@"%@", dateString);
}

1.2 dateFromString:方法
例如计算两个时间差值
- (void)pleaseInsertStarTimeo:(NSString *)time1 andInsertEndTime:(NSString *)time2{
// 1.将时间转换为date
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
formatter.dateFormat = @"yyyy-MM-dd HH:mm:ss";
NSDate *date1 = [formatter dateFromString:time1];
NSDate *date2 = [formatter dateFromString:time2];
// 2.创建日历
NSCalendar *calendar = [NSCalendar currentCalendar];
NSCalendarUnit type = NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay | NSCalendarUnitHour | NSCalendarUnitMinute | NSCalendarUnitSecond;
// 3.利用日历对象比较两个时间的差值
NSDateComponents *cmps = [calendar components:type fromDate:date1 toDate:date2 options:0];
// 4.输出结果
NSLog(@"两个时间相差%ld年%ld月%ld日%ld小时%ld分钟%ld秒", cmps.year, cmps.month, cmps.day, cmps.hour, cmps.minute, cmps.second);
}
计算两个时间差
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
NSString *time1 = [self getCurTime];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSString *time2 = [self getCurTime];
[self pleaseInsertStarTimeo:time1 andInsertEndTime:time2];
});
}

这可能是最常见的 iOS 性能瓶颈。经过多方努力寻找,下面是 ISO8601 转成 NSDate
的 NSDateFormatter
的最著名替代品。
- strptime
+ (NSDate *)easyDateFormatter{
time_t t;
struct tm tm;
char *iso8601 = "2016-09-18";
strptime(iso8601, "%Y-%m-%d", &tm);
tm.tm_isdst = -1;
tm.tm_hour = 0;//当tm结构体中的tm.tm_hour为负数,会导致mktime(&tm)计算错误
/**
//NSString *iso8601String = @"2016-09-18T17:30:08+08:00";
//%Y-%m-%d [iso8601String cStringUsingEncoding:NSUTF8StringEncoding]
{
tm_sec = 0
tm_min = 0
tm_hour = 0
tm_mday = 18
tm_mon = 9
tm_year = 116
tm_wday = 2
tm_yday = 291
tm_isdst = 0
tm_gmtoff = 28800
tm_zone = 0x00007fd9b600c31c "CST"
}
ISO8601时间格式:2004-05-03T17:30:08+08:00 参考Wikipedia
*/
t = mktime(&tm);
//http://pubs.opengroup.org/onlinepubs/9699919799/functions/mktime.html
//secondsFromGMT: The current difference in seconds between the receiver and Greenwich Mean Time.
return [NSDate dateWithTimeIntervalSince1970:t + [[NSTimeZone localTimeZone] secondsFromGMT]];
}
调用
- (void)strpTimeTest {
NSDate *date = [DateTool easyDateFormatter];
NSDateFormatter *dateFormatter = [DateTool cachedDateFormatter];
NSString* dateString = [dateFormatter stringFromDate:date];
NSLog(@"dateString = %@",dateString);
}

1.3 sqlite3
- (void)sqlite3Test {
sqlite3 *db = NULL;
NSString *iso8601String = @"2016-09-18";
sqlite3_open(":memory:", &db);
sqlite3_stmt *statement = NULL;
sqlite3_prepare_v2(db, "SELECT strftime('%s', ?);", -1, &statement, NULL);
sqlite3_bind_text(statement, 1, [iso8601String UTF8String], -1, SQLITE_STATIC);
sqlite3_step(statement);
int64_t value = sqlite3_column_int64(statement, 0);
sqlite3_clear_bindings(statement);
sqlite3_reset(statement);
}
1.4 NSFileManager
- – (NSDictionary )attributesOfItemAtPath:(NSString )filePath error:(NSError *)error
当试图获取磁盘中一个文件的属性信息时,使用 –[NSFileManager attributesOfItemAtPath:error:] 会浪费大量时间读取你可能根本不需要的附加属性。这时你可以使用 stat 代替 NSFileManager,直接获取文件属性:
// 1.4 NSFileManager
- (void)fileManagerTest {
struct stat statbuf;
NSString *filePath = [[NSBundle mainBundle] pathForResource:@"money" ofType:@"txt"];
const char *cpath = [filePath fileSystemRepresentation];
if (cpath && stat(cpath, &statbuf) == 0) {
NSNumber *fileSize = [NSNumber numberWithUnsignedLongLong:statbuf.st_size];
NSDate *modificationDate = [NSDate dateWithTimeIntervalSince1970:statbuf.st_mtime];
NSDate *creationDate = [NSDate dateWithTimeIntervalSince1970:statbuf.st_ctime];
// etc
NSLog(@"fileSize = %@",fileSize);
NSDateFormatter *dateFormatter = [DateTool cachedDateFormatter];
NSString* modificationTime = [dateFormatter stringFromDate:modificationDate];
NSString* creationTime = [dateFormatter stringFromDate:creationDate];
NSLog(@"modificationTime = %@",modificationTime);
NSLog(@"creationTime = %@",creationTime);
}
}

1.5 NSObjectRuntime
- NSLog(NSString *format, …)
NSLog() 写消息到 Apple 的系统日志。当通过 Xcode 变异运行程序时,被写出的日志会展现在调试终端,同事也会写到设备产品终端日志中。此外,系统会在主线程序列化 NSLog() 的内容。即使是最新的 iOS 设备,NSLog() 输出调试信息所花的时间也是无法忽略的。所以在产品环境中推荐尽可能少的使用 NSLog()。
#ifdef DEBUG
// Only log when attached to the debugger
#define DLog(...) NSLog(__VA_ARGS__)
#else
#define DLog(...) /* */
#endif
// Always log, even in production
#define ALog(...) NSLog(__VA_ARGS__)
1.6 NSString
- +(instancetype)stringWithFormat:(NSString *)format,, …
创建 NSString 不是特别昂贵,但是当在紧凑循环(比如作为字典的键值)中使用时, +[NSString stringWithFormat:] 的性能可以通过使用类似 asprintf 的 C 函数显著提高。
// 1.6 stringWithFormat提升
- (void)stringWithFormatTest {
NSString *firstName = @"Daniel";
NSString *lastName = @"Amitay";
char *buffer;
asprintf(&buffer, "Full name: %s %s", [firstName UTF8String], [lastName UTF8String]);
NSString *fullName = [NSString stringWithCString:buffer encoding:NSUTF8StringEncoding];
free(buffer);
NSLog(@"fullName = %@",fullName);
}

二 UIKit
2.1 UIImage
- +(UIImage )imageNamed:(NSString )fileName
如果 boundle 中得某个图片只显示一次,推荐使用 imageWithContentsOfFile:
,系统就不会缓存该图片。
2.2 UIView
-
clearsContextBeforeDrawing
属性
注释:If you set the value of this property to NO, you are responsible for ensuring the contents of the view are drawn properly in your drawRect: method. If your drawing code is already heavily optimized, setting this property is NO can improve performance, especially during scrolling when only a portion of the view might need to be redrawn.
将 UIView 的属性 clearsContextBeforeDrawing 设置为 NO 在多数情况下可以提高绘制性能,尤其是在你自己用绘制代码实现了一个定制 view 的时候。
-
frame
属性
当设置一个 UIView 的 frame 属性时,应该保证坐标值和像素位置对齐,否则将会触发反锯齿降低性能,也有可能引起图形界面的边界模糊(译者注:尤其是涉及到绘制文字时将会引起文字模糊不清,非 retina 设备特别明显)。
一种简单直接的办法就是使用 CGRectIntegral() 自动将 CGRect 的值四舍五入到整数。对于像素密度大于1的设备,可以将坐标值近似为 1.0f / screen.scale 整数倍。
三 QuartzCore
3.1 CALayer
-
allowsGroupOpacity
属性
注释:
When true, and the layer's opacity property is less than one, the
layer is allowed to composite itself as a group separate from its
parent. This gives the correct results when the layer contains
multiple opaque components, but may reduce performance.
The default value of the property is read from the boolean
UIViewGroupOpacity property in the main bundle's Info.plist. If no
value is found in the Info.plist the default value is YES for
applications linked against the iOS 7 SDK or later and NO for
applications linked against an earlier SDK.
在 iOS7 中,这个属性表示 layer 的 sublayer 是否继承父 layer 的透明度,主要用途是当在动画中改变一个 layer 的透明度时(会引起子 view 的透明度显示出来)。但是如果你不需要这种绘制类型,可以关闭这个属性来提高性能。
-
drawsAsynchronously
属性
注释
/* When true, the CGContext object passed to the -drawInContext: method
* may queue the drawing commands submitted to it, such that they will
* be executed later (i.e. asynchronously to the execution of the
* -drawInContext: method). This may allow the layer to complete its
* drawing operations sooner than when executing synchronously. The
* default value is NO. */
/* Any drawing that you do in your delegate’s drawLayer:inContext:
method or your view’s drawRect: method normally occurs
synchronously on your app’s main thread. In some situations,
though, drawing your content synchronously might not offer the best
performance. If you notice that your animations are not performing
well, you might try enabling the drawsAsynchronously property on
your layer to move those operations to a background thread. If you
do so, make sure your drawing code is thread safe. */
-
shadowPath
属性
如果要操作 CALayer 的阴影属性,推荐设置 layer 的 shadowPath 属性,系统将会缓存阴影减少不必要的重绘。但当改变 layer 的 bounds 时,一定要重设 shadowPath。
- (void)layerTest {
CALayer *layer = [[CALayer alloc] init];
layer.shadowOpacity = 0.5f;
layer.shadowRadius = 10.0f;
layer.shadowOffset = CGSizeMake(0.0f, 10.0f);
UIBezierPath *bezierPath = [UIBezierPath bezierPathWithRect:layer.bounds];
layer.shadowPath = bezierPath.CGPath;
}
注释
/** Letting Core Animation determine the shape of a shadow can be
expensive and impact your app’s performance. Rather than letting
Core Animation determine the shape of the shadow, specify the
shadow shape explicitly using the shadowPath property of CALayer.
When you specify a path object for this property, Core Animation
uses that shape to draw and cache the shadow effect. For layers
whose shape never changes or rarely changes, this greatly improves
performance by reducing the amount of rendering done by Core
Animation. */
-
shouldRasterize
属性
如果 layer 只需要绘制依此,那么可以设置 CALayer 的属性 shouldRasterize 为 YES。但是如果该 layer 让然会被移动、缩放或者变形,那么将 shouldRasterize 设置为 YES 会损伤绘制性能,因为系统每次绘制完后会尝试再次重绘。
注释:
/** When the value of this property is YES, the layer is rendered as a
bitmap in its local coordinate space and then composited to the
destination with any other content. Shadow effects and any filters in
the filters property are rasterized and included in the bitmap. */
本文参考 Yong 的 iOS App 性能备忘,非常感谢.
- 如有错误,欢迎指正,多多点赞,打赏更佳,您的支持是我写作的动力。
网友评论