23 9月

使用JSON和WatchKit构建简单的天气应用程序

文 / Gregory Tareyev:iOS开发者,http://iamchill.com联合创始人。

原文链接:http://www.appcoda.com/weather-watchkit-app/

编辑注:这是论坛游客Gregory Tareyev发的帖,他是iOS开发人员和Chill(iamchill.co)的联合创始人。开发的首个可穿戴通信工具让你和朋友们动动指尖就能互动。在本文中,Gregory将分享自己做Apple Watch开发的经验,向你展示如何使用第三方API和WatchKit构建简单的天气应用程序。我们已经写了两篇与WatchKit有关的文章,都是使用Swift语言完成的。一些读者提到是否能有一篇使用Objective-C的文章,这篇便是。

Gregory的文章从这里开始……

大家好,我是Gregory Tareyev(可以来tareyev.ru联系我),是iOS开发人员和Chill(iamchill.co)的联合创始人。第一款可穿戴的通信工具终于搞定了。最近我们在Product Hunt上展示的产品有着不俗的表现,使我们在社区内获得了广泛的影响力并吸引了主要的技术博客报道这次发布。我们也在讨论使用加速器的问题,并考虑使用最好的那种。为构建应用程序而着实努力着,我相信你们每个人都可以为此而努力。

55d1486e117dc_middle

在这里想要与你分享我的Apple Watch开发经验。很有趣,也不难。

为了调动你的兴趣,我要解释在可穿戴设备上进行开发为何如此重要。

  1. 市场仍然不太大,这等同于作为先行者,你可以获得更大的市场份额。
  2. 市场将会获得极大增长,这等同于相关产品规模会随着市场同步增长。

55d14acdba958_middle

来源:图表由Business Insider提供。

在本文中,构建一个简单的Apple Watch天气应用程序,要完成两件事:

  • 如何在使用WatchKit在应用程序中解析JSON数据
  • 如何使用OpenWeatherMap的API(一旦明白其中的道理,你应该能够选择任何基于JSON的API来使用)

我们开始吧!

应用程序示例

示例程序是一款非常简单的天气应用程序,会使用OpenWeatherMap的API得到特定城市(例如:伦敦)的天气信息。这是应用程序最终样例的截图。

55d14b794590b

创建Xcode工程

首先,创建一个Single View Application,并按照如下截图设置项目信息。没错儿,我们使用Objective-C来做,我觉得Objective-C仍然很重要,还是要继续使用的。

55d14bcd1445f_middle

创建一个Watch应用程序,到Xcode顶部菜单栏选取File > New > Target…,选择Apple Watch > WatchKit App。由于使用WatchKit App模版,所以会生成构建Watch应用程序所需要的一切。

55d14ccb2a8f8_middle

取消选择“Include Notification Scene”,其余项保留,点击Finish。

simple-weather-watch-app

你会得到一条警告信息问是否要激活,点击Activate就是了。然后就会看到两个新文件夹:WatchKit Extension和WatchKit App。

simple-weather-watch-scheme

设计应用程序的用户接口

接下来,我们要着手设计应用程序的用户接口。为此,点击“SimpleWeather WatchKit App”下的Interface.Storyboard文件。

首先,从对象库(Object Library)拖拽一个Lable控件,名称设置为“Weather in London”。控件上字体大小可能需要调整一下。然后拖拽一个Image控件。接着再来一个Button控件。你会发现图像和按钮都会自动布局,垂直堆放。把按钮的Title改为“Update”,颜色调为绿色并调整图像的尺寸。

simple-weather-watch-ui

标签用来显示气象类型,而图像显示气象类型的插图。Update按钮是与用户交互的唯一元素,用来更新天气信息。

Interface Builder可以让你看到Apple Watch不同版本的视图。默认情况下,Interface Controller被设置成Any Screen Size。可以点击Interface Builder下方的“Any Screen Size”按钮,在Apple Watch 38/42mm这两种尺寸间切换。如果切换到Apple Watch 42mm,就会发现图像不完全合适,要调整图像大小,把所有控件布局弄好为止。因为更改图像大小时,Xcode会自动添加对布局做特殊处理。这种情况只在选择Apple Watch 42mm的才出现。

