阅读视图

发现新文章,点击刷新页面。

YYCache(一)

YYCache(一)

前言:

我们一般对网络请求下来的比较大的数据做缓存,如果没有网络,或者是请求到的标识和之前的标识一致,表示数据没有变动,则可以使用缓存加载,不需要重新网络拉取数据,这里一般使用YYCache

Pasted Graphic 1.png

git上把YYCache pod下来:

Pasted Graphic.png

可以看到YYCache的文件结构还是相对简单,除了YYCache这个对外使用的接口文件,还有YYDiskCache这个磁盘缓存,YYKVStorage这个元数据键值存储,还有YYMemoryCache这个内存缓存。

一、YYCache:

YYCache是一个线程安全的键值缓存。 它使用YYMemoryCache将对象存储在一个小而快速的内存缓存中, 并使用YYDiskCache将对象持久化到一个大而慢的磁盘缓存中。

属性列表:

属性就三个:
@interface YYCache : NSObject
/** 缓存名字,只读*/
@property (copy, readonly) NSString *name;
/** 内存缓存.*/
@property (strong, readonly) YYMemoryCache *memoryCache;
/** 磁盘缓存.*/
@property (strong, readonly) YYDiskCache *diskCache;

方法列表:

初始化:

初始化:
/**
用指定的名称创建一个新实例。
 */
- (nullable instancetype)initWithName:(NSString *)name;
/**
用指定的路径创建一个新实例。
 */
- (nullable instancetype)initWithPath:(NSString *)path NS_DESIGNATED_INITIALIZER;

/**
便捷初始化,用指定的名称创建一个新实例。
 */
+ (nullable instancetype)cacheWithName:(NSString *)name;
/**
便捷初始化,用指定的路径创建一个新实例
 */
+ (nullable instancetype)cacheWithPath:(NSString *)path;

访问方法:


/**
返回一个布尔值,一个指定的键是否在缓存中,可能会阻塞线程直到文件读取完成。
 */
- (BOOL)containsObjectForKey:(NSString *)key;

/**
返回指定键相对应的值。
 */
- (nullable id<NSCoding>)objectForKey:(NSString *)key;

/**
设置缓存中指定键的值。
 */
- (void)setObject:(nullable id<NSCoding>)object forKey:(NSString *)key;

/**
删除缓存中指定键的值,如果为nil,则此方法无效。
 */
- (void)removeObjectForKey:(NSString *)key;
/**
清空缓存。
 */
- (void)removeAllObjects;
/**
用block清空缓存。可以通过参数得到进度。
 */
- (void)removeAllObjectsWithProgressBlock:(nullable void(^)(int removedCount, int totalCount))progress
                                 endBlock:(nullable void(^)(BOOL error))end;

.m文件的初始化方法initWithName

- (instancetype)initWithName:(NSString *)name {
    if (name.length == 0) return nil;
    NSString *cacheFolder = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) firstObject];
    NSString *path = [cacheFolder stringByAppendingPathComponent:name];
    return [self initWithPath:path];
}

发现initWithName会调用另一个初始化方法initWithPathYYMemoryCache的初始化方法:

- (instancetype)initWithPath:(NSString *)path {
    return [self initWithPath:path inlineThreshold:1024 * 20]; // 20KB
}
查看initWithPath:(NSString *)path
             inlineThreshold:(NSUInteger)threshold发现:

- (instancetype)initWithPath:(NSString *)path
             inlineThreshold:(NSUInteger)threshold {
…
    
    YYKVStorageType type;
    if (threshold == 0) {
        type = YYKVStorageTypeFile;
    } else if (threshold == NSUIntegerMax) {
        type = YYKVStorageTypeSQLite;
    } else {
        type = YYKVStorageTypeMixed;
    }
_inlineThreshold = threshold;
…
}

YYDiskCache中可以看到内联阈值是20KB_inlineThreshold被初始化为20KB

点进YYKVStorageType看,发现有三种存储类型:
一、文件 二、数据库 三、自选

