献给初学iOS的小盆友们——微博app项目开发之七第一次获取微博数据

这篇博客介绍了如何在iOS app中开发微博应用,包括存储Access Token、授权界面逻辑、获取微博数据、解决数据固定问题以及添加下拉刷新控件。博主详细讲解了每个步骤,从创建YGAccount模型到利用AFNetworking和MJExtension框架处理数据,再到使用SDWebImage加载图片。最后实现了动态加载更多微博的功能。
摘要由CSDN通过智能技术生成

上节课我们已经用request token 换取到了access token,但是经过验证我们发现,每一次输入一样的账号和密码后,获取的access token 都是一样的,也就是我们不是每次都需要获取access token。所以今天我们就讲解下如何存储access token 以及如何用access token 获得打开存储微博数据的神秘盒子。

本节主要内容:

  • 存储access token
  • 授权界面的业务逻辑
  • 获取微博数据
  • 解决数据固定问题
  • 添加刷新控件

本节资料

第七节代码资料

7.1 存储Access Token

到底access token 有什么意义呢?其实它保证了一个应用有权利获取一个用户的数据。因为access token 是和用户绑定在一起的,所以在相同的应用内,输入相同的用户,access token 也是固定的。我们不必每次都向服务器请求一个access token,只需要把其存储起来即可,这里稍微经过思考就知道,可以把获取到的含有access token 的信息转换为模型再存储,这也是我们第一次采用面向模型的思想。
首先依据上节课最后打印出的json数据创建一个YGAccount模型:

/*
 "access_token" = "2.00tpZG4Guufa6C308aae9678ElqtPC";
 "expires_in" = 157679999;
 "remind_in" = 157679999;
 uid = 5838220445;
 */

/**
 *  获取数据的访问命令牌
 */
@property (nonatomic, copy) NSString *access_token;
/**
 *  账号的有效期
 */
@property (nonatomic, copy) NSString *expires_in;
/**
 *  用户唯一标识符
 */
@property (nonatomic, copy) NSString *uid;

/**
 *   过期时间 = 当前保存时间+有效期
 */
@property (nonatomic, strong) NSDate *expires_date;

/**
 *  账号的有效期
 */
@property (nonatomic, copy) NSString *remind_in;

+ (instancetype)accountWithDict:(NSDictionary *)dict;

在YGAccount的实现方法里,就要用到KVC赋值和归档解档了,KVC底层的实现原理就是遍历字典中所有的key,一个个获取到key后,就会去模型里查找有没有setKey方法,如果有就直接调用并赋值。如果没有setKey,就会寻找有没有带下划线的key,如果有就直接拿到并赋值,如果还没有找到,就查找有没有Key的属性,如果有就赋值,最后如果在模型里找不到对应的Key就会报错。 归档解档就需要模型遵守NSCoding协议,代码如下:

#define YGAccountTokenKey @"token"
#define YGUidKey @"uid"
#define YGExpires_inKey @"exoires"
#define YGExpires_dateKey @"date"
+ (instancetype)accountWithDict:(NSDictionary *)dict
{
    YGAccount *account = [[self alloc] init];

    [account setValuesForKeysWithDictionary:dict];

    return account;
}

- (void)setExpires_in:(NSString *)expires_in
{
    _expires_in = expires_in;

    // 计算过期的时间 = 当前时间 + 有效期
    _expires_date = [NSDate dateWithTimeIntervalSinceNow:[expires_in longLongValue]];
}

// 归档的时候调用:告诉系统哪个属性需要归档,如何归档
- (void)encodeWithCoder:(NSCoder *)aCoder
{
    [aCoder encodeObject:_access_token forKey:YGAccountTokenKey];
    [aCoder encodeObject:_expires_in forKey:YGExpires_inKey];
    [aCoder encodeObject:_uid forKey:YGUidKey];
    [aCoder encodeObject:_expires_date forKey:YGExpires_dateKey];
}

// 解档的时候调用:告诉系统哪个属性需要解档,如何解档

- (id)initWithCoder:(NSCoder *)aDecoder
{
    if (self = [super init]) {

        // 一定要记得赋值
        _access_token =  [aDecoder decodeObjectForKey:YGAccountTokenKey];
        _expires_in = [aDecoder decodeObjectForKey:YGExpires_inKey];
        _uid = [aDecoder decodeObjectForKey:YGUidKey];
        _expires_date = [aDecoder decodeObjectForKey:YGExpires_dateKey];
    }
    return self;
}

