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

Objective-C 在 iOS 网络请求与数据交互中的实践

2023-11-041.5k 阅读

一、iOS 网络请求基础

在 iOS 开发中,网络请求是实现应用与服务器数据交互的关键环节。Objective-C 作为 iOS 开发的传统语言,提供了多种方式来进行网络请求。

1.1 NSURLConnection

NSURLConnection 是早期 iOS 开发中常用的网络请求类。它基于委托模式,在使用时,需要创建一个 NSURLRequest 对象来表示请求,然后通过 NSURLConnection 的类方法来发起请求。

示例代码如下:

NSURL *url = [NSURL URLWithString:@"http://example.com/api/data"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];

NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
if (connection) {
    // 连接成功
} else {
    // 连接失败
}

在上述代码中,首先创建了一个 NSURL 对象,指定了请求的 URL 地址。然后基于这个 NSURL 创建了 NSURLRequest 对象。最后通过 NSURLConnection 的初始化方法,将请求和委托对象传入。委托对象需要遵守 NSURLConnectionDelegate 协议,以处理网络请求过程中的各种事件,如接收到响应、接收到数据、请求完成或失败等。

// NSURLConnectionDelegate 协议方法示例
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
    // 处理响应,如检查 HTTP 状态码
    NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
    NSInteger statusCode = httpResponse.statusCode;
    if (statusCode == 200) {
        // 成功响应,可开始准备接收数据
    }
}

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
    // 处理接收到的数据,可累加到一个可变数据对象中
    if (!self.receivedData) {
        self.receivedData = [NSMutableData data];
    }
    [self.receivedData appendData:data];
}

- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
    // 请求完成,可将接收到的数据转换为合适的格式,如 JSON
    NSString *responseString = [[NSString alloc] initWithData:self.receivedData encoding:NSUTF8StringEncoding];
    NSLog(@"Response: %@", responseString);
}

- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
    // 处理请求失败的错误
    NSLog(@"Request failed: %@", error);
}

然而,NSURLConnection 存在一些局限性。它是基于同步或异步的方式,在异步请求时,需要手动管理线程和数据的接收与处理。而且在 iOS 9.0 之后,苹果推荐使用 NSURLSession 来替代 NSURLConnection

1.2 NSURLSession

NSURLSession 是 iOS 7.0 引入的新一代网络请求框架,相比 NSURLConnection,它提供了更强大、灵活和高效的网络请求功能。NSURLSession 基于任务(NSURLSessionTask)来管理网络请求,有三种类型的任务:NSURLSessionDataTask 用于获取数据,NSURLSessionUploadTask 用于上传数据,NSURLSessionDownloadTask 用于下载文件。

创建 NSURLSession 和发起 NSURLSessionDataTask 的示例代码如下:

NSURL *url = [NSURL URLWithString:@"http://example.com/api/data"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];

NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
    if (!error) {
        NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
        if (httpResponse.statusCode == 200) {
            NSString *responseString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
            NSLog(@"Response: %@", responseString);
        }
    } else {
        NSLog(@"Request failed: %@", error);
    }
}];
[dataTask resume];

在上述代码中,首先创建了 NSURLNSURLRequest 对象,与 NSURLConnection 类似。然后通过 [NSURLSession sharedSession] 获取一个共享的 NSURLSession 实例。接着使用 NSURLSessiondataTaskWithRequest:completionHandler: 方法创建一个 NSURLSessionDataTask,并传入请求和完成处理块。在完成处理块中,可以处理接收到的数据、响应以及错误。最后,调用 resume 方法启动任务。

NSURLSession 还支持配置不同的会话(NSURLSessionConfiguration),可以设置缓存策略、超时时间、代理等。例如,创建一个自定义配置的 NSURLSession

NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
configuration.timeoutIntervalForRequest = 15; // 设置请求超时时间为 15 秒

NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:nil];

上述代码中,首先创建了一个默认的会话配置 NSURLSessionConfiguration,然后设置了请求的超时时间。接着通过 sessionWithConfiguration:delegate:delegateQueue: 方法创建了一个自定义配置的 NSURLSession,并指定了委托对象和委托队列。如果委托队列设置为 nil,则会在系统默认的全局队列中处理委托方法。

NSURLSession 的委托方法与 NSURLConnection 有相似之处,但也有一些不同。例如,处理数据接收的委托方法为 URLSession:dataTask:didReceiveData:

- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
    // 处理接收到的数据
    if (!self.receivedData) {
        self.receivedData = [NSMutableData data];
    }
    [self.receivedData appendData:data];
}

通过委托方法,可以更细粒度地控制网络请求的过程,如在数据接收过程中进行实时处理等。

二、数据格式处理

在 iOS 网络请求与数据交互中,常见的数据格式有 JSON 和 XML。Objective-C 提供了相应的类来处理这些数据格式。