typedef NS_ENUM(NSUInteger, YYKVStorageType) {
    /// The `value` is stored as a file in file system.
    YYKVStorageTypeFile = 0,
    
    /// The `value` is stored in sqlite with blob type.
    YYKVStorageTypeSQLite = 1,
    
    /// The `value` is stored in file system or sqlite based on your choice.
    YYKVStorageTypeMixed = 2,
};

再看赋值:

- (void)setObject:(id<NSCoding>)object forKey:(NSString *)key {
…
    NSData *value = nil;

    if (!value) return;
    NSString *filename = nil;
    if (_kv.type != YYKVStorageTypeSQLite) {
        if (value.length > _inlineThreshold) {
            filename = [self _filenameForKey:key];
        }
    }
    Lock();
    [_kv saveItemWithKey:key value:value filename:filename extendedData:extendedData];
    Unlock();
}

可以看到value.length > _inlineThresholdfilename会被赋值,点击saveItemWithKey方法:

- (BOOL)saveItemWithKey:(NSString *)key value:(NSData *)value filename:(NSString *)filename extendedData:(NSData *)extendedData {

…
    if (filename.length) {
        if (![self _fileWriteWithName:filename data:value]) {
            return NO;
        }
        if (![self _dbSaveWithKey:key value:value fileName:filename extendedData:extendedData]) {
            [self _fileDeleteWithName:filename];
            return NO;
        }
        return YES;
    } else {
        if (_type != YYKVStorageTypeSQLite) {
            NSString *filename = [self _dbGetFilenameWithKey:key];
            if (filename) {
                [self _fileDeleteWithName:filename];
            }
        }
        return [self _dbSaveWithKey:key value:value fileName:nil extendedData:extendedData];
    }
}

可以看到filename.length如果存在值,就直接写成文件,如果不大于20KB,使用sqlite写入。

作者:NSURLCache、FBDiskCache 都是基于 SQLite 数据库的。基于数据库的缓存可以很好的支持元数据、扩展方便、数据统计速度快,也很容易实现 LRU 或其他淘汰算法,唯一不确定的就是数据库读写的性能,为此我评测了一下 SQLite 在真机上的表现。iPhone 6 64G 下,SQLite 写入性能比直接写文件要高,但读取性能取决于数据大小:当单条数据小于 20K 时,数据越小 SQLite 读取性能越高;单条数据大于 20K 时,直接写为文件速度会更快一些。

二、YYDiskCache:

(那些相同的参数和方法就不重新写)

属性列表:


/**
如果对象的数据大小(以字节为单位)大于此值,则对象将
存储为文件,否则对象将存储在sqlite中。
0表示所有对象将存储为分开的文件,NSUIntegerMax表示所有对象
对象将存储在sqlite中。
默认值为20480 (20KB)。
 */
@property (readonly) NSUInteger inlineThreshold;
/**
如果这个块不是nil,那么这个块将被用来存档对象
NSKeyedArchiver。您可以使用此块来支持不支持的对象
遵守' NSCoding '协议。
默认值为空。
 */
@property (nullable, copy) NSData *(^customArchiveBlock)(id object);
/**
如果这个块不是nil,那么这个块将被用来解存档对象
NSKeyedUnarchiver。您可以使用此块来支持不支持的对象
遵守' NSCoding '协议。
默认值为空。
 */
@property (nullable, copy) id (^customUnarchiveBlock)(NSData *data);
/**
当需要将对象保存为文件时,将调用此块来生成
指定键的文件名。如果块为空,缓存使用md5(key)作为默认文件名。默认值为空。
 */
@property (nullable, copy) NSString *(^customFileNameBlock)(NSString *key);
#pragma mark - Limit
/**
缓存应容纳的最大对象数。
默认值为NSUIntegerMax,即不限制。
这不是一个严格的限制-如果缓存超过限制,缓存中的一些对象
缓存可以稍后在后台队列中被清除。
 */
