zoukankan      html  css  js  c++  java
  • IOS 读取博客园 RSS

    前言:

    本文地址:http://www.cnblogs.com/SugarLSG/p/3953399.html

    RSS,根据维基百科的描述:RSS(简易信息聚合)是一种消息来源格式规范,用以聚合经常发布更新数据的网站,例如博客文章、新闻、音频或视频的网摘。Really Simple Syndication"聚合真的很简单"就是RSS的英文原意。

    各大网络新闻网站均会有自己的 RSS 源,这次我选择了博客园的:http://feed.cnblogs.com/blog/sitehome/rss

    准备:

    开发语言:Object-C

    开发环境:Xcode Version 5.1.1 (5B1008)

    部署环境:iPhone ios7.1

    使用第3方类库:AFNetworking2.0、GDataXML

    博客园 RSS 数据结构分析:

    打开 http://feed.cnblogs.com/blog/sitehome/rss,获取到的数据是 XML 格式的,结构如下:

    (2014-09-03 10:40)

    结构很清楚,<feed>为根节点,接着几个子节点分别描述标题、子标题、id、更新时间(UTC,+8 转为北京时间,下同)、生成者;

    接着,可以看成是由多个(事实是20个)<entry>组成的一个博文数组,每一个<entry>节点为一篇博文信息。展开节点可看到一篇博文信息包括了 id(即博文路径)、标题、摘要、发布时间、更新时间、作者信息、详细内容等。

    弄清楚了博客园 RSS 数据的结构,就可以着手开发了。

    首先解决两个问题:1、数据获取;2、数据解析;

    数据获取,简单 GET 形式即可,我使用了当下最热门的 AFNetworking,已更新到2.0版本,Git 的地址:https://github.com/AFNetworking/AFNetworking

    数据解析,这次数据源格式是 XML,网上有很多优秀的第3方类库可供选择,如何选择可参考:http://www.cnblogs.com/dotey/archive/2011/05/11/2042000.html 或 http://www.raywenderlich.com/553/xml-tutorial-for-ios-how-to-choose-the-best-xml-parser-for-your-iphone-project,我使用 GDataXML;

    开始项目:

    ——准备

    Xcode 新建一个工程项目,就叫 SGCnblogs,我习惯手写代码创建 Controller、View 等,所以这里没用到 storyboard。

    删了 Tests Target,分好层级:

    使用 GDataXML,还需做一下配置:

    1、添加 libxml2.dylib;

    2、为 Header Search Paths 添加 /usr/include/libxml2;为 Other Linker Flags 添加 -lxml2;

    新建一个 SGHomeTableViewController,继承自 UITableViewController,用来显示博文列表;

    SGAppDelegate 里实例化 SGHomeTableViewController,并使用 UINavigationController 做界面切换模式:

    SGHomeTableViewController *homeVC = [[SGHomeTableViewController alloc] init];
    homeVC.title  = @"博客园";
    UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:homeVC];
    self.window.rootViewController = navigationController;

    根据 RSS 数据的结构,分别新建3个 Model:SGCnblogsFeedModel、SGCnblogsEntryModel、SGCnblogsAuthorModel,加上各自的属性:

    @interface SGCnblogsFeedModel : NSObject
    
    @property (nonatomic, strong) NSString *title;
    @property (nonatomic, strong) NSString *subtitle;
    @property (nonatomic, strong) NSString *id;
    @property (nonatomic, strong) NSString *updated;
    @property (nonatomic, strong) NSString *generator;
    
    @property (nonatomic, strong) NSMutableArray *enties;
    
    @end
    @interface SGCnblogsEntryModel : NSObject
    
    @property (nonatomic, strong) NSString *id;
    @property (nonatomic, strong) NSString *title;
    @property (nonatomic, strong) NSString *summary;
    @property (nonatomic, strong) NSString *published;
    @property (nonatomic, strong) NSString *updated;
    @property (nonatomic, strong) SGCnblogsAuthorModel *author;
    @property (nonatomic, strong) NSString *content;
    
    @end
    @interface SGCnblogsAuthorModel : NSObject
    
    @property (nonatomic, strong) NSString *name;
    @property (nonatomic, strong) NSString *uri;
    
    @end

    ——数据获取

    新建一个 Helper -- SGCnblogsRSSHelper,用于请求 RSS 数据。对外提供一个类方法:

    + (void)requestCnblogsRSSWithHandleSuccess:(void (^)(SGCnblogsFeedModel *feedModel))success handleFailure:(void (^)(NSError *error))failure;

    success 用于处理请求成功后的操作,传入 Response 数据(已封装成 SGCnblogsFeedModel);

    failure 用于处理请求失败后的操作,传入错误信息;

    #define URL_CNBLOGSRSS @"http://feed.cnblogs.com/blog/sitehome/rss"
    
    + (void)requestCnblogsRSSWithHandleSuccess:(void (^)(SGCnblogsFeedModel *feedModel))success handleFailure:(void (^)(NSError *error))failure
    {
        // 使用 AFNetworking
        AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
        
        // 用 GET 方式请求 RSS
        [manager GET:URL_CNBLOGSRSS
                parameters:nil
                success:^(AFHTTPRequestOperation *task, id responseObject) {
                    NSLog(@"responseObject: %@", responseObject);
                }
                failure:^(AFHTTPRequestOperation *task, NSError *error) {
                    NSLog(@"error: %@", error);
                }
         ];
    }

    使用 AFHTTPRequestOperationManager 的 GET 方法,先做个简单测试,Debug 后看到打印出的部分 log 如下:

    "无法接收这个类型的内容"。

    AFHTTPRequestOperationManager 可设置 request 和 response 的类型,跟踪进 [AFHTTPRequestOperationManager manager] 方法可看到这里实例化出来的 response 类型是 [AFJSONResponseSerializer serializer],我需要的是 application/atom+xml,只需在实例化后再加上以下两句:

        // 设置请求
        manager.responseSerializer = [AFHTTPResponseSerializer serializer];
        manager.responseSerializer.acceptableContentTypes = [NSSet setWithObject:@"application/atom+xml"];

    再次 Debug 测试:

    数据拿到了,接着就是解析、封装。

    我在这步卡了很久,原因是 Debug 进入后,看到的数据并不是 XML 字符串格式的,而是这样的:

    error?!!后来找了很久,才知道原来要这么转才能得到 XML 字符串:

                    // 处理 Response 数据
                    NSString *rssString = nil;
                    if ([responseObject isKindOfClass:[NSData class]]) {
                        rssString = [[NSString alloc] initWithData:responseObject encoding:NSUTF8StringEncoding];
                    } else {
                        rssString = (NSString *)responseObject;
                    }

    ——数据解析、封装

    XML 字符串数据,最终解析、封装成 SGCnblogsFeedModel。我在 SGCnblogsFeedModel 中对外提供一个构造函数(initWithRSSString:),传进 XML 字符串后,自动解析、封装成 SGCnblogsFeedModel 实例:

    - (id)initWithRSSString:(NSString *)rssString
    {
        if (self = [super init]) {
            // 使用 GDataXML 解析字符串
            GDataXMLDocument *xmlDoc = [[GDataXMLDocument alloc] initWithXMLString:rssString options:0 error:nil];
            GDataXMLElement *rootEle = [xmlDoc rootElement];
            
            self.title = [[[rootEle elementsForName:@"title"] objectAtIndex:0] stringValue];
            self.subtitle = [[[rootEle elementsForName:@"subtitle"] objectAtIndex:0] stringValue];
            self.id = [[[rootEle elementsForName:@"id"] objectAtIndex:0] stringValue];
            self.updated = [[[rootEle elementsForName:@"updated"] objectAtIndex:0] stringValue];
            self.generator = [[[rootEle elementsForName:@"generator"] objectAtIndex:0] stringValue];
            self.enties = [[NSMutableArray alloc] init];
            
            for (GDataXMLElement *entryEle in [rootEle elementsForName:@"entry"]) {
                SGCnblogsEntryModel *entryModel = [[SGCnblogsEntryModel alloc] init];
                
                entryModel.id = [[[entryEle elementsForName:@"id"] objectAtIndex:0] stringValue];
                entryModel.title = [[[entryEle elementsForName:@"title"] objectAtIndex:0] stringValue];
                entryModel.summary = [[[entryEle elementsForName:@"summary"] objectAtIndex:0] stringValue];
                entryModel.published = [[[entryEle elementsForName:@"published"] objectAtIndex:0] stringValue];
                entryModel.updated = [[[entryEle elementsForName:@"updated"] objectAtIndex:0] stringValue];
                entryModel.content = [[[entryEle elementsForName:@"content"] objectAtIndex:0] stringValue];
                
                GDataXMLElement *authorEle = [[entryEle elementsForName:@"author"] objectAtIndex:0];
                entryModel.author = [[SGCnblogsAuthorModel alloc] init];
                entryModel.author.name = [[[authorEle elementsForName:@"name"] objectAtIndex:0] stringValue];
                entryModel.author.uri = [[[authorEle elementsForName:@"uri"] objectAtIndex:0] stringValue];
                
                [self.enties addObject:entryModel];
            }
        }
        
        return self;
    }

    最简单粗暴的解析,这里不多做说明了。

    SGCnblogsRSSHelper 里的 GET 方法修改成这样:

        // 用 GET 方式请求 RSS
        [manager GET:URL_CNBLOGSRSS
                parameters:nil
                success:^(AFHTTPRequestOperation *task, id responseObject) {
                    // 处理 Response 数据
                    NSString *rssString = nil;
                    if ([responseObject isKindOfClass:[NSData class]]) {
                        rssString = [[NSString alloc] initWithData:responseObject encoding:NSUTF8StringEncoding];
                    } else {
                        rssString = (NSString *)responseObject;
                    }
                    
                    if (rssString && [rssString length] > 0) {
                        SGCnblogsFeedModel *feedModel = [[SGCnblogsFeedModel alloc] initWithRSSString:rssString];
                        if (feedModel) {
                            success(feedModel);
                            return;
                        }
                    }
                    
                    failure([NSError errorWithDomain:@"can not parse the rss xml string." code:0 userInfo:nil]);
                }
                failure:^(AFHTTPRequestOperation *task, NSError *error) {
                    failure(error);
                }
         ];

    ——数据显示

    在 SGHomeTableViewController 中增加一个 SGCnblogsFeedModel 属性,同时实现 UITableViewDataSource 中以下4个 Delegate:

    - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView;

    - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section;

    - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath;

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

    并在 viewDidLoad 中,绑定数据(同样也可以放在 init 方法中,这样就不需要 reload data 了):

    @interface SGHomeTableViewController()
    
    @property (nonatomic, strong) SGCnblogsFeedModel *feedModel;
    
    @end
    
    
    
    - (void)viewDidLoad
    {
        [super viewDidLoad];
        
        [SGCnblogsRSSHelper requestCnblogsRSSWithHandleSuccess:^(SGCnblogsFeedModel *feedModel) {
            self.feedModel = feedModel;
            // 重新渲染界面
            [self.tableView reloadData];
        } handleFailure:^(NSError *error) {
            NSLog(@"%@", error);
        }];
    }
    
    
    #pragma mark - UITableViewDataSource
    
    /**
     设置 Section 数
     */
    - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
    {
        return 1;
    }
    
    /**
     设置每个 Section 对应的数据行数
     */
    - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
    {
        return self.feedModel && self.feedModel.enties ? self.feedModel.enties.count : 0;
    }
    
    /**
     设置每行高度
     */
    - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
    {
        return 105;
    }
    
    /**
     绑定每行数据
     */
    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
    {
        // 重用 TableViewCell
        SGCnblogsEntryTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"SGCnblogEntryTableViewCell"];
        if (!cell) {
            cell = [[SGCnblogsEntryTableViewCell alloc] init];
        }
        
        // 加载界面
        [cell loadView:self.feedModel && self.feedModel.enties ? [self.feedModel.enties objectAtIndex:indexPath.row] : nil];
        
        return cell;
    }

    这里用到 SGCnblogsEntryTableViewCell,这个是我自定义的一个继承自 UITableViewCell 的 TableViewCell,用于显示博文列表页每一行的数据,这个类对外提供一个实例方法:

    - (void)loadView:(SGCnblogsEntryModel *)entryModel;

    简单的将数据显示出来,这里不贴出代码了,看看 Run 的结果:

    ——界面跳转

    接着实现列表每一行的点击事件,当点击某一行时,界面切换到博文详细页。只需在 SGHomeTableViewController 中实现 UITableViewDelegate 中的 

    tableView:didSelectRowAtIndexPath: 方法即可:

    #pragma mark - UITableViewDelegate
    
    /**
     处理点击事件
     */
    - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
    {
        SGCnblogsEntryModel *entryModel = self.feedModel && self.feedModel.enties ? [self.feedModel.enties objectAtIndex:indexPath.row] : nil;
        SGEntryViewController *entryVC = [[SGEntryViewController alloc] initWithEntryModel:entryModel];
        
        [self.navigationController pushViewController:entryVC animated:YES];
    }

    获取到对应的 Model 后,使用 Navigation Push 一个新 Controller。

    SGEntryViewController 是我自定义的一个 ViewController,继承自 UIViewController,增加一个 SGCnblogsEntryModel 属性,并且对外提供一个构造函数(initWithEntryModel:)。

    这个界面用于显示博文的详细内容,代码不贴出来了,看看简单效果:

    我还想增加一个功能,能够跳转到原文页面。

    于是我在这个界面的 NavigationItem 处添加一个右侧按钮,点击跳转到原文页面。在 initWithEntryModel: 中添加以下代码:

            // 查看原文按钮
            UIBarButtonItem *rightBarBtn = [[UIBarButtonItem alloc] initWithTitle:@"原文" style:UIBarButtonItemStyleBordered target:self action:@selector(selectedRightAction:)];
            self.navigationItem.rightBarButtonItem = rightBarBtn;

    并且实现对应的点击响应方法(selectedRightAction:):

    - (void)selectedRightAction:(UIBarButtonItem *)rightBarBtn
    {
        SGWebViewController *webVC = [[SGWebViewController alloc] initWithUrl:self.entryModel.id];
        [self.navigationController pushViewController:webVC animated:YES];
    }

    SGWebViewController,继承自 UIViewController,用来加载网络页面,这里没做返回、前进、重加载等功能,只是简单显示。

    直接贴代码和效果图:

    @interface SGWebViewController()
    
    @property (nonatomic, strong) NSString *url;
    
    @end
    
    
    @implementation SGWebViewController
    
    - (id)initWithUrl:(NSString *)url
    {
        if (self = [super init]) {
            self.url = url;
        }
        return self;
    }
    
    - (void)viewDidLoad
    {
        [super viewDidLoad];
        
        NSURL *url = [NSURL URLWithString:self.url];
        NSURLRequest *request = [NSURLRequest requestWithURL:url];
        UIWebView *webView = [[UIWebView alloc] init];
        webView.frame = CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height);
        //  适应屏幕大小
        webView.scalesPageToFit = YES;
        [webView loadRequest:request];
        [self.view addSubview:webView];
    }

    结语:

    最后的文档结构是:

    项目已放到 Github 上:https://github.com/SugarLSG/SGCnblogs

    本文地址:http://www.cnblogs.com/SugarLSG/p/3953399.html

  • 相关阅读:
    Ubuntu 16.04下SecureCRT无法输入中文的解决思路
    Ubuntu 16.04安装SecureCRT替代XShell
    SVN提交时报错:Commit blocked by pre-commit hook (exit code 1) with no output.
    Spring Cloud ZooKeeper集成Feign的坑2,服务调用了一次后第二次调用就变成了500,错误:Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is com.n
    Spring Cloud ZooKeeper集成Feign的坑1,错误:Consider defining a bean of type 'org.springframework.web.client.RestTemplate' in your configuration.
    Ubuntu 16.04 GNOME下解决Sublime Text3中文输入(ibus)(转)
    Spring Cloud简介/版本选择/ZooKeeper例子搭建简单说明
    Maven奇怪的问题,当找不到Maven输出的提示错误时可以试下这个方法
    Ubuntu 16.04系统启动时卡在:(initramfs)
    Eclipse创建Maven多模块工程
  • 原文地址:https://www.cnblogs.com/SugarLSG/p/3953399.html
Copyright © 2011-2022 走看看