对于之前的微博项目,同理其他类似的新闻类应用,为了节省用户流量,并且保存上一次的浏览记录,应该制作离线缓存,每次打开的时候自动加载离线缓存,显示数据, 用户需要查看最新数据的时候进行手动刷新。
注1:微博数据量很大,必须使用数据库来制作离线缓存;
注2:每次加载离线缓存的时候,不是一下子将离线缓存全部加载进内存,而是加载最新的20条,上拉刷新的时候,再从缓存中加载20条,直到缓存数据全部加载完,还需要加载以前的微博数据的时候再去发送网络请求;
注1:在移动端使用数据库的时候,不需像服务器那样,每个模型对应一张表,同时还需要建立表与表之间的关系(比如微博模型中有用户模型、图片模型数组等等),这样在移动端太复杂;
注2:可以将一条微博数据模型或者新浪返回的一条微博字典,打包成NSData的二进制数据,作为一条数据;
注3:再添加一个idstr字段,用于到时候判断需要加载比idstr小或者大的微博数据,即最新或者以前的微博数据,来方便查询;
实现:将HMShop模型存入数据库
注:存对象的时候要将对象打包成NSData!!!
#import "HMShop.h"
@implementation HMShop
- (void)encodeWithCoder:(NSCoder *)encoder
{
[encoder encodeObject:self.name forKey:@"name"];
[encoder encodeDouble:self.price forKey:@"price"];
}
- (id)initWithCoder:(NSCoder *)decoder
{
if (self = [super init]) {
self.name = [decoder decodeObjectForKey:@"name"];
self.price = [decoder decodeDoubleForKey:@"price"];
}
return self;
}
@end
#import "HMViewController.h"
#import "HMShop.h"
#import "FMDB.h"
@interface HMViewController ()
@property (nonatomic, strong) FMDatabase *db;
@end
@implementation HMViewController
- (void)viewDidLoad
{
[super viewDidLoad];
[self setup];
// [self addShops];
[self readShops];
}
- (void)setup
{
// 初始化
NSString *path = @"/Users/apple/Desktop/shops.data";
self.db = [FMDatabase databaseWithPath:path];
[self.db open];
// 2.创表
[self.db executeUpdate:@"CREATE TABLE IF NOT EXISTS t_shop (id integer PRIMARY KEY, shop blob NOT NULL);"];
}
- (void)addShops
{
/* 直接利用NSKeyedArchiver存储,不适用于大批量数据!!! */
// NSMutableArray *shops = [NSMutableArray array];
// for (int i = 0; i<1000; i++) {
// HMShop *shop = [[HMShop alloc] init];
// shop.name = [NSString stringWithFormat:@"商品--%d", i];
// shop.price = arc4random() % 10000;
// [shops addObject:shop];
// }
// [NSKeyedArchiver archiveRootObject:shops toFile:@"/Users/apple/Desktop/shops.data"];
/* 利用数据库存储 */
for (int i = 0; i<100; i++) {
HMShop *shop = [[HMShop alloc] init];
shop.name = [NSString stringWithFormat:@"商品--%d", i];
shop.price = arc4random() % 10000;
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:shop];
[self.db executeUpdateWithFormat:@"INSERT INTO t_shop(shop) VALUES (%@);", data];
}
}
- (void)readShops
{
// NSMutableArray *shops = [NSKeyedUnarchiver unarchiveObjectWithFile:@"/Users/apple/Desktop/shops.data"];
// NSLog(@"%@", [shops subarrayWithRange:NSMakeRange(20, 10)]);
FMResultSet *set = [self.db executeQuery:@"SELECT * FROM t_shop LIMIT 10,10;"];
while (set.next) {
NSData *data = [set objectForColumnName:@"shop"];
HMShop *shop = [NSKeyedUnarchiver unarchiveObjectWithData:data];
NSLog(@"%@", shop);
}
}
@end
1>添加HWStatusTool类,专门用来对微博数据进行数据库存储/读取
2>将新浪返回的字典数组存入数据库,注意:一个字典对应一条数据,并且需要添加idstr字段,用于判断到时候需要加载最新的还是以前的数据
// 微博工具类:用来处理微博数据的缓存
#import
@interface HWStatusTool : NSObject
/**
* 根据请求参数去沙盒中加载缓存的微博数据
*
* @param params 请求参数
*/
+ (NSArray *)statusesWithParams:(NSDictionary *)params;
/**
* 存储微博数据到沙盒中
*
* @param statuses 需要存储的微博数据
*/
+ (void)saveStatuses:(NSArray *)statuses;
@end
#import "HWStatusTool.h"
#import "FMDB.h"
@implementation HWStatusTool
static FMDatabase *_db;
+ (void)initialize
{
// 1.打开数据库
NSString *path = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:@"statuses.sqlite"];
_db = [FMDatabase databaseWithPath:path];
[_db open];
// 2.创表
[_db executeUpdate:@"CREATE TABLE IF NOT EXISTS t_status (id integer PRIMARY KEY, status blob NOT NULL, idstr text NOT NULL);"];
}
+ (NSArray *)statusesWithParams:(NSDictionary *)params
{
// 根据请求参数生成对应的查询SQL语句
NSString *sql = nil;
if (params[@"since_id"]) {
sql = [NSString stringWithFormat:@"SELECT * FROM t_status WHERE idstr > %@ ORDER BY idstr DESC LIMIT 20;", params[@"since_id"]];
} else if (params[@"max_id"]) {
sql = [NSString stringWithFormat:@"SELECT * FROM t_status WHERE idstr <= %@ ORDER BY idstr DESC LIMIT 20;", params[@"max_id"]];
} else {
sql = @"SELECT * FROM t_status ORDER BY idstr DESC LIMIT 20;";
}
// 执行SQL
FMResultSet *set = [_db executeQuery:sql];
NSMutableArray *statuses = [NSMutableArray array];
while (set.next) {
NSData *statusData = [set objectForColumnName:@"status"];
NSDictionary *status = [NSKeyedUnarchiver unarchiveObjectWithData:statusData];
[statuses addObject:status];
}
return statuses;
}
+ (void)saveStatuses:(NSArray *)statuses
{
// 要将一个对象存进数据库的blob字段,最好先转为NSData
// 一个对象要遵守NSCoding协议,实现协议中相应的方法,才能转成NSData
for (NSDictionary *status in statuses) {
// NSDictionary --> NSData
NSData *statusData = [NSKeyedArchiver archivedDataWithRootObject:status];
[_db executeUpdateWithFormat:@"INSERT INTO t_status(status, idstr) VALUES (%@, %@);", statusData, status[@"idstr"]];
}
}
@end
在首页控制器中进行上拉/下拉刷新部分代码如下:
/**
* UIRefreshControl进入刷新状态:加载最新的数据
*/
- (void)loadNewStatus
{
// 1.拼接请求参数
HWAccount *account = [HWAccountTool account];
NSMutableDictionary *params = [NSMutableDictionary dictionary];
params[@"access_token"] = account.access_token;
// 取出最前面的微博(最新的微博,ID最大的微博)
HWStatusFrame *firstStatusF = [self.statusFrames firstObject];
if (firstStatusF) {
// 若指定此参数,则返回ID比since_id大的微博(即比since_id时间晚的微博),默认为0
params[@"since_id"] = firstStatusF.status.idstr;
}
// 定义一个block处理返回的字典数据
void (^dealingResult)(NSArray *) = ^(NSArray *statuses){
// 将 "微博字典"数组 转为 "微博模型"数组
NSArray *newStatuses = [HWStatus objectArrayWithKeyValuesArray:statuses];
// 将 HWStatus数组 转为 HWStatusFrame数组
NSArray *newFrames = [self stausFramesWithStatuses:newStatuses];
// 将最新的微博数据,添加到总数组的最前面
NSRange range = NSMakeRange(0, newFrames.count);
NSIndexSet *set = [NSIndexSet indexSetWithIndexesInRange:range];
[self.statusFrames insertObjects:newFrames atIndexes:set];
// 刷新表格
[self.tableView reloadData];
// 结束刷新
[self.tableView headerEndRefreshing];
// 显示最新微博的数量
[self showNewStatusCount:newStatuses.count];
};
// 2.先尝试从数据库中加载微博数据
NSArray *statuses = [HWStatusTool statusesWithParams:params];
if (statuses.count) { // 数据库有缓存数据
dealingResult(statuses);
} else {
// 2.发送请求
[HWHttpTool get:@"https://api.weibo.com/2/statuses/friends_timeline.json" params:params success:^(id json) {
[HWStatusTool saveStatuses:json[@"statuses"]];
dealingResult(json[@"statuses"]);
} failure:^(NSError *error) {
HWLog(@"请求失败-%@", error);
// 结束刷新刷新
[self.tableView headerEndRefreshing];
}];
}
}
/**
* 加载更多的微博数据
*/
- (void)loadMoreStatus
{
// 1.拼接请求参数
HWAccount *account = [HWAccountTool account];
NSMutableDictionary *params = [NSMutableDictionary dictionary];
params[@"access_token"] = account.access_token;
// 取出最后面的微博(最新的微博,ID最大的微博)
HWStatusFrame *lastStatusF = [self.statusFrames lastObject];
if (lastStatusF) {
// 若指定此参数,则返回ID小于或等于max_id的微博,默认为0。
// id这种数据一般都是比较大的,一般转成整数的话,最好是long long类型
long long maxId = lastStatusF.status.idstr.longLongValue - 1;
params[@"max_id"] = @(maxId);
}
// 处理字典数据
void (^dealingResult)(NSArray *) = ^(NSArray *statuses) {
// 将 "微博字典"数组 转为 "微博模型"数组
NSArray *newStatuses = [HWStatus objectArrayWithKeyValuesArray:statuses];
NSArray *newFrames = [self stausFramesWithStatuses:newStatuses];
// 将更多的微博数据,添加到总数组的最后面
[self.statusFrames addObjectsFromArray:newFrames];
// 刷新表格
[self.tableView reloadData];
// 结束刷新(隐藏footer)
[self.tableView footerEndRefreshing];
};
// 2.加载沙盒中的数据
NSArray *statuses = [HWStatusTool statusesWithParams:params];
if (statuses.count) {// 将 HWStatus数组 转为 HWStatusFrame数组
dealingResult(statuses);
} else {
// 3.发送请求
[HWHttpTool get:@"https://api.weibo.com/2/statuses/friends_timeline.json" params:params success:^(id json) {
// 缓存新浪返回的字典数组
[HWStatusTool saveStatuses:json[@"statuses"]];
dealingResult(json[@"statuses"]);
} failure:^(NSError *error) {
HWLog(@"请求失败-%@", error);
// 结束刷新
[self.tableView footerEndRefreshing];
}];
}
}
一般情况下,清楚缓存往往是清除图片缓存(比较大),如果利用SDWebImage,一般使用其自带的方法就可以清除图片缓存,但是若一些应用是下载文件的应用,需要清除缓存则需自己处理。
注:这里为显示方便,直接将缓存大小显示在导航栏标题,用于测试图片缓存清除
// 字节大小
int byteSize = [SDImageCache sharedImageCache].getSize;
// M大小
double size = byteSize / 1000.0 / 1000.0;
self.navigationItem.title = [NSString stringWithFormat:@"缓存大小(%.1fM)", size];
- (void)clearCache
{
// 提醒
UIActivityIndicatorView *circle = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
[circle startAnimating];
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:circle];
// 清除缓存
[[SDImageCache sharedImageCache] clearDisk];
// 显示按钮
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"清除缓存" style:0 target:self action:@selector(clearCache)];
self.navigationItem.title = [NSString stringWithFormat:@"缓存大小(0M)"];
}
NSString分类方法:计算当前文件\文件夹的内容大小
/**
* 计算当前文件\文件夹的内容大小
*/
- (NSInteger)fileSize
{
NSFileManager *mgr = [NSFileManager defaultManager];
// 判断是否为文件
BOOL dir = NO;
BOOL exists = [mgr fileExistsAtPath:self isDirectory:&dir];
// 文件\文件夹不存在
if (exists == NO) return 0;
if (dir) { // self是一个文件夹
// 遍历caches里面的所有内容 --- 直接和间接内容
NSArray *subpaths = [mgr subpathsAtPath:self];
NSInteger totalByteSize = 0;
for (NSString *subpath in subpaths) {
// 获得全路径
NSString *fullSubpath = [self stringByAppendingPathComponent:subpath];
// 判断是否为文件
BOOL dir = NO;
[mgr fileExistsAtPath:fullSubpath isDirectory:&dir];
if (dir == NO) { // 文件
totalByteSize += [[mgr attributesOfItemAtPath:fullSubpath error:nil][NSFileSize] integerValue];
}
}
return totalByteSize;
} else { // self是一个文件
return [[mgr attributesOfItemAtPath:self error:nil][NSFileSize] integerValue];
}
}
计算Caches文件夹大小
- (void)fileOperation
{
// 文件管理者
NSFileManager *mgr = [NSFileManager defaultManager];
// 缓存路径
NSString *caches = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
HWLog(@"%d", [caches fileSize]);
[mgr removeItemAtPath:caches error:nil];
}
7.1 静态分析
1>检测代码是否有潜在的内存泄露
2>编译器觉得不太合适的代码
3>工具:Analyze
7.2 动态分析
1>检测程序在运行过程中的内存变化
2>工具:Profile
1.Allocations:能看清楚app的内存变化
2.Leaks:能看清楚app在何时产生了内存泄露
注:内存泄露和内存溢出区别
内存泄露:该释放的对象没有被释放;
内存溢出:内存不够用;