申请开发者
想要拉到到新浪微博的数据,首先让自己成为开发者。申请成为开发者账号很简单,只要有新浪微博的账号即可。
在开发的过程中,我们需要拿到几下几个值:
AppKey :分配给每个第三方应用的 app key。用于鉴权身份,显示来源等功能。
AppSecret :分配给每个第三方应用的 app 私钥。
RedirectURI:应用回调页面,可在新浪微博开放平台->我的应用->应用信息->高级应用->授权设置->应用回调页中找到。
为了方便测试,除了可以使用当前开发者账户外,还可以自己添加15个关联测试账号。
开发实现
授权相关文档:http://open.weibo.com/wiki/%E6%8E%88%E6%9D%83%E6%9C%BA%E5%88%B6
实现思路:
- 拼接参数,请求OAuth2/authorize ,拿到授权Code值。
- 通过授权code值及相关申请的参数,拿到用户授权UID及过期时间
- 将返回的用户信息存入沙盒里
- 下次打开应用时,先从沙盒里获取,获取为nil时再请求网络
导入相关第三方库
直接在cocoapods里导入以下的类库
- 网络请求:AFNetworking
- 吐丝:MBProgressHUD
platform :ios,‘7.0‘ pod "AFNetworking","2.5.4" pod ‘MBProgressHUD‘, ‘~> 0.9.1‘
由于在很多地方都会使用到AFNetworking,为了不让该第三方库类“污染”,特意给AFNetworking封装一层,方便以后切换第三方网络请求类,同时也方便引用管理。
HttpTool.h
#import <Foundation/Foundation.h> @interface HttpTool : NSObject + (void)get:(NSString *)url params:(NSDictionary *)params success:(void (^)(id json))success failure:(void (^)(NSError *error))failure; + (void)post:(NSString *)url params:(NSDictionary *)params success:(void (^)(id json))success failure:(void (^)(NSError *error))failure; @end
HttpTool.m
#import "HttpTool.h" #import "AFNetworking.h" @implementation HttpTool + (void)get:(NSString *)url params:(NSDictionary *)params success:(void (^)(id json))success failure:(void (^)(NSError *error))failure { // 1.创建请求管理者 AFHTTPRequestOperationManager *mgr = [AFHTTPRequestOperationManager manager]; mgr.responseSerializer.acceptableContentTypes = [NSSet setWithObjects:@"application/json", @"text/html", @"text/json", @"text/javascript",@"text/plain", nil]; // 2.发送请求 [mgr GET:url parameters:params success:^(AFHTTPRequestOperation *operation, id responseObject) { if (success) { success(responseObject); } } failure:^(AFHTTPRequestOperation *operation, NSError *error) { if (failure) { failure(error); } }]; } + (void)post:(NSString *)url params:(NSDictionary *)params success:(void (^)(id json))success failure:(void (^)(NSError *error))failure { // 1.创建请求管理者 AFHTTPRequestOperationManager *mgr = [AFHTTPRequestOperationManager manager]; mgr.responseSerializer.acceptableContentTypes = [NSSet setWithObjects:@"application/json", @"text/html", @"text/json", @"text/javascript",@"text/plain", nil]; // 2.发送请求 [mgr POST:url parameters:params success:^(AFHTTPRequestOperation *operation, id responseObject) { if (success) { success(responseObject); } } failure:^(AFHTTPRequestOperation *operation, NSError *error) { if (failure) { failure(error); } }]; } @end
定义账户常量
申请下来的账号信息,我们使用常用来存储,当然,一些常用的不变的通用字符串,我们也统一放在常量里,需要避免使用宏。可以参考:http://www.jianshu.com/p/f83335e036b5
Const.h
#import <Foundation/Foundation.h> extern NSString * const AppKey; extern NSString * const RedirectURI; extern NSString * const AppSecret;
Const.m
#import "Const.h" // 账号信息 NSString * const AppKey = @"762197719"; NSString * const RedirectURI = @"http://www.baidu.com"; NSString * const AppSecret = @"89d739c387e3a69aaad0270da66c02ff";
归档access_token工具类
由于access_token不经常变,我们需要将拿到的access_token归档存起来,每次登录的时候,先判断access_token是否有效(存在且不过期)。
归档使用模型,需要实现NSCoding协议,定义模型
Account.h
#import <Foundation/Foundation.h> @interface Account : NSObject<NSCoding> /** string 用于调用access_token,接口获取授权后的access token。*/ @property (nonatomic, copy) NSString *access_token; /** string access_token的生命周期,单位是秒数。*/ @property (nonatomic, copy) NSNumber *expires_in; /** string 当前授权用户的UID。*/ @property (nonatomic, copy) NSString *uid; /** access token的创建时间 */ @property (nonatomic, strong) NSDate *created_time; /** 用户的昵称 */ @property (nonatomic, copy) NSString *name; + (instancetype)accountWithDict:(NSDictionary *)dict; @end
Account.m
#import "Account.h" @implementation Account +(instancetype)accountWithDict:(NSDictionary *)dict { Account *account = [[self alloc] init]; account.access_token = dict[@"access_token"]; account.uid = dict[@"uid"]; account.expires_in = dict[@"expires_in"]; // 获得账号存储的时间(accessToken的产生时间) account.created_time = [NSDate date]; return account; } /** * 当一个对象要归档进沙盒中时,就会调用这个方法 * 目的:在这个方法中说明这个对象的哪些属性要存进沙盒 */ - (void)encodeWithCoder:(NSCoder *)encoder { [encoder encodeObject:self.access_token forKey:@"access_token"]; [encoder encodeObject:self.expires_in forKey:@"expires_in"]; [encoder encodeObject:self.uid forKey:@"uid"]; [encoder encodeObject:self.created_time forKey:@"created_time"]; [encoder encodeObject:self.name forKey:@"name"]; } /** * 当从沙盒中解档一个对象时(从沙盒中加载一个对象时),就会调用这个方法 * 目的:在这个方法中说明沙盒中的属性该怎么解析(需要取出哪些属性) */ - (id)initWithCoder:(NSCoder *)decoder { if (self = [super init]) { self.access_token = [decoder decodeObjectForKey:@"access_token"]; self.expires_in = [decoder decodeObjectForKey:@"expires_in"]; self.uid = [decoder decodeObjectForKey:@"uid"]; self.created_time = [decoder decodeObjectForKey:@"created_time"]; self.name = [decoder decodeObjectForKey:@"name"]; } return self; } @end
为了方便直接归档和从归档里读取模型,定义一个AccountTool工具类
AccountTool.h
#import <Foundation/Foundation.h> #import "Account.h" @interface AccountTool : NSObject /** * 存储账号信息 * * @param account 账号模型 */ + (void)saveAccount:(Account *)account; /** * 返回账号信息 * * @return 账号模型(如果账号过期,返回nil) */ + (Account *)getAccount; @end
AccountTool.m
// // AccountTool.m // Weibo // // Created by jiangys on 15/10/17. // Copyright © 2015年 Jiangys. All rights reserved. // #import "AccountTool.h" #import "Account.h" // 账号的存储路径 #define AccountPath [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:@"account.archive"] @implementation AccountTool /** * 存储账号信息 * * @param account 账号模型 */ + (void)saveAccount:(Account *)account { // 自定义对象的存储必须用NSKeyedArchiver,不再有什么writeToFile方法 [NSKeyedArchiver archiveRootObject:account toFile:AccountPath]; } /** * 返回账号信息 * * @return 账号模型(如果账号过期,返回nil) */ + (Account *)getAccount { // 加载模型 Account *account = [NSKeyedUnarchiver unarchiveObjectWithFile:AccountPath]; /* 验证账号是否过期 */ // 过期的秒数 long long expires_in = [account.expires_in longLongValue]; // 获得过期时间 NSDate *expiresTime = [account.created_time dateByAddingTimeInterval:expires_in]; // 获得当前时间 NSDate *now = [NSDate date]; // 如果expiresTime <= now,过期 /** NSOrderedAscending = -1L, 升序,右边 > 左边 NSOrderedSame, 一样 NSOrderedDescending 降序,右边 < 左边 */ NSComparisonResult result = [expiresTime compare:now]; if (result != NSOrderedDescending) { // 过期 return nil; } return account; } @end
获取并存储access_token
控制器需要调用authorize获得的code值,再通过code值获取access_token
OAuthViewController.h
#import <UIKit/UIKit.h> @interface OAuthViewController : UIViewController @end
OAuthViewController.m
// // OAuthViewController.m // Weibo // // Created by jiangys on 15/10/17. // Copyright © 2015年 Jiangys. All rights reserved. // #import "OAuthViewController.h" #import "Const.h" #import "HttpTool.h" #import "MBProgressHUD+YS.h" #import "Account.h" #import "AccountTool.h" @interface OAuthViewController() <UIWebViewDelegate> @end @implementation OAuthViewController - (void)viewDidLoad { [super viewDidLoad]; // 1.创建一个webView UIWebView *webView=[[UIWebView alloc] init]; webView.frame=self.view.bounds; webView.delegate=self; [self.view addSubview:webView]; // 2.用webView加载新浪登录页面 NSString *urlStr=[NSString stringWithFormat:@"https://api.weibo.com/oauth2/authorize?client_id=%@&redirect_uri=%@",AppKey,RedirectURI]; NSURLRequest *requestUrl=[NSURLRequest requestWithURL:[NSURL URLWithString:urlStr]]; [webView loadRequest:requestUrl]; } #pragma mark - webView代理方法 -(void)webViewDidStartLoad:(UIWebView *)webView { [MBProgressHUD showMessage:@"正在加载..."]; } -(void)webViewDidFinishLoad:(UIWebView *)webView { [MBProgressHUD hideHUD]; } -(void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error { [MBProgressHUD hideHUD]; } /** * 监听所有跳转方法 */ -(BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType { NSString *url=request.URL.absoluteString; NSRange range=[url rangeOfString:@"code="]; if (range.length != 0) { // 截取code=后面的参数值 NSUInteger fromIndex=NSMaxRange(range); NSString *code=[url substringFromIndex:fromIndex]; // 利用code换取一个accessToken [self accessTokenWithCode:code]; // 禁止加载回调地址 return NO; } return true; } /** * 获取token * * @param code 授权码 */ - (void)accessTokenWithCode:(NSString *)code { NSMutableDictionary *params=[NSMutableDictionary dictionary]; params[@"client_id"] = AppKey; params[@"client_secret"] = AppSecret; params[@"grant_type"] = @"authorization_code"; params[@"redirect_uri"] = RedirectURI; params[@"code"] = code; [HttpTool post:@"https://api.weibo.com/oauth2/access_token" params:params success:^(id json) { [MBProgressHUD hideHUD]; // 将返回的账号字典数据 --> 模型,存进沙盒 Account *account = [Account accountWithDict:json]; // 存储账号信息 [AccountTool saveAccount:account]; // 切换窗口的根控制器 UIWindow *window = [UIApplication sharedApplication].keyWindow; [window switchRootViewController]; }failure:^(NSError *error) { [MBProgressHUD hideHUD]; YSLog(@"--MBProgressHUD_error--%@",error); }]; } @end
登录验证access_token
每次登录的时候,都需要判断access_token是否有效(存在且不过期)
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { self.window=[[UIWindow alloc]init]; self.window.frame=[UIScreen mainScreen].bounds; // 2.设置根控制器 Account *account = [AccountTool getAccount]; if (account) { // 之前已经登录成功过 [self.window switchRootViewController]; } else { self.window.rootViewController = [[OAuthViewController alloc] init]; } [self.window makeKeyAndVisible]; return YES; }
章节源代码下载:http://pan.baidu.com/s/1sjmoEw1
新浪微博Github:https://github.com/jiangys/Weibo