IOS ARC模式下图片上传到FTP的详解

之前写了一个利用FTP进行下载的文章,同FTP下载一样,现在网络上大多数的文件的上传都是通过ASIHttpRequest库以form表单的形式完成的,比较简单,代码逻辑也比较清晰,我就不多说了。而今天要跟大家分享的是在iOS端使用ftp的形式进行上传的方式。
同理也是因为自己公司需要用到FTP进行上传文件,所以写了这篇文章:

原理

同下载一样,也使用到了CFNetWork框架,也是只能做一些FTP基础操作,对于复杂的操作无法完成,详细情况大家可以看我的上一篇文章

添加依赖库

新建项目,所需要的依赖库如图:
IOS ARC模式下图片上传到FTP的详解_第1张图片

核心代码

在试图控制器WKViewController.h里面添加如下代码:

enum {
    kSendBufferSize = 32768
};

@interface WKViewController : UIViewController <UITextFieldDelegate,NSStreamDelegate>
{
    uint8_t _buffer[kSendBufferSize];
}


@property (retain, nonatomic) IBOutlet UITextField *fileInput;
@property (retain, nonatomic) IBOutlet UITextField *serverInput;
@property (retain, nonatomic) IBOutlet UITextField *accountInput;
@property (retain, nonatomic) IBOutlet UITextField *passwordInput;
@property (retain, nonatomic) IBOutlet UILabel *status;
- (IBAction)sendAction:(id)sender;//点击上传

- (IBAction)textFieldDoneEditing:(id)sender;//Did End On Exit 事件
@end

其中首先要遵循NSStreamDelegate协议,另外kSendBufferSize为缓冲区的大小设置,可以自己调节,其他的只是一些UI上的控件,大家自己行搭建

然后去到同一个控制器的.m文件里
首先定义相关内部变量与存取方法:

@interface WKViewController ()
//内部变量
@property (nonatomic, readonly) BOOL              isSending;
@property (nonatomic, strong)   NSOutputStream *  networkStream;
@property (nonatomic, strong)   NSInputStream *   fileStream;
@property (nonatomic, readonly) uint8_t *         buffer;
@property (nonatomic, assign)   size_t            bufferOffset;
@property (nonatomic, assign)   size_t            bufferLimit;

@end

//存取方法
@implementation WKViewController
@synthesize fileInput = _fileInput;
@synthesize serverInput = _serverInput;
@synthesize status = _status;
@synthesize accountInput = _accountInput;
@synthesize passwordInput = _passwordInput;


- (uint8_t *)buffer
{
    return self->_buffer;
}

size_t与uint8_t为c扩展的类型变量,在用于跨语言的时候比较方便,uint8_t为一个字节大小的无符号int类型,size_t在32位与64位机器上代表不同的大小,分别为4个字节与8个字节。
因为buffer被声明为一个数组,所以必须要使用自定义的getter方法,synthesised方法是不会被编译的。

之后我们要点击上传按钮,触发一个上传事件,并且生成networkStream流

