MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Objective-C 网络编程基础与实践

2022-12-084.2k 阅读

一、Objective-C 网络编程概述

在现代应用开发中,网络功能是不可或缺的一部分。Objective-C 作为一种广泛应用于 iOS 和 macOS 开发的编程语言,提供了丰富的框架和工具来实现网络编程。通过网络编程,应用程序可以与服务器进行数据交互,获取远程资源,实现实时通信等功能。

(一)网络编程涉及的概念

  1. URL(统一资源定位符):它是互联网上资源的地址,用于标识网络上的各种资源,如网页、图片、数据文件等。在 Objective-C 网络编程中,URL 是访问远程资源的基础。例如,一个典型的 URL https://www.example.com/api/data,其中 https 是协议,www.example.com 是主机名,/api/data 是路径。
  2. HTTP(超文本传输协议):这是一种用于分布式、协作式和超媒体信息系统的应用层协议。它是目前网络应用中最常用的协议之一。HTTP 基于请求 - 响应模型,客户端发送请求到服务器,服务器返回响应。请求和响应都包含头部信息和可选的正文内容。常见的 HTTP 方法有 GET、POST、PUT、DELETE 等。
  3. 网络请求与响应:客户端发起网络请求,通常携带一些参数和请求头信息,服务器接收并处理请求后返回响应。响应包含状态码(如 200 表示成功,404 表示未找到资源等)、响应头和响应体。

二、使用 NSURLSession 进行网络请求

NSURLSession 是 iOS 7 及以上版本引入的强大网络框架,它提供了更灵活、高效的网络请求处理方式,替代了较老的 NSURLConnection。

(一)创建 NSURLSession

  1. 共享会话:可以通过 [NSURLSession sharedSession] 获取一个共享的 NSURLSession 实例。这个共享会话适用于大多数常见的网络请求场景,它会复用网络连接,提高效率。
NSURLSession *sharedSession = [NSURLSession sharedSession];
  1. 配置会话:如果需要自定义会话的配置,比如设置超时时间、缓存策略等,可以创建一个 NSURLSessionConfiguration 对象,并基于此创建 NSURLSession
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
// 设置请求超时时间为 10 秒
configuration.timeoutIntervalForRequest = 10; 
NSURLSession *customSession = [NSURLSession sessionWithConfiguration:configuration];

(二)发送 GET 请求

GET 请求通常用于从服务器获取数据。以下是发送 GET 请求的示例代码:

NSURL *url = [NSURL URLWithString:@"https://www.example.com/api/data"];
NSURLSessionDataTask *dataTask = [[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
    if (!error && data) {
        NSString *responseString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
        NSLog(@"GET 请求响应数据: %@", responseString);
    } else {
        NSLog(@"GET 请求错误: %@", error);
    }
}];
[dataTask resume];

在上述代码中,首先创建了一个 NSURL 对象表示请求的 URL。然后通过 NSURLSessiondataTaskWithURL:completionHandler: 方法创建一个数据任务,该任务会在后台线程执行请求。完成处理程序会在请求完成后被调用,根据返回的数据和错误情况进行相应处理。

(三)发送 POST 请求

POST 请求常用于向服务器提交数据,比如用户登录信息、表单数据等。示例代码如下:

NSURL *url = [NSURL URLWithString:@"https://www.example.com/api/submit"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
request.HTTPMethod = @"POST";
// 设置请求体数据
NSString *postString = @"param1=value1&param2=value2";
request.HTTPBody = [postString dataUsingEncoding:NSUTF8StringEncoding];
NSURLSessionDataTask *dataTask = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
    if (!error && data) {
        NSString *responseString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
        NSLog(@"POST 请求响应数据: %@", responseString);
    } else {
        NSLog(@"POST 请求错误: %@", error);
    }
}];
[dataTask resume];

这里首先创建了一个可变的 NSURLRequest 对象,并设置其 HTTP 方法为 POST。然后构建请求体数据,并将其设置到请求对象的 HTTPBody 属性中。最后通过 NSURLSession 发送请求并处理响应。

三、处理网络响应

当网络请求完成后,会得到服务器返回的响应。正确处理响应对于应用程序的正常运行至关重要。

(一)解析 HTTP 状态码

HTTP 状态码可以告诉我们请求的结果。在响应处理程序中,可以通过 NSHTTPURLResponse 类来获取状态码。

NSURLSessionDataTask *dataTask = [[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
    if (!error && data) {
        NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
        NSInteger statusCode = httpResponse.statusCode;
        if (statusCode == 200) {
            // 处理成功响应
            NSString *responseString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
            NSLog(@"成功响应数据: %@", responseString);
        } else {
            NSLog(@"非成功状态码: %ld", (long)statusCode);
        }
    } else {
        NSLog(@"请求错误: %@", error);
    }
}];

