第八章:iOS网络应用编程

一、检测网络状态

当应用程序需要访问网络时,它首先应该检查设备的网络状态,确认设备的网络环境及连接情况,并针对这些情况提醒用户做出相应的处理。最好能监听设备的网络状态的改变,当设备网络状态连接、断开时,程序也应该有相应的处理。

1、检查网络状态

检查设备的网络状态,需要如下两个步骤:

  • ①、下载、添加Reachability类;

通过Xcode的帮助系统搜索Reachability,接下来下就可以在“Sample Code”分类中看到Reachability实例项目;

选择“Reachability”列表项,即可查看Reachability项目:

单击“Open Project”按钮,可以打开该项目;

  • ②、为项目添加SystemConfiguration.framework框架。

项目实例:

(1)、创建实例,检查网络状态:

- (IBAction)testNetStatus:(id)sender {
     NSString* site = self.siteField.text;
     //创建访问指定站点的Reachability
     Reachability* reach = [Reachability reachabilityWithHostName:site];
     //判断该设备的网络状态
     switch ([reach currentReachabilityStatus])
     {
          //不能访问
          case NotReachable:
               [self showAlert:[NSString stringWithFormat:@"不能访问%@",site]];
               break;
          case ReachableViaWWAN:
               [self showAlert:[NSString stringWithFormat:@"使用3G/4G网络访问%@",site]];
               break;
          case ReachableViaWiFi:
              [self showAlert:[NSString stringWithFormat:@"使用WiFi网络访问%@",site]];
               break;
          default:
               break;
      }
}

(2)、判断是否WIFI可用:

- (IBAction)testWifi:(id)sender {
     if ([[Reachability reachabilityForLocalWiFi]currentReachabilityStatus] ==           ReachableViaWiFi) {
          [self showAlert:@"WIFI网络已经连接"];
     }else{
         [self showAlert:@"WIFI网络不可用"];
      }
}

(3)、判断是否3G/4G可用:

- (IBAction)testInternet:(id)sender {
     if ([[Reachability reachabilityForInternetConnection]currentReachabilityStatus] == NotReachable) {
          [self showAlert:@"3G/4G网络已经连接"];
     }else{
          [self showAlert:@"3G/4G网络不可用"];
    }
}

2、监听网络状态改变

除了直接检测网络连接状态之外,有时候程序还需要监听网络状态的改变——当网络断开连接时,提醒用户,网络连接已经断开,应用可能需要暂停;当网络重新连接时,再次提醒用户,应用可以继续进行。

程序获取Reachability对象之后,调用Reachability对象的startNotifier方法即可开启该对象的监听状态——当Reachability的连接状态发生改变时,该对象只要使用默认的通知中心监听该通知即可。

在应用程序委托类中添加:

注意:一定要将reach设置为成员变量;

@interface AppDelegate ()
{
     Reachability* reach;
}
@end
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
     [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(reachabilityChanged:) name:kReachabilityChangedNotification object:nil];
     reach = [Reachability reachabilityWithHostName:@"www.baidu.com"];
     [reach startNotifier];
     return YES;
}
- (void)reachabilityChanged:(NSNotification *)note{
     //通过通知对象获取被监听的Reachability对象
     Reachability* curReach = [note object];
     //获取Reachability对象的网络状态
     NetworkStatus status = [curReach currentReachabilityStatus];
     NSLog(@"statue = %u",status);
     if (status == NotReachable) {
          [[[UIAlertView alloc]initWithTitle:@"提醒" message:@"网络连接失败"      delegate:nil cancelButtonTitle:@"YES" otherButtonTitles: nil]show];
      }
}

二、使用CFNetwork实现TCP协议的通信

TCP/IP通信协议是一种可靠的网络协议,他在通信的两端各创建一个通信接口,从而在通信的两端之间形成网络虚拟链路。一旦建立了虚拟的网络链路,两端的程序就可以通过虚拟链路进行通信。CFNetwork对基于TCP协议的网络通信提供了良好的封装,CFNetwork使用CFSocket来代表两端的通信接口,还可以通过CFStream读/写数据。

1、IP地址与端口号

IP地址用于唯一地标识网络中的一个通信实体,这个通信实体既可以是一台主机,也可以是一台打印机,或者是路由器的某一个端口。在基于IP协议的网络中传输的数据包,都必须使用IP地址来进行标识。

IP地址是数字型的,是一个32位(32bit),但为了更加便于记忆,通常把它分成4个8位二进制数,没每8位之间用圆点隔开,每个8位整数可以转换成一个0~255的十进制整数,因此我们看到的IP地址常常是这样的形式:201.9.128.88。

IP地址被分成了A、B、C、D、E五类,每个类型的网络标识和主机标识各有规则。

  • A类:10.0.0.0~10.255.255.255
  • B类:172.16.0.0~172.31.255.255
  • C类:192.168.00~192.168.255.255

IP地址用于唯一标识网络上的一个通信实体,但一个通信实体可以有过个通信程序同时提供网络服务,此时还需要使用端口。

端口是一个16位的整数,用于表示数据交给哪个通信程序处理。因此,端口就是应用程序与外界交流的出入口,它是一种抽象的数据,同一台机器上不能有两个程序使用同一个端口,端口可以从0到65535,通常分为3类:

  • 公认端口(Well Known Ports):从0到1023,他们紧密绑定(Binding)一些特定的服务;
  • 注册端口(Registered Ports):从1024到49151,他们松散绑定一些服务。应用程序通常使用这个范围内的端口;
  • 动态和/或私有端口(Dynamic and/or Private Ports):从49152到65535,这些端口是应用程序使用的动态端口,应用程序一般不会主动使用这些端口。

2、TCP协议基础

IP协议是Internet上使用的一个关键协议,它的全称是Internet Protocol,即Internet协议,通常简称IP协议。通过使用IP协议,使Internet称为一个允许连接不同类型的计算机和不同操作系统的网络。保证计算机能发送和接收分组数据。IP协议负责将消息从一个主机传送到另一个主机没消息在传送的过程中被分割成一个个小包。