现在我们有了Account模型,但当我们需要存储和获取account的时候,从哪里获取比较好呢。为了代码和逻辑的清晰,也为了以后维护的方便,我们决定再建一个类,专门处理存储和获取,这样我们只需要在这个类里直接调用合适的方法便可以得到account数据,让其他人不需要关心底层是怎么封装,怎么存储的。你可能现在不太明白我在说什么,但是当你仔细瞅瞅代码后,就知道这个设计方法多优雅美妙了。上代码:

@class YGAccount;

@interface YGAccountTool : NSObject

+ (void)saveAccount:(YGAccount *)account;

+ (YGAccount *)account;

可以看到,我们在YGAccountTool里,只设置里了两个方法,一个是存储account,一个是直接获取account ,如果以后直接就调用这两个方法,岂不是特别的方便,执行代码如下:

#define YGAccountFileName [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0] stringByAppendingPathComponent:@"account.data"]

// 类方法一般用静态变量代替成员属性

static YGAccount *_account;
+ (void)saveAccount:(YGAccount *)account
{
    [NSKeyedArchiver archiveRootObject:account toFile:YGAccountFileName];
}

+ (YGAccount *)account
{
    if (_account == nil) {

        _account = [NSKeyedUnarchiver unarchiveObjectWithFile:YGAccountFileName];

        // 判断下账号是否过期,如果过期直接返回Nil
        // 2015 < 2017
        if ([[NSDate date] compare:_account.expires_date] != NSOrderedAscending) { // 过期
            return nil;
        }

    }
    return _account;
}

所以下面我们就可以直接利用刚创建的类,在得到请求成功返回的数据后,把其转为account模型,并存储啦,代码如下:

        // 字典转模型
        YGAccount *account = [YGAccount accountWithDict:responseObject];
        // 保存账号信息:
        // 数据存储一般我们开发中会搞一个业务类,专门处理数据的存储
        // 以后我不想归档,用数据库,直接改业务类
        [YGAccountTool saveAccount:account];

7.2 授权界面的业务逻辑

亲们自己分析下就知道,微博应用应该首先进入授权界面,因为没有授权就没法显示东西,然后再进入新特性界面。但是如果我们直接把代码加到AppDelegate会发现,代码越来越多,越来越难找到,所以我们专门新建一个工具类,用来处理新特性界面的业务逻辑,也就是给代码搬搬家,使整个项目变得更整洁点。代码搬家的活,就不在这写出来了,请大家参考资料代码的YGRootVcTool类。AppDelegate 内的业务逻辑则变为:

// 判断下有没有授权
    if ([YGAccountTool account]) { // 已经授权
        // 选择根控制器
        [YGRootVcTool chooseRootViewController:self.window];

    }else{ // 进行授权
        YGOAuthViewController *oauthVc = [[YGOAuthViewController alloc] init];

        // 设置窗口的根控制器
        self.window.rootViewController = oauthVc;

    }

最后在AFNpost请求成功回调的方法内,添加如下一行代码,表示当我们存储完account数据后,便可以展示新特性界面或者这界面了。

// 进入主页或者新特性,选择窗口的根控制器
[YGRootVcTool chooseRootViewController:YGKeyWindow];

这样的代码是不是感觉很清爽,以后我们会创建其他的工具类来封装其他的代码,慢慢往下看吧。

7.3 获取微博数据

获取微博数据就需要再次查看微博API文档,文档内清楚的写明请求方法,请求所需参数,以及请求后返回的数据。当我们分析了其返回数据后,发现我们需要再次依据返回信息,创建合适的模型来接受数据。我们这里并不把所有的请求数据都存储到模型,只需要选择那些展示数据必要的参数即可。这里我们需要导入新的外部框架MJExtension,来给我们处理JSON数据转模型。截取的参数为:

//created_at    string  微博创建时间
//idstr string  字符串型的微博ID
//text  string  微博信息内容
//source    string  微博来源
//user  object  微博作者的用户信息字段 详细
//retweeted_status  object  被转发的原微博信息字段,当该微博为转发微博时返回 
//reposts_count int 转发数
//comments_count    int 评论数
//attitudes_count       int 表态数
//pic_urls  配图数组