@property NSUInteger countLimit;
/**
在开始清除对象之前,缓存可以保留的最大总开销。
默认值为NSUIntegerMax,即不限制。
这不是一个严格的限制-如果缓存超过限制,缓存中的一些对象
缓存可以稍后在后台队列中被清除。
 */
@property NSUInteger costLimit;
/**
缓存中对象的最大过期时间。

>值为DBL_MAX,即无限制。
这不是一个严格的限制-如果一个对象超过了限制,对象可以
稍后在后台队列中被驱逐。
 */
@property NSTimeInterval ageLimit;
/**
缓存应保留的最小空闲磁盘空间(以字节为单位)。

>默认值为0,表示不限制。
如果可用磁盘空间低于此值,缓存将删除对象
释放一些磁盘空间。这不是一个严格的限制——如果空闲磁盘空间没有了
超过限制,对象可能稍后在后台队列中被清除。
 */
@property NSUInteger freeDiskSpaceLimit;
/**
自动修整检查时间间隔以秒为单位。默认值是60(1分钟)。
缓存保存一个内部计时器来检查缓存是否达到
它的极限,一旦达到极限,它就开始驱逐物体。
 */
@property NSTimeInterval autoTrimInterval;
/**
设置“YES”为调试启用错误日志。
 */
@property BOOL errorLogsEnabled;

方法列表:

/**
指定的初始化式。
threshold数据存储内联阈值,单位为字节。如果对象的数据
Size(以字节为单位)大于此值,则对象将存储为
文件,否则对象将存储在sqlite中。0表示所有对象都会
NSUIntegerMax表示所有对象都将被存储
sqlite。如果您不知道对象的大小,20480是一个不错的选择。
在第一次初始化后,您不应该更改指定路径的这个值。
如果指定路径的缓存实例在内存中已经存在,
该方法将直接返回该实例,而不是创建一个新实例。
 */
- (nullable instancetype)initWithPath:(NSString *)path inlineThreshold:(NSUInteger)threshold NS_DESIGNATED_INITIALIZER;
/**
返回此缓存中的对象数量。
 */
- (NSInteger)totalCount;
/**
以字节为单位的对象开销总数。
 */
- (NSInteger)totalCost;
#pragma mark - 修剪
/**
使用LRU从缓存中移除对象,直到' totalCount '低于指定值。
 */
- (void)trimToCount:(NSUInteger)count;
/**
使用LRU从缓存中移除对象,直到' totalCost '低于指定值,完成后调用回调。
 */
- (void)trimToCost:(NSUInteger)cost;
/**
使用LRU从缓存中删除对象,直到所有过期对象被指定值删除为止。
 */
- (void)trimToAge:(NSTimeInterval)age;
/**
从对象中获取扩展数据。
 */
+ (nullable NSData *)getExtendedDataFromObject:(id)object;
/**
将扩展数据设置为一个对象。
 */
+ (void)setExtendedData:(nullable NSData *)extendedData toObject:(id)object;

@end

三、YYMemoryCache:

属性列表:

/** 存中的对象数量(只读)*/
@property (readonly) NSUInteger totalCount;

/** 缓存中对象的总开销(只读)*/
@property (readonly) NSUInteger totalCost;

/**
自动修整检查时间间隔以秒为单位。默认是5.0。
 */
@property NSTimeInterval autoTrimInterval;
/**
如果' YES ',当应用程序收到内存警告时,缓存将删除所有对象。
 */
@property BOOL shouldRemoveAllObjectsOnMemoryWarning;
/**
如果是,当应用程序进入后台时,缓存将删除所有对象。
 */
@property BOOL shouldRemoveAllObjectsWhenEnteringBackground;
/**
当应用程序收到内存警告时要执行的块。
 */
@property (nullable, copy) void(^didReceiveMemoryWarningBlock)(YYMemoryCache *cache);
/**
当应用程序进入后台时执行的一个块。
 */
