IOS 讀取博客園 RSS

前言:

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

RSS,根據維基百科的描述:RSS(簡易信息聚合)是一種消息來源格式規範,用以聚合常常發佈更新數據的網站,例如博客文章、新聞、音頻或視頻的網摘。Really Simple Syndication"聚合真的很簡單"就是RSS的英文原意。ios

各大網絡新聞網站均會有本身的 RSS 源,此次我選擇了博客園的:http://feed.cnblogs.com/blog/sitehome/rssgit

 

準備:

開發語言:Object-Cgithub

開發環境:Xcode Version 5.1.1 (5B1008)web

部署環境:iPhone ios7.1數組

使用第3方類庫:AFNetworking2.0、GDataXML網絡

 

博客園 RSS 數據結構分析:

打開 http://feed.cnblogs.com/blog/sitehome/rss,獲取到的數據是 XML 格式的,結構以下:數據結構

(2014-09-03 10:40)app

結構很清楚,<feed>爲根節點,接着幾個子節點分別描述標題、子標題、id、更新時間(UTC,+8 轉爲北京時間,下同)、生成者;iphone

接着,能夠當作是由多個(事實是20個)<entry>組成的一個博文數組,每個<entry>節點爲一篇博文信息。展開節點可看到一篇博文信息包括了 id(即博文路徑)、標題、摘要、發佈時間、更新時間、做者信息、詳細內容等。

 

弄清楚了博客園 RSS 數據的結構,就能夠着手開發了。

首先解決兩個問題:一、數據獲取;二、數據解析;

數據獲取,簡單 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,還需作一下配置:

一、添加 libxml2.dylib;

二、爲 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

相關文章
相關標籤/搜索