simple-weather-watch-ui-42

理解JSON和OpenWeatherMap的API

已经说过了,我们使用OpenWeatherMap的API来得到天气数据。为了理解它是如何工作的,要打开这个链接:http://api.openweathermap.org/data/2.5/weather?q=London,uk。拷贝结果并粘贴到http://json.parser.online.fr。你会看到结构化的JSON数据。在这里,我们感兴趣的是天气类型,也就是dictionary类型数据“weather”下的“main”键。这就是我们要在屏幕上显示的信息。

openweather-api-json

作为参考,可以在http://openweathermap.org/api查看API文档。

现在,一起来看如何解析JSON数据,并把天气信息在应用程序中展现出来。

打开Assistant Editor,按住Control键拖拽Label控件到InterfaceController.h文件的代码中做关联,把outlet命名为“weatherType”。

add-label-outlet

重复上述过程,将Image控件也做好关联,把Outlet命名为“weatherImage”。在给Update按钮做关联时,不要选择Outlet,选择Action类型,并命名为“updateAction”。

add-update-action

在InterfaceController.m文件中为updateAction方法添加逻辑代码:

- (IBAction)updateAction
{
    NSURLRequest* requestForWeatherData = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://api.openweathermap.org/data/2.5/weather?q=London,uk"]];
    NSURLResponse* response = nil;
    NSError* error = nil; //do it always
 
    NSData* data = [NSURLConnection sendSynchronousRequest:requestForWeatherData returningResponse:&response error:&error]; //for saving all of received data in non-serialized view
    
    NSMutableDictionary *allData = [ NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:&error]; //data in serialized view
    NSString* currentWeather = nil;
    
    NSArray* weather = allData[@"weather"];
    
    for (NSDictionary* weatherDictionary in weather)
    {
        currentWeather = weatherDictionary[@"main"];
    }
}

该方法中,使用了NSURLConnection对OpenWeatherMap同步发出请求。可以使用NSJSONSerialization把JSON数据转换为Foundation基础库类型(例如:NSDictionary)。我们对数据进行解析以后将天气类型存入“currentWeather”变量中。

接下来,要更新标签和图像。

weather-image-if

这样代码看起来不是太美观,对吧?

为了避免对天气类型进行硬编码,可以创建如下方法。这样会使代码更灵活。

  1. -(void)setImageAndTextWithWeather:(NSString* ) weatherName
  2. {
  3.      // Use the weather type as the weather image name. For example, if the weather name is “Rainy”, the image name is set to “rainy.jpg”. 
  4.      // Set the weather type to the given weather name
  5. }

