这次是我第一次写的项目总结,虽然这只是一个小项目,但确实是获益良多。虽然说是独立完成,但其实在做的过程中,也有和大家交流了很多意见。尽管如此,我对这个尚不能算写好的项目,还是有非常多的不满意。不过碍于能力有限,不满意的地方也就先将就将就。日后有兴趣的话,肯定会再次改进,完善得更好。
项目的主要内容就是用Xcode写出一个具有基本功能的iPhone新浪微博客户端,其中必不可少地需要调用新浪微博应用的API,途中遇到的“难题”也不少,不过大部分都算解决了。
一、获取用户Access Token
一开始的第一个麻烦,由于微博使用OAuth2.0的授权方式,主要流程是去指定的网页,在用户输入账号和密码授权后,获取回调网页地址上的Access Token。往后对API的调用,全部都是通过Access Token来进行身份认证,一个Access Token对应一个应用和一个用户,都是唯一的。具体新建微博应用的过程,我也不多说,主要就对调用API获取Access Token进行说明。
http://open.weibo.com/wiki/Oauth2 这里已经有基本的说明,我主要调用的是“Javascript Client的验证授权”,因为返回的地址里已经包含了Access Token,不需要二次调用。我在UIViewController上面拉了一个UIWebView,UIWebView用了Outlet。
代码如下:
1 - (void) viewDidAppear:(BOOL)animated 2 { 3 NSString *urlStr = [[NSString alloc] initWithFormat:@"%@?client_id=%@&redirect_uri=%@&response_type=token&display=%@", AuthorizeUrl, AppKey, RedirectUri, DisplayMode]; 4 5 NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:urlStr]]; 6 [accessWebView loadRequest:request]; 7 accessWebView.delegate = self; 8 }
其中AuthorizeUrl为
#define AuthorizeUrl @"https://api.weibo.com/oauth2/authorize"
AppKey可以在“应用信息”找到,不过RedirectUri必须和自己在应用中填写的网址一样,否则会出错。
然后,等用户授权完,就要判断网址是否有我们需要的Access Token,有的话再进行下一步。这里就要用到<UIWebViewDelegate>协议,来使用方法webViewDidFinishLoad,
1 - (void)webViewDidFinishLoad:(UIWebView *)webView 2 { 3 NSURL *url = [webView.request URL]; 4 5 if ([url.lastPathComponent isEqualToString:CmpUrl]) { 6 tokenData *tmp = [self.userToken initWithUrl:url]; 7 if (tmp != nil) { 8 self.userToken = tmp; 9 [self.userToken saveToken]; 10 [self performSegueWithIdentifier:@"FirstView" sender:nil]; 11 } else { 12 [webView goBack]; 13 } 14 }
我在这里封装了几个方法,saveToken是用plist进行保存token的方法,用于后续登陆免输密码,这里先跳过。主要是[url.lastPathComponent isEqualToString:CmpUrl],由于请求用户授权时,加载完成后也会调用webViewDidFinishLoad,所以必须判断Access Token出现了没有。如果去到了回调网址“https://api.weibo.com/oauth2/default.html”,则url.lastPathComponent为“default.html”。根据这点再提取Access Token,具体方法就是用NSString的方法去提取URL里Access Token的位置。不过我也做了一个错误处理,以防用户按了“取消”后,程序就“无处可点”。所以如果页面网址类似“https://api.weibo.com/oauth2/default.html#error_uri=%2Foauth2%2Fauthorize”,就重新返回授权的页面。(好吧,我承认我有点猥琐)
具体initWithUrl的方法
1 - (id) initWithUrl:(NSURL *)url 2 { 3 if (self = [super init]) { 4 NSString *urlStr = [url absoluteString]; 5 NSArray *strarr = [urlStr componentsSeparatedByString:@"&"]; 6 7 if ([[strarr objectAtIndex:0] isEqualToString:ErrUrl]) { 8 NSLog(@"Error!!\n"); 9 return nil; 10 } else { 11 NSString *tmpToken = [[strarr objectAtIndex:0] substringFromIndex:55]; 12 NSString *tmpUid = [[strarr objectAtIndex:3] substringFromIndex:4]; 13 14 NSDictionary *tmpDic = [weiboUrl weiboAPIUserShowWithToken:tmpToken andUid:tmpUid OrScreenName:nil]; 15 16 self.token = tmpToken; 17 self.uid = tmpUid; 18 self.userName = [tmpDic objectForKey:@"name"]; 19 } 20 } 21 22 return self; 23 }
新浪微博的API又被我封装到另一个类weiboUrl中,其中weiboAPIUserShowWithToken: andUid: OrScreenName:就是返回用户个人资料字典的一个类方法。
1 + (NSDictionary *)weiboAPIUserShowWithToken:(NSString *)token andUid:(NSString *)uid OrScreenName:(NSString *)screenname 2 { 3 NSError *error; 4 NSString *urlstr; 5 if (!screenname) 6 urlstr = [NSString stringWithFormat:@"%@?access_token=%@&uid=%@", UserShow, token, uid]; 7 else 8 urlstr = [NSString stringWithFormat:@"%@?access_token=%@&screen_name=%@", UserShow, token, [screenname stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]; 9 10 NSURL *url = [[NSURL alloc]initWithString:urlstr]; 11 NSData* data = [NSData dataWithContentsOfURL: url]; 12 NSDictionary *returnDic = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&error]; 13 14 return returnDic; 15 }
由于新浪微博返回的都是JSON数据,所以可以直接用NSJSONSerialization的方法进行编码。
二、调用API和处理数据
微博API众多,如果每次都要打一次API,查错的时候必定非常麻烦。所以我就封装了几个常用的API。
1 //以下是封装WeiboAPI的方法 2 3 //根据用户ID获取用户信息,返回NSDictionary类型的数据 4 + (NSDictionary *) weiboAPIUserShowWithToken:(NSString *)token andUid:(NSString *)uid OrScreenName:(NSString *)screenname; 5 6 //获取当前登录用户及其所关注用户的最新微博,返回其微博的NSMutableArray数组,可指定参数since_id, max_id, count,对应即比since_id时间晚的微博, 7 //小于或等于max_id的微博,单页返回的记录条数 8 + (NSMutableArray *) weiboAPIHomeTimeLineWithToken:(NSString *)token andSinceid:(NSString *)sinceid andMaxid:(NSString *)maxid andCount:(NSString *)count; 9 10 //测试status的汉字数是否超过140个 11 + (BOOL) testStatusLengthWithString:(NSString *)sourceString; 12 13 //发布一条新微博 14 + (NSDictionary *) weiboAPIUpdateStatusesWithToken:(NSString *)token andStatus:(NSString *)status andLat:(float)latitude andLong:(float)longtitude; 15 16 //对一条微博进行评论 17 + (NSDictionary *) weiboAPICreateCommentsWithToken:(NSString *)token andComment:(NSString *)comment andID:(NSString *)ID andCommentori:(int)comment_ori; 18 19 //转发一条微博 20 + (NSDictionary *) weiboAPIRepostStatusesWithToken:(NSString *)token andStatus:(NSString *)status andID:(NSString *)ID andIscomment:(int)is_comment; 21 22 //添加一条微博到收藏里 23 + (NSDictionary *) weiboAPICreateFavouritesWithToken:(NSString *)token andID:(NSString *)ID; 24 25 //根据微博ID返回某条微博的评论列表 26 + (NSMutableArray *) weiboAPICommentsShowWithToken:(NSString *)token andSinceid:(NSString *)sinceid andMaxid:(NSString *)maxid andCount:(NSString *)count andID:(NSString *)ID; 27 28 //获取当前登录用户所接收到的评论列表 29 + (NSMutableArray *) weiboAPICommentsToMeWithToken:(NSString *)token andSinceid:(NSString *)sinceid andMaxid:(NSString *)maxid andCount:(NSString *)count; 30 31 //获取最新的提到登录用户的微博列表,即@我的微博 32 + (NSMutableArray *) weiboAPIStatusesMentionsWithToken:(NSString *)token andSinceid:(NSString *)sinceid andMaxid:(NSString *)maxid andCount:(NSString *)count; 33 34 //回复一条评论 35 + (NSDictionary *) weiboAPICommentsReplyWithToken:(NSString *)token andComment:(NSString *)comment andID:(NSString *)ID andCID:(NSString *)CID; 36 37 //获取当前登录用户的收藏列表 38 + (NSMutableArray *) weiboAPIFavouritesWithToken:(NSString *)token andCount:(int)count andPage:(int)page;
不过,这里不得说一下,说来惭愧,我有很多的API都未严格按照新浪给出的类型封装,很多都直接使用NSString类型。由于赶时间所以这么做,但其实严格按照类型封装会更好,我后面还会提到为什么这样说。
封装好API以后,每次就不用复制、粘贴一大段新浪API,而且可读性相对好了点。不过由于,这类API我都只是对数据进行了一点“粗加工”,使用起来还不方便(因为返回的要么是NSDictionary类型,要么就是NSMutableArray,里面也是NSDictionary)。所以还要再将数据再封装一次,说到这里,我又惭愧了,因为我将评论和微博等都全封装成同一个类型。如果科学点,其实也是要分开的。我封装的类大概有这些属性
1 @property (strong, nonatomic) NSString *ID; 2 @property (strong, nonatomic) NSString *SID; 3 @property (strong, nonatomic) NSString *text; 4 @property (strong, nonatomic) NSString *source; 5 @property (strong, nonatomic) NSString *time; 6 @property (strong, nonatomic) NSString *userName; 7 8 @property (strong, nonatomic) NSURL *originalPicURL; 9 @property (strong, nonatomic) NSURL *bmiddlePicURL; 10 @property (strong, nonatomic) NSURL *profileImageURL; 11 @property (strong, nonatomic) NSURL *largeImageURL; 12 13 @property (strong, nonatomic) UIImage *profileImage; 14 @property (strong, nonatomic) UIImage *largeImage; 15 @property (strong, nonatomic) UIImage *bmiddleImage; 16 17 @property (strong, nonatomic) NSString *retweetName; 18 @property (strong, nonatomic) NSString *retweetText;
里面还封装了用字典初始化属性各个值的方法
+ (weiboCellData *) weiboCellDataWithSourceDictionary: (NSDictionary *)sourceDictionary
最后再新建一个NSMutableArray的类别来生成全部weiCellData类型的数组,这个就不多说了。
下一步在首页的ControllerviewDidLoad方法
self.cellsDataArray = [NSMutableArray weiboCellDataArrayWithSourceArray:[weiboUrl weiboAPIHomeTimeLineWithToken:self.userToken.token andSinceid:@"0" andMaxid:@"0" andCount:@"50"]];
这样,tableView所有Cell的内容就都已经有了,接下来就是处理Cell内容的排列。由于微博博文长短不一,所以高度必须要为每一个Cell进行特别的设置。
1 static NSString *CellIdentifier = @"weiboCell"; 2 3 UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier]; 4 if (cell == nil) { 5 cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier]; 6 } 7 8 9 weiboCellData *myCell = [self.cellsDataArray objectAtIndex:indexPath.row]; 10 UILabel *cellName = (UILabel *)[cell viewWithTag:1]; 11 UILabel *cellText = (UILabel *)[cell viewWithTag:2]; 12 UILabel *cellRetweet = (UILabel *)[cell viewWithTag:3]; 13 UIImageView *cellImage = (UIImageView *)[cell viewWithTag:4]; 14 15 16 cellName.text = myCell.userName; 17 cellText.text = myCell.text; 18 19 //设置原文的label高度 20 CGSize constraint = CGSizeMake(cellText.frame.size.width, 20000.0f); 21 CGSize textSize = [cellText.text sizeWithFont:[UIFont systemFontOfSize:14.0] constrainedToSize:constraint lineBreakMode:UILineBreakModeWordWrap]; 22 [cellText setFrame:CGRectMake(cellText.frame.origin.x, cellText.frame.origin.y, cellText.frame.size.width, textSize.height)]; 23 24 25 if (myCell.retweetText != nil) { 26 cellRetweet.text = [NSString stringWithFormat:@"%@:%@", myCell.retweetName, myCell.retweetText]; 27 //设置转发文的高度 28 constraint = CGSizeMake(cellRetweet.frame.size.width, 20000.0f); 29 CGSize retweetSize = [cellRetweet.text sizeWithFont:[UIFont systemFontOfSize:14.0] constrainedToSize:constraint lineBreakMode:UILineBreakModeWordWrap]; 30 [cellRetweet setFrame:CGRectMake(cellRetweet.frame.origin.x, cellText.frame.origin.y + textSize.height + frameGap, cellRetweet.frame.size.width, retweetSize.height)]; 31 } else 32 [cellRetweet setFrame:CGRectMake(cellRetweet.frame.origin.x, cellText.frame.origin.y + textSize.height + frameGap, cellRetweet.frame.size.width, 0)];
其中frameGap = 5.0,textSize主要为获取一个系统字体为14的合适label大小,再通过setFrame,就可以设置好原文的label。下面转发文的原理基本一样,不过需要判断一下是否有转发和修改一下origin。另外还要重载tableView的一个方法,
1 - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath 2 { 3 static NSString *CellIdentifier = @"weiboCell"; 4 5 UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier]; 6 if (cell == nil) { 7 cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier]; 8 } 9 10 UILabel *cellText = (UILabel *)[cell viewWithTag:2]; 11 UILabel *cellRetweet = (UILabel *)[cell viewWithTag:3]; 12 weiboCellData *myCell = [self.cellsDataArray objectAtIndex:indexPath.row]; 13 14 CGSize constraint = CGSizeMake(cellText.frame.size.width, 20000.0f); 15 CGSize textSize = [myCell.text sizeWithFont:[UIFont systemFontOfSize:14.0] constrainedToSize:constraint lineBreakMode:UILineBreakModeWordWrap]; 16 17 cellRetweet.text = [NSString stringWithFormat:@"%@:%@", myCell.retweetName, myCell.retweetText]; 18 constraint = CGSizeMake(cellRetweet.frame.size.width, 20000.0f); 19 CGSize retweetSize = [cellRetweet.text sizeWithFont:[UIFont systemFontOfSize:14.0] constrainedToSize:constraint lineBreakMode:UILineBreakModeWordWrap]; 20 21 if (myCell.retweetText != nil) 22 return (43.0 + textSize.height + 3 * frameGap + retweetSize.height); 23 else 24 return (43.0 + textSize.height + 2 * frameGap); 25 }
这段也不需要解释,和之前差不多,这种写法的好处是可以先在storyboard设置好custom的Cell调整好位置,最后才在代码匹配高度。就算以后改变Cell的位置,代码也基本不用改,在storyboard那里改动就好。原本我想直接在- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath设置Cell的大小,不过都失败了。还没有知道什么原因,知道的话,能告诉我一下么。
至于cellImage就用GCD异步下载,我也并不是很熟悉其中的用法。而且我的程序刚开始也出现了一个关键的问题,当滑动速度非常快的时候,头像会出现闪动和错误,不过这个问题总算解决了。
1 dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); 2 3 if (myCell.profileImage) { 4 cellImage.image = myCell.profileImage; 5 } else if ([self.imageDic objectForKey:myCell.userName]) { 6 cellImage.image = [self.imageDic objectForKey:myCell.userName]; 7 myCell.profileImage = cellImage.image; 8 } else { 9 dispatch_async(concurrentQueue, ^{ 10 11 dispatch_sync(concurrentQueue, ^{ 12 // for (int i = 0; i < 100000000; i++);/*模拟长时间下载*/ 13 [myCell downloadProfileImage]; 14 if (myCell.profileImage) 15 [self.imageDic setObject:myCell.profileImage forKey:myCell.userName]; 16 }); 17 18 dispatch_sync(dispatch_get_main_queue(), ^{ 19 cellImage.image = [self.imageDic objectForKey:cellName.text]; 20 }); 21 }); 22 }
由于我不想每次都使用字典这种比较耗时的操作,所以尽可能使用属性来读取头像。具体思路很简单,就是先查看Cell对应的数组属性profileImage有没下载好的头像,没有就去Controller的字典imageDic,根据cellName来寻找,并将对应的数组元素赋值。由于cellName相同的,头像必相同,所以可以通过这一点减少重复头像的下载。如果依然没有,那就只能够下载了,这里使用了一个异步里面包含了两个同步,头像下载好后,再根据cellName来对cellImage赋值。这样做,就可以防止跳图的现象产生。另外NSMutableDictionary不可以添加nil,所以添加前必须进行一次简单的判断。此外,我又做了一个很猥琐的设计,将存放头像的字典改为全局静态变量。因此其他Controller都可以在加载完成后,立即匹配头像,再一次跳过下载重复头像。其实这么做主要是为了应对低网速下的环境,如果每次都重复下载头像,体验肯定会大打折扣。
微博必不可少的刷新,我也实现了,不过并不是单纯的刷新,属于增量加载。判断一下比已加载的最新一条微博还要新的微博是否达到50条,如果超过就直接替换原数组,否则就将原数组加在新数组后面。这样就不会错过任何的微博,而且刷新卡顿时间短一些,毕竟大多数情况下都不会有达到50条的新微博,而且可以减少加载后面更多微博的压力。不过,内存会使用得更多,也有不足。
1 - (IBAction)refreshButton:(UIBarButtonItem *)sender { 2 NSString *tmpSinceid = [[self.cellsDataArray objectAtIndex:0] ID]; 3 __block NSMutableArray *refreshDataArray; 4 5 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 6 refreshDataArray = [NSMutableArray weiboCellDataArrayWithSourceArray:[weiboUrl weiboAPIHomeTimeLineWithToken:self.userToken.token andSinceid:tmpSinceid andMaxid:@"0" andCount:@"50"]]; 7 dispatch_async(dispatch_get_main_queue(), ^{ 8 9 if ([refreshDataArray count]) { 10 if ([refreshDataArray count] >= 50) { 11 self.cellsDataArray = refreshDataArray; 12 } else { 13 [refreshDataArray addObjectsFromArray:self.cellsDataArray]; 14 self.cellsDataArray = refreshDataArray; 15 } 16 } 17 18 [self.tableView reloadData]; 19 }); 20 });21 }
然后“查看更多”的按键也是类似的做法。不过由于我封装API的时候,把max_id也改成NSString,所以在加载好后面微博的时候,还要把第一条重复的微博删掉,真的很马虎。由于微博的API我都封装了,这里没有看到具体实现。其实微博API就GET和POST两种调用方法,GET是获取数据的时候用,POST是发送数据,下面我会举两个具体的例子。
三、发微博和需要注意的小细节
GET方法
1 + (NSDictionary *)weiboAPIUserShowWithToken:(NSString *)token andUid:(NSString *)uid OrScreenName:(NSString *)screenname 2 { 3 NSError *error; 4 NSString *urlstr; 5 if (!screenname) 6 urlstr = [NSString stringWithFormat:@"%@?access_token=%@&uid=%@", UserShow, token, uid]; 7 else 8 urlstr = [NSString stringWithFormat:@"%@?access_token=%@&screen_name=%@", UserShow, token, [screenname stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]; 9 10 NSURL *url = [[NSURL alloc]initWithString:urlstr]; 11 NSData* data = [NSData dataWithContentsOfURL: url]; 12 NSDictionary *returnDic = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&error]; 13 14 return returnDic; 15 }
POST方法
1 + (NSDictionary *)weiboAPIUpdateStatusesWithToken:(NSString *)token andStatus:(NSString *)status andLat:(float)latitude andLong:(float)longtitude 2 { 3 if ([weiboUrl testStatusLengthWithString:status] == NO) 4 return nil; 5 6 NSString *requestStr = [NSString stringWithFormat:@"&access_token=%@&status=%@&lat=%f&long=%f", token, status, latitude, longtitude]; 7 const char *requestCstr = [requestStr UTF8String]; 8 9 10 NSData *RequestData = [NSData dataWithBytes:requestCstr length:strlen(requestCstr)]; 11 12 13 NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString: UpdateStatuses]]; 14 [request setHTTPMethod:@"POST"]; 15 [request setHTTPBody:RequestData]; 16 NSData *returnData = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:nil]; 17 18 NSDictionary *returnDic = [NSJSONSerialization JSONObjectWithData:returnData options:kNilOptions error:nil]; 19 20 return returnDic; 21 }
POST以后会有返回数据,根据返回的数据可以判断微博是否发送成功等状态,所以我封装的时候都会有返回数据的字典。说到POST,就不得不说微博“内容不超过140个汉字”的限制。微博所说的汉字是一个汉字或者汉字的标点就计数加1,英文或者英文的标点就计数加0.5,总计数是向上取整。全角等我尚未测试,所以不做解释。主要问题是如何计算字数出是否符合“内容不超过140个汉字”的标准。这里我提供三种方法给大家,其中一种是借鉴Kay_Sprint的《ios小项目——新浪微博客户端总结》其中的方法。
第一种是我自己想出来的
1 + (BOOL) testStatusLengthWithString:(NSString *)sourceString 2 { 3 const char *UTFStr = [sourceString UTF8String]; 4 5 unsigned int A = strlen(UTFStr); //汉字UTF8编码在strlen中每个占3位 6 unsigned int B = [sourceString length]; //NSString length方法汉字和英文均占用1位 7 8 unsigned int x = (A - B) / 2; //解二元一次方程,可得汉字和英文的个数 9 unsigned int y = B - x; 10 11 12 if (2 * x + y > 280) { //微博API接口限制汉字为140个,英文两个算一个汉字 13 UIAlertView *al = [[UIAlertView alloc] initWithTitle:@"Error!" message:@"内容不超过140个汉字" delegate:self cancelButtonTitle:@"确定" otherButtonTitles:nil, nil]; 14 [al show]; 15 return (NO); 16 } else 17 return YES; 18 }
如果超出字数,就会弹出UIAlertView的警告。这个理解起来应该不算难吧??
第二种是我从网上找回来的
1 NSString *test = [NSString stringWithString:@"这是一个中文test1"]; 2 NSStringEncoding enc = CFStringConvertEncodingToNSStringEncoding(kCFStringEncodingGB_18030_2000); 3 NSLog(@”%@ length is: %d”,test,[test lengthOfBytesUsingEncoding:enc]);
出处是http://www.xffox.com/blog/archives/327,这样的话,要计算相对的长度也不是什么问题。不过由于这种编码方法,我也不是很清楚,所以具体会遇到什么问题,我也不知道。
第三种就是Kay_Sprint的
1 -(int)textLength:(NSString *)dataString 2 { 3 float sum = 0.0; 4 for(int i=0;i<[dataString length];i++) 5 { 6 NSString *character = [dataString substringWithRange:NSMakeRange(i, 1)]; 7 if([character lengthOfBytesUsingEncoding:NSUTF8StringEncoding] == 3) 8 { 9 sum++; 10 } 11 else 12 sum += 0.5; 13 } 14 15 return ceil(sum); 16 }
具体作用可以看Kay_Sprint的说明。
这样的话,调用新浪微博API的障碍就基本扫清了,其余的接口都是大同小异,不会有什么大的变化。
不过转发的时候也要注意转发的文字,光标位置在最前面,而且键盘是自动打开的。
此外,为了不要每次都重复提取Access Token,我还用plist做了一下储存
1 - (void) saveToken 2 { 3 NSString *tokenFile = [[NSBundle mainBundle] pathForResource:fileName ofType:@"plist"]; 4 NSArray *tmpArray = [[NSArray alloc]initWithObjects:self.token, self.uid, self.userName, nil]; 5 6 [tmpArray writeToFile:tokenFile atomically:YES]; 7 }
加载Access Token
1 + (id) loadToken 2 { 3 tokenData *tmpid = [[tokenData alloc] init]; 4 5 NSString *tokenFile = [[NSBundle mainBundle] pathForResource:fileName ofType:@"plist"]; 6 NSArray *tmpArray = [[NSArray alloc] initWithContentsOfFile:tokenFile]; 7 8 if (tmpArray == nil) 9 return nil; 10 11 tmpid.token = [tmpArray objectAtIndex:0]; 12 tmpid.uid = [tmpArray objectAtIndex:1]; 13 tmpid.userName = [tmpArray objectAtIndex:2]; 14 15 return tmpid; 16 }
如何要注销账号,我就先清空Access Token,然后dismissModal。最后返回最初的Controller的时,只要判定一下Access Token是否存在和是否过期即可。
四、View的复用
我先发我那充满违和感的storyboard出来吧
很明显看到我有几个View是密集地多次指向的,因为那些都是经过多次复用的,不过在这视图上看的确充满违和感。但是View复用可以减少大量的冗余代码,我是通过View的Controller属性的状态来判断现在的应该执行什么操作,改变其状态则可以在prepareForSegue等发生时进行操作。例如发新微博的View其实还可以用于评论、转发,那么新建一个独立的View作用不大。而且,查看微博内容的View也会被经常调用,就只是微博的ID不同。对它做小小的改动,完全可以胜任任何环境。
具体的操作,我也会在源代码里给出来。
五、最后的话
这个山寨新浪微博客户端还有很多的不足,特别是错误异常处理做得很不好,空微博、空收藏、无网络连接下的POST问题等等,我都没有去解决。这些就导致了闪退、假死的问题。类封装马虎的问题,导致了我每次发现有需要添加的新属性,都往同一个类加。最后变得很臃肿,经常有大量的属性没有切实使用。虽然我也有几个View复用了,但其实还有可以提升的空间。功能的不完善是很明显的,主流客户端都做了缓存,即便在低网速的环境也不会在加载View的时候卡顿太久。网络切换时容易造成闪退等等各种问题暂时就先搁置,留待以后我有能力解决再一一慢慢除虫。最后还是要感谢Kay_Sprint等人的指导和交流,不然我一个星期也搞不出这货。