TCP协议被称为一种端对端协议,这是因为它为两台计算机之间的连接起到了重要作用——当一台计算机需要与另一台远程计算机连接时,TCP协议会让他们建立一个连接:用于发送和接收数据的虚拟链路。

TCP协议负责收集这些信息包,并将其适当的次序放好传送,在接受端收到后再将其正确的还原。TCP协议保证了数据包在传送中准确无误。TCP协议使用重发机制:当一个通信实体发送一个消息给另一个通信实体后,需要收到另一个通信实体的确认信息,如果没有收到另一个通信实体的确认信息,则会再次重发刚才的信息。

通过这种重发机制,TCP协议想应用提供可靠的通信连接,使它能够自动适应网上的各种变化。即使在Internet暂时出现堵塞的情况下,TCP也能够保证通信的可靠性。

综上所述:虽然IP和TCP这两个协议的功能不尽相同,也可以分开单独使用,但他们是在同一时期作为一个协议来设计的,并且在功能上也是互补的。这有两者结合,才能保证Internet在复杂的环境下正常运行。凡是要连接到Internet的计算机,都必须同时安装和使用这两个协议,因此在实际中常把这两个协议统称为TCP/IP协议。

3、使用CFSocket实现TCP服务器端

使用CFSocket建立服务器端的步骤如下:

  • (1)、创建一个监听Socket Accept(Socket连接)的CFSocket,并为kCFSocketAcceptCallBack事件绑定回调函数;
  • (2)、调用CFSocketSetAddress()函数,将服务器的CFSocket绑定到本地IP地址和端口;
  • (3)、将CFSocket作为source添加到指定线程的CFRunLoop上,并运行该线程的CFRunLoop,从而保证该CFSocket能持续不断的接受来自客户端的连接。
#import <sys/socket.h>
#import <arpa/inet.h>
#import <Foundation/Foundation.h>
//读取数据的回调函数
void readStream(CFReadStreamRef iStream,CFStreamEventType eventType,void *clientCallBackInfo){
     UInt8 buff[2048];
     CFIndex hasRead = CFReadStreamRead(iStream, buff, 2048);
     if (hasRead > 0) {
         //强制只处理hasRead前面的数据
        buff[hasRead] = ‘\0‘;
         printf("接受到的数据:%s\n",buff);
    }
}
//客户端连接进来的回调函数
void TCPServerAcceptCallBack(CFSocketRef socket,CFSocketCallBackType type,CFDataRef address,const void *data, void *info){
     //如故宫客户端Socket连接进来
     if (kCFSocketAcceptCallBack == type) {
         //获取本地Socket的Handle
         CFSocketNativeHandle nativeSocketHandle = *(CFSocketNativeHandle*)data;
         uint8_t name[SOCK_MAXADDRLEN];
         socklen_t nameLen = sizeof(name);
         //获取对方Socket信息,还有getsocketname()函数则用于获取本程序所在Scoket信息
         if (getpeername(nativeSocketHandle, (struct sockaddr *)name, &nameLen) != 0) {
             NSLog(@"error");
             exit(1);
        }
         //获取连接信息
         struct sockaddr_in * addr_in = (struct sockaddr_in *)name;
         NSLog(@"%s:%d连接进来了",inet_ntoa(addr_in->sin_addr),addr_in->sin_port);
         CFReadStreamRef iStream;
         CFWriteStreamRef oStream;
         //创建一组可读写的CFStream
         CFStreamCreatePairWithSocket(kCFAllocatorDefault, nativeSocketHandle, &iStream, &oStream);
         if (iStream && oStream) {
             //打开输入流和输出流
             CFReadStreamOpen(iStream);
             CFWriteStreamOpen(oStream);
             CFStreamClientContext streamContext = {0,NULL,NULL,NULL};
             if (!CFReadStreamSetClient(iStream, kCFStreamEventHasBytesAvailable,          readStream/*回调函数,当有可读的数据时调用*/, &streamContext)) {
                 exit(1);
            }
             CFReadStreamScheduleWithRunLoop(iStream, CFRunLoopGetCurrent(),     kCFRunLoopCommonModes);
             const char* str = "您好,您收到了Mac服务器的新年祝福!\n";
             //向客户端输出数据
             CFWriteStreamWrite(oStream, (UInt8*)str, strlen(str) + 1);
         }
     }
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
     //创建Socket,指定TCPServerAcceptCallBack
     //作为kCFSocketAcceptCallBack事件的监听函数
     CFSocketRef _socket = CFSocketCreate(kCFAllocatorDefault
            , PF_INET//指定协议类,如果该参数为0或者负数,则默认为PF_INET
            , SOCK_STREAM// 指定Socket类型,如果协议族为PF_INET,且该参数为0或负数,则它默认为SOCK_STREAM,如果要使用UDP协议,则该参数指定为SCOCK_DGRAM
            , IPPROTO_TCP//指定通信协议。如果前一个参数为SOCK_STREAM,默认使用TCP协议;如果前一个参数为SOCK_DGRAM,默认使用UDP协议
            , kCFSocketAcceptCallBack//该参数指定下一个回调函数所监听的时间类型
            , TCPServerAcceptCallBack//回调函数
            , NULL);
     if (_socket == NULL) {
         NSLog(@"创建Socket失败!");
         return 0;
    }
     int optval = 1;
     //设置允许重用本地地址和端口
     setsockopt(CFSocketGetNative(_socket), SOL_SOCKET, SO_REUSEADDR, (void *)&optval, sizeof(optval));
     //定义sockaddr_in类型的变量,该变量将作为CFSocket的地址
     struct sockaddr_in addr4;
     memset(&addr4, 0, sizeof(addr4));
    addr4.sin_len = sizeof(addr4);
    addr4.sin_family = AF_INET;
     //设置该服务器监听本机任意可用的IP地址
     //addr4.sin_addr.s_addr = htonl(INADDR_ANY);
     //设置服务器监听地址
    addr4.sin_addr.s_addr = inet_addr("192.168.1.101");
     //设置服务器监听端口
    addr4.sin_port = htons(30000);
     //将IPv4的地址转换为CFDataRef
     CFDataRef address = CFDataCreate(kCFAllocatorDefault, (UInt8 *)&addr4,     sizeof(addr4));
     //将CFSocket绑定到指定IP地址
     if (CFSocketSetAddress(_socket, address)!= kCFSocketSuccess){
         NSLog(@"地址绑定失败");
         //如果_socket不为NULL,释放_socket
         if ((_socket)) {
             CFRelease(_socket);
             exit(1);
        }
        _socket = NULL;
     }
     NSLog(@"---启动循环监听客户端连接---");
     //获取当前线程的CFRunLoop
     CFRunLoopRef cfRunLoop = CFRunLoopGetCurrent();
     //将_socket 包装成CFRunLoopSource
    CFRunLoopSourceRef source = CFSocketCreateRunLoopSource(kCFAllocatorDefault, _socket, 0);
     //为CFRunLoop对象添加source
    CFRunLoopAddSource(cfRunLoop, source, kCFRunLoopCommonModes);
     CFRelease(source);
     //运行当前线程的CFRunLoop
    CFRunLoopRun();
    }
    return 0;
}