#pragma mark 上传事件
- (IBAction)sendAction:(id)sender {

    NSURL *url;//ftp服务器地址
    NSString *filePath;//图片地址
    NSString *account;//账号
    NSString *password;//密码
    CFWriteStreamRef ftpStream;

    //获得输入
    url =[NSURL URLWithString:_serverInput.text];
    filePath = _fileInput.text;
    //    filePath = [[NSBundle mainBundle] pathForResource:@"test" ofType:@"png"];
    account =_accountInput.text;
    password = _passwordInput.text;


    //添加后缀(文件名称)
    url=CFBridgingRelease(CFURLCreateCopyAppendingPathComponent(NULL, (CFURLRef)url, (CFStringRef)[filePath lastPathComponent], false));

    //读取文件,转化为输入流
    self.fileStream = [NSInputStream inputStreamWithFileAtPath:filePath];
    [self.fileStream open];

    //为url开启CFFTPStream输出流
    ftpStream = CFWriteStreamCreateWithFTPURL(NULL, (__bridge CFURLRef) url);
    self.networkStream = (__bridge NSOutputStream *) ftpStream;

    //设置ftp账号密码
    [self.networkStream setProperty:account forKey:(id)kCFStreamPropertyFTPUserName];
    [self.networkStream setProperty:password forKey:(id)kCFStreamPropertyFTPPassword];

    //设置networkStream流的代理,任何关于networkStream的事件发生都会调用代理方法
    self.networkStream.delegate = self;

    //设置runloop
    [self.networkStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
    [self.networkStream open];

    //完成释放链接
    CFRelease(ftpStream);
}

实现NSStreamDelegate的代理方法

#pragma mark 回调方法
- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode
{
    //aStream 即为设置为代理的networkStream
    switch (eventCode) {
        case NSStreamEventOpenCompleted: {
            NSLog(@"NSStreamEventOpenCompleted");
        } break;
        case NSStreamEventHasBytesAvailable: {
            NSLog(@"NSStreamEventHasBytesAvailable");
            assert(NO);     // 在上传的时候不会调用
        } break;
        case NSStreamEventHasSpaceAvailable: {
            NSLog(@"NSStreamEventHasSpaceAvailable");
            NSLog(@"bufferOffset is %zd",self.bufferOffset);
            NSLog(@"bufferLimit is %zu",self.bufferLimit);
            if (self.bufferOffset == self.bufferLimit) {
                NSInteger   bytesRead;
                bytesRead = [self.fileStream read:self.buffer maxLength:kSendBufferSize];

                if (bytesRead == -1) {
                    //读取文件错误
                    [self _stopSendWithStatus:@"读取文件错误"];
                } else if (bytesRead == 0) {

                    NSLog(@"UpLoad Success");

                    //文件读取完成 上传完成
                    [self _stopSendWithStatus:nil];
                } else {
                    self.bufferOffset = 0;
                    self.bufferLimit  = bytesRead;
                }
            }

            if (self.bufferOffset != self.bufferLimit) {
                //写入数据
                NSInteger bytesWritten;//bytesWritten为成功写入的数据
                bytesWritten = [self.networkStream write:&self.buffer[self.bufferOffset] maxLength:self.bufferLimit - self.bufferOffset];
                assert(bytesWritten != 0);
                if (bytesWritten == -1) {
                    [self _stopSendWithStatus:@"网络写入错误"];
                } else {
                    self.bufferOffset += bytesWritten;
                }
            }
        } break;
        case NSStreamEventErrorOccurred: {
            [self _stopSendWithStatus:@"Stream打开错误"];
            assert(NO);
        } break;
        case NSStreamEventEndEncountered: {
            // 忽略
        } break;
        default: {
            assert(NO);
        } break;
    }
}

bytesWritten为实际写入的数据量,虽然缓冲区大小是固定的值,但每次写入并不一定是充满缓冲区,可以看到NSLog的输出值。(上传图片大小为428492字节,约430k)。

    首先是NSStreamEventOpenCompleted打开事件完成的回调,然后不断发送NSStreamEventHasSpaceAvailable消息完成整个上传过程。

    可以看到在传输的时候,bufferOffset并不是每次都是32768,所以self.bufferOffset!= self.bufferLimit是一个续传上次未完数据的过程,而当self.bufferOffset = self.bufferLimit时,即上次的32768已经传完了,此时将bufferOffset重新设置为0。

最后处理上传的结果:

//结果处理
- (void)_stopSendWithStatus:(NSString *)statusString
{
    if (self.networkStream != nil) {
        [self.networkStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
        self.networkStream.delegate = nil;
        [self.networkStream close];
        self.networkStream = nil;
    }
    if (self.fileStream != nil) {
        [self.fileStream close];
        self.fileStream = nil;
    }
    [self _sendDidStopWithStatus:statusString];
}

- (void)_sendDidStopWithStatus:(NSString *)statusString
{
    if (statusString == nil) {
        statusString = @"上传成功";
    }
    _status.text = statusString;
}

在此方法中关闭链接、循环、设置代理为空

以上就是关于IOS FTP的上传图片一些比较核心的内容,以及开发过程中需要注意的几个点,另外我已经将写好的demo上传了GitHub,大家可以去我的git上下载已经写好的代码:https://github.com/WJeaner/FtpUpLoad.git

你可能感兴趣的:(iOS)