(二)解析响应数据

  1. JSON 数据解析:在现代网络应用中,JSON(JavaScript Object Notation)是一种非常流行的数据交换格式。Objective-C 提供了 NSJSONSerialization 类来解析 JSON 数据。
NSURLSessionDataTask *dataTask = [[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
    if (!error && data) {
        NSError *jsonError;
        id jsonObject = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:&jsonError];
        if (!jsonError && jsonObject) {
            if ([jsonObject isKindOfClass:[NSDictionary class]]) {
                NSDictionary *jsonDict = (NSDictionary *)jsonObject;
                NSLog(@"解析后的 JSON 字典: %@", jsonDict);
            } else if ([jsonObject isKindOfClass:[NSArray class]]) {
                NSArray *jsonArray = (NSArray *)jsonObject;
                NSLog(@"解析后的 JSON 数组: %@", jsonArray);
            }
        } else {
            NSLog(@"JSON 解析错误: %@", jsonError);
        }
    } else {
        NSLog(@"请求错误: %@", error);
    }
}];
  1. XML 数据解析:虽然 JSON 更为常用,但在某些场景下仍会遇到 XML(可扩展标记语言)数据。可以使用 NSXMLParser 来解析 XML 数据。这里以一个简单的 XML 数据解析为例,假设 XML 数据如下:
<?xml version="1.0" encoding="UTF - 8"?>
<root>
    <item>
        <name>Item 1</name>
        <value>Value 1</value>
    </item>
    <item>
        <name>Item 2</name>
        <value>Value 2</value>
    </item>
</root>

解析代码如下:

@interface XMLParser : NSObject <NSXMLParserDelegate>
@property (nonatomic, strong) NSMutableArray *items;
@property (nonatomic, strong) NSMutableDictionary *currentItem;
@property (nonatomic, strong) NSString *currentElement;
@end

@implementation XMLParser
- (instancetype)init {
    self = [super init];
    if (self) {
        _items = [NSMutableArray array];
    }
    return self;
}

- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary<NSString *,NSString *> *)attributeDict {
    if ([elementName isEqualToString:@"item"]) {
        _currentItem = [NSMutableDictionary dictionary];
    }
    _currentElement = elementName;
}

- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string {
    if (_currentItem && _currentElement) {
        NSMutableArray *array = _currentItem[_currentElement];
        if (!array) {
            array = [NSMutableArray array];
            _currentItem[_currentElement] = array;
        }
        [array addObject:string];
    }
}

- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName {
    if ([elementName isEqualToString:@"item"]) {
        [_items addObject:_currentItem];
        _currentItem = nil;
    }
    _currentElement = nil;
}

@end

// 使用解析器
NSURLSessionDataTask *dataTask = [[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
    if (!error && data) {
        XMLParser *parser = [[XMLParser alloc] init];
        NSXMLParser *xmlParser = [[NSXMLParser alloc] initWithData:data];
        xmlParser.delegate = parser;
        BOOL success = [xmlParser parse];
        if (success) {
            NSLog(@"解析后的 XML 数据: %@", parser.items);
        } else {
            NSLog(@"XML 解析失败");
        }
    } else {
        NSLog(@"请求错误: %@", error);
    }
}];

四、网络请求的优化与处理

在实际应用中,网络请求可能会面临各种问题,如网络不稳定、请求超时等。需要对网络请求进行优化和妥善处理。

(一)设置合理的超时时间

通过 NSURLSessionConfigurationtimeoutIntervalForRequest 属性可以设置请求的超时时间。如果请求在指定时间内未完成,将会触发超时错误。合理设置超时时间可以避免应用程序长时间等待无响应的请求。

NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
configuration.timeoutIntervalForRequest = 15; // 设置超时时间为 15 秒
NSURLSession *customSession = [NSURLSession sessionWithConfiguration:configuration];

(二)处理网络变化

应用程序需要能够感知网络状态的变化,以便做出相应的处理。可以使用 Reachability 类来监测网络连接状态。首先,下载并导入 Reachability 类到项目中。然后,在需要监测的地方添加如下代码:

#import "Reachability.h"

@interface ViewController ()
@property (nonatomic, strong) Reachability *reachability;
@end

@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    self.reachability = [Reachability reachabilityForInternetConnection];
    [self.reachability startNotifier];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(reachabilityChanged:) name:kReachabilityChangedNotification object:nil];
    [self updateInterfaceWithReachability:self.reachability];
}

- (void)reachabilityChanged:(NSNotification *)note {
    Reachability *reachability = note.object;
    [self updateInterfaceWithReachability:reachability];
}

