在iOS中,最常见的网络请求方式是NSURLSession,它是苹果推荐的现代API,简单安全且易于拓展。
一次完整的网络请求流程:
-
构造 NSURL 对象
-
创建 NSURLSessionDataTask
-
发起请求(resume)
-
在回调中解析数据
-
回到主线程更新 UI
下面,我们用一个简单的程序来试一下。
objectivec
NSString *city = @"Beijing";
NSString *apiKey = @"你的API密钥";
NSString *urlString = [NSString stringWithFormat:@"https://api.weatherapi.com/v1/current.json?key=%@&q=%@&lang=zh", apiKey, city];
urlString = [urlString stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];
NSURL *url = [NSURL URLWithString:urlString];
NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (error) {
NSLog(@"请求失败:%@", error.localizedDescription);
return;
}
NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
NSLog(@"天气数据:%@", json);
dispatch_async(dispatch_get_main_queue(), ^{
// 更新 UI
});
}];
[task resume];
首页

objectivec
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(handleAddCityNotification:)
name:@"AddNewCityNotification"
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(handleDeleteCityNotification:)
name:@"DeleteCityNotification"
object:nil];
接收两个通知,一个是搜索页面的城市,另一个是详情页面的删除指令。
objectivec
-(void) handleAddCityNotification:(NSNotification *)notification {
NSDictionary *userInfo = [notification userInfo];
NSString *newCity = userInfo[@"cityName"];
BOOL exists = NO;
for (NSDictionary *city in self.cityData) {
if ([city[@"name"] isEqualToString:newCity]) {
exists = YES;
break;
}
}
if (!exists) {
[self.cityData addObject:@{@"name": newCity}];
[self createUrl];
}
}
- (void)handleDeleteCityNotification:(NSNotification *)notification {
NSDictionary *userInfo = [notification userInfo];
NSString *cityName = userInfo[@"cityName"];
// 从城市列表中删除该城市
NSMutableArray *citiesToRemove = [NSMutableArray array];
for (NSDictionary *city in self.cityData) {
if ([city[@"name"] isEqualToString:cityName]) {
[citiesToRemove addObject:city];
}
}
[self.cityData removeObjectsInArray:citiesToRemove];
[self.tableView reloadData];
}
这个页面要执行两个操作: 点击加号添加城市,删除城市。


这里给出我的网络请求代码:
objectivec
- (void)createUrl {
if (self.cityData.count == 0) return;
dispatch_group_t group = dispatch_group_create();
NSURLSession *session = [NSURLSession sharedSession];
NSMutableArray *tempDicArray = [NSMutableArray array];
NSLog(@"1");
for (NSDictionary *city in self.cityData) {
dispatch_group_enter(group);
NSString *cityName = city[@"name"];
NSString *urlString = [NSString stringWithFormat:@"https://api.weatherapi.com/v1/current.json?key=2db0265c1d084416b9275428252207&q=%@&lang=zh ", cityName];
urlString = [urlString stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];
NSURL *url = [NSURL URLWithString:urlString];
NSURLSessionDataTask *task = [session dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (error) {
NSLog(@"错错错!! %@: %@", city, error.localizedDescription);
dispatch_group_leave(group);
return;
}
if (data) {
NSError *jsonError;
//解析数据
NSDictionary *dic = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonError];
if (jsonError) {
NSLog(@"JSON解析失败: %@", jsonError.localizedDescription);
} else {
NSLog(@"API响应: %@", dic);
// 检查是否有错误
if (dic[@"error"]) {
NSLog(@"API返回错误: %@", dic[@"error"][@"message"]);
} else {
@synchronized (tempDicArray) {
//赋值到tempDicArray
[tempDicArray addObject:dic];
NSLog(@"成功添加城市: %@", cityName);
}
}
}
} else {
NSLog(@"无响应数据");
}
dispatch_group_leave(group);
}];
[task resume];
}
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"所有请求完成,获取到%ld个城市数据", tempDicArray.count);
//最终到dicArray
self.dicArray = tempDicArray;
[self.tableView reloadData];
});
}
objectivec
// 添加支持滑动删除
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath {
return YES;
}
搜索页面
objectivec
-(void) addCityToMain:(NSDictionary *)cityInfo {
NSString *cityName = cityInfo[@"name"];
NSDictionary *userInfo = @{@"cityName": cityName};
[[NSNotificationCenter defaultCenter] postNotificationName:@"AddNewCityNotification" object:nil userInfo:userInfo];
// 关闭添加页面
// [self dismissViewControllerAnimated:YES completion:nil];
}
objectivec
//选中哪个就传给主页
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[tableView deselectRowAtIndexPath:indexPath animated:YES];
if (indexPath.row < self.searchResults.count) {
NSDictionary *city = self.searchResults[indexPath.row];
DetailViewController *detailVC = [[DetailViewController alloc] init];
detailVC.cityName = city[@"name"];
detailVC.canAddCity = YES;
UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:detailVC];
nav.modalPresentationStyle = UIModalPresentationFullScreen;
[self presentViewController:nav animated:YES completion:nil];
}
}
当我们点击其中一行时,我们会跳转到该城市
城市详细天气