2.1 JSON 处理

JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,在网络应用中广泛使用。在 Objective-C 中,可以使用 NSJSONSerialization 类来处理 JSON 数据。

将 JSON 数据解析为 Objective-C 对象的示例代码如下:

NSData *jsonData = // 从网络请求或其他来源获取的 JSON 数据
NSError *error;
id jsonObject = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingAllowFragments error:&error];
if (!error) {
    if ([jsonObject isKindOfClass:[NSDictionary class]]) {
        NSDictionary *jsonDict = (NSDictionary *)jsonObject;
        // 处理字典数据,如获取特定键的值
        NSString *value = jsonDict[@"key"];
    } else if ([jsonObject isKindOfClass:[NSArray class]]) {
        NSArray *jsonArray = (NSArray *)jsonObject;
        // 处理数组数据,如遍历数组
        for (id item in jsonArray) {
            if ([item isKindOfClass:[NSDictionary class]]) {
                NSDictionary *dictItem = (NSDictionary *)item;
                // 处理字典项
            }
        }
    }
} else {
    NSLog(@"JSON parsing error: %@", error);
}

在上述代码中,使用 JSONObjectWithData:options:error: 方法将 NSData 类型的 JSON 数据解析为 id 类型的对象。options 参数可以设置解析选项,NSJSONReadingAllowFragments 表示允许解析非顶级对象的 JSON 数据。解析成功后,可以根据对象的类型(字典或数组)进行进一步处理。

将 Objective-C 对象转换为 JSON 数据的示例代码如下:

NSDictionary *dataDict = @{
    @"key1": @"value1",
    @"key2": @(2),
    @"key3": @[ @{ @"subKey": @"subValue" } ]
};

NSError *error;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:dataDict options:NSJSONWritingPrettyPrinted error:&error];
if (!error) {
    NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
    NSLog(@"JSON String: %@", jsonString);
} else {
    NSLog(@"JSON serialization error: %@", error);
}

在这段代码中,首先创建了一个包含不同类型数据的字典 dataDict。然后使用 dataWithJSONObject:options:error: 方法将字典转换为 NSData 类型的 JSON 数据。NSJSONWritingPrettyPrinted 选项会使生成的 JSON 数据格式更易读。转换成功后,可以将 NSData 转换为 NSString 并打印。

2.2 XML 处理

XML(eXtensible Markup Language)也是一种常用的数据格式,虽然相比 JSON 稍显复杂,但在一些场景下仍被使用。在 Objective-C 中,可以使用 NSXMLParser 来解析 XML 数据。

假设我们有如下简单的 XML 数据:

<root>
    <item>
        <name>Item 1</name>
        <value>Value 1</value>
    </item>
    <item>
        <name>Item 2</name>
        <value>Value 2</value>
    </item>
</root>

解析该 XML 数据的示例代码如下:

NSData *xmlData = // 从网络请求或其他来源获取的 XML 数据
NSXMLParser *parser = [[NSXMLParser alloc] initWithData:xmlData];
parser.delegate = self;
[parser parse];

上述代码创建了一个 NSXMLParser 对象,并将其委托设置为当前对象(当前对象需要遵守 NSXMLParserDelegate 协议),然后调用 parse 方法开始解析。

NSXMLParserDelegate 协议中有多个方法来处理解析过程中的不同事件:

// 开始解析文档
- (void)parserDidStartDocument:(NSXMLParser *)parser {
    self.items = [NSMutableArray array];
    self.currentItem = nil;
    self.currentElement = nil;
}

// 遇到开始标签
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary<NSString *,NSString *> *)attributeDict {
    if ([elementName isEqualToString:@"item"]) {
        self.currentItem = [NSMutableDictionary dictionary];
    }
    self.currentElement = elementName;
}

// 遇到结束标签
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName {
    if ([elementName isEqualToString:@"item"]) {
        [self.items addObject:self.currentItem];
        self.currentItem = nil;
    }
    self.currentElement = nil;
}

// 遇到字符数据
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string {
    if (self.currentItem && self.currentElement) {
        self.currentItem[self.currentElement] = string;
    }
}

// 解析完成
- (void)parserDidEndDocument:(NSXMLParser *)parser {
    NSLog(@"Parsed Items: %@", self.items);
}

在上述委托方法中,parserDidStartDocument: 方法在开始解析文档时调用,用于初始化一些变量。parser:didStartElement:namespaceURI:qualifiedName:attributes: 方法在遇到开始标签时调用,可根据标签名进行相应处理,如创建新的字典用于存储当前项的数据。parser:didEndElement:namespaceURI:qualifiedName: 方法在遇到结束标签时调用,可根据标签名完成当前项的处理,如将当前项添加到数组中。parser:foundCharacters: 方法在遇到字符数据时调用,将字符数据添加到当前项的对应键中。parserDidEndDocument: 方法在解析完成后调用,可对解析结果进行最后的处理。