上面程序中main()函数作为程序的入口,程序从该函数开始执行。main()函数的第一段粗体字代码创建了一个CGSocket对象,并制定了该CFSocket使用TCP协议,基于流进行输入/输出。

接下来程序创建一个sockaddr_in类型的结构体变量,该结构体变量将会作为CFSocket绑定的监听地址,因此程序为socketaddr_in类型的结构体变量制定了IP地址和端口,然后程序中的粗体字代码调用了CFSocketSetAddress()函数将指定CFSocket绑定到指定的IP地址和端口。

main()函数的最后一段粗体字代码将该CFSocket作为source添加到主线程CFRunLoop上,并运行主线程的CFRunLoop,从而保证该CFSocket能持续不断地接受来自客户端的连接。

该程序的另一个重点是TCPServerAcceptCallBack回调函数——当CFSocket接受来自客户端的连接后,该函数将会被调用。该函数主要做了如下4件事情:

  • ①、调用getpeername()函数获取远程Socket的相关信息,从而在控制台打印出连接进来的客户端IP地址和端口;
  • ②、调用CFStreamCreatePairWithSocket()函数通过CFSocket获取CFReadStreamRef、CFWriterStreamRef,接下来程序即可通过这两个流进行读/写网络数据;
  • ③、再次使用主线程的CFRunLoop监听来自客户端的数据;
  • ④、向客户端发送一段字符串。

在终端中运行:clang -fobjc-arc -framework Foundation main.m即可生成a.out文件,在终端中运行该a.out文件。

4、使用CFSocket实现TCP客户端

创建TCP客户端同样通过CFSocket完成。使用CFSocket创建Socket客户端的步骤如下:

  • (1)、创建一个不监听任何事件或监听Connection的CFSocket。如果过要监听Connection,则需要为kCFSocketConnectCallBack事件绑定回调函数;
  • (2)、调用CFSocketConnectToAddress()函数,将客户端的CFSocket连接到指定的IP地址和端口的服务器上;
  • (3)、得到客户端的CFSocket之后,即可直接使用CFSocket对应的CFSocketNativeHandle进行读/写,也可通过CFSocket获取CFReadStreamRef、CFWriteStreamRef后进行读/写;
#import "ViewController.h"
#import <sys/socket.h>
#import <arpa/inet.h>
#import <netinet/in.h>
@interface ViewController ()
{
     CFSocketRef _socket;
     BOOL _isOnline;
}
@end
@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
     //创建Socket,无需回调函数
     _socket = CFSocketCreate(kCFAllocatorDefault
            , PF_INET//指定协议族,如果该参数为0活着负数,则默认为PF_INET
            , SOCK_STREAM//指定Socket类型,如果协议族为PF_INET,且该参数为0活着负数,则它会默认为SOCK_STREAM,如果要使用UDP协议,则该参数指定为SOCK_DGRAM
            , IPPROTO_TCP//指定默认协议,如果前一个参数为SOCK_STREAM,默认使用TCP协议,如果前一个参数为SOCK_DGRAM,默认使用UDP协议
            , kCFSocketNoCallBack//该参数直嘀咕下一回调函数所监听的事件类型
            , nil
            , NULL);
     if (_socket != nil) {
         //定义sockaddr_in类型的变量,该变量将作为CFSocket的地址
         struct sockaddr_in addr4;
         memset(&addr4, 0, sizeof(addr4));
        addr4.sin_len = sizeof(addr4);
        addr4.sin_family = AF_INET;
         //设置连接远程服务器的地址
        addr4.sin_addr.s_addr = inet_addr("192.168.1.101");
         //设置连接远程服务器的监听端口
        addr4.sin_port = htons(30000);
         //将IPv4的地址转换为CFDataRef
         CFDataRef address = CFDataCreate(kCFAllocatorDefault, (UInt8 *)&addr4,     sizeof(addr4));
         //连接远程服务器的Scoket,并返回连接结果
         CFSocketError resule = CFSocketConnectToAddress(_socket
, address//指定远程服务器的IP和端口
, 5//指定连接超时时长,如果该参数为负数,则把连接操作放在后台,当_socket消息类型为kCFSocketConnectCallBack,将会在连接成功或失败的时候在后台触发回调函数,
            );
         if (resule == kCFSocketSuccess) {
             _isOnline = YES;
             // 启动新线程来读取服务器响应的数据
                [NSThread detachNewThreadSelector:@selector(readStream) toTarget:self withObject:nil];
         }
    }
}
- (void)readStream{
     char buffer[2048];
     ssize_t hasRead;
     //与本机关联的Socket,如果已经失效返回-1:INVALID_SOCKET
     while ((hasRead = recv(CFSocketGetNative(_socket), buffer, sizeof(buffer), 0))) {
         NSLog(@"%@",[[NSString alloc]initWithBytes:buffer length:hasRead          encoding:NSUTF8StringEncoding]);
    }
}
- (IBAction)clicked:(id)sender {
     if (_isOnline) {
         NSString* stringTosend = @"来自iOS客户端的问候";
         const char* data = [stringTosend UTF8String];
         send(CFSocketGetNative(_socket), data, strlen(data) + 1, 1);
    }else{
         NSLog(@"暂未连接服务器");
     }
}
@end