很不错!我们把方法放到真实的代码中:

  1. -(void)setImageAndTextWithWeather:(NSString* ) weatherName
  2. {
  3.         NSString* weatherNameWithoutSpaces = [weatherName stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; //delete potential spaces in JSON array
  4.         [_weatherImage setImageNamed:[weatherNameWithoutSpaces stringByAppendingString:@“.jpg”]];
  5.     NSMutableAttributedString *customString = [[NSMutableAttributedString alloc] initWithString:weatherNameWithoutSpaces];
  6.     [customString addAttribute:NSFontAttributeName
  7.                          value:[UIFont systemFontOfSize:18]
  8.                   range:NSMakeRange(0, [weatherNameWithoutSpaces length])]; //Making text more readable by creating a custom string
  9.     [_weatherType setAttributedText:customString];
  10. }

最后,在updateAction方法的结尾加上这行代码:

[self setImageAndTextWithWeather:currentWeather];

在Asset Catalog中添加图像

运行应用程序之前,下载这些图像。解压缩后把这些图像全部添加到SimpleWeather WatchKit App文件夹下的Images.xcassets里。

simple-weather-images

可以随便添加更多带有天气类型名称的图像。应用程序不用更改任何代码就可以运行。

测试应用程序

差不多了!现在可以在Apple Watch模拟器中构建并运行应用程序了。选择“WatchKitDemo WatchKit App”项目,并选择合适的设备,然后点击Run按钮,测试Apple Watch应用程序。另外,还可以修改模拟器显示的尺寸,选择Hardware > External Displays > Apple Watch – 38mm。

55d151607af6f_middle

很棒!你开发完成了一款Apple Watch天气应用程序。

作为参考,可以在这里下载最终的项目

http://www.csdn.net/article/2015-08-17/2825474/2

13 8月

如何使用iOS SDK获取和解析JSON数据

文 / ziad tamim:资深iOS开发人员,创业公司移动战略咨询顾问。自App Store上线以来,编写超过80款应用程序。目前,他经营着一家叫做TAMIN LAB的移动开发工作室。

原文链接:http://www.appcoda.com/fetch-parse-json-ios-programming-tutorial/

编辑注:本周,ziad tamim将向你展示如何获取和解析JSON数据。在本文中,我们会关注为iOS应用程序添加对JSON的支持,通过使用Meetup API来展示如何创建一个简单应用程序。这篇文章也许比我们之前介绍的其他文章要复杂一点儿,这正是你所需要的有关JSON和设计模式的基础知识。

JSON编程指南由此开始……

首先,什么是JSON?JSON(short for JavaScript Object Notation)是一个基于文本的,轻量级的,便于数据排序和交换的一种机制。通常用于在客户端/服务器模式的应用程序中表示结构化的数据,并进行数据的交互,作为XML格式数据的替代物。我们日常中所使用的很多服务都适用了基于JSON的API。大多数iOS应用程序都使用JSON格式的网络服务将数据发送到它们的后端web服务。包括Twitter,Facebook和Flick在内都是这样做的。

json-intro-tutorial

{
    "title": "The Amazing Spider-man",
    "release_date": "03/07/2012",
    "director": "Marc Webb",
    "cast": [
        {
            "name": "Andrew Garfield",
            "character": "Peter Parker"
        },
        {
            "name": "Emma Stone",
            "character": "Gwen Stacy"
        },
        {
            "name": "Rhys Ifans",
            "character": "Dr. Curt Connors"
        }
    ]
}

不难发现,JSON数据比XML更具可读性,更容易解析。如果你对JSON一无所知,可以看看这个JSON Guide

iOS5发布以来,iOS SDK更容易获取和分析JSON数据。在这篇文章中,我们将展示如何使用内置API查询来自Meetup基于JSON的API,并恰当地处理返回值。

我们的应用程序

编写代码以前,先来看看要构建什么样的应用程序。

在本文中,我们要创建一个叫做BrowserMeetup的简单应用程序,将会使用Meetup的公共API。从来没听说过Meetup吗,它是全球最大的本地聚会网络平台。你可以免费使用它来组织在当地的聚会,还可以从上千个已经组织好的聚会中找到其中的一个。像其他社交网站一样,Meetup为你的服务端访问其数据提供了开放API。

BrowseMeetup应用程序会使用Meetup的web服务来寻找附近的聚会。应用程序会获得当前的地理位置并自动加载附近的聚会。

Screen-Shot-2013-08-24-at-11.32.09-PM-e1377937354961

注:该应用程序使用Core Location框架,如果你不了解该框架,可以参考How To Get the User Location in iPhone App

动手开发应用程序

可以开始构建和管理BrowseMeetup应用程序了。启动Xcode并创建一个主-从视图类型的应用程序(Master-Detail Application)。项目选项设置中,你要选择Storyboard和ARC(Automatic Reference Counting)选项。该应用仅提供iPhone视图,因此在设备选项中选择iPhone并保存项目。删除掉storyboard中的DetailsViewController,像下面这样设计用户界面:

Screen-Shot-2013-08-25-at-1.24.08-AM-e1377871383429

本文着重介绍JSON的获取和解析。所以为了节省时间就不去建立项目了,你可以直接下载项目模版。模版已经事先构建好了用户接口并实现了Core Location功能。

提示:如果不懂怎么用table view,想知道导航条是如何工作的,可以参考我们免费的iOS资源指南

使用Meetup的API

使用Meetup API前,请在其上先创建一个账号。打开APIs Doc,点击“Request to join this Meetup group”按钮。然后填写必要的信息,点击“Sign Up”。一步一步进行直至完成。

Screen-Shot-2013-08-26-at-10.06.27-PM

我们将使用其中的一个API(https://api.meetup.com/2/groups)来获取在某个地点举行的Meetup聚会信息。调用过程允许开发者使用经纬度来定位。可以使用API console来测试调用。

这是一段请求后简单的JSON响应信息(https://api.meetup.com/2/groups?&sign=true&lat=51.509980&lon=-0.133700&page=1):

{
"results": [
{
"lon": -0.10000000149011612,
"visibility": "public",
"organizer": {
"name": "William Brown",
"member_id": 3817216
},
"link": "http://www.meetup.com/french-32/",
"state": "17",
"join_mode": "approval",
"who": "LFM members",
"country": "GB",
"city": "London",
"id": 63974,
"category": {
"id": 16,
"name": "language/ethnic identity",
"shortname": "language"
},
"topics": [
{
"id": 185,
"urlkey": "french",
"name": "French Language"
},
{
"id": 264,
"urlkey": "friends",
"name": "Friends"
},
{
"id": 3304,
"urlkey": "expatfrench",
"name": "Expat French"
}
],
"timezone": "Europe/London",
"group_photo": {
"photo_link": "http://photos3.meetupstatic.com/photos/event/7/4/a/b/600_929867.jpeg",
"highres_link": "http://photos3.meetupstatic.com/photos/event/7/4/a/b/highres_929867.jpeg",
"thumb_link": "http://photos3.meetupstatic.com/photos/event/7/4/a/b/thumb_929867.jpeg",
"photo_id": 929867
},
"created": 1034097734000,
"description": "<p>The London French Meetup is the biggest group of French speakers of all ages and nationalities in London. We hold regular events to meet up, talk in French and share interests in French culture whilst having a good time.</p>
<p>We have two main events per month where we have the whole of the upstairs of a pub.</p>
<p>In addition, we organise other regular events such as outings to: restaurants, trendy bars, french films, live music, sports related activities, outdoor events and more...</p>
<p>The organising team is made of volunteers from different nationalities and ages. Our members are made up of: 1/3 French nationals, 1/3 British nationals and 1/3 other nationalities and francophone countries. If you have any ideas or suggestions for events or would like to help please let us know.</p>
<p>A bientôt.</p>
<p>LFM Team.</p>",
"name": "London French Meetup",
"rating": 4.37,
"urlname": "french-32",
"lat": 51.52000045776367,
"members": 4889
}
],
"meta": {
"lon": -0.1337,
"count": 1,
"signed_url": "http://api.meetup.com/2/groups?radius=25.0&order=id&desc=false&offset=0&format=json&lat=51.50998&page=1&fields=&lon=-0.1337&sig_id=109020062&sig=4532ed8f987f940748ebfba0f483a26f756dcba3",
"link": "http://www.meetup.com/2/groups",
"next": "http://www.meetup.com/2/groups?radius=25.0&order=id&format=json&lat=51.50998&page=1&desc=false&offset=1&fields=&sign=true&lon=-0.1337",
"total_count": 4501,
"url": "http://www.meetup.com/2/groups?radius=25.0&order=id&format=json&lat=51.50998&page=1&desc=false&offset=0&fields=&sign=true&lon=-0.1337",
"id": "",
"title": "Meetup Groups v2",
"updated": 1377876449000,
"description": """",
"method": "Groups",
"lat": 51.50998
}
}

应用程序设计及工作原理

如前所述,Meetup API提供了请求某方位聚会信息的方法。响应数据将用JSON格式发送。我们需要一个对象,可以从已编码的数据中检索数据,并从中构建我们的域对象(domain object )。下面是关于应用程序设计的介绍,展示如何构建类,以及这些类如何获得meetup聚会信息:

Screen-Shot-2013-08-16-at-10.01.27-PM-e1377877588261

对于有些人来说,也许有点儿复杂。我来带你过一遍。创建MeetupManager是为了请求的Meetup聚会所获得的方位,就像门面一样。如果你没有听说过设计模式中的门面模式(Facade Pattern)的话,可以把它想象成其他类的协调者。门面模式试图为界面试图控制器提供一个简化的接口,并屏蔽掉底层的实现。

MeetupCommunicator用来与Meetup API进行通讯。一旦Meetup返回JSON格式响应,我们就将该响应传递给GroupBuilder,GroupBuilder构建了Group对象。

MasterViewController使用Core Location得到当前的方位,并通知MeetupManager得到该方位的Meetup聚会信息。MeetupManager协调其他的类来检索得到的那些聚会信息。一旦检索成功,MeetupManager会通过代理与MasterViewController通信,并将检索到的聚会信息传递过去。然后MasterViewController将传递过来的聚会信息在table view中展现出来。

创建JSON数据模型

接下来我们将要实现实体类(model层)。Group类表示BrowseMeetup应用中聚会的信息,用来存储从Meetup返回的聚会信息。下面是一个JSON响应中的Group对象示例:

{
     lon: -71.12999725341797,
     visibility: "public",
     organizer: {
          name: "Emma",
          member_id: 2161382
     },
     link: "http://www.meetup.com/bloggers/",
     state: "MA",
     join_mode: "closed",
     who: "Bloggers",
     country: "US",
     city: "Cambridge",
     id: 21458,
    category: {
          id: 34,
          name: "tech",
          shortname: "tech"
     },
     topics: [
          {
               id: 198,
               urlkey: "blog",
               name: "Blog"
          },
          {
               id: 772,
               urlkey: "writers",
               name: "Writers"
          }
     ],
     timezone: "US/Eastern",
     group_photo: {
     photo_link: "http://photos3.meetupstatic.com/photos/event/6/4/9/5/600_25749.jpeg",
     highres_link: "http://photos3.meetupstatic.com/photos/event/6/4/9/5/highres_25749.jpeg",
     thumb_link: "http://photos3.meetupstatic.com/photos/event/6/4/9/5/thumb_25749.jpeg",
     photo_id: 25749
     },
     created: 1034097731000,
     description: "This is a group for people that are interested in blogging and meeting others who are interested in blogging. Topics discussed range from blog content to blog software. All interest levels are welcome.",
     name: "The Greater Boston Area Weblogger Meetup Group",
     rating: 3.33,
     urlname: "bloggers",
     lat: 42.38999938964844,
     members: 119
}

上面的JSON响应代表单个Meetup Group。我们不会使用所有返回来的数据。而是简单地使用“name”,“description”,“who”,“country”和“city”字段。这些字段对我们来说足够了。现在要使用Objective-C类模版来创建一个新文件,命名为Group,将它设置为NSObject的子类,并在头文件中添加如下代码:

@interface Group : NSObject
@property (strong, nonatomic) NSString *name;
@property (strong, nonatomic) NSString *description;
@property (strong, nonatomic) NSString *who;
@property (strong, nonatomic) NSString *country;
@property (strong, nonatomic) NSString *city;
@end

我们将使用这些属性信息,在应用程序中实现我们之前描述的最终结果。

使用Meetup API获取JSON 数据

首先,使用Objective-C的协议模版创建一个文件,并命名为MeetupCommunicatorDelegate,在文件中填充如下代码:

@protocol MeetupCommunicatorDelegate 
- (void)receivedGroupsJSON:(NSData *)objectNotation;
- (void)fetchingGroupsFailedWithError:(NSError *)error;
@end

MeetupCommunicator类负责Meetup API在获取JSON数据过程中的通讯。它依赖于MeetupCommunicatorDelegate来处理解析JSON数据的任务。通讯类本身并不知道JSON数据是如何被处理的。通讯类只关心为Meetup API和获取JSON原始结果数据的过程创建连接。

代理被创建以后,创建另一个类文件,并命名为MeetupCommunicator。打开头文件,键入如下代码:

#import <CoreLocation/CoreLocation.h>
@protocol MeetupCommunicatorDelegate;
@interface MeetupCommunicator : NSObject
@property (weak, nonatomic) id<MeetupCommunicatorDelegate> delegate;
- (void)searchGroupsAtCoordinate:(CLLocationCoordinate2D)coordinate;
@end

我们创建了一个属性,用来跟踪记录通讯代理。然后定义了一个方法,用来查找特定方位的聚会信息。接下来,打开MeetupCommunicator.m文件,替换成如下代码:

#import "MeetupCommunicator.h"
#import "MeetupCommunicatorDelegate.h"
#define API_KEY @"1f5718c16a7fb3a5452f45193232"
#define PAGE_COUNT 20
@implementation MeetupCommunicator
- (void)searchGroupsAtCoordinate:(CLLocationCoordinate2D)coordinate
{
    NSString *urlAsString = [NSString stringWithFormat:@"https://api.meetup.com/2/groups?lat=%f&lon=%f&page=%d&key=%@", coordinate.latitude, coordinate.longitude, PAGE_COUNT, API_KEY];
    NSURL *url = [[NSURL alloc] initWithString:urlAsString];
    NSLog(@"%@", urlAsString);
    [NSURLConnection sendAsynchronousRequest:[[NSURLRequest alloc] initWithURL:url] queue:[[NSOperationQueue alloc] init] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
        if (error) {
            [self.delegate fetchingGroupsFailedWithError:error];
        } else {
            [self.delegate receivedGroupsJSON:data];
        }
    }];
}
@end

Meetup API需要一个key来工作。如果你注册了账号,需要从API Key页面得到key的话,那么简单地点击一下文本框旁边的?图标就可以得到key。把里面的文本拷贝出来,用自己的API key将API_KEY宏的值替换掉。

正如上面提到的,我们使用下面的Meetup API来查找指定方位的聚会信息。API接受经纬度格式的方位信息。下面就是Meetup URL连接的示例:

https://api.meetup.com/2/groups?lat=51.509980&lon=-0.133700&page=20&key=1f5718c16a7fb3a5452f45193232

在方法的实现中,我们首先使用特定的经纬度,以及聚会的数量和API key来构建API URL。为了在UI部分不卡住,我们通过使用NSURLConnection中的“sendAsynchronousRequest:”方法为URL请求异步加载数据。最终检索到JSON数据,将它传递给代理进一步进行处理。

解析JSON数据并绑定Group对象

MeetupManager收到JSON格式的数据时,使用GroupBuilder的类方法将数据转换为Group对象。使用Objective-C类模版创建一个新文件,命名为GroupBuilder。打开头文件,粘贴如下代码:

#import <Foundation/Foundation.h>
@interface GroupBuilder : NSObject
+ (NSArray *)groupsFromJSON:(NSData *)objectNotation error:(NSError **)error;
@end

接下来,打开“GroupBuilder.m”文件,实现对应的方法:

#import "GroupBuilder.h"
#import "Group.h"
@implementation GroupBuilder
+ (NSArray *)groupsFromJSON:(NSData *)objectNotation error:(NSError **)error
{
    NSError *localError = nil;
    NSDictionary *parsedObject = [NSJSONSerialization JSONObjectWithData:objectNotation options:0 error:&localError];
    if (localError != nil) {
        *error = localError;
        return nil;
    }
    NSMutableArray *groups = [[NSMutableArray alloc] init];
    NSArray *results = [parsedObject valueForKey:@"results"];
    NSLog(@"Count %d", results.count);
    for (NSDictionary *groupDic in results) {
        Group *group = [[Group alloc] init];
        for (NSString *key in groupDic) {
            if ([group respondsToSelector:NSSelectorFromString(key)]) {
                [group setValue:[groupDic valueForKey:key] forKey:key];
            }
        }
        [groups addObject:group];
    }
    return groups;
}
@end

方法“groupsFromJSON”被设计为将原始的JSON数据转换成Group对象的数组。iOS 5发布以来,iOS SDK中名为NSJSONSerialization的类用来解析JSON数据。开发人员使用该类可以将JSON数据转换成Foundation框架中的对象,也可以将Foundation框架中的对象转回JSON数据。

当使用NSJSONSerialization读取JSON数据时,所有的键列表(keyed list)都被自动转换成NSDictionary对象。数组则会被转换为NSArray实例。键列表中任何带有已命名项名称的字符串都会被转换为NSString,而纯数字字符串会被转换为NSNumber对象。最后,任何为空的值都会被用NSNull表示。

在本文前面部分向你展示的响应示例中,Meetup API返回的JSON响应包括两部分,结果数据和元(meta)信息。而我们只需要“结果”部分。代码相当直观明了,我们循环遍历了所有的结果,查看了其中的每一个NSDictionary。然后创建了Group对象,对其填充了必要信息,然后将Group对象添加到可变数组当中。

用MeetupManager把各部分集中起来

你现在应该明白JSON是如何工作的了,如何解析数据并将数据转换成对象。接下来,我们要实现MeetupManager,作为底层类的协调者来使用。

首先,使用Objective-C协议模版创建一个文件,命名为MeetupManagerDelegate。在MeetupManagerDelegate.h文件中添加如下代码:

@protocol MeetupManagerDelegate
- (void)didReceiveGroups:(NSArray *)groups;
- (void)fetchingGroupsFailedWithError:(NSError *)error;
@end

这个代理声明了两个方法,当group对象需要的时候会被MeetupManager调用。当从Meetup响应中检索到的group列表被解析时,调用第一个方法。而当发生错误的时候,第二个方法会被调用。MeetupManagerDelegate将会被MasterViewController实现,我们会在随后内容中讲解。

接下来,使用Objective-C类模版创建一个新文件,命名为MeetupManager。打开头文件,添加如下代码:

#import <Foundation/Foundation.h>
#import <CoreLocation/CoreLocation.h>
#import "MeetupManagerDelegate.h"
#import "MeetupCommunicatorDelegate.h"
@class MeetupCommunicator;
@interface MeetupManager : NSObject<MeetupCommunicatorDelegate>
@property (strong, nonatomic) MeetupCommunicator *communicator;
@property (weak, nonatomic) id<MeetupManagerDelegate> delegate;
- (void)fetchGroupsAtCoordinate:(CLLocationCoordinate2D)coordinate;
@end

前面说过,MeetupManager扮演着门面的角色。应用程序的控制器可与实体类(Group)一起工作,不需要知道任何关于网络连接的细节,也不需要知道JSON如何获取和解析,以及group对象如何创建。控制器需要知道的仅仅是使用“fetchGroupsAtCoordinate:”方法获取Meetup聚会的信息。

我们设置一个属性来记录稍后要用到的communicator实例,还有用来追踪MeetupManagerDelegate的属性。“fetchGroupsAtCoordinate:”方法将被控制器用来获取group。

下一步,打开MeetupManager.m文件,替换成下面的代码:

#import "GroupBuilder.h"
#import "MeetupCommunicator.h"
@implementation MeetupManager
- (void)fetchGroupsAtCoordinate:(CLLocationCoordinate2D)coordinate
{
    [self.communicator searchGroupsAtCoordinate:coordinate];
}
#pragma mark - MeetupCommunicatorDelegate
- (void)receivedGroupsJSON:(NSData *)objectNotation
{
    NSError *error = nil;
    NSArray *groups = [GroupBuilder groupsFromJSON:objectNotation error:&error];
    if (error != nil) {
        [self.delegate fetchingGroupsFailedWithError:error];
    } else {
        [self.delegate didReceiveGroups:groups];
    }
}
- (void)fetchingGroupsFailedWithError:(NSError *)error
{
    [self.delegate fetchingGroupsFailedWithError:error];
}

在这里实现了fetchGroupsAtCoordinate:coordinate方法,通过使用communicator的searchGroupsAtCoordinate:coordinate方法来获取group。还实现了MeetupCommunicatorDelegate下面的那些方法,用来处理communicator检索到的JSON格式的结果。

协议receivedGroupsJSON:objectNotation第一个方法中代码使用GroupBuilder的类方法将JSON数据填入Group对象,然后将Group对象通知给方法的代理。如果在处理请求的过程中出现任何问题,我们就调用代理的另一个方法(fetchingGroupsFailedWithError)来通知控制器,让控制器知道出现了问题。

显示Group列表

首要任务就是要把这些类都拿来放到一起让MeetupManager能够工作。打开MasterViewController.m文件,导入所需的头文件,更新接口,并且声明MeetupManager实例:

#import "Group.h"
#import "MeetupManager.h"
#import "MeetupCommunicator.h"
@interface MasterViewController () <MeetupManagerDelegate> {
    NSArray *_groups;
    MeetupManager *_manager;
}

随后我们就要实现在MeetupManagerDelegate中所定义的方法。但首先我们先在viewDidLoad方法中要实例化MeetupManager:

- (void)viewDidLoad
{
    [super viewDidLoad];
    _manager = [[MeetupManager alloc] init];
    _manager.communicator = [[MeetupCommunicator alloc] init];
    _manager.communicator.delegate = _manager;
    _manager.delegate = self;
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(startFetchingGroups:)
                                                 name:@"kCLAuthorizationStatusAuthorized"
                                               object:nil];
}

正在实例化一个新的manager,然后用一个新的实例给它的communicator属性赋值,最后让当前的视图控制器追踪一切变化。当警告弹出告诉用户使用定位服务开始,就调用startFetchingGroups:方法从服务端获取group,这里的观察者(observer)会捕捉一切用户的响应。

接下来,打开“MasterViewController.m”,键入如下代码开始获取信息:

- (void)startFetchingGroups:(NSNotification *)notification
{
    [_manager fetchGroupsAtCoordinate:self.locationManager.location.coordinate];
}

由于视图控制器应该遵循MeetupManagerDelegate协议,所以要实现其中的方法:

- (void)didReceiveGroups:(NSArray *)groups
{
    _groups = groups;
    [self.tableView reloadData];
}
- (void)fetchingGroupsFailedWithError:(NSError *)error
{
    NSLog(@"Error %@; %@", error, [error localizedDescription]);
}

当从Meetup服务端返回的实例中获得了Meetup聚会信息,将调用“didReceiveGroups:”方法。我们这里要做的是简单地在table view里面显示新数据。

最后,构建table view的相关方法:

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return _groups.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    DetailCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell" forIndexPath:indexPath];
    Group *group = _groups[indexPath.row];
    [cell.nameLabel setText:group.name];
    [cell.whoLabel setText:group.who];
    [cell.locationLabel setText:[NSString stringWithFormat:@"%@, %@", group.city, group.country]];
    [cell.descriptionLabel setText:group.description];
    return cell;
}

上段代码非常直观,我们从数组中检索那些group,然后将group的信息填入表格中。

编译和运行

现在,可以测试这个应用程序了。在模拟器中编译并运行(如果注册了iOS开发者计划,可以使用真实的iOS设备)。将模拟器的位置设置为英国伦敦,你就会得到像下面截屏中的Meetup聚会信息了。

1439437273757_757

提示:如果你不了解iPhone模拟器中的方位测试,可以看看Core Location tutorial

总结

在本文中,我们通过采用与JSON数据相配的设计模式,讲解了编程的很多方面,你应该具有了获取和解析JSON数据的实践经验。

如果你对设计模式是生手,这篇文章可能稍许复杂。但这不应该难住你而放弃在应用程序中使用JSON。iOS的SDK让这一过程变得容易了。总之就是,简简单单地创建一个URL连接,通过内置的NSJSONSerialization类获取和解析JSON数据。

在网上可以找到各种各样的免费API(比如KivaTMDb)。当作练习,尝试使用另一个API再开发一款应用程序。也就是说,你可以使用Kiva API开发一款显示近期筹贷款列表简单应用程序。

作为参考,可以在这里下载全部Xcode项目代码