三、网络请求的优化与安全

在 iOS 网络请求与数据交互中,优化和安全是至关重要的方面。

3.1 网络请求优化

  1. 缓存策略:合理使用缓存可以减少网络请求次数,提高应用性能。NSURLSession 提供了多种缓存策略,可以在 NSURLRequest 中设置。例如,使用 NSURLRequestReturnCacheDataElseLoad 策略,优先从缓存中获取数据,如果缓存中没有,则发起网络请求:
NSURL *url = [NSURL URLWithString:@"http://example.com/api/data"];
NSURLRequest *request = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestReturnCacheDataElseLoad timeoutInterval:15];
  1. 批量请求:如果应用需要发起多个网络请求,可以考虑将这些请求合并为一个批量请求。这样可以减少网络连接的建立次数,提高效率。例如,可以将多个 API 请求的数据整合到一个请求中,在服务器端进行处理后返回。
  2. 异步请求与多线程:使用异步请求可以避免阻塞主线程,保证应用的流畅性。NSURLSession 的任务默认是异步执行的。同时,可以根据需要合理使用多线程来处理网络请求的结果,如在后台线程中解析 JSON 数据,然后将解析结果传递到主线程更新 UI。

3.2 网络请求安全

  1. HTTPS:使用 HTTPS 协议可以加密网络传输的数据,防止数据被窃取或篡改。在 iOS 开发中,NSURLSession 对 HTTPS 有很好的支持。但需要注意处理服务器证书验证问题。可以通过实现 NSURLSessionDelegate 协议中的 URLSession:didReceiveChallenge:completionHandler: 方法来验证服务器证书:
- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * _Nullable credential))completionHandler {
    if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
        NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
        completionHandler(NSURLSessionAuthChallengeUseCredential, credential);
    }
}

上述代码简单地接受服务器的信任,但在实际应用中,应该进行更严格的证书验证,如验证证书的颁发机构、有效期等。 2. 数据加密:除了使用 HTTPS 进行传输层加密,对于一些敏感数据,还可以在应用层进行加密。例如,可以使用 AES 等加密算法对请求数据和响应数据进行加密和解密。在 Objective-C 中,可以使用第三方库如 CommonCrypto 来实现 AES 加密。 3. 输入验证:对网络请求的输入数据进行严格验证,防止 SQL 注入、XSS 等安全漏洞。例如,在发送表单数据时,对用户输入的数据进行转义处理,确保数据的安全性。

四、实战案例:一个简单的 iOS 应用数据交互

下面通过一个简单的 iOS 应用案例,来展示 Objective-C 在 iOS 网络请求与数据交互中的完整应用。假设我们要开发一个简单的新闻应用,从服务器获取新闻列表数据并展示。

  1. 创建项目:使用 Xcode 创建一个新的 iOS 项目,选择 Single View Application 模板。
  2. 搭建界面:在 ViewController.xib 中添加一个 UITableView 用于展示新闻列表。
  3. 网络请求与数据处理:在 ViewController.m 中编写如下代码:
#import "ViewController.h"

@interface ViewController () <UITableViewDataSource, UITableViewDelegate, NSURLSessionDataDelegate>

@property (nonatomic, strong) UITableView *tableView;
@property (nonatomic, strong) NSMutableArray *newsList;
@property (nonatomic, strong) NSURLSession *session;
@property (nonatomic, strong) NSMutableData *receivedData;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    self.newsList = [NSMutableArray array];
    self.receivedData = nil;

    NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
    self.session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:nil];

    NSURL *url = [NSURL URLWithString:@"http://example.com/api/news"];
    NSURLRequest *request = [NSURLRequest requestWithURL:url];

    NSURLSessionDataTask *dataTask = [self.session dataTaskWithRequest:request];
    [dataTask resume];

    self.tableView = [[UITableView alloc] initWithFrame:self.view.bounds style:UITableViewStylePlain];
    self.tableView.dataSource = self;
    self.tableView.delegate = self;
    [self.view addSubview:self.tableView];
}

- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
    if (!self.receivedData) {
        self.receivedData = [NSMutableData data];
    }
    [self.receivedData appendData:data];
}

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
    if (!error) {
        NSError *jsonError;
        id jsonObject = [NSJSONSerialization JSONObjectWithData:self.receivedData options:NSJSONReadingAllowFragments error:&jsonError];
        if (!jsonError) {
            if ([jsonObject isKindOfClass:[NSArray class]]) {
                NSArray *newsArray = (NSArray *)jsonObject;
                for (id newsDict in newsArray) {
                    if ([newsDict isKindOfClass:[NSDictionary class]]) {
                        [self.newsList addObject:newsDict];
                    }
                }
                dispatch_async(dispatch_get_main_queue(), ^{
                    [self.tableView reloadData];
                });
            }
        } else {
            NSLog(@"JSON parsing error: %@", jsonError);
        }
    } else {
        NSLog(@"Request failed: %@", error);
    }
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return self.newsList.count;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell" forIndexPath:indexPath];
    NSDictionary *news = self.newsList[indexPath.row];
    cell.textLabel.text = news[@"title"];
    cell.detailTextLabel.text = news[@"summary"];
    return cell;
}

