普通视图
iOS应用数据持久化 FMDB
iOS应用数据持久化 SQLite
对象序列化
YYCache(二)
YYCache(二)
代码测试:
初始化YYCache实例:
#import <YYCache/YYCache.h>
#import "ViewController.h"
@interface ViewController ()
@property (nonatomic, strong) YYCache *contactsCache;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.contactsCache = [YYCache cacheWithName:@"Contacts”];
}
添加一个通讯录模型:
@interface ContactsModel : NSObject
@property (nonatomic, strong) NSString *name;
@property (nonatomic, strong) NSString *phoneNumber;
@end
添加10条数据:
for (int i = 0; i < 10; i ++) {
ContactsModel *model = [ContactsModel new];
model.name = [NSString stringWithFormat:@"张%d",i];
model.phoneNumber = [NSString stringWithFormat:@"1588889999%d",i];
[self.contactsCache setObject:model forKey:
[NSString stringWithFormat:@"kContacts_%d",i]];
}
提示Sending 'ContactsModel *__strong' to parameter of incompatible type 'id<NSCoding> _Nullable:
给ContactsModel添加<NSCoding>协议:
@interface ContactsModel : NSObject<NSCoding>
//通讯类内部的两个属性变量分别转码
- (void)encodeWithCoder:(nonnull NSCoder *)coder {
[coder encodeObject:_name forKey:@"name"];
[coder encodeObject:_phoneNumber forKey:@"phoneNumber"];
}
//分别把两个属性变量根据关键字进行逆转码,最后返回一个Contacts类的对象
- (nullable instancetype)initWithCoder:(nonnull NSCoder *)coder {
if (self = [super init]) {
if (coder) {
_name = [coder decodeObjectOfClass:[NSString class]
forKey:@"name"];
_phoneNumber = [coder decodeObjectOfClass:[NSString class]
forKey:@"phoneNumber"];
}
}
return self;
}
提示消失,用循环把值取出来:
for (int i = 0; i < 10; i++) {
ContactsModel *model = (ContactsModel *)[self.contactsCache
objectForKey:[NSString stringWithFormat:@"kContacts_%d",i]];
NSLog(@"name = %@ phoneNumber = %@",model.name, model.phoneNumber);
}
运行打印:
YYCacheDemo[3304:73220] name = 张0 phoneNumber = 15888899990
YYCacheDemo[3304:73220] name = 张1 phoneNumber = 15888899991
YYCacheDemo[3304:73220] name = 张2 phoneNumber = 15888899992
YYCacheDemo[3304:73220] name = 张3 phoneNumber = 15888899993
YYCacheDemo[3304:73220] name = 张4 phoneNumber = 15888899994
YYCacheDemo[3304:73220] name = 张5 phoneNumber = 15888899995
YYCacheDemo[3304:73220] name = 张6 phoneNumber = 15888899996
YYCacheDemo[3304:73220] name = 张7 phoneNumber = 15888899997
YYCacheDemo[3304:73220] name = 张8 phoneNumber = 15888899998
YYCacheDemo[3304:73220] name = 张9 phoneNumber = 15888899999
打印self.contactsCache.memoryCache和self.contactsCache.diskCache,发现数据一样
主要功能:
一、添加限制
二、数据修剪
YYMemoryCache:
YYDiskCach:
![]()
测试给内存添加数量限制:
self.contactsCache.memoryCache.countLimit = 5;
YYCacheDemo[4272:102918] name = (null) phoneNumber = (null)
YYCacheDemo[4272:102918] name = (null) phoneNumber = (null)
YYCacheDemo[4272:102918] name = (null) phoneNumber = (null)
YYCacheDemo[4272:102918] name = (null) phoneNumber = (null)
YYCacheDemo[4272:102918] name = (null) phoneNumber = (null)
YYCacheDemo[4272:102918] name = 张5 phoneNumber = 15888899995
YYCacheDemo[4272:102918] name = 张6 phoneNumber = 15888899996
YYCacheDemo[4272:102918] name = 张7 phoneNumber = 15888899997
YYCacheDemo[4272:102918] name = 张8 phoneNumber = 15888899998
YYCacheDemo[4272:102918] name = 张9 phoneNumber = 15888899999
发现前面的
5个数据都被移除了
修剪最大个数为8个:
[self.contactsCache.memoryCache trimToCount:8];
运行:
YYCacheDemo[4461:108978] name = (null) phoneNumber = (null)
YYCacheDemo[4461:108978] name = (null) phoneNumber = (null)
YYCacheDemo[4461:108978] name = 张2 phoneNumber = 15888899992
YYCacheDemo[4461:108978] name = 张3 phoneNumber = 15888899993
YYCacheDemo[4461:108978] name = 张4 phoneNumber = 15888899994
YYCacheDemo[4461:108978] name = 张5 phoneNumber = 15888899995
YYCacheDemo[4461:108978] name = 张6 phoneNumber = 15888899996
YYCacheDemo[4461:108978] name = 张7 phoneNumber = 15888899997
YYCacheDemo[4461:108978] name = 张8 phoneNumber = 15888899998
YYCacheDemo[4461:108978] name = 张9 phoneNumber = 15888899999
清空所有缓存:
[self.contactsCache removeAllObjects];
内存缓存:
YYCacheDemo[6819:162854] memory name = (null) phoneNumber = (null)
YYCacheDemo[6819:162854] memory name = (null) phoneNumber = (null)
YYCacheDemo[6819:162854] memory name = (null) phoneNumber = (null)
YYCacheDemo[6819:162854] memory name = (null) phoneNumber = (null)
YYCacheDemo[6819:162854] memory name = (null) phoneNumber = (null)
YYCacheDemo[6819:162854] memory name = (null) phoneNumber = (null)
YYCacheDemo[6819:162854] memory name = (null) phoneNumber = (null)
YYCacheDemo[6819:162854] memory name = (null) phoneNumber = (null)
YYCacheDemo[6819:162854] memory name = (null) phoneNumber = (null)
YYCacheDemo[6819:162854] memory name = (null) phoneNumber = (null)
磁盘缓存:
YYCacheDemo[6819:162854] disk name = (null) phoneNumber = (null)
YYCacheDemo[6819:162854] disk name = (null) phoneNumber = (null)
YYCacheDemo[6819:162854] disk name = (null) phoneNumber = (null)
YYCacheDemo[6819:162854] disk name = (null) phoneNumber = (null)
YYCacheDemo[6819:162854] disk name = (null) phoneNumber = (null)
YYCacheDemo[6819:162854] disk name = (null) phoneNumber = (null)
YYCacheDemo[6819:162854] disk name = (null) phoneNumber = (null)
YYCacheDemo[6819:162854] disk name = (null) phoneNumber = (null)
YYCacheDemo[6819:162854] disk name = (null) phoneNumber = (null)
YYCacheDemo[6819:162854] disk name = (null) phoneNumber = (null)
源码:
YYMemoryCache的初始化:
- (instancetype)init {
self = super.init;
pthread_mutex_init(&_lock, NULL);
_lru = [_YYLinkedMap new];
_queue = dispatch_queue_create("com.ibireme.cache.memory", DISPATCH_QUEUE_SERIAL);
_countLimit = NSUIntegerMax;
_costLimit = NSUIntegerMax;
_ageLimit = DBL_MAX;
_autoTrimInterval = 5.0;
_shouldRemoveAllObjectsOnMemoryWarning = YES;
_shouldRemoveAllObjectsWhenEnteringBackground = YES;
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_appDidReceiveMemoryWarningNotification) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_appDidEnterBackgroundNotification) name:UIApplicationDidEnterBackgroundNotification object:nil];
[self _trimRecursively];
return self;
}
一个_YYLinkedMap的实例,查看_YYLinkedMap:
@interface _YYLinkedMap : NSObject {
@package
CFMutableDictionaryRef _dic; // do not set object directly
NSUInteger _totalCost;
NSUInteger _totalCount;
_YYLinkedMapNode *_head; // MRU, do not change it directly
_YYLinkedMapNode *_tail; // LRU, do not change it directly
BOOL _releaseOnMainThread;
BOOL _releaseAsynchronously;
}
发现_YYLinkedMap是一个双向链表,有两个_YYLinkedMapNode类型节点,还有一个CFMutableDictionaryRef的字典_dic,_dic是真正存放数据的地方。
递归修剪:
- (void)_trimRecursively {
__weak typeof(self) _self = self;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(_autoTrimInterval * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
__strong typeof(_self) self = _self;
if (!self) return;
[self _trimInBackground];
[self _trimRecursively];
});
}
- (void)_trimInBackground {
dispatch_async(_queue, ^{
[self _trimToCost:self->_costLimit];
[self _trimToCount:self->_countLimit];
[self _trimToAge:self->_ageLimit];
});
}
每隔
_autoTrimInterval秒就自动调用修整内存数据,_autoTrimInterval默认是5秒。
添加数据:
- (void)setObject:(id)object forKey:(id)key withCost:(NSUInteger)cost {
….
_YYLinkedMapNode *node = CFDictionaryGetValue(_lru->_dic, (__bridge const void *)(key));
NSTimeInterval now = CACurrentMediaTime();
if (node) {
_lru->_totalCost -= node->_cost;
_lru->_totalCost += cost;
node->_cost = cost;
node->_time = now;
node->_value = object;
[_lru bringNodeToHead:node];
} else {
node = [_YYLinkedMapNode new];
node->_cost = cost;
node->_time = now;
node->_key = key;
node->_value = object;
[_lru insertNodeAtHead:node];
}
…
pthread_mutex_unlock(&_lock);
}
由于_lru = [_YYLinkedMap new]; ,可以看到就是操作_YYLinkedMap双链表,使用的是pthread_mutex锁。
YYCache(一)
Fastlane自动化打包到蒲公英
离屏渲染(二)
离屏渲染(一)
启动优化clang插桩(三)
一、获取符号
先把获取符号的代码写在touchBegan里面:
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
// NSLog(@"%s",__func__);
// 因为不知道有多少个,所有用while循环
while(YES){
// 将node取出来
SYNode *node = OSAtomicDequeue(&symbolList, offsetof(SYNode, next));
// 取到node为空退出当前循环
if(node == NULL){
break;
}
// 打印拿到符号的信息
Dl_info info;
dladdr(node->pc,&info);
printf("%s\n",info.dli_sname);
}
}
点击运行,会打印出一堆的touchesBegan。
-[ViewController touchesBegan:withEvent:]
-[ViewController touchesBegan:withEvent:]
-[ViewController touchesBegan:withEvent:]
-[ViewController touchesBegan:withEvent:]
-[ViewController touchesBegan:withEvent:]
-[ViewController touchesBegan:withEvent:]
-[ViewController touchesBegan:withEvent:]
-[ViewController touchesBegan:withEvent:]
-[ViewController touchesBegan:withEvent:]
…
回到Build setting,将原来标记那里添加一个参数func:
![]()
再次运行,点击屏幕打印:
-[ViewController touchesBegan:withEvent:]
-[SceneDelegate sceneDidBecomeActive:]
-[SceneDelegate sceneWillEnterForeground:]
-[ViewController viewDidLoad]
-[SceneDelegate window]
-[SceneDelegate scene:willConnectToSession:options:]
-[SceneDelegate window]
-[SceneDelegate setWindow:]
-[SceneDelegate window]
-[AppDelegate application:didFinishLaunchingWithOptions:]
main
这样就拿到了所有的符号。
二、处理符号
因为队列是先进后出,所以我们需要做一个取反的操作,而且还有一些是重复的符号,我们需要去掉,处理完这些步骤之后的这些符号就是程序启动时候的顺序。
先给函数添加下划线:
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
// NSLog(@"%s",__func__);
// 初始化一个数组来装载有顺序的数据
NSMutableArray *symbolNames = [NSMutableArray array];
//因为不知道有多少个,所有用while循环
while(YES){
//将node取出来
SYNode *node = OSAtomicDequeue(&symbolList, offsetof(SYNode, next));
// 取到node为空退出当前循环
if(node == NULL){
break;
}
//打印拿到符号的信息
Dl_info info;
dladdr(node->pc,&info);
printf("%s\n",info.dli_sname);
//转为OC字符串
NSString *name = @(info.dli_sname );
//判断是否是方法
BOOL isMethod = [name hasPrefix:@"+["] ||
[name hasPrefix:@"-["];
//拿到处理后的符号
NSString * symbolName = isMethod? name : [@“_” stringByAppendingString:name];
// 添加进数组
[symbolNames addObject:symbolName];
}
NSLog(@"%@",symbolNames);
}
运行打印,得到:
(
"-[ViewController touchesBegan:withEvent:]",
"-[SceneDelegate sceneDidBecomeActive:]",
"-[SceneDelegate sceneWillEnterForeground:]",
"-[SceneDelegate window]",
"-[SceneDelegate scene:willConnectToSession:options:]",
"-[SceneDelegate window]",
"-[SceneDelegate setWindow:]",
"-[SceneDelegate window]",
"-[AppDelegate application:didFinishLaunchingWithOptions:]",
"_main"
)
这样main函数就加上了下划线“_”
三、符号逆序
直接反向遍历:
NSEnumerator *em = [symbolNames reverseObjectEnumerator];
NSLog(@"%@",em.allObjects);
运行打印,得到:
(
"_main",
"-[AppDelegate application:didFinishLaunchingWithOptions:]",
"-[SceneDelegate window]",
"-[SceneDelegate setWindow:]",
"-[SceneDelegate window]",
"-[SceneDelegate scene:willConnectToSession:options:]",
"-[SceneDelegate window]",
"-[ViewController viewDidLoad]",
"-[SceneDelegate sceneWillEnterForeground:]",
"-[SceneDelegate sceneDidBecomeActive:]",
"-[ViewController touchesBegan:withEvent:]"
)
这就得到我们想要的顺序。
四、符号去重
// 初始化去重后的数组
NSMutableArray *funcs = [NSMutableArray arrayWithCapacity:symbolNames.count];
// 定义表示
NSString *name;
// 判断是否数组里已经存在,不存在则添加
while (name = [em nextObject]) {
if(![funcs containsObject:name]){
[funcs addObject:name];
}
}
NSLog(@"%@",funcs);
运行打印,得到:
(
"_main",
"-[AppDelegate application:didFinishLaunchingWithOptions:]",
"-[SceneDelegate window]",
"-[SceneDelegate setWindow:]",
"-[SceneDelegate scene:willConnectToSession:options:]",
"-[ViewController viewDidLoad]",
"-[SceneDelegate sceneWillEnterForeground:]",
"-[SceneDelegate sceneDidBecomeActive:]",
"-[ViewController touchesBegan:withEvent:]"
)
可以发现里面已经没有了重复的符号
五、生成.order文件
// 拼接成一个字符串
NSString *funcsStr = [funcs componentsJoinedByString:@"\n"];
// 文件路径
NSString *filePath = [NSTemporaryDirectory() stringByAppendingString:@"TraceDemo.order"];
// 文件的内容
NSData *file = [funcsStr dataUsingEncoding:NSUTF8StringEncoding];
// 写入文件
[[NSFileManager defaultManager] createFileAtPath:filePath contents:file attributes:nil];
// 打印路径
NSLog(@"%@",NSHomeDirectory());
运行打印:
TraceDemo[31577:752540] /Users/xxxx/Library/Developer/CoreSimulator/Devices/876D0DEB-7AC9-4B67-A877-DB2BC4B5BD10/data/Containers/Data/Application/702BBFFB-D619-4B19-814C-0C9CXXXXX
Tmp文件下可以看到一个.order文件
![]()
打开文件:
写入的内容就是我们想要的内容,这样就可以把.order文件复制进项目里。
![]()
Order File添加文件位置:
![]()
Link Map File打开:
![]()
运行,然后找到这个LinkMap文件:
![]()
打开和.order文件对比:
![]()
发现完全一致。
重排之后减少多少时间,就需要用
Instruments工具的System Trace去做具体对比。
六、使用swift情况
如果项目使用swift的话,跟重排和使用OC类似。创建一个swift文件:
import Foundation
class SwiftPage: NSObject{
@objc class public func swiftFunc(){
print("我是swift")
}
}
导入头文件:
#import "TraceDemo-Swift.h"
添加方法:
- (void)viewDidLoad {
[super viewDidLoad];
[SwiftPage swiftFunc];
}
运行:
我是swift
点击屏幕打印:
(
"_main",
"-[AppDelegate application:didFinishLaunchingWithOptions:]",
"-[SceneDelegate window]",
"-[SceneDelegate setWindow:]",
"-[SceneDelegate scene:willConnectToSession:options:]",
"-[ViewController viewDidLoad]",
"-[SceneDelegate sceneWillEnterForeground:]",
"-[SceneDelegate sceneDidBecomeActive:]",
"-[ViewController touchesBegan:withEvent:]"
)
发现并没有打印swift方法,因为swift并不是clang编译的,clang插桩只能编译C、C++和OC,这里就需要用在Other Swift Flags添加两个标记:-sanitize-coverage=func、-sanitize=undefined。
![]()
再次运行:
(
"_main",
"-[AppDelegate application:didFinishLaunchingWithOptions:]",
"-[SceneDelegate window]",
"-[SceneDelegate setWindow:]",
"-[SceneDelegate scene:willConnectToSession:options:]",
"-[ViewController viewDidLoad]",
"_$s9TraceDemo9SwiftPageC9swiftFuncyyFZTo",
"_$s9TraceDemo9SwiftPageC9swiftFuncyyFZ",
"_$ss27_finalizeUninitializedArrayySayxGABnlF",
"_$sSa12_endMutationyyF",
"_$ss5print_9separator10terminatoryypd_S2StFfA0_",
"_$ss5print_9separator10terminatoryypd_S2StFfA1_",
"-[SceneDelegate sceneWillEnterForeground:]",
"-[SceneDelegate sceneDidBecomeActive:]",
"-[ViewController touchesBegan:withEvent:]"
)
可以看到swift方法,因为swift方法自带混淆,这里swift也捕获到了,这里就完成了OC和swift的二进制重排。在项目需要上线的时候,删除一开始的标记-fsanitize-coverage=func,trace-pc-guard和其他测试代码。
启动优化clang插桩(二)
启动优化clang插桩(一)
启动优化clang插桩(一)
一、了解Clang
首先到Clang地址:Clang Documentation
![]()
PCs指的是CPU的寄存器,用来存储将要执行的下一条指令的地址,Tracing PCs就是跟踪CPU将要执行的代码。
二、如何使用
网页下拉有个Example
使用之前要在工程添加标记:
![]()
编译器就会在每一行代码的边缘插入这一段函数:
__sanitizer_cov_trace_pc_guard(&guard_variable)
打开实例demo,在Build Settings 搜索 Other c Flag 填入 -fsanitize-coverage=trace-pc-guard
![]()
项目会报未定义符号的错:
![]()
这就需要去定义这两个符号,先把这两个函数复制过来:
先把代码复制进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项目里没什么用,直接删除
此时还会包一个错误:
![]()
这个__sanitizer_symbolize_pc(PC, "%p %F %L", PcDescr, sizeof(PcDescr));函数没有什么作用,直接删除即可。
三、代码调试
cmd + r运行,此时终端会打印一些信息:
![]()
删除两个函数里面的注释,先注释第二个的内容,然后运行
INIT: 0x1025c5478 0x1025c54f0
这是运行打印得到的地址,就是函数(uint32_t *start, uint32_t *stop)的start和stop两个指针的地址
stop存储的就是我们工程里面符号的个数
for (uint32_t *x = start; x < stop; x++)
*x = ++N;
看一下这个
for循环,start会先复制给*x,x++就是内存平移,按照uint32_t的大小去平移,而uint32_t的定义是typedef unsigned int uint32_t;是无符号整型,占4个字节,所以每次按4个字节平移。
start和stop里面存的是什么,打断点调试:
![]()
先看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_t按4个字节来存储发现start就是 0 1 2 3 4…,再看stop,由于stop的已经是结束位置,读取的数据是在start和stop之间的数据,所以需要向前平移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,也可以循环外面打印结果:
可以得到:
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,
结论
这就说明了通过这个方法整个项目里的符号,它都能捕获到。