首页 > 系统 > iOS > 正文

iOS app: Weather Forecast and Huawei Remote Control

2019-11-09 16:24:17
字体:
来源:转载
供稿:网友

工程结构如下:

ViewController :捕捉用户手势,发指令给华为盒子。WeatherInfo :从网络服务获取空气质量指数和天气预报。Settings.bundle :app的一些设置放到iOS的设置里。

重要代码记录如下:

ViewController.h

////  ViewController.h//  HuaWeiRemoteCtl////  Created by jia xiaodong on 12/16/15.//  Copyright (c) 2015 homemade. All rights reserved.//#import <UIKit/UIKit.h>#import <Foundation/Foundation.h>#import <SystemConfiguration/SystemConfiguration.h>#import "WeatherInfo.h"enum NetworkStatus{	NETWORK_NOT_REACHABLE	= 0,	NETWORK_THRU_WIFI		= 1, // network traffic is through local Wifi	NETWORK_THRU_WWAN		= 2, // 3G, GPRS, Edge or other cellular data network};@interface ViewController : UIViewController<NSURLConnectionDelegate>{	// These magic numbers are extracted from http://health.vmall.com/mediaQ/controller.jsp	enum ActionCode	{		ACTION_OK		= 0,		ACTION_LEFT		= 1,		ACTION_DOWN		= 2,		ACTION_RIGHT	= 3,		ACTION_UP		= 4,		ACTION_BACK		= 5,		ACTION_HOME		= 6,		ACTION_MENU		= 7,		ACTION_POWER	= 8,		ACTION_VOL_UP	= 9,		ACTION_VOL_DOWN	= 10	};		NSString* mBoxipAddress;		//! user can pan his finger to four directions	enum ActionDirection	{		DIRECTION_INVALID,		DIRECTION_LEFT	= ACTION_LEFT,		DIRECTION_DOWN	= ACTION_DOWN,		DIRECTION_RIGHT	= ACTION_RIGHT,		DIRECTION_UP	= ACTION_UP	} mCurrDirection, mPrevDirection;	//! process long press gesture when finger panning	NSTimer* mRepeatDelayer;	NSTimer* mActionRepeater;		//! Single-tap | Double-tap OK	BOOL mIsDoubleTapOK;	UITapGestureRecognizer* mTapGesture;		BOOL mIsShowingForecastInfo;	BOOL mIsShowingTodayDetails;    LocationID mGeoLocation;		//! monitor device's network traffic path	enum NetworkStatus mNetworkStatus;		//! weather info	UILabel* mWeatherInfoBoard;	UIScrollView* mScrollLabel;	// text's too long, so need a scroll-effect	UIButton* mToggleInfoButton;	WeatherFullReport* mWeatherInfo;	NSMutableString* mLabelText;	UIActivityIndicatorView* mBusyCircle;        //    BOOL mAqiReady, mDetailReady;}@property (nonatomic, copy) NSString* BoxIPAddress;@property (atomic, assign) enum ActionDirection CurrentDir;@property (atomic, assign) enum ActionDirection PreviousDir;- (IBAction)BtnVolDown:(UIButton *)sender;- (IBAction)BtnVolUp:(UIButton *)sender;- (IBAction)power:(id)sender;- (IBAction)home:(id)sender;- (IBAction)menu:(id)sender;- (IBAction)back:(id)sender;//! all remote control's functionalities- (void)performAction:(enum ActionCode)code;//! delegate methods- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error;//!- (void)registerTapGesture;//!- (void)resetPanTimer;+ (BOOL)isValidIPv4Address:(NSString*)ip;//! //! monitor device's network traffic path- (void) startMonitorNetwork;- (void) stopMonitorNetwork;- (void) parseReachabilityFlags: (SCNetworkReachabilityFlags)flags;//!- (void) toggleWeatherButton;@endViewController.mm