@end

在上述代码中,viewDidLoad 方法中创建了 NSURLSessionNSURLSessionDataTask 发起网络请求。在 URLSession:dataTask:didReceiveData: 方法中处理接收到的数据,在 URLSession:task:didCompleteWithError: 方法中解析 JSON 数据并更新 UI。tableView:numberOfRowsInSection:tableView:cellForRowAtIndexPath: 方法用于配置 UITableView 的显示。

通过这个简单的案例,我们可以看到 Objective-C 在 iOS 网络请求与数据交互中的完整流程,从网络请求的发起、数据的接收与处理,到最终在界面上的展示。

综上所述,Objective-C 在 iOS 网络请求与数据交互中有着丰富的功能和成熟的实现方式。通过合理选择网络请求框架、正确处理数据格式、优化网络请求以及确保安全,开发者可以构建出高效、稳定且安全的 iOS 应用。无论是传统的项目还是新的 iOS 开发需求,Objective-C 的这些技术仍然具有重要的参考价值和应用场景。在实际开发中,需要根据项目的具体需求和特点,灵活运用这些技术,以实现最佳的用户体验和应用性能。同时,随着 iOS 开发技术的不断发展,也需要关注新的框架和技术,如 Combine 框架等,以进一步提升开发效率和应用质量。但 Objective-C 的基础网络请求与数据交互知识,始终是 iOS 开发者不可或缺的技能之一。在面对复杂的网络环境和多样化的数据需求时,扎实的基础能够帮助开发者更好地应对各种挑战,实现功能强大且用户友好的 iOS 应用。无论是小型的个人应用还是大型的企业级应用,Objective-C 在网络请求与数据交互方面都能够提供稳定可靠的解决方案。开发者可以根据实际情况,结合各种优化和安全措施,打造出高性能、高安全性的 iOS 应用,满足不同用户群体的需求。同时,持续学习和关注行业动态,不断探索新的技术和方法,也是提升自身开发能力和保持竞争力的关键。通过不断实践和总结经验,开发者能够在 Objective-C 的 iOS 网络请求与数据交互领域不断创新和突破,为用户带来更加优质的应用体验。在未来的 iOS 开发中,Objective-C 虽然可能不再是主流的开发语言,但它所积累的丰富经验和技术沉淀,将继续为 iOS 开发者提供宝贵的借鉴和指导。无论是在维护旧有项目还是在探索新的开发思路时,Objective-C 在网络请求与数据交互方面的知识都将发挥重要的作用。开发者可以通过深入研究和实践,挖掘更多的潜力,为 iOS 应用开发贡献更多的价值。随着移动互联网的持续发展,对 iOS 应用的网络性能和数据交互质量的要求也将越来越高。Objective-C 的网络请求与数据交互技术,作为 iOS 开发的重要组成部分,将在这个过程中不断演进和完善。开发者需要紧跟时代的步伐,不断优化和改进应用的网络功能,以适应日益增长的用户需求和复杂的网络环境。通过不断地学习和实践,将 Objective-C 的网络技术与新的开发理念相结合,创造出更加优秀的 iOS 应用。同时,也要注重代码的可维护性和可扩展性,以便在应用的生命周期内能够轻松应对各种变化和需求。在 Objective-C 的 iOS 网络请求与数据交互开发中,每一个细节都可能影响到应用的整体性能和用户体验。从请求的发起时机到数据的处理方式,从缓存策略的选择到安全机制的实施,都需要开发者精心设计和优化。通过不断地积累经验和学习新技术,开发者能够在这个领域取得更好的成果,为 iOS 应用的发展做出更大的贡献。在实际项目中,还可能会遇到各种复杂的情况,如网络不稳定、数据格式不一致等。这就需要开发者具备扎实的技术功底和解决问题的能力,能够灵活运用 Objective-C 的网络请求与数据交互技术,找到合适的解决方案。同时,与团队成员的协作和沟通也至关重要,共同探讨和解决问题,能够提高开发效率和应用的质量。总之,Objective-C 在 iOS 网络请求与数据交互中的实践是一个不断探索和创新的过程,需要开发者持续投入精力,不断提升自己的技术水平,以打造出更加出色的 iOS 应用。