上面程序中的第一段粗体字代码创建了一个CFSocket,该CFSocket同样使用了TCP协议,并且是基于SOCK_STREAM流的Socket,然后程序创建了一个struct sockaddr_in结果体变量,该结构体变量代表远程故武器的地址。

上面程序中的第二段代码粗体字代码调用了CFSocketConnectToAddress()函数将CFSocket连接到远程服务器地址——如果连接成功,接口得到一个进行网络读/写的CFSocket。然后程序以readStream作为新线程的执行体,启动了一个新线程,其中readStream方法中的粗体字代码调用了recv()函数从指定CFSocket读取数据。而clicked:方法则用于向服务器发送数据,该方法中的粗体字代码调用了send()函数想CFSocket发送数据。

5、使用CocoaAsyncSocket实现TCP客户端

直接使用CFSocket虽然可以实现基于TCP协议的网络通信,但采用这种方式进行编程十分繁琐,为了降低直接使用CFSocket编程的复杂度,另外有开发者提供了一套开元的CocoaAsyncSocket库。

CocoaAsyncSocket封装了CFNetwork底层的CFSocket和CFStream,并提供了异步操作,从而可简化Socket网络编程。CocoaAsyncSocket不仅支持了TCP协议的网络编程,也支持UDP协议的网络编程。从这个角度来说,CocoaAsyncSocket是CFSocket的绝佳替代者。

 CocoaAsyncSocket主要有以下特性:
  • (1)、非阻塞方式的读和写,而且可设置超时时长;
  • (2)、自动的Socket接受,如果调用它接受连接,它将为每个连接启动新的实例,当然也可以立即关闭这些连接;
  • (3)、委托(delegate)支持。错误、连接、接收、完整的读取、完整的写入、进度以及断开连接,都可通过委托模式调用;
  • (4)、所有操作都封装在一个类中,开发者无需操作Socket或流,该类封装了所有操作。
    下载和安装CocoaAsyncSocket的步骤如下:
    
  • (1)、CocoaAsyncSocket的官方网址是http://github.com/robbiehanson/CocoaAsyncSocket;登陆该站点,单机页面中间的“xx release”(其中xx代表一个数值,比如47,代表给项目一共发布了47个版本)链接;
  • (2)、浏览器将会打开一个新的列表页面,该列表页面中列出了CocoaAsyncSocket的所有发布版本,建议下载最新的CocoaAsyncSocket。
  • (3)、下载完成将得到一个压缩文件,解压后将看到如下文件结构:
    • ①、RunLoop:该目录下包含了AsyncSocket、AsyncUdpSocket两个雷德源文件和Xcode目录。其中AsyncSocket就是基于TCP协议的CocoaAsyncSocket实现,AsyncUdpSocket就是基于UDP协议的AsyncUdpSocket实现。而Xcode目录下则包含了使用CocoaAsyncSocket开发服务器与客户端的示例项目;
    • ②、GCD:该目录下的内容与RunLoop目录下的内容基本相似,只是类名变成了GCDAsyncSocket、GCDAsyncUdpSocket,这是因为目录下的CocoaAsyncSocket是基于GCD的实现;
    • ③、Vendor:其他相关类;
    • ④、其他杂项文件。
  • (4)、如果程序打算使用RunLoop版本的CocoaAsyncSocket,则需要将RunLoop目录下的AsyncSocket、AsyncUdpSocket两个类的类文件复制到项目中,如果打算使用GCD版本的CocoaAsyncSocket,则需要将GCD目录下的GCDAsyncSocket、GCDAsyncUdpSocket两个类的类文件复制到项目中。除此之外,还需要为项目增加CFNetwork.framework框架。
    添加CocoaAsyncSocket支持之后,使用AsyncSocket开发TCP客户端的步骤如下:
    
  • ①、创建一个AsyncSocket对象,创建该对象时需要指定该AsyncSocket的delegate,该delegate必须实现AsyncSocketDelegate协议,该delegate对象负责处理AsyncSocket在网络通信过程中的各种事件;
  • ②、调用AsyncSocket对象的connectToHost:onPort:error:方法控制AsyncSocket对象连接制定IP地址、制定端口的服务器程序,该方法的最后一个参数用于接收连接错误;
  • ③、为AsyncSocket的delegate对象(实现AsyncSocketDelegate协议的对象)实现特定的方法,该delegate负责处理AsyncSocket在网络通信过程中的各种事件;
  • ④、如果需要发送数据,则调用AsyncSocket的writeData:withTimeout:tag:方法;如果需要读取数据,则调用AsyncSocket的readData:withTimeout:tag:方法。当数据发送完成回数据读取完成时,都会激发AsyncSocket的delegate对象的特定方法;

三、使用NSURLConnection

如果只是为了读取HTTP等服务器数据,或向服务器提交数据,iOS还提供了NSURLConnection类,NSURLConnection使用NSURLRequest向远程服务器发送同步或异步请求,病获取服务器响应的数据。除了NSURLRequest之外,还可以使用NSMutableURLRequest(NSURLRequest的可变子类)向服务器发送数据。

1、使用NSURLConnection从网络获取数据