////  ViewController.m//  HuaWeiRemoteCtl////  Created by jia xiaodong on 12/16/15.//  Copyright (c) 2015 homemade. All rights reserved.//#import "ViewController.h"#include <memory>#include <netinet/in.h>NSString* KEY_BOX_IP_ADDRESS = @"box_ip_address";NSString* KEY_DOUBLE_TAP_OK  = @"double_tap_ok";NSString* DEFAULT_IP_ADDRESS = @"192.168.1.106";	// default box IPv4 addressNSString* KEY_FORECAST_INFO	 = @"forecast_info";NSString* KEY_TODAY_DETAILS	 = @"detailed_forecast";NSString* KEY_ALARM_INFO	 = @"alarm_info";NSString* KEY_LOCATION       = @"location_setting";static SCNetworkReachabilityRef sReachabilityRef;//! callback which can receive device's event of network path changingvoid ReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkReachabilityFlags flags, void* info){	ViewController* checker = static_cast<ViewController *>(info);	[checker parseReachabilityFlags:flags];}@interface ViewController ()@end@implementation ViewController@synthesize BoxIPAddress = mBoxIPAddress;@synthesize CurrentDir   = mCurrDirection;@synthesize PreviousDir  = mPrevDirection;#pragma mark -#pragma mark variable initialization- (void)viewDidLoad{    [super viewDidLoad];	// Do any additional setup after loading the view, typically from a nib.	mBoxIPAddress = DEFAULT_IP_ADDRESS;	mIsDoubleTapOK = YES;	mCurrDirection = mPrevDirection = DIRECTION_INVALID;	mRepeatDelayer = nil;	mActionRepeater = nil;	mTapGesture = nil;		// double tap: button OK	[self registerTapGesture];		// pinch open: volume up; pinch close: volume down	UIPinchGestureRecognizer* pinch = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(handlePinch:)];	[self.view addGestureRecognizer:pinch];	[pinch release];	// pan to up, down, left and right direction	UIPanGestureRecognizer* pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePan:)];	[self.view addGestureRecognizer:pan];	[pan release];		//! show AQI at screen bottom	const int PADDING = 5, HEIGHT = 30;	CGRect rect = [self.view bounds];	mToggleInfoButton = [[UIButton alloc] initWithFrame:CGRectMake(PADDING, rect.size.height-PADDING-HEIGHT, rect.size.width-PADDING*2, HEIGHT)];	[mToggleInfoButton setBackgroundColor:[UIColor grayColor]];	[mToggleInfoButton setTitle:@"查看天气" forState:UIControlStateNormal];	[mToggleInfoButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];	[mToggleInfoButton setTitleColor:[UIColor darkGrayColor] forState:UIControlStateHighlighted];	[mToggleInfoButton addTarget:self action:@selector(toggleWeatherButton) forControlEvents:UIControlEventTouchUpInside];	[self.view addSubview:mToggleInfoButton];	mWeatherInfoBoard = nil;	mScrollLabel = nil;	mLabelText = nil;	mBusyCircle = nil;	mWeatherInfo = nil;		[self getCurrentNetworkPath];	[self startMonitorNetwork];}- (void)didReceiveMemoryWarning{    [super didReceiveMemoryWarning];    // Dispose of any resources that can be recreated.}#pragma mark -#pragma mark user action feedback- (IBAction)BtnVolDown:(UIButton *)sender {	[self performAction:ACTION_VOL_DOWN];}- (IBAction)BtnVolUp:(UIButton *)sender {	[self performAction:ACTION_VOL_UP];}- (IBAction)power:(id)sender {	[self performAction:ACTION_POWER];}- (IBAction)home:(id)sender {	[self performAction:ACTION_HOME];}- (IBAction)menu:(id)sender {	[self performAction:ACTION_MENU];}- (IBAction)back:(id)sender {	[self performAction:ACTION_BACK];}- (void)performAction:(enum ActionCode)code {	// all remote control functionalities must work under same local network (WiFi).	if (mNetworkStatus != NETWORK_THRU_WIFI)	{		return;	}	dispatch_async(dispatch_get_main_queue(), ^{		NSString* urlTemplate = [[NSString alloc] initWithFormat:@"http://%@:7766/remote?key=%d", self.BoxIPAddress, code];		NSLog(@"[HuaWei] request: %@", urlTemplate);				NSURL* url = [[NSURL alloc] initWithString:urlTemplate];		NSURLRequest* request = [[NSURLRequest alloc] initWithURL:url													  cachePolicy:NSURLRequestReloadIgnoringCacheData												  timeoutInterval:1.0];		NSURLConnection* connection = [[NSURLConnection alloc] initWithRequest:request																	  delegate:self															  startImmediately:YES];		[connection release];		[request	release];		[url release];		[urlTemplate release];	});}- (void)handlePan:(UIPanGestureRecognizer*)gesture {	if (gesture.state == UIGestureRecognizerStateBegan)	{		mCurrDirection = mPrevDirection = DIRECTION_INVALID;	}	else if (gesture.state == UIGestureRecognizerStateChanged)	{		CGPoint pt = [gesture translationInView:self.view];		float xabs = fabsf(pt.x);		float yabs = fabsf(pt.y);		if (xabs > yabs)		{			mCurrDirection = pt.x > 0 ? DIRECTION_RIGHT: DIRECTION_LEFT;		}		else if (xabs < yabs)		{			mCurrDirection = pt.y > 0 ? DIRECTION_DOWN : DIRECTION_UP;		}				if (mPrevDirection != mCurrDirection)		{			//! Firstly, respond to user's input			[self performAction:(enum ActionCode)mCurrDirection];			//! Secondly, begin to monitor user's long press			//! If user keeps initial direction for more than 0.5 second, accelerate that input			[self resetPanTimer];			mRepeatDelayer = [NSTimer scheduledTimerWithTimeInterval:0.5														 target:self													   selector:@selector(startRepeater)													   userInfo:nil														repeats:NO];	// no repeat: run-loop won't keep reference			mPrevDirection = mCurrDirection;		}	}	else if (gesture.state == UIGestureRecognizerStateEnded)	{		mCurrDirection = mPrevDirection = DIRECTION_INVALID;		[self resetPanTimer];	}}- (void)startRepeater{	mRepeatDelayer = nil;	// no repeat: run-loop won't keep reference. so no need to invalidate it	mActionRepeater = [NSTimer scheduledTimerWithTimeInterval:0.2													   target:self													 selector:@selector(handleLongPress:)													 userInfo:nil													  repeats:YES];}- (void)handleTap:(UITapGestureRecognizer*)gesture {	CGPoint pt = [gesture locationInView:self.view];	if (pt.y > 170)	// upper screen is full of buttons	{		[self performAction:ACTION_OK];	}}- (void)handlePinch:(UIPinchGestureRecognizer*)gesture {	if (gesture.state == UIGestureRecognizerStateEnded)	{		[self performAction:(gesture.scale > 1.0f ? ACTION_VOL_UP : ACTION_VOL_DOWN)];	}}- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {	/*	NSString* text = [[NSString alloc] initWithFormat:@"[%@] %@", mBoxIPAddress, [error localizedDescription]];	UIAlertView* alert = [[UIAlertView alloc] initWithTitle:@"Hua Wei Box"													message:text												   delegate:nil										  cancelButtonTitle:@"OK"										  otherButtonTitles:nil, nil];	[alert show];	[alert release];	[text release];	 */}- (void)handleLongPress:(NSTimer*) timer {	enum ActionCode action = (enum ActionCode)self.CurrentDir;	[self performAction:action];}- (void)resetPanTimer {	[mRepeatDelayer invalidate]; // Run-loop will release timer's reference	mRepeatDelayer = nil;	[mActionRepeater invalidate];	mActionRepeater = nil;}- (void)registerTapGesture {	NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];	//! [1] Box IP address	NSUserDefaults *config = [NSUserDefaults standardUserDefaults];	NSString* ip = [config stringForKey:KEY_BOX_IP_ADDRESS];	if ([ViewController isValidIPv4Address:ip] && [ip compare:mBoxIPAddress] != NSOrderedSame)	{		mBoxIPAddress = ip;	}		//! [2] Is double-tap / single-tap effective	BOOL isDoubleTapOK = [config boolForKey:KEY_DOUBLE_TAP_OK];	if (isDoubleTapOK != mIsDoubleTapOK || mTapGesture == nil)	{		mIsDoubleTapOK = isDoubleTapOK;		[self.view removeGestureRecognizer:mTapGesture];		[mTapGesture release];		mTapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTap:)];		[mTapGesture setNumberOfTapsRequired:(mIsDoubleTapOK ? 2 : 1)];		[self.view addGestureRecognizer:mTapGesture];	}		mIsShowingForecastInfo = [config boolForKey:KEY_FORECAST_INFO];	mIsShowingTodayDetails = [config boolForKey:KEY_TODAY_DETAILS];    mGeoLocation = static_cast<LocationID>([config integerForKey:KEY_LOCATION]);	[pool release];}+ (BOOL)isValidIPv4Address:(NSString*)ip {	NSString * regex = @"^([01]?//d//d?|2[0-4]//d|25[0-5])//."						"([01]?//d//d?|2[0-4]//d|25[0-5])//."						"([01]?//d//d?|2[0-4]//d|25[0-5])//."						"([01]?//d//d?|2[0-4]//d|25[0-5])$";		NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", regex];	return [predicate evaluateWithObject:ip];}#pragma mark -#pragma mark network status detection- (void) startMonitorNetwork {	if (sReachabilityRef)	{		SCNetworkReachabilityContext context = {0, self, NULL, NULL, NULL};		if (SCNetworkReachabilitySetCallback(sReachabilityRef, ReachabilityCallback, &context))		{			SCNetworkReachabilityScheduleWithRunLoop(sReachabilityRef, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);		}	}}- (void) getCurrentNetworkPath {	struct sockaddr_in zeroAddr;	bzero(&zeroAddr, sizeof(zeroAddr));	zeroAddr.sin_len = sizeof(zeroAddr);	zeroAddr.sin_family = AF_INET;		sReachabilityRef =  SCNetworkReachabilityCreateWithAddress(NULL, (struct sockaddr *) &zeroAddr);	SCNetworkReachabilityFlags flags;	SCNetworkReachabilityGetFlags(sReachabilityRef, &flags);		[self parseReachabilityFlags:flags];}- (void) stopMonitorNetwork {	if (sReachabilityRef)	{		SCNetworkReachabilityUnscheduleFromRunLoop(sReachabilityRef, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);	}}- (void) parseReachabilityFlags: (SCNetworkReachabilityFlags)flags{	if (flags & kSCNetworkFlagsReachable)	{		mNetworkStatus = (flags & kSCNetworkReachabilityFlagsIsWWAN) ? NETWORK_THRU_WWAN : NETWORK_THRU_WIFI;	}	else	{		mNetworkStatus = NETWORK_NOT_REACHABLE;	}}#pragma mark -#pragma mark UI of weather info- (void) toggleWeatherButton{    NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];    std::unique_ptr<NSAutoreleasePool, void(*)(NSAutoreleasePool*)> scopePool(pool, [](NSAutoreleasePool* p) {        [p release];    });    	if (mWeatherInfo == nil)	{		if (mNetworkStatus == NETWORK_NOT_REACHABLE)		{			UIAlertView* alert = [[UIAlertView alloc] initWithTitle:@"天气"															message:@"没网,请先联网!"														   delegate:nil												  cancelButtonTitle:@"OK"												  otherButtonTitles:nil];			[alert show];			[alert release];			return;		}		mLabelText = [[NSMutableString alloc] init];		[self startActivityAnimation];		[mToggleInfoButton setTitle:@"关闭" forState:UIControlStateNormal];        mWeatherInfo = [[WeatherFullReport alloc] initWithLocation:mGeoLocation];        [self checkWeatherSetting];        [mWeatherInfo queryWithCompletionHandler:^(BOOL aqiReady, BOOL otherReady) {            if (aqiReady) {                mAqiReady = TRUE;            }            if (otherReady) {                mDetailReady = TRUE;            }            			[mLabelText setString:@""];			[self organizeTextForPresentation];			dispatch_async(dispatch_get_main_queue(), ^{				if (mScrollLabel != nil) {					[self destroyScrollLabel];				}				[self createScrollLable];				[mWeatherInfoBoard setText:mLabelText];                				if (mAqiReady && mDetailReady) {					[self stopActivityAnimation];                    mAqiReady = mDetailReady = FALSE; // reset				}			});		}];	}	else	{		[self destroyScrollLabel];		[mWeatherInfo release]; mWeatherInfo = nil;		[mLabelText release]; mLabelText = nil;		[self stopActivityAnimation];		[mToggleInfoButton setTitle:@"查看天气" forState:UIControlStateNormal];	}}- (void)checkWeatherSetting{    mWeatherInfo.details.options = WEATHER_NOW;    if (mIsShowingForecastInfo) {        mWeatherInfo.details.options |= WEATHER_FORECAST;    }    if (mIsShowingTodayDetails) {        mWeatherInfo.details.options |= WEATHER_DETAIL_FORECAST;    }}- (void) createScrollLable{	CGRect rect = [self.view bounds];	CGFloat PADDING = 5;	CGFloat X = PADDING, Y = PADDING + 170;	CGFloat SCROLL_WIDTH = rect.size.width-2*PADDING;	CGFloat SCROLL_HEIGHT = rect.size.height-Y-50;		mScrollLabel = [[UIScrollView alloc] initWithFrame:CGRectMake(X, Y, SCROLL_WIDTH, SCROLL_HEIGHT)];		UIFont* font = [UIFont systemFontOfSize:14];	CGSize contentSize = [mLabelText sizeWithFont:font constrainedToSize:CGSizeMake(SCROLL_WIDTH, 2048)];		mWeatherInfoBoard = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, contentSize.width, contentSize.height)];	mWeatherInfoBoard.font = font;	mWeatherInfoBoard.lineBreakMode = NSLineBreakByWordWrapping;	mWeatherInfoBoard.numberOfLines = 0;	mWeatherInfoBoard.backgroundColor = [UIColor clearColor];		mScrollLabel.contentSize = mWeatherInfoBoard.frame.size;	[mScrollLabel addSubview:mWeatherInfoBoard];	[self.view addSubview:mScrollLabel];}- (void) destroyScrollLabel{	[mScrollLabel removeFromSuperview];	[mWeatherInfoBoard removeFromSuperview];	[mWeatherInfoBoard release]; mWeatherInfoBoard = nil;	[mScrollLabel release]; mScrollLabel = nil;}- (void) organizeTextForPresentation{    if ([@"" compare:mWeatherInfo.aqi.result] != NSOrderedSame) {		[mLabelText appendFormat:@"%@", mWeatherInfo.aqi.result];	}	    if ([@"" compare:mWeatherInfo.details.result] != NSOrderedSame) {		[mLabelText appendFormat:@"/n%@", mWeatherInfo.details.result];	}}- (void) startActivityAnimation{	CGRect screen = self.view.bounds;	mBusyCircle = [[UIActivityIndicatorView alloc] initWithFrame:CGRectMake(screen.size.width/2, screen.size.height/2, 10, 10)];	[mBusyCircle setColor:[UIColor grayColor]];	[self.view addSubview:mBusyCircle];	[mBusyCircle startAnimating];}- (void) stopActivityAnimation{	if (mBusyCircle) {		[mBusyCircle stopAnimating];		[mBusyCircle removeFromSuperview];		[mBusyCircle release]; mBusyCircle = nil;	}}@endWeatherInfo.h