给出我的每小时天气预报代码
objectivec
- (void)setupHourlyForecastData {
NSArray *forecastDays = self.weatherData[@"forecast"][@"forecastday"];
if (forecastDays.count == 0) return;
//WeatherAPI 返回的 forecast 结构中,每天包含一个 hour 数组(24 小时)。为了支持"跨天展示",你先拼接所有天的小时数组
// 拼接所有 forecastday 的 hour 数据
NSMutableArray *allHours = [NSMutableArray array];
for (NSDictionary *day in forecastDays) {
NSArray *hours = day[@"hour"];
if (hours) [allHours addObjectsFromArray:hours];
}
if (allHours.count == 0) return;
UIView *hourlyCard = [self.contentView viewWithTag:1000];
UIScrollView *scrollView = [hourlyCard viewWithTag:1001];
if (!scrollView) return;
CGFloat itemWidth = 60;
CGFloat spacing = 10;
//当前小时
NSDate *now = [NSDate date];
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
formatter.dateFormat = @"yyyy-MM-dd HH";
NSString *nowHourString = [formatter stringFromDate:now];
NSInteger startIndex = 0;
//找出当前时间匹配的小时
for (int i = 0; i < allHours.count; i++) {
NSString *hourString = [allHours[i][@"time"] substringToIndex:13];
if ([hourString isEqualToString:nowHourString]) {
startIndex = i;
break;
}
}
//主要是防止数组越界
NSInteger count = MIN(24, allHours.count - startIndex);
//给7个格子赋值
for (int i = 0; i < count; i++) {
NSDictionary *hour = allHours[startIndex + i];
NSString *time = [hour[@"time"] substringFromIndex:11];
NSString *temp = [NSString stringWithFormat:@"%.0f°", [hour[@"temp_c"] floatValue]];
NSString *iconUrl = [NSString stringWithFormat:@"https:%@", hour[@"condition"][@"icon"]];
UIView *hourView = [[UIView alloc] initWithFrame:CGRectMake(i * (itemWidth + spacing), 0, itemWidth, 70)];
UILabel *timeLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, itemWidth, 20)];
timeLabel.text = time;
timeLabel.font = [UIFont systemFontOfSize:12];
timeLabel.textColor = [UIColor whiteColor];
timeLabel.textAlignment = NSTextAlignmentCenter;
[hourView addSubview:timeLabel];
UIImageView *icon = [[UIImageView alloc] initWithFrame:CGRectMake(10, 20, 40, 30)];
icon.contentMode = UIViewContentModeScaleAspectFit;
[hourView addSubview:icon];
[self loadIcon:iconUrl forImageView:icon];
UILabel *tempLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 50, itemWidth, 20)];
tempLabel.text = temp;
tempLabel.font = [UIFont systemFontOfSize:12];
tempLabel.textColor = [UIColor whiteColor];
tempLabel.textAlignment = NSTextAlignmentCenter;
[hourView addSubview:tempLabel];
[scrollView addSubview:hourView];
}
scrollView.contentSize = CGSizeMake((itemWidth + spacing) * count, 70);
}//每小时天气
同时,我下面的每部分都以卡片形式呈现
objectivec
//卡片
- (UIView *)createCardViewWithFrame:(CGRect)frame {
UIView *card = [[UIView alloc] initWithFrame:frame];
card.backgroundColor = [UIColor colorWithWhite:1 alpha:0.2];
card.layer.cornerRadius = 20;
card.layer.borderWidth = 1;
card.layer.borderColor = [UIColor colorWithWhite:1 alpha:0.3].CGColor;
return card;
}
//卡片标题
- (UILabel *)createSectionTitleLabel:(NSString *)title frame:(CGRect)frame {
UILabel *label = [[UILabel alloc] initWithFrame:frame];
label.text = title;
label.font = [UIFont systemFontOfSize:20 weight:UIFontWeightBold];
label.textColor = [UIColor whiteColor];
return label;
}
我的背景可以根据天气不同自动切换,代码如下:
objectivec
- (void)setBackgroundBasedOnWeather {
if (!self.weatherData) return;
NSDictionary *current = self.weatherData[@"current"];
NSDictionary *condition = current[@"condition"];
NSInteger code = [condition[@"code"] integerValue];
NSString *bgImageName;
// 晴天
if (code == 1000) {
bgImageName = @"pic1.jpg";
}
// 多云或阴天
else if (code >= 1003 && code <= 1009) {
bgImageName = @"pic2.jpg";
}
// 雨(小雨~大雨)
else if ((code >= 1063 && code <= 1087) || (code >= 1150 && code <= 1195) || (code >= 1240 && code <= 1246)) {
bgImageName = @"pic3.jpg";
}
// 雪(小雪~暴雪)
else if ((code >= 1066 && code <= 1074) || (code >= 1114 && code <= 1237)) {
bgImageName = @"pic4.jpg";
}
// 雷暴、雨夹雪等恶劣天气
else if (code >= 1273 && code <= 1282) {
bgImageName = @"pic5.jpg";
}
// 默认
else {
bgImageName = @"pic1.jpg";
}
self.backgroundImageView.image = [UIImage imageNamed:bgImageName];
}
objectivec
//日期字符串转换为周几
- (NSString *)formatDate:(NSString *)dateString {
NSDateFormatter *inputFormatter = [[NSDateFormatter alloc] init];
inputFormatter.dateFormat = @"yyyy-MM-dd";
NSDate *date = [inputFormatter dateFromString:dateString];
NSDateFormatter *outputFormatter = [[NSDateFormatter alloc] init];
outputFormatter.locale = [NSLocale localeWithLocaleIdentifier:@"zh_CN"];
outputFormatter.dateFormat = @"EEE";
return [outputFormatter stringFromDate:date];
}
详情页的删除按钮:
objectivec
- (void)deleteCity {
if (self.cityName) {
NSDictionary *userInfo = @{@"cityName": self.cityName};
[[NSNotificationCenter defaultCenter] postNotificationName:@"DeleteCityNotification"
object:nil
userInfo:userInfo];
[self dismissViewControllerAnimated:YES completion:nil];
}
}
关于多个城市横向滑动查看详情,我使用了UIPageController。
objectivec
- (id)init {
self = [super initWithTransitionStyle:UIPageViewControllerTransitionStyleScroll
navigationOrientation:UIPageViewControllerNavigationOrientationHorizontal
options:nil];
return self;
}
这段代码实现了页面的横向滑动效果。
objectivec
// MainVC.m
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[tableView deselectRowAtIndexPath:indexPath animated:YES];
NSMutableArray *cityNames = [NSMutableArray array];
for (NSDictionary *city in self.cityData) {
[cityNames addObject:city[@"name"]];
}
CitiesDetailViewController *containerVC = [[CitiesDetailViewController alloc] init];
containerVC.cityNames = cityNames;
containerVC.initialIndex = indexPath.section;
containerVC.modalPresentationStyle = UIModalPresentationFullScreen;
[self presentViewController:containerVC animated:YES completion:nil];
}
objectivec
// CitiesDetailViewController.m
- (void)viewDidLoad {
[super viewDidLoad];
self.delegate = self;
self.dataSource = self;
self.cities = self.cityNames; // 城市名列表由外部传入
self.currentIndex = self.initialIndex; // 初始城市索引也由外部设置
DetailViewController *initialVC = [self detailViewControllerForIndex:self.initialIndex];
[self setViewControllers:@[initialVC] direction:UIPageViewControllerNavigationDirectionForward animated:NO completion:nil];
objectivec
#pragma mark - Page View Controller Data Source
// 获取前一个VC
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(UIViewController *)viewController {
//下标
NSInteger index = [(DetailViewController *)viewController index];
//不是第一个就去上一个
if (index > 0) {
return [self detailViewControllerForIndex:index - 1];
}
return nil;
}
//获取后一个VC
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerAfterViewController:(UIViewController *)viewController {
NSInteger index = [(DetailViewController *)viewController index];
if (index < self.cities.count - 1) {
return [self detailViewControllerForIndex:index + 1];
}
return nil;
}
//进入指定页面
- (DetailViewController *)detailViewControllerForIndex:(NSInteger)index {
if (index < 0 || index >= self.cities.count) return nil;
DetailViewController *detailVC = [[DetailViewController alloc] init];
detailVC.cityName = self.cities[index];
detailVC.index = index;
detailVC.canAddCity = NO;
return detailVC;
}
- (void)deleteCurrentCity {
if (self.cities.count == 0) {
[self dismissViewControllerAnimated:YES completion:nil];
return;
}
// 获取当前显示的视图控制器
DetailViewController *currentVC = (DetailViewController *)self.viewControllers.firstObject;
NSInteger currentIndex = currentVC.index;
// 防止索引越界
if (currentIndex >= self.cities.count) {
[self dismissViewControllerAnimated:YES completion:nil];
return;
}
NSString *cityName = self.cities[currentIndex];
// 发送删除通知
NSDictionary *userInfo = @{@"cityName": cityName};
[[NSNotificationCenter defaultCenter] postNotificationName:@"DeleteCityNotification"
object:nil
userInfo:userInfo];
// 从数据源中删除
NSMutableArray *mutableCities = [self.cities mutableCopy];
[mutableCities removeObjectAtIndex:currentIndex];
self.cities = [mutableCities copy];
// 如果删除后没有城市了,关闭页面
if (self.cities.count == 0) {
[self dismissViewControllerAnimated:YES completion:nil];
return;
}
// 计算新的当前索引
NSInteger newIndex;
if (currentIndex >= self.cities.count) {
newIndex = self.cities.count - 1;
} else {
newIndex = currentIndex;
}
// 确定导航方向:如果删除的是最后一个,则向前翻一页,否则保持当前页
UIPageViewControllerNavigationDirection direction = UIPageViewControllerNavigationDirectionForward;
if (newIndex < currentIndex) {
direction = UIPageViewControllerNavigationDirectionReverse;
}
// 获取新的视图控制器
DetailViewController *newVC = [self detailViewControllerForIndex:newIndex];
if (newVC) {
[self setViewControllers:@[newVC]
direction:direction
animated:YES
completion:nil];
self.currentIndex = newIndex;
}
}