工程结构如下:
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
新闻热点
疑难解答