////  WeatherInfo.h//  HuaWeiRemoteCtl////  Created by jia xiaodong on 5/2/16.//  Modified on 2/7/17//typedef NS_ENUM(NSUInteger, LocationID){    Beijing_GuoFengMeiLun,    Beijing_ZhongGuanCun,    ShiJiaZhuang_WorkerHospital,    QinHuangDao_LuLong};#pragma mark -#pragma mark a common interface for general purpose Weather info provider@protocol WeatherServiceProvider <NSObject>@required- (void)launchQuery:(LocationID)location completionHander:(void(^)(BOOL success))handler;+ (NSString*)StringFromId:(LocationID)loc;@end#pragma mark -#pragma mark Air Quality Index from aqicn.org/** http://aqicn.org/map/beijing/cn/ A professional AQI website, free of charge, gobally covered.*/@interface AirQualityIndex : NSObject <WeatherServiceProvider>{	NSString* mPM2_5;}@property (readonly) NSString* result;- (void)launchQuery:(LocationID)location completionHander:(void(^)(BOOL success))handler;+ (NSString*)StringFromId:(LocationID)loc;@end#pragma mark -#pragma mark a base class for detailed weather infotypedef NS_OPTIONS(NSUInteger, WeatherOption){    WEATHER_NOW             = 1<<0, // today's weather    WEATHER_FORECAST        = 1<<1, // weather forecast for several days of future    WEATHER_DETAIL_FORECAST = 1<<2, // detailed forecast (for today only, hourly forecast)    WEATHER_ALARM           = 1<<3  // not implemented};@interface DetailedWeatherInfo : NSObject <WeatherServiceProvider>{    WeatherOption mWeatherOptions;    NSString* mResult;}@property WeatherOption options;@property (nonatomic, copy) NSString* result;@end#pragma mark -#pragma mark weather service from www.heweather.com (free for personal use)/** Registered as a free user by an email address, you can get 3000 queries per day. Personal key: http://console.heweather.com/my/service Docs: http://docs.heweather.com/224291 City list: http://docs.heweather.com/224293 */@interface HeWeatherInfoNode : NSObject{    NSString* date;    NSString* astro;            // rise/set time of sun and moon    NSString* condition;        // sunny, cloudy, rainny, ...    NSString* temperature;      // Celsius degree    NSString* humidity;         // relative humidity (%)    NSString* probability;      // probability of precipitation    NSString* precipitation;    // amount of precipitation (mm)    NSString* pressure;         // atmospheric pressure (mmHg)    NSString* uv;               // ultraviolet-ray radiation degree    NSString* visibility;       // km    NSString* wind;             // wind}@property (nonatomic, copy) NSString *date, *astro, *condition, *probability;@property (nonatomic, copy) NSString *temperature, *humidity, *precipitation;@property (nonatomic, copy) NSString *pressure, *uv, *visibility, *wind;@property (nonatomic, readonly) NSString *description;- (id)initWithJson:(NSDictionary *)nfo;@end@interface HeWeather : DetailedWeatherInfo{    NSString* mAqi;              // air quality index    HeWeatherInfoNode* mNow;    NSArray<HeWeatherInfoNode *> *mForecast;    NSArray<HeWeatherInfoNode *> *mDetailedForecast;}@end#pragma mark -#pragma mark put all weather info together to make a report@interface WeatherFullReport : NSObject{    LocationID mLocation;	AirQualityIndex			*mAQI;	DetailedWeatherInfo		*mWeatherProvider;}@property (nonatomic) LocationID location;@property (nonatomic, readonly) AirQualityIndex *aqi;@property (nonatomic, readonly) DetailedWeatherInfo *details;- (id)initWithLocation:(LocationID)location;- (void)queryWithCompletionHandler:(void (^)(BOOL aqiReady, BOOL otherReady))handler;@endWeatherInfo.mm

