微博 Demo 性能的优化
抛弃了以往在 Controller 中保存 Model 数组的传统方法,而是添加了用于存放布局和数据对象的数组,其中的布局是从 API 获取到数据后,在后台线程中计算后并保存的,这样当 Cell 需要显示时直接从内存中获取,不需要再进行计算。优点显而易见,缺点是 UIKit 的控件不是线程安全的,所以依赖 YYText 中的控件实现。
头像图片的圆角实现方式是在图片下载后,使用 Core Graphic 将图片用贝塞尔路径切成圆角后放到内存中使用。YYKit 的 Base 下有常用的 Category 实现,其中包括了切圆角的实现。
Macros 宏
TARGET_INTERFACE_BUILDER 宏用于区分代码是否需要在 IB 中执行
#if !TARGET_INTERFACE_BUILDER
// this code will run in the app itself
#else
// this code will execute only in IB
#endif
事务机制
在非 IB 中设置的 YYTextView 需要更新布局时使用了事务机制
[[YYTransaction transactionWithTarget:self selector:@selector(_updateIfNeeded)] commit];
事务提交时调用 YYTransactionSetup
static void YYTransactionSetup() {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
transactionSet = [NSMutableSet new];
CFRunLoopRef runloop = CFRunLoopGetMain();
CFRunLoopObserverRef observer;
observer = CFRunLoopObserverCreate(CFAllocatorGetDefault(),
kCFRunLoopBeforeWaiting | kCFRunLoopExit,
true, // repeat
0xFFFFFF, // after CATransaction(2000000)
YYRunLoopObserverCallBack, NULL);
CFRunLoopAddObserver(runloop, observer, kCFRunLoopCommonModes);
CFRelease(observer);
});
}
static void YYRunLoopObserverCallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) {
if (transactionSet.count == 0) return;
NSSet *currentSet = transactionSet;
transactionSet = [NSMutableSet new];
[currentSet enumerateObjectsUsingBlock:^(YYTransaction *transaction, BOOL *stop) {
[transaction.target performSelector:transaction.selector];
}];
}
YYTransactionSetup 中创建一个RunLoop 的监听,当 Runloop 空闲或者即将退出时执行事务的 selector 即上面的 _updateIfNeeded 方法。
全局并发控制
使用 YYDispatchQueuePool 会创建一个指定优先级,MAX_QUEUE_COUNT 为 32 个串行队列分发池代替并行队列。使用串行队列可以控制线程数量,避免创建过多线程影响性能,但是串行队列又不能利用多核 CPU 优势,所以使用队列池来管理队列。
获取队列的实现如下
static dispatch_queue_t YYDispatchContextGetQueue(YYDispatchContext *context) {
int32_t counter = OSAtomicIncrement32(&context->counter);
// 如果越界,变为负数,则转为正数
if (counter < 0) counter = -counter;
void *queue = context->queues[counter % context->queueCount];
return (__bridge dispatch_queue_t)(queue);
}
参数 YYDispatchContext 定义如下
typedef struct {
const char name; // 分发名称
void *queues; // 队列数组指针
uint32_t queueCount; // 队列个数
int32_t counter; // 获取队列的次数
} YYDispatchContext;
C 语言的知识
strdup / strcpy / memcpy 的区别
if (name) {
context->name = strdup(name);
}
strcpy 从源字符串中查找不是结束符 ‘\0’ 的内存都复制过去,如果目标字符串空间不够则会导致崩溃;memcpy 内存拷贝,不考虑字符串结束符,strdup 先用 malloc 函数分配与源字符串相同大小空间,然后将源字符串的内容复制到该内存地址,再将该地址返回。
好用的 YYFPSLabel
使用 Instruments 查看帧率不如直接在应用中查看来的直接,YYFPSLabel 可以直接显示帧率。
CADisplayLink _link = [CADisplayLink displayLinkWithTarget:[YYWeakProxy proxyWithTarget:self] selector:@selector(tick:)];
[_link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
// CADisplayLink 刷新执行的函数
- (void)tick:(CADisplayLink *)link {
if (_lastTime == 0) {
_lastTime = link.timestamp;
return;
}
// 计算 fps
_count++;
NSTimeInterval delta = link.timestamp - _lastTime;
// 不够 1s 不处理
if (delta < 1) return;
_lastTime = link.timestamp;
float fps = _count / delta;
_count = 0;
// 显示 fps 的值 ...
}
CADisplayLink 和 NSTimer 的区别
CADisplayLink 可以以屏幕刷新率相同的频率将内容绘制到屏幕上的定时器,精确度相当高,但是如果调用的方法比较耗时,超过了屏幕刷新周期,就会导致跳过若干次回调调用机会。
NSTimer 的精确度稍低,比如 NSTimer 触发时间到的时候,如果 runloop 出在阻塞状态,触发时间就会被延迟到下一个 runloop 周期。
CADisplayLink 适合 UI 不停重绘,比如自定义动画引擎或者视频播放的渲染。NSTimer 适合需要单次或循环定时处理的任务。
CADisplayLink 的重要属性
timestamp:当前时间的时间戳
frameInterval:NSInteger类型的值,用来设置间隔多少帧调用一次 selector方法 ,默认值是1,即每帧都调用一次。
duration:readOnly 的 CFTimeInterval 值,表示两次屏幕刷新之间的时间间隔。需要注意的是,该属性在 target 的 selector 被首次调用以后才会被赋值。selector 的调用间隔时间计算方式是:调用间隔时间 = duration × frameInterval
Timer 内存泄漏的避免
YYWeakProxy,利用消息转发机制,将消息 转发给 target,解决了 self 被 timer 强引用,timer 又被 runloop 强引用的问题。调用方法如下
_link = [CADisplayLink displayLinkWithTarget:[YYWeakProxy proxyWithTarget:self] selector:@selector(tick:)];
[_link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
实现机制
@property (nullable, nonatomic, weak, readonly) id target;
- (instancetype)initWithTarget:(id)target {
_target = target;
return self;
}
- (id)forwardingTargetForSelector:(SEL)selector {
return _target;
}
target 为 weak,如果 _target 为空 就会调用如下方法,不实现如下方法,则会崩溃,所以作者应该是随便调用两个函数,保证不崩溃
- (void)forwardInvocation:(NSInvocation *)invocation {
void *null = NULL;
[invocation setReturnValue:&null];
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector {
return [NSObject instanceMethodSignatureForSelector:@selector(init)];
}
奇技淫巧
人都趋向于犯懒的,只是有的人偷懒是靠动脑子的
{
NSMutableAttributedString *one = [[NSMutableAttributedString alloc] initWithString:@"Shadow"];
// ...
}
{
NSMutableAttributedString *one = [[NSMutableAttributedString alloc] initWithString:@"Inner Shadow"];
// ...
}
\uFFFC 为对象占位符,目的是当富文本中有图像时只复制文本信息
NSString *const YYTextAttachmentToken = @"\uFFFC”;
成员变量访问修饰符 @package
@implementation YYTextContainer {
@package
BOOL _readonly;
}
@package 是框架级的,对于本框架内相当于 @protect,框架外相当于 @private
@interface TestClass : NSObject {
@package NSString *testPackage;
}
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
TestClass *testClass = [[TestClass alloc] init];
NSLog(@"%@", testClass->testPackage); // OK
}
@end
使用方法
如果想要返回某个类的子类,可以实现 modelCustomClassForDictionary。例如
@implementation YYShape
+ (Class)modelCustomClassForDictionary:(NSDictionary*)dictionary {
if (dictionary[@"radius"] != nil) {
return [YYCircle class];
} else if (dictionary[@"width"] != nil) {
return [YYRectangle class];
} else if (dictionary[@"y2"] != nil) {
return [YYLine class];
} else {
return [self class];
}
}