NSURLConnection可用于根据URL加载服务器响应,该对象的方法并不多,如果使用该对象来异步加载服务器响应,则需要为该对象指定一个遵守NSURLConnectionDelegate协议的对象,该对象作为NSURLConnection的delegate,负责处理异步加载过程中的事件。

除此之外,还可使用NSURLConnection的 + (NSData )sendSynchronousRequest:(NSURLRequest )request returningResponse:(NSURLResponse )response error:(NSError )error;类方法来同步加载服务器响应。

  • (1)、NSURLConnection大致提供了如下常用方法:
①、 @property (readonly, copy) NSURLRequest *originalRequest NS_AVAILABLE(10_8, 5_0);//获取该NSURLConnection最初的NSURLRequest对象的深拷贝;
②、 @property (readonly, copy) NSURLRequest *currentRequest NS_AVAILABLE(10_8, 5_0);//返回该NSURLConnection当前使用的NSURLRequest对象;
  • (2)、采用同步请求的方式获取网络数据的方法如下:
+ (NSData *)sendSynchronousRequest:(NSURLRequest *)request returningResponse:(NSURLResponse **)response error:(NSError **)error;

第一个参数:发送请求的NSURLRequest对象;

第二个参数:需要传入NSURLRequest对象的指针,用于获取服务端的响应对象;

第三个参数:用于保存获取的错误信息;

  • (3)、采用异步请求的方式获取网络数据的方法如下:
①、 + (NSURLConnection*)connectionWithRequest:(NSURLRequest *)request delegate:(id)delegate;//采用异步请求的方式获取数据。第二个参数作为NSURLRequest的delegate;
②、 - (instancetype)initWithRequest:(NSURLRequest *)request delegate:(id)delegate;//与上一个方法基本相同,只是该方法是实例方法,必须现alloc,再调用该方法;
③、 - (instancetype)initWithRequest:(NSURLRequest *)request delegate:(id)delegate startImmediately:(BOOL)startImmediately NS_AVAILABLE(10_5, 2_0);//与前一个方法的功能基本类似,只是多了一个startImmediately参数,该参数控制是否立即发送请求;
④、 + (void)sendAsynchronousRequest:(NSURLRequest*) request
                          queue:(NSOperationQueue*) queue
              completionHandler:(void (^)(NSURLResponse* response, NSData* data, NSError* connectionError)) handler NS_AVAILABLE(10_7, 5_0);//该方法需要额外指定NSOperationQueue参数,表明请求交给指定的NSOperationQueue处理。
⑤、 - (void)start NS_AVAILABLE(10_5, 2_0);//开始发送请求。只有当通过③方法发送请求,且最后一个参数为NO时,才需要就调用该方法。
  • (4)、使用NSURLConnection从网络获取数据的步骤如下:

    • ①、创建NSURLRequest对象,该对象代表对远程服务器的请求,该对象可以包括请求的URL、缓存策略、超时时长等信息;
    • ②、调用NSURLConnection的实例方法或类方法,以NSURLRequest对象为参数创建NSURLConnection还可发送请求;
    • ③、如果调用方法以异步方式加载服务器响应,则需要为NSURLConnection对象指定delegate对象,因此还需要为delegate对象实现特定的方法。

例如:

- (void)viewDidLoad {
    [super viewDidLoad];
    NSString* str = @"https://www.crazyit.org/ethos.php";
    totalData = [[NSMutableData alloc]init];
    //以指定NSString创建NSURL对象
    NSURL* url = [NSURL URLWithString:str];
    //创建NSURLRequest对象
//    NSURLRequest* request = [NSURLRequest requestWithURL:url];
    //通过这种方式创建NSURLRequest可以指定缓存策略,超时时长
    NSURLRequest* request = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:5];
    //以指定URL、delegate创建连接、发送请求
    NSURLConnection* conn = [NSURLConnection connectionWithRequest:request delegate:self];
    //如果conn为nil,则直接返回
    if (conn != nil) {
        return;
    }
}
#pragma mark -- NSURLConnectionDataDelegate
//当服务器响应生成时激发该方法
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response{
    NSLog(@"-- didReceiveResponse --");
    NSLog(@"响应的数据类型:%@",response.MIMEType);
    //获取响应数据的长度,如果不能检测到长度,则返回NSURLResponseUnknowLength(-1)
    NSLog(@"响应的数据长度为:%lld",response.expectedContentLength);
    NSLog(@"响应的数据所使用的字符集:%@",response.textEncodingName);
    NSLog(@"响应的文件名:%@",response.suggestedFilename);
}
//每次读取服务器响应的数据时,都会激发该方法
//对于一个请求而言,服务器数据可能要分几次才能读取,因此该方法将会被触发多次
//如果程序需要将这些数据转化成字符串,则建议使用NSMutableData来收集这些数据,然后整体
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{
    [totalData appendData:data];
}
//当连接服务器出现错误时激发该方法,可通过error获取错误信息
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error{
    NSLog(@"--error--");
}
//当数据load完成时激发该方法,可通过error获取错误信息
- (void)connectionDidFinishLoading:(NSURLConnection *)connection{
    NSLog(@"--finisgLoading--");
    NSString* content = [[NSString alloc]initWithData:totalData encoding:NSUTF8StringEncoding];
    [totalData setLength:0];
    NSLog(@"content = %@",content);
}

2、使用NSMutableURLRequest向服务器发送数据

NSMutableURLRequest可以添加请求头,请求参数;

NSMutableURLRequest新增了如下常用方法:

(1)、 - (void)addValue:(NSString *)value forHTTPHeaderField:(NSString *)field;//该方法用于为NSMutableURLRequest添加请求头;
(2)、 @property (readonly, copy) NSDictionary *allHTTPHeaderFields;//该方法通过一个NSDictionary一次性地为NSMutableURLRequest设置多个请求头;
(3)、 @property (readonly, copy) NSData *HTTPBody;//设置NSMutableURLRequest的请求体数据——也就是设置请求参数;
(4)、 @property (readonly, retain) NSInputStream *HTTPBodyStream;//以NSInputStream为参数设置NSMutableURLRequest的请求体数据。该方法与(3)方法只能设置一个;
(5)、 @property (copy) NSString *HTTPMethod;//设置提交请求的方式,要么是POST,要么是GET,默认是GET。
(6)、 @property BOOL HTTPShouldHandleCookies;//设置该HTTP请求是否处理Cookie;
(7)、 - (void)setValue:(NSString *)value forHTTPHeaderField:(NSString *)field;//为指定的请求头设置请求值。

例如:

- (void)viewDidLoad {
    [super viewDidLoad];
    NSString* str = @"http://192.168.1.88.8888/abc/login.jsp";
    totalData = [[NSMutableData alloc]init];
    //以指定的NSString创建NSURL对象
    NSURL* url = [NSURL URLWithString:str];
    //创建NSURLRequest对象
//    NSURLRequest* request = [NSURLRequest requestWithURL:url];
    //通过这种方式创建NSURLRequest可以指定缓存策略,超时时长
    NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringLocalAndRemoteCacheData timeoutInterval:5];
    //---------下面代码开始设置请求参数-------------
    //准备请求参数
    NSString* post = [NSString stringWithFormat:@"name=%@&pass=%@",@"crazyit.org",@"疯狂软件"];
    //将请求参数转换为NSData
    NSData* postData = [post dataUsingEncoding:NSUTF8StringEncoding];
    NSString* postLength = [NSString stringWithFormat:@"%ld",[postData length]];
    //设置请求的方式,默认GET请求
    [request setHTTPMethod:@"POST"];
    //添加两个请求头
    [request setValue:postLength forHTTPHeaderField:@"Content-Length"];
    [request setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
    //将请求数据设为HTTP请求体
    [request setHTTPBody:postData];
    //以指定URL、delegate创建连接、发送请求
    NSURLConnection* conn = [NSURLConnection connectionWithRequest:request delegate:self];
    //如果conn为nil 则直接返回
    if (conn == nil) {
        return;
    }
}

四、XML解析

XML文件是一种平台无关的数据交换格式,当iOS应用需要与其他应用或者应用服务器进行通信时,如果数据量娇较小,当然可以选择简单的文本数据。但当数据量交较大而且数据之间具有严格的结构关系时,使用 简单的文本数据就比较麻烦了,此时可以要么选择XML文档作为数据交换格式,要么选择JSON作为数据交换格式。

1、DOM与SAX

当XML文档作为数据交换工具时,应用程序必须采用合适的方式来获取XML文档里包含有用信息,这就需要程序来解析XML文档了。为了利用XML文档的结构特征进行解析,现在有两套比较流行的规范;

  • ①、DOM:Document Object Model,即文档对象模型,它是由W3C推荐的处理XML文档的规范。
  • ②、SAX:Simple API for XML,它并不是W3C推荐的标准,但却是整个XML行业的事实规范。

在iPhone开发中,XML的解析有很多选择,iOS SDK提供了NSXMLParser和libxml2两个类库,另外还有很多第三方库可选,例如GDataXML,TBXML,TouchXML、KissXML等;

介绍如下:

  • (1)、NSXMLParser:这是iOS SDK自带的解析器,基于SAX解析,纯Objective-C实现的,因此iOS应用可以直接使用该解析器,但它的性能并不好,而且使用起来也不太方方便;
  • (2)、libxml2:这也是iOS SDK自带的解析器,它是一套开源的解析器,是Linux内核的系统上很常用的解析器。它的功能非常强大,可同时支持DOM和SAX解析,而且性能也很好,但并不是基于Objective-C实现的,而是采用C语言实现的,因此用起来不太方便;
  • (3)、GDataXML:这是第三方提供的基于DOM的XML解析类库,功能很强大,支持读取和修改XML文档,也支持XPath方式查询,而且性能也很快,因此这是一套非常流行的第三方解析类库;

    -(4)、TBXML:这是一套轻量级的基于DOM的XML解析类库,有很好的性能和低内存占用,可以在低内存损耗的条件下快速提取内容。但功能比较简单,不支持对XML文档进行校验,也不支持XPath方式查询,而且只能读,不能修改XML文档。

  • (5)、TouchXML:这是另一基于DOM的XML解析类库,与TBXML的功能和特点几乎相同,唯一的优势就是支持XPath方式查询。
  • (6)、KissXML:这是一套基于TouchXML的XML解析类库,在TouchXML继承上提供了对XML文档修改的功能。

2、使用NSXMLParser解析XML文档

NSXMLParser是基于SAX的解析器,也就是所谓的”事件驱动器”,因此使用NSXMLParser解析的关键就是实现SAX的事件处理器——该事件处理器负责处理NSXMLParser解析XML过程中的各种事件;

NSXMLParser解析的事件处理采用了委托事件处理,因此开发者只要为NSXMLParser指定一个delegate对象即可,该delegate对象必须实现NSXMLParserDelegate协议,该协议定义了如下常用方法。