////  WeatherInfo.mm//  HuaWeiRemoteCtl////  Created by jia xiaodong on 5/2/16.//  Modified on 2/7/17//#import <Foundation/Foundation.h>#import "WeatherInfo.h"#include <memory>#pragma mark -#pragma mark AirQualityIndex@implementation AirQualityIndex@synthesize result = mPM2_5;- (id)init{	if (self = [super init])	{		mPM2_5 = nil;	}	return self;}/* After analyzing aqicn.org by Web Developer Tools in Firefox, I got below 2 APIs: https://api.waqi.info/api/feed/@{city_id}/obs.en.json  : super detailed info https://api.waqi.info/api/feed/@{city_id}/now.json     : concise info  the 2nd is fairly enough to fit my needs. I only need an AQI value, not its components. */- (void)launchQuery:(LocationID)loc completionHander:(void(^)(BOOL success))handler{    NSString* location = [AirQualityIndex StringFromId:loc];    NSString* urlTemplate = [[NSString alloc] initWithFormat:@"https://api.waqi.info/api/feed/@%@/now.json", location];    NSURL* url = [[NSURL alloc] initWithString:urlTemplate];    NSURLsessionDataTask* webRequest = [[NSURLSession sharedSession] dataTaskWithURL:url                                                                   completionHandler:^(NSData* data, NSURLResponse* response, NSError* error)    {        if (!error && ([(NSHTTPURLResponse*)response statusCode] == 200))        {            [self parseResponse:data];                        // callback            if (handler)            {                handler(mPM2_5 != nil);            }        }    }];    [webRequest resume];    [url release];    [urlTemplate release];}//! the location code is found through Web Developer Tools in Firefox//! when opening aqicn.org webpage.+ (NSString*)StringFromId:(LocationID)loc{    switch (loc) {        case Beijing_ZhongGuanCun: // Wanliu, Haidian            return @"452";        case Beijing_GuoFengMeiLun:// BDA, Yizhuang            return @"460";        case ShiJiaZhuang_WorkerHospital:            return @"644";        case QinHuangDao_LuLong:            return @"5614";        default:            return @"";    }}- (void)parseResponse:(NSData*)response{    std::unique_ptr<NSAutoreleasePool, void(*)(NSAutoreleasePool*)> scopePool(        [[NSAutoreleasePool alloc] init],           // pool ptr        [](NSAutoreleasePool* p) { [p release]; }); // deleter        NSError* err = nil;    NSDictionary* info = [NSJSONSerialization JSONObjectWithData:response                                                         options:NSJSONReadingMutableLeaves                                                           error:&err];    NSDictionary* dict = [info objectForKey:@"rxs"];    if (!dict) {        return;    }    if ([@"1" compare:[dict objectForKey:@"ver"]] != NSOrderedSame) {        return;    }    if ([@"ok" compare:[dict objectForKey:@"status"]] != NSOrderedSame) {        return;    }    dict = [[dict objectForKey:@"obs"] objectAtIndex:0];    if ([@"ok" compare:[dict objectForKey:@"status"]] != NSOrderedSame) {        return;    }    dict = [dict objectForKey:@"msg"];    NSNumber* aqi = [dict objectForKey:@"aqi"];    NSDictionary* city = [dict objectForKey:@"city"];    dict = [dict objectForKey:@"time"];    NSString* time = [NSString stringWithFormat:@"%@, %@",[dict objectForKey:@"s"], [dict objectForKey:@"tz"]];    mPM2_5 = [[NSString alloc] initWithFormat:@"%@/n%@/nAQI (from aqicn.org): %lu/n", time, [city objectForKey:@"name"], [aqi unsignedLongValue]];}@end#pragma mark -#pragma mark DetailedWeatherInfo: not a discrete class@implementation DetailedWeatherInfo@synthesize options = mWeatherOptions;@synthesize result;- (id)init{    if (self = [super init]) {        mWeatherOptions = WEATHER_NOW;        mResult = nil;    }    return self;}- (void)dealloc{    [mResult release];    [super dealloc];}@end#pragma mark -#pragma mark HeWeather: a so called "free forever" service@implementation HeWeatherInfoNode@synthesize date, astro, condition, temperature, humidity;@synthesize precipitation, pressure, uv, visibility, wind;@synthesize probability;@dynamic description;- (id)init{    if (self = [super init]) {        self.date = nil;        self.astro = nil;        self.condition = nil;        self.temperature = nil;        self.humidity = nil;        self.probability = nil;        self.precipitation = nil;        self.pressure = nil;        self.uv = nil;        self.visibility = nil;        self.wind = nil;    }    return self;}- (id)initWithJson:(NSDictionary *)nfo{    self = [self init];    if (self) {        NSString* value = nil;        if ((value = [nfo objectForKey:@"date"])) {            self.date = [value copy];        } else {            NSDate* now = [NSDate date];            NSDateFormatter *formatter = [[NSDateFormatter alloc] init];            [formatter setDateFormat:@"yyyy-MM-dd HH:mm"];            self.date = [[formatter stringFromDate:now] copy];            [formatter release];        }                NSDictionary *dict = [nfo objectForKey:@"astro"];        if (dict) {            NSString *sunrise = [dict objectForKey:@"sr"];            NSString *sunset = [dict objectForKey:@"ss"];            NSString *moonrise = [dict objectForKey:@"mr"];            NSString *moonset = [dict objectForKey:@"ms"];            self.astro = [[NSString alloc] initWithFormat:@"日升落: %@ - %@; 月升落: %@ - %@", sunrise, sunset, moonrise, moonset];        }                if ((dict = [nfo objectForKey:@"cond"])) {            if ((value = [dict objectForKey:@"txt"])) {                self.condition = [value copy];            } else {                self.condition = [[NSString alloc] initWithFormat:@"日%@,夜%@", [dict objectForKey:@"txt_d"], [dict objectForKey:@"txt_n"]];            }        }                if ((value = [nfo objectForKey:@"tmp"])) {            if ([value isKindOfClass:[NSDictionary class]]) {                dict = [nfo objectForKey:@"tmp"];                value = [NSString stringWithFormat:@"[%@,%@]", [dict objectForKey:@"min"], [dict objectForKey:@"max"]];            }            NSMutableString* temp = [NSMutableString stringWithFormat:@"温度: %@", value];            if ((value = [nfo objectForKey:@"fl"])) {                [temp appendFormat:@"; 体感温度: %@", value];            }            self.temperature = [temp copy];        }                if ((value = [nfo objectForKey:@"hum"])) {            self.humidity = [[NSString alloc] initWithFormat:@"相对湿度: %@%%", value];        }                if ((value = [nfo objectForKey:@"pop"])) {            float p = [value floatValue];            if (p > 0) {                self.probability = [[NSString alloc] initWithFormat:@"降水概率: %d%%", NSUInteger(p*100)];            }        }        if ((value = [nfo objectForKey:@"pcpn"])) {            if ([value floatValue] > 0) {                self.precipitation = [[NSString alloc] initWithFormat:@"降水量: %@mm", value];            }        }                if ((value = [nfo objectForKey:@"pres"])) {            self.pressure = [[NSString alloc] initWithFormat:@"大气压: %@mmHg", value];        }                if ((value = [nfo objectForKey:@"uv"])) {            self.uv = [[NSString alloc] initWithFormat:@"紫外线: %@", value];        }                if ((value = [nfo objectForKey:@"vis"])) {            self.visibility = [[NSString alloc] initWithFormat:@"能见度: %@km", value];        }                if ((dict = [nfo objectForKey:@"wind"])) {            NSString *dir = [dict objectForKey:@"dir"];            NSString *lvl = [dict objectForKey:@"sc"];            NSString *spd = [dict objectForKey:@"spd"];            self.wind = [[NSString alloc] initWithFormat:@"%@%@级, %@km/h", dir, lvl, spd];        }    }    return self;}- (void)dealloc{    [date release];    [astro release];    [condition release];    [temperature release];    [humidity release];    [probability release];    [precipitation release];    [pressure release];    [uv release];    [visibility release];    [wind release];    [super dealloc];}- (NSString *)description{    NSMutableString *desc = [NSMutableString stringWithFormat:@"------- %@ -------", date];    if (condition) {        [desc appendFormat:@"/n%@", condition];    }    if (astro) {        [desc appendFormat:@"/n%@", astro];    }    if (temperature) {        [desc appendFormat:@"/n%@", temperature];    }    if (humidity) {        [desc appendFormat:@"/n%@", humidity];    }    if (probability) {        [desc appendFormat:@"/n%@", probability];    }    if (precipitation) {        [desc appendFormat:@"/n%@", precipitation];    }    if (pressure) {        [desc appendFormat:@"/n%@", pressure];    }    if (uv) {        [desc appendFormat:@"/n%@", uv];    }    if (visibility) {        [desc appendFormat:@"/n%@", visibility];    }    if (wind) {        [desc appendFormat:@"/n%@", wind];    }    return [desc copy];}@end@implementation HeWeather@dynamic result;- (id)init{    if (self = [super init]) {        mAqi = nil;        mNow = nil;        mForecast = nil;        mDetailedForecast = nil;    }    return self;}- (void)dealloc{    [mAqi release];    [mNow release];    //[mForecast enumerateObjectsUsingBlock:^(HeWeatherInfoNode *obj, NSUInteger idx, BOOL *stop) {    //    [obj release];    //}];    [mForecast release];    //[mDetailedForecast enumerateObjectsUsingBlock:^(HeWeatherInfoNode *obj, NSUInteger idx, BOOL *stop) {    //    [obj release];    //}];    [mDetailedForecast release];        [super dealloc];}+ (NSString*)StringFromId:(LocationID)loc{    switch (loc) {        case Beijing_GuoFengMeiLun: // Tongzhou, Beijing            return @"CN101010600";        case Beijing_ZhongGuanCun:            return @"CN101010200";  // Haidian, Beijing        case ShiJiaZhuang_WorkerHospital:            return @"CN101090101";  // Shijiazhuang City        case QinHuangDao_LuLong:            return @"CN101091105";  // Lulong County, Qinhuangdao City        default:            return @"CN101010100"; // beijing    }}- (void)launchQuery:(LocationID)loc completionHander:(void(^)(BOOL success))handler{    NSString* serviceTemplate = @"https://free-api.heweather.com/v5/[api]?key=2dae4ca04d074a1abde0c113c3292ae1&city=";    serviceTemplate = [serviceTemplate stringByReplacingOccurrencesOfString:@"[api]" withString:@"weather"];    serviceTemplate = [serviceTemplate stringByAppendingString:[HeWeather StringFromId:loc]];    NSURL* url = [NSURL URLWithString:serviceTemplate];    NSURLSessionDataTask* webRequest = [[NSURLSession sharedSession] dataTaskWithURL:url                                                                   completionHandler:^(NSData* data, NSURLResponse* response, NSError* error)    {        BOOL success = (!error && ([(NSHTTPURLResponse*)response statusCode] == 200));        if (success)        {            [self parseResponse:data];        }        if (handler) // callback        {            handler(success);        }    }];    [webRequest resume];}- (void)parseResponse:(NSData*)response{    std::unique_ptr<NSAutoreleasePool, void(*)(NSAutoreleasePool*)> scopePool(        [[NSAutoreleasePool alloc] init],           // pool ptr        [](NSAutoreleasePool* p) { [p release]; }); // deleter    NSError* err = nil;    NSDictionary* info = [NSJSONSerialization JSONObjectWithData:response                                                         options:NSJSONReadingMutableLeaves                                                           error:&err];    NSArray* array = [info objectForKey:@"HeWeather5"];    if (!array || [array count] == 0) {        return;    }    NSDictionary* node = [array objectAtIndex:0]; // only 1 node available and meaningful    if ([@"ok" compare:[node objectForKey:@"status"]] != NSOrderedSame) {        return;    }    [self parseAqi:[[node objectForKey:@"aqi"] objectForKey:@"city"]];    [self parseNowInfo:[node objectForKey:@"now"]];    if (self.options & WEATHER_FORECAST)    {        [self parseDailyForecast:[node objectForKey:@"daily_forecast"]];    }    if (self.options & WEATHER_DETAIL_FORECAST)    {        [self parseDetailedForecast:[node objectForKey:@"hourly_forecast"]];    }}- (void)parseAqi:(NSDictionary *)info{    NSString* aqi = [info objectForKey:@"aqi"];    NSString* p10 = [info objectForKey:@"pm10"];    NSString* p25 = [info objectForKey:@"pm25"];    NSString* qty = [info objectForKey:@"qlty"];    mAqi = [[NSString alloc] initWithFormat:@"AQI:%@, PM10:%@, PM2.5:%@, %@", aqi, p10, p25, qty];}- (void)parseNowInfo:(NSDictionary *)now{    mNow = [[HeWeatherInfoNode alloc] initWithJson:now];}- (void)parseDailyForecast:(NSArray *)forecast{    NSMutableArray<HeWeatherInfoNode *> *array = [[NSMutableArray alloc] init];    [forecast enumerateObjectsUsingBlock:^(NSDictionary *obj, NSUInteger idx, BOOL *stop) {        HeWeatherInfoNode *node = [[HeWeatherInfoNode alloc] initWithJson:obj];        [array insertObject:node atIndex:idx];        [node release];    }];    mForecast = [[NSArray alloc] initWithArray:array];    [array release];}- (void)parseDetailedForecast:(NSArray *)forecast{    NSMutableArray<HeWeatherInfoNode *> *array = [[NSMutableArray alloc] init];    [forecast enumerateObjectsUsingBlock:^(NSDictionary *obj, NSUInteger idx, BOOL *stop) {        HeWeatherInfoNode *node = [[HeWeatherInfoNode alloc] initWithJson:obj];        [array insertObject:node atIndex:idx];        [node release];    }];    mDetailedForecast = [[NSArray alloc] initWithArray:array];    [array release];}- (NSString *)result{    if (mNow) {        NSMutableString* tmp = [[NSMutableString alloc] initWithString:mNow.description];                if (self->mAqi) {            [tmp appendFormat:@"/n%@/n", self->mAqi];        }        [mForecast enumerateObjectsUsingBlock:^(HeWeatherInfoNode *obj, NSUInteger idx, BOOL *stop) {            [tmp appendFormat:@"/n%@/n", obj.description];        }];        [mDetailedForecast enumerateObjectsUsingBlock:^(HeWeatherInfoNode *obj, NSUInteger idx, BOOL *stop) {            [tmp appendFormat:@"/n%@", obj.description];        }];        mResult = [tmp copy];        [tmp release];    }    return mResult;}@end#pragma mark -#pragma mark WeatherFullReport@implementation WeatherFullReport@synthesize aqi = mAQI;@synthesize details = mWeatherProvider;@synthesize location = mLocation;- (id)initWithLocation:(LocationID)loc{    if (self = [super init]) {        mLocation = loc;        mAQI = [[AirQualityIndex alloc] init];        mWeatherProvider = [[HeWeather alloc] init];    }    return self;}- (void) queryWithCompletionHandler:(void (^)(BOOL aqiReady, BOOL otherReady))handler;{    [mAQI launchQuery:mLocation completionHander:^(BOOL success){        if (handler)        {            handler(YES, NO);        }    }];    [mWeatherProvider launchQuery:mLocation completionHander:^(BOOL success){        if (handler)        {            handler(NO, YES);        }    }];}- (void) dealloc{    [mAQI release];    [mWeatherProvider release];    [super dealloc];}@end


发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表