- (void)updateInterfaceWithReachability:(Reachability *)reachability {
    NetworkStatus status = [reachability currentReachabilityStatus];
    if (status == NotReachable) {
        NSLog(@"网络不可用");
        // 可以在此处提示用户网络不可用,暂停网络请求等
    } else if (status == ReachableViaWiFi) {
        NSLog(@"通过 WiFi 连接");
    } else if (status == ReachableViaWWAN) {
        NSLog(@"通过移动数据连接");
    }
}

- (void)dealloc {
    [[NSNotificationCenter defaultCenter] removeObserver:self name:kReachabilityChangedNotification object:nil];
    [self.reachability stopNotifier];
}
@end

(三)数据缓存

为了减少不必要的网络请求,提高应用程序的性能,可以对网络数据进行缓存。NSURLSession 本身提供了一些缓存策略,如 NSURLRequestUseProtocolCachePolicyNSURLRequestReloadIgnoringLocalCacheData 等。

NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
configuration.requestCachePolicy = NSURLRequestUseProtocolCachePolicy;
NSURLSession *customSession = [NSURLSession sessionWithConfiguration:configuration];

此外,还可以手动实现自定义的缓存机制,比如将网络数据存储到本地文件或者数据库中,并在下次请求相同数据时先检查缓存是否存在且有效。

五、使用 AFNetworking 框架进行网络编程

AFNetworking 是一个广泛使用的第三方网络框架,它对 NSURLSession 进行了更高级的封装,提供了更简洁、易用的 API,同时支持多种请求方式、数据解析和缓存策略等。

(一)AFNetworking 的安装

  1. CocoaPods 安装:在项目的 Podfile 文件中添加 pod 'AFNetworking',然后在终端执行 pod install 命令即可安装。
  2. 手动安装:从 AFNetworking 的官方 GitHub 仓库下载源代码,将其添加到项目中,并配置相应的依赖。

(二)发送 GET 请求

#import <AFNetworking/AFNetworking.h>

AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
[manager GET:@"https://www.example.com/api/data" parameters:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
    NSLog(@"GET 请求成功: %@", responseObject);
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
    NSLog(@"GET 请求失败: %@", error);
}];

在上述代码中,首先创建了一个 AFHTTPSessionManager 对象,然后通过 GET 方法发送 GET 请求。parameters 参数用于传递请求参数,如果没有参数则设置为 nilsuccessfailure 块分别处理请求成功和失败的情况。

(三)发送 POST 请求

AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
NSDictionary *parameters = @{@"param1": @"value1", @"param2": @"value2"};
[manager POST:@"https://www.example.com/api/submit" parameters:parameters progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
    NSLog(@"POST 请求成功: %@", responseObject);
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
    NSLog(@"POST 请求失败: %@", error);
}];

这里构建了一个包含请求参数的字典,并通过 POST 方法发送 POST 请求。

(四)数据解析与序列化

AFNetworking 支持自动解析多种数据格式,如 JSON、XML 等。默认情况下,它会根据响应的 Content - Type 头信息来选择合适的解析器。对于 JSON 数据,它会自动将响应数据解析为 NSDictionaryNSArray

AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
// 设置响应序列化器为 JSON 序列化器
manager.responseSerializer = [AFJSONResponseSerializer serializer];
[manager GET:@"https://www.example.com/api/jsonData" parameters:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
    if ([responseObject isKindOfClass:[NSDictionary class]]) {
        NSDictionary *jsonDict = (NSDictionary *)responseObject;
        NSLog(@"解析后的 JSON 字典: %@", jsonDict);
    } else if ([responseObject isKindOfClass:[NSArray class]]) {
        NSArray *jsonArray = (NSArray *)responseObject;
        NSLog(@"解析后的 JSON 数组: %@", jsonArray);
    }
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
    NSLog(@"请求失败: %@", error);
}];

如果需要处理 XML 数据,可以设置响应序列化器为 AFXMLParserResponseSerializer

AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
manager.responseSerializer = [AFXMLParserResponseSerializer serializer];
[manager GET:@"https://www.example.com/api/xmlData" parameters:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, NSXMLParser *  _Nullable responseObject) {
    // 处理 XML 解析器对象
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
    NSLog(@"请求失败: %@", error);
}];

(五)网络请求的缓存

AFNetworking 提供了方便的缓存机制。可以通过设置 AFHTTPSessionManagerrequestSerializercachePolicy 属性来控制缓存策略。

AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
manager.requestSerializer.cachePolicy = NSURLRequestReturnCacheDataElseLoad;