@property (nullable, copy) void(^didEnterBackgroundBlock)(YYMemoryCache *cache);
/**
如果' YES ',键值对将在主线程上释放,否则在后台线程上释放。默认为NO。。
 */
@property BOOL releaseOnMainThread;
/**
如果' YES ',键值对将在主线程上释放,否则在后台线程上释放。默认为NO。
 */
@property BOOL releaseAsynchronously;

总结:

对比一下NSCache:

@interface NSCache <KeyType, ObjectType> : NSObject
@property (copy) NSString *name;
@property (nullable, assign) id<NSCacheDelegate> delegate;
- (nullable ObjectType)objectForKey:(KeyType)key;
- (void)setObject:(ObjectType)obj forKey:(KeyType)key; // 0 cost
- (void)setObject:(ObjectType)obj forKey:(KeyType)key cost:(NSUInteger)g;
- (void)removeObjectForKey:(KeyType)key;
- (void)removeAllObjects;
@property NSUInteger totalCostLimit;// limits are imprecise/not strict
@property NSUInteger countLimit;// limits are imprecise/not strict
@property BOOL evictsObjectsWithDiscardedContent;
@end
@protocol NSCacheDelegate <NSObject>
@optional
- (void)cache:(NSCache *)cache willEvictObject:(id)obj;
@end

会发现NSCache相对简单很多,YYCache对内存和磁盘缓存给了很多个接口去准备控制缓存的数量和生命周期。

离屏渲染(二)

离屏渲染是在当前屏幕帧缓冲区外增加了一个临时新的缓冲区对将要进行显示的图片进行渲染操作再写回帧缓冲区,过个过程造成性能损耗。

离屏渲染(一)

离屏渲染是在当前屏幕帧缓冲区外增加了一个临时新的缓冲区对将要进行显示的图片进行渲染操作再写回帧缓冲区,过个过程造成性能损耗。

启动优化clang插桩(一)

启动优化clang插桩(一)

一、了解Clang

首先到Clang地址:Clang Documentation Pasted Graphic.pngPCs指的是CPU的寄存器,用来存储将要执行的下一条指令的地址,Tracing PCs就是跟踪CPU将要执行的代码。

二、如何使用

网页下拉有个Example Pasted Graphic 1.png 使用之前要在工程添加标记: Pasted Graphic 2.png

编译器就会在每一行代码的边缘插入这一段函数:__sanitizer_cov_trace_pc_guard(&guard_variable)

打开实例demo,在Build Settings 搜索 Other c Flag 填入 -fsanitize-coverage=trace-pc-guard

1__#$!@%!#__Pasted Graphic 1.png

项目会报未定义符号的错:

Pasted Graphic 7.png

这就需要去定义这两个符号,先把这两个函数复制过来:

Pasted Graphic 5.png 先把代码复制进ViewController

extern "C" void __sanitizer_cov_trace_pc_guard_init(uint32_t *start,
                                                    uint32_t *stop) {
  static uint64_t N;  // Counter for the guards.
  if (start == stop || *start) return;  // Initialize only once.
  printf("INIT: %p %p\n", start, stop);
  for (uint32_t *x = start; x < stop; x++)
    *x = ++N;  // Guards should start from 1.
}
// This callback is inserted by the compiler on every edge in the
// control flow (some optimizations apply).
// Typically, the compiler will emit the code like this:
//    if(*guard)
//      __sanitizer_cov_trace_pc_guard(guard);
// But for large functions it will emit a simple call:
//    __sanitizer_cov_trace_pc_guard(guard);
extern "C" void __sanitizer_cov_trace_pc_guard(uint32_t *guard) {
  if (!*guard) return;  // Duplicate the guard check.
  // If you set *guard to 0 this code will not be called again for this edge.
  // Now you can get the PC and do whatever you want:
  //   store it somewhere or symbolize it and print right away.
  // The values of `*guard` are as you set them in
  // __sanitizer_cov_trace_pc_guard_init and so you can make them consecutive
  // and use them to dereference an array or a bit vector.
  void *PC = __builtin_return_address(0);
  char PcDescr[1024];
  // This function is a part of the sanitizer run-time.
  // To use it, link with AddressSanitizer or other sanitizer.
  __sanitizer_symbolize_pc(PC, "%p %F %L", PcDescr, sizeof(PcDescr));
  printf("guard: %p %x PC %s\n", guard, *guard, PcDescr);
}