(1)、 - (void)parserDidStartDocument:(NSXMLParser *)parser;//开始处理XML文档时激发该方法;
(2)、 - (void)parserDidEndDocument:(NSXMLParser *)parser;//结束处理XML文档激发该方法;
(3)、 - (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict;//开始处理XML元素时激发该方法;
(4)、 - (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName;//结束处理XML元素时激发该方法。
(5)、 - (NSData *)parser:(NSXMLParser *)parser resolveExternalEntityName:(NSString *)name systemID:(NSString *)systemID;//开始处理内部实体激发该方法;
(6)、 - (void)parser:(NSXMLParser *)parser parseErrorOccurred:(NSError *)parseError;//解析出现错误时激发该方法;
(7)、 - (void)parser:(NSXMLParser *)parser validationErrorOccurred:(NSError *)validationError;//XML文档验证错误时激发该方法;
(8)、 - (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string;//解析XML文档遇到字符串内容时激发该方法;
(9)、 - (void)parser:(NSXMLParser *)parser foundIgnorableWhitespace:(NSString *)whitespaceString;//解析XML文档遇到空白时激发该方法;
(10)、 - (void)parser:(NSXMLParser *)parser foundProcessingInstructionWithTarget:(NSString *)target data:(NSString *)data;//解析XML文档的处理指令时激发该方法;
(11)、 - (void)parser:(NSXMLParser *)parser foundComment:(NSString *)comment;//处理XML文档的注释时激发该方法;
(12)、 - (void)parser:(NSXMLParser *)parser foundCDATA:(NSData *)CDATABlock;//处理XML文档的CDATA内容时激发该方法;

使用NSXMLParser解析XML文档步骤如下:

  • ①、创建NSXMLParser对象;
  • ②、为NSXMLParser对象指定delegate对象,该delegate对象必须实现NSXMLParserDelegate协议,并根据需要实现协议中特定的方法;
  • ③、调用NSXMLParser对象的parse方法开始解析;

3、使用libxml2解析XML文档

libxml2并不是使用OC实现的,而是使用C语言实现的库,因此使用C风格函数进行处理;

  • ①、为项目添加libxml2.dylib库。
  • ②、添加头文件搜索路径。添加头文件的搜索路径的方法是:在Xcode的导航面板中选择项目,然后选择Dock区域中TARGETS列表下的项目图标。接下来打开中间编辑区域中的『Build Settings』标签页,在编辑区域左上角的搜索框中输入『search』字符串,找到『Header Search Paths』处,添加『/usr/include/libxml2』

4、使用GDataXML解析XML文档

GDataXML是第三方开源的XML解析库,它其实是对libxml2的包装,因此底层依然需要依赖libxml2。需要执行libxml2的两个处理。

具体使用方法请查看GDataXML的文件详情。

五、JSON解析

1、JSON的基本知识

JSON的全称是JavaScript Object Notation,即JavaScript对象符号,它是一种轻量级的数据变换格式。JSON数据格式即适合人进行读/写,也适合计算机本身解析和生成。最早的时候,JSON是JavaScript语言的数据交换格式,后来慢慢发展成一种语言无关的数据交换格式。

JSON主要有如下两种数据结构:

  • ①、由key-value对组成的数据结构,这种数据结构在不同的语言中有不同的实现。例如:在JavaScript中是一个对象,在OC重是一种NSDictionary对象,在C语言中则是一个struct,等等。
  • ②、有序集合。这种数据结构在不同语言中可能有NSArray、vector、数组和序列等的实现。

    在JavaScript中主要有两种JSON语法,其中一种用于创建对象,另一种用于创建数组。

(1)、使用JSON语法创建对象

(2)、使用JSON语法创建数组

2、使用NSJSONSerialization处理json数据

NSJSONSerialization类提供了如下方法支持JSON解析或生成:

(1)、 + (id)JSONObjectWithData:(NSData *)data options:(NSJSONReadingOptions)opt error:(NSError **)error;//该方法负责将指定NSData中包含的JSON数据转换为OC对象;
(2)、 + (id)JSONObjectWithStream:(NSInputStream *)stream options:(NSJSONReadingOptions)opt error:(NSError **)error;//该方法负责将指定输入流包含的JSON数据转换为OC对象;
(3)、 + (NSData *)dataWithJSONObject:(id)obj options:(NSJSONWritingOptions)opt error:(NSError **)error;//该方法负责将指定JSON对象转换为NSData对象(该对象包含JSON数据);
(4)、 + (NSInteger)writeJSONObject:(id)obj toStream:(NSOutputStream *)stream options:(NSJSONWritingOptions)opt error:(NSError **)error;//该方法负责将指定JSON对象转换为JSON字符串输出刀指定输出流;
(5)、 + (BOOL)isValidJSONObject:(id)obj;//该方法用于验证指定对象是否可以正常转换为JSON数据;

NSJSONSerialization并不能把任意对象都转换为JSON数据,上面最后一个方法用于验证指定对象是否可以正常转换为JSON数据。NSJSONSerialization只能将满足如下条件的对象转换为JSON数据。

  • ①、顶级对象只能是NSArray或者NSDictionary;
  • ②、集合中包含的对象只能是NSString、NSNumber、NSArray、NSDictionary或NSNull对象;
  • ③、所有NSDictionary的key只能是NSString;
  • ④、NSNumber包装的数值不能是NaN或Infinity;

3、使用SBJson解析JSON数据

SBJson同样可以完成双向转换,它最主要的两个工具类就是SBJsonParser和SBJsonWriter,其中SBJsonParser负责把NSData或NSString形式的JSON数据转换为OC对象;而SBJsonWriter则负责把OC对象转换为NSData或NSString形式的JSON数据;

使用SBJson的不足之处:它是属于第三方框架,因此需要开发者自行下载安转。

具体使用方法,请自行查看SBJson代码;

4、使用JSONKit解析JSON数据

JSONKit的最大优势就是速度快,其也属于第三方框架,需开发者自行下载安装;

具体使用方法,请查看JSONKit代码;

六、使用AFNetworking实现网络通信

1、提交GET请求与提交POST请求

AFNetworking属于第三方框架,因此需要开发者自行下载安装。使用AFNetworking步骤如下:

  • ①、创建AFHTTPRequestOperationManager对象;
  • ②、根据服务器响应内容的不同,为AFHTTPRequestOperationManager对象指定不同的解析器。该对象默认的解析器要求服务器响应是JSON数据或Plist数据。如果服务器响应数据是其他格式的,则需要手动设置响应解析器。
  • ③、如果需要发送GET请求,调用AFHTTPRequestOperationManager对象的GET:
- (AFHTTPRequestOperation *)GET:(NSString *)URLString
                                          parameters:(id)parameters
                                                 success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success
                                                  failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure;

方法即可;如果要发送POST请求,调用该对象的POST:

- (AFHTTPRequestOperation *)POST:(NSString *)URLString
                                            parameters:(id)parameters
                                                  success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success
                                                  failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure;

方法即可。两个方法都可指定通信成功、通信失败的代码块。

  • ④、再success:参数指定的代码块中处理服务器响应成功的正确数据;在failure:参数指定的代码块中处理服务器响应的错误数据;

2、处理JSON或Plist响应

3、处理XML响应

4、上传文件

时间: 2024-08-06 00:59:32

第八章:iOS网络应用编程的相关文章

iOS网络编程笔记——Socket编程

一.什么是Socket通信: Socket是网络上的两个程序,通过一个双向的通信连接,实现数据的交换.这个双向连路的一端称为socket.socket通常用来实现客户方和服务方的连接.socket是TCP/IP协议的一个十分流行的编程接口.一个socket由一个IP地址和一个端口号唯一确定.TCP/IP协议的传输层又有两种协议:TCP(传输控制协议)和UDP(用户数据报协议).TCP是基于连接的,而UDP是无连接的:TCP对系统资源的要求较多,而UDP少:TCP保证数据的正确性而UDP可能丢包:

iOS网络编程开发—网络编程基础

iOS网络编程开发—网络编程基础 一.网络编程 1.简单说明 在移动互联网时代,移动应用的特征有: (1)几乎所有应用都需要用到网络,比如QQ.微博.网易新闻.优酷.百度地图 (2)只有通过网络跟外界进行数据交互.数据更新,应用才能保持新鲜.活力 (3)如果没有了网络,也就缺少了数据变化,无论外观多么华丽,终将变成一潭死水 移动网络应用 = 良好的UI + 良好的用户体验 + 实时更新的数据 新闻:网易新闻.新浪新闻.搜狐新闻.腾讯新闻 视频:优酷.百度视频.搜狐视频.爱奇艺视频 音乐:QQ音乐

iOS多线程编程

1. 进程,线程, 任务 进程:一个程序在运行时,系统会为其分配一个进程,用以管理他的一些资源. 线程:进程内所包含的一个或多个执行单元称为线程,线程一般情况下不持有资源,但可以使用其所在进程的资源. 任务:进程或线程中要做的事情. 在引入线程的操作系统中,通常把进程作为分配资源的基本单位,而把线程作为独立运行和独立调度的基本单位. 线程比进程更小,对其调度的开销小,能够提高系统内多个任务的并发执行程度. 一个程序至少有一个进程,一个进程至少有一个线程.一个程序就是一个进程,而一个程序中的多个任

深入浅出iOS函数式编程与响应式编程概念

简介 本篇文章主要回顾一下——iOS函数式编程 && 响应式编程概念,如何一步步实现函数式编程的过程,对阅读Masonry && SnapKit源码有一定的帮助. 图片描述 作为一个iOS 开发者,那么你一定用过Masnory/ SnapKit: Masonry是一个OC开发中,非常好用的自动布局的第三方框架: SnapKit是Masonry团队打造的Swift版本的自动布局框架: 如果你没有用过,在自动布局中用的是苹果原生的或者原生的升级版VFL语言,那我只好为你点“赞”

线程同步-iOS多线程编程指南(四)-08-多线程

首页 编程指南 Grand Central Dispatch 基本概念 多核心的性能 Dispatch Sources 完结 外传:dispatch_once(上) Block非官方编程指南 基础 内存管理 揭开神秘面纱(上) 揭开神秘面纱(下) iOS多线程编程指南 关于多线程编程 线程管理 Run Loop 线程同步 附录 Core Animation编程指南 Core Animation简介 基本概念 渲染架构 几何变换 查看目录 中文手册/API ASIHTTPRequest Openg

iOS网络编程(六) NSURLSession详解

昨夜浏览Demo的时候,看到别人请求网络数据用的是NSURLSession,当时就在想这里什么,怎么没有用过,引起了我的好奇心,遂去百度-谷歌-官方文档一一查看,有了一定的了解,原来NSURLSession是iOS7中新的网络接口,它与咱们熟悉的NSURLConnection是并列的. 查找资料,写了一个小Demo,大家可以看看,有什么不足的地方,可以留言帮我指出来. // // HMTRootViewController.m // // // Created by HMT on 14-6-7.

iOS 并行编程:Operation Queues

1 简介 1.1 功能        Operation Queue也是IOS的一种并行编程技术,类似Dispatch Queue可以帮助用户管理多线程.但是Operation Queue将任务封装在NSOperation对象中,从而可以更好的控制任务的执行.并且Dispatch Queue的先入先出的执行方式不同,Operation Queue任务的执行顺序可以控制.其中IOS是将任务交给NSOperation对象进行管理,其中NSOperation是个抽象类,必须被继承,目前系统预定义了两个

iOS DLNA编程

iOS DLNA编程 近期实现了iOS下的DLNA,发现ios下的DLNA编程资料很少,其实DLNA文档还好 (28m) ,但是真的确定要看文档自己去实现么? 下面先介绍一下DLNA的基本概念,已经有概念的同学可以跳过,直接看iOS下的DLNA库 什么是DLNA DLNA的全称是DIGITAL LIVING NETWORK ALLIANCE, 其宗旨是Enjoy your music, photos and videos, anywhere anytime, DLNA (Digital Livi

IOS socket编程--Asyncsocket

iPhone的标准推荐是CFNetwork 库编程,其封装好的开源库是 cocoa AsyncSocket库,用它来简化CFNetwork的调用,它提供了异步操作 主要特性有: 队列的非阻塞的读和写,而且可选超时.你可以调用它读取和写入,它会当完成后告知你 自动的socket接收.如果你调用它接收连接,它将为每个连接启动新的实例,当然,也可以立即关闭这些连接 委托(delegate)支持.错误.连接.接收.完整的读取.完整的写入.进度以及断开连接,都可以通过委托模式调用 基于run loop的,