• 山寨新浪微博客户端与新浪微博API调用的总结


      这次是我第一次写的项目总结,虽然这只是一个小项目,但确实是获益良多。虽然说是独立完成,但其实在做的过程中,也有和大家交流了很多意见。尽管如此,我对这个尚不能算写好的项目,还是有非常多的不满意。不过碍于能力有限,不满意的地方也就先将就将就。日后有兴趣的话,肯定会再次改进,完善得更好。

      项目的主要内容就是用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等人的指导和交流,不然我一个星期也搞不出这货。

     

  • 相关阅读:
    [ABC142F] Pure
    [ABC141F] Xor Sum 3
    tarjan缩点
    LoadRunner录制:事务
    LoadRunner录制:脚本调试
    linux性能监控命令
    Python 3 解析 html
    Python 3 操作json 文件
    Python 数据驱动工具:DDT
    selenium 问题:OSError: [WinError 6] 句柄无效
  • 原文地址:https://www.cnblogs.com/ipinka/p/2593972.html
Copyright © 2020-2023  润新知