把头文件也粘贴进来:

#include <stdint.h>
#include <stdio.h>
#include <sanitizer/coverage_interface.h>

两个方法里面都有extern “C”extern “C”的主要作用是为了能够正确实现C++去调用其他C语言的代码,加上extern “C”就会指示作用域内的代码按照C语言区编译,而不是C++,这个extern “C”在OC项目里没什么用,直接删除

此时还会包一个错误:

Pasted Graphic 8.png

这个__sanitizer_symbolize_pc(PC, "%p %F %L", PcDescr, sizeof(PcDescr));函数没有什么作用,直接删除即可。

三、代码调试

cmd + r运行,此时终端会打印一些信息:

Pasted Graphic 9.png

删除两个函数里面的注释,先注释第二个的内容,然后运行

INIT: 0x1025c5478 0x1025c54f0

这是运行打印得到的地址,就是函数(uint32_t *start, uint32_t *stop)startstop两个指针的地址

stop存储的就是我们工程里面符号的个数

for (uint32_t *x = start; x < stop; x++)
        *x = ++N;

看一下这个for循环,start会先复制给*xx++就是内存平移,按照uint32_t的大小去平移,而uint32_t的定义是typedef unsigned int uint32_t; 是无符号整型,占4个字节,所以每次按4个字节平移。

startstop里面存的是什么,打断点调试:

Pasted Graphic 10.png

先看start:

INIT: 0x1042a5278 0x1042a52e0
(lldb) x 0x1042a5278
0x1042a5278: 01 00 00 00 02 00 00 00 03 00 00 00 04 00 00 00  ................
0x1042a5288: 05 00 00 00 06 00 00 00 07 00 00 00 08 00 00 00  ................
(lldb)

由于uint32_t4个字节来存储发现start就是 0 1 2 3 4…,再看stop,由于stop的已经是结束位置,读取的数据是在startstop之间的数据,所以需要向前平移4个字节得到其真实数据。

(lldb) x (0x1042a52e0-4)
0x1042a52dc: 1a 00 00 00 00 00 00 00 00 00 00 00 fe f1 29 04  ..............).
0x1042a52ec: 01 00 00 00 00 00 00 00 00 00 00 00 90 40 2a 04  .............@*.
(lldb)

可以得到1a 就是26,也可以循环外面打印结果: Pasted Graphic 11.png 可以得到:

TraceDemo[16814:301325] 26

也是26个符号。

四、测试验证方法

可以验证一下,添加一个函数:

void test(void) {
    NSLog(@"%s",__func__);
}

符号变成27

TraceDemo[16911:304537] 27

再添加一个block

void (^block) (void) = ^{
    NSLog(@"%s",__func__);
};

符号变成28

TraceDemo[16933:305465] 28

添加一个数据类型属性:

@property (nonatomic ,assign) int age;

由于系统自动生成getter、setter方法,符号变成30

TraceDemo[16975:306816] 30

添加一个对象属性:

@property (nonatomic ,copy) NSString *str;

符号变成33

TraceDemo[17041:308780] 33

对象属性由于ARC,系统自动除了生成getter、setter方法外还生成了cxx_destruct()析构函数

添加一个方法:

- (void)test{
}

符号变成34

TraceDemo[17114:311256] 34

在其他类AppDelegate类中添加一个属性:

@interface AppDelegate : UIResponder <UIApplicationDelegate>
@property (nonatomic, strong) NSString *name;
@end

符号变成37:

TraceDemo[17266:316294] 37

符号变成37

结论

这就说明了通过这个方法整个项目里的符号,它都能捕获到。

❌