此外,AFNetworking 还支持自定义缓存,例如使用 AFImageRequestOperation 来缓存图片,通过设置 AFImageCache 来管理图片缓存。

六、Socket 编程基础

除了基于 HTTP 的网络请求,在某些场景下,如实时通信、游戏开发等,需要使用 Socket 进行网络编程。

(一)Socket 概念

Socket 是应用层与传输层之间的接口,它提供了一种网络通信的机制。在 iOS 和 macOS 开发中,可以使用 CFStreamGCDAsyncSocket 等框架来进行 Socket 编程。Socket 分为 TCP(传输控制协议)和 UDP(用户数据报协议)两种类型。TCP 是面向连接的、可靠的协议,适用于对数据准确性要求高的场景;UDP 是无连接的、不可靠的协议,但传输速度快,适用于对实时性要求高但对数据准确性要求相对较低的场景,如视频流、音频流传输等。

(二)使用 CFStream 进行 TCP Socket 编程

  1. 创建 CFStream
CFReadStreamRef readStream;
CFWriteStreamRef writeStream;
CFStreamCreatePairWithSocketToHost(kCFAllocatorDefault, (CFStringRef)@"www.example.com", 80, &readStream, &writeStream);

这里创建了一对 CFReadStreamCFWriteStream 用于与指定主机和端口进行通信。 2. 配置和打开流

CFStreamSetProperty(readStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue);
CFStreamSetProperty(writeStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue);
CFStreamOpen(readStream);
CFStreamOpen(writeStream);

设置流的属性并打开流。 3. 读写数据

char buffer[1024];
CFIndex bytesRead = CFReadStreamRead(readStream, buffer, sizeof(buffer));
if (bytesRead > 0) {
    NSString *receivedData = [[NSString alloc] initWithBytes:buffer length:bytesRead encoding:NSUTF8StringEncoding];
    NSLog(@"接收到的数据: %@", receivedData);
}
NSString *sendString = @"Hello, Server!";
NSData *sendData = [sendString dataUsingEncoding:NSUTF8StringEncoding];
CFIndex bytesWritten = CFWriteStreamWrite(writeStream, [sendData bytes], [sendData length]);

通过 CFReadStreamRead 读取数据,通过 CFWriteStreamWrite 发送数据。 4. 关闭流

CFStreamClose(readStream);
CFStreamClose(writeStream);
CFRelease(readStream);
CFRelease(writeStream);

(三)使用 GCDAsyncSocket 进行 TCP Socket 编程

  1. 安装 GCDAsyncSocket:可以通过 CocoaPods 安装,在 Podfile 中添加 pod 'GCDAsyncSocket' 并执行 pod install
  2. 创建和连接 Socket
#import <GCDAsyncSocket/GCDAsyncSocket.h>

@interface ViewController () <GCDAsyncSocketDelegate>
@property (nonatomic, strong) GCDAsyncSocket *socket;
@end

@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    self.socket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()];
    NSError *error;
    [self.socket connectToHost:@"www.example.com" onPort:80 error:&error];
    if (error) {
        NSLog(@"连接错误: %@", error);
    }
}
  1. 实现代理方法
- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(UInt16)port {
    NSLog(@"已连接到服务器: %@:%hu", host, port);
    NSString *sendString = @"Hello, Server!";
    NSData *sendData = [sendString dataUsingEncoding:NSUTF8StringEncoding];
    [sock writeData:sendData withTimeout:-1 tag:0];
}

- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag {
    NSString *receivedData = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
    NSLog(@"接收到的数据: %@", receivedData);
    [sock readDataWithTimeout:-1 tag:0];
}
  1. 关闭 Socket
- (void)dealloc {
    [self.socket disconnect];
}

七、总结网络编程要点与拓展

在 Objective - C 的网络编程中,无论是基于 NSURLSession 的原生网络请求,还是使用 AFNetworking 这样的第三方框架,都需要深入理解网络请求的流程、响应处理以及各种优化策略。同时,Socket 编程为实现实时性较高的网络应用提供了有力手段。在实际开发中,要根据应用的具体需求选择合适的网络编程方式。

在未来的开发中,随着网络技术的不断发展,如 5G 的普及,网络应用对性能和实时性的要求会越来越高。Objective - C 的网络编程也需要不断跟进,例如更高效地处理大数据量的传输、更好地利用新的网络特性等。开发者需要持续关注相关技术的更新,以打造出更优秀的网络应用。同时,安全也是网络编程中至关重要的一环,要注意对网络请求和数据传输进行加密处理,防止数据泄露和恶意攻击。

总之,Objective - C 的网络编程是一个丰富而不断发展的领域,通过深入学习和实践,可以开发出功能强大、稳定高效的网络应用。