我们创建的三个类来保存以上信息,因为user是个字典,且pic_urls是个数组,里面存放的是字典,需要新建它们各自的模型来存储。如下图

这里写图片描述

然后,我们转到YGHomeViewController执行文件内,创建一个名为loadNewStatus来请求最新的微博,代码如下:

#pragma mark - 请求最新的微博
- (void)loadNewStatus
{
    // 创建请求管理者
    AFHTTPRequestOperationManager *mgr = [AFHTTPRequestOperationManager manager];

    // 创建一个参数字典
    NSMutableDictionary *params = [NSMutableDictionary dictionary];

    params[@"access_token"] = [YGAccountTool account].access_token;

    // 发送get请求
    [mgr GET:@"https://api.weibo.com/2/statuses/friends_timeline.json" parameters:params success:^(AFHTTPRequestOperation *operation, id responseObject) { // 请求成功的时候调用

        NSLog(@"%@",responseObject);
        // 获取到微博数据 转换成模型
        // 获取微博字典数组

        NSArray *dictArr = responseObject[@"statuses"];
        // 把字典数组转换成模型数组
        NSArray *statuses = (NSMutableArray *)[YGStatus mj_objectArrayWithKeyValuesArray:dictArr];
        //保存模型数组
        [self.statuses addObjectsFromArray:statuses];
        // 刷新表格
        [self.tableView reloadData];

    } failure:^(AFHTTPRequestOperation *operation, NSError *error) {

    }];
}

可以看到我们在代码中分五步走:
第一 首先创建号GET请求所需要的参数字典params,
第二 在get请求成功后的回调函数内,把获取到的statuses 的JSON数组转化为NSArray,得到的是含有一个个字典的数组,
第三 再把字典数组通过MJExtension框架内的方法转化为含有YGStatuses模型的数组,
第四 然后把得到的微博模型数组保存到对象的数组实例中,
第五 最后刷新表格。
为了能把获取到的数据初步的展示出来,我们要设置tableView 的数据源代理如下:

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return self.statuses.count;
}


- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {

    static NSString *ID =@"cell";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];

    // Configure the cell...
    if (cell ==nil) {
        cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:ID];
    }
    YGStatus *status =self.statuses[indexPath.row];
    cell.textLabel.text =status.user.name;
    [cell.imageView sd_setImageWithURL:status.user.profile_image_url placeholderImage:[UIImage imageNamed:@"timeline_image_placeholder"]];
    cell.detailTextLabel.text = status.text;
    return cell;
}

可以看到,以上代码里就需要导入一个新的专门处理图片下载的外部框架SDWebImage。这个框架有很多的优点,例如它是异步下载,不会卡死主线程,而且它内部做了数据缓存,不会重复下载同样的图片,节省用户流量,解决了循环利用的问题,不会让图片错乱。关于这个框架,如果你想深入学习,请百度或者谷歌吧,或者我以后也会出专题,好好讲一下各种框架的使用和优缺点。如果你此时运行项目就会发现,我们终于可以看到微博数据了。如下图:
这里写图片描述

如果你按部就班把代码敲到此时,是不是感觉有点激动呢?更激动更有趣的还在后面呢。

7.4 解决数据固定问题

可以发现,以上代码生成的微博数据只有二十条,而且不论怎么拖动都不会更新数据。怎么才能加载更多的数据呢?经过分析我们发现,每条微博都有一个idstr,且微博越新,idstr越大,所以要想加载更新的数据,解决一下两个问题:
1.更改代码,可以加载比以前获得微博的idstr更大的微博。
2.添加下拉刷新控件,下拉的时候刷新更多微博

我们首先更改loadNewStatus方法如下:

#pragma mark - 请求最新的微博
- (void)loadNewStatus
{
    // 创建请求管理者
    AFHTTPRequestOperationManager *mgr = [AFHTTPRequestOperationManager manager];

    // 创建一个参数字典
    NSMutableDictionary *params = [NSMutableDictionary dictionary];
    if (self.statuses.count) {
        params[@"since_id"] = [self.statuses[0] idstr];
    }

    params[@"access_token"] = [YGAccountTool account].access_token;

    // 发送get请求
    [mgr GET:@"https://api.weibo.com/2/statuses/friends_timeline.json" parameters:params success:^(AFHTTPRequestOperation *operation, id responseObject) { // 请求成功的时候调用

        NSLog(@"%@",responseObject);
        // 获取到微博数据 转换成模型
        // 获取微博字典数组

        NSArray *dictArr = responseObject[@"statuses"];
        // 把字典数组转换成模型数组
        NSArray *statuses = (NSMutableArray *)[YGStatus mj_objectArrayWithKeyValuesArray:dictArr];

        NSIndexSet *indexSet = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, statuses.count)];
        // 把最新的微博数插入到最前面
        [self.statuses insertObjects:statuses atIndexes:indexSet];

        // 刷新表格
        [self.tableView reloadData];

    } failure:^(AFHTTPRequestOperation *operation, NSError *error) {

    }];
}

请注意我们对代码做了怎么样的改动:首先我们在加载新数据前要判断是否已经有了微博,如果有我们就获取最前面微博的idstr赋给params参数,这样我们就可以请求比之前微博更新的微博了。然后在获取到新微博后,把新微博插入到微博数组的最前面,这样每次运行时都会刷新显示最新的微博。

7.5 添加刷新控件

可以发现以上只能每运行一次才能刷新一次微博,这岂不是很蛋疼,所以我们要搞一个下拉刷新控件,只需要拖拽下屏幕就可以刷新微博啦。这里就有需要导入一个叫MJRefresh的外部框架来专门给我们添加各种刷新控件。现在view DidLoad方法如下:

//添加下拉刷新控件,请求最新微博数据
     self.tableView.mj_header = [MJRefreshNormalHeader headerWithRefreshingTarget:self refreshingAction:@selector(loadNewStatus)];
    [self.tableView.mj_header beginRefreshing];

 //添加上拉刷新旧微博控件
    self.tableView.mj_footer = [MJRefreshAutoNormalFooter footerWithRefreshingTarget:self refreshingAction:@selector(loadMoreStatus)];

可见我们不仅仅增加了下拉刷新,也添加了上拉刷新,其实原理时一样的,只不过上拉刷新用的是maxid参数。loadMoreStatus代码如下:

#pragma mark - 请求更多旧的微博
- (void)loadMoreStatus
{
    // 创建请求管理者
    AFHTTPRequestOperationManager *mgr = [AFHTTPRequestOperationManager manager];
    // 创建一个参数字典
    NSMutableDictionary *params = [NSMutableDictionary dictionary];
    if (self.statuses.count) { // 有微博数据,才需要下拉刷新
        long long maxId = [[[self.statuses lastObject] idstr] longLongValue] - 1;
        params[@"max_id"] = [NSString stringWithFormat:@"%lld",maxId];
    }
    params[@"access_token"] = [YGAccountTool account].access_token;

    // 发送get请求
    [mgr GET:@"https://api.weibo.com/2/statuses/friends_timeline.json" parameters:params success:^(AFHTTPRequestOperation *operation, id responseObject) { // 请求成功的时候调用

        // 结束上拉刷新
        [self.tableView.mj_footer endRefreshing];

        // 获取到微博数据 转换成模型
        // 获取微博字典数组
        NSArray *dictArr = responseObject[@"statuses"];
        // 把字典数组转换成模型数组
        NSArray *statuses = (NSMutableArray *)[YGStatus mj_objectArrayWithKeyValuesArray:dictArr];

        // 把数组中的元素添加进去
        [self.statuses addObjectsFromArray:statuses];

        // 刷新表格
        [self.tableView reloadData];

    } failure:^(AFHTTPRequestOperation *operation, NSError *error) {

    }]; 
}

注意在loadNewStatus内别忘了添加结束刷新代码,不然他就一直不停的显示正在刷新:

//结束下拉刷新
[self.tableView.mj_header endRefreshing];

这样我们再次运行项目就可以发现,更新微博和加载就微博都完美实现了。

今天的代码有点略多,但是总结起来中心思想就是建立模型,字典转换模型以及设计合适的工具类简化代码。下节课我们会封装优化我们的代码,让以后的开发更方便。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值