iOS开发笔记网络篇-使用AFN的download方法实现文件的下载(单任务、多任务、断点下载)

吐槽 o(╯□╰)o

最近在写的一个项目里面涉及到资源包的下载操作,由于用户在使用过程中有可能会同时下载多个资源包,那么就需要对下载功能进行设计和封装。最开始使用的是使用AFN的下载方法来进行实现的,不过当时项目需求要求不是太高,所以没有处理一些特殊情况,比如任务数量的控制,优先级还有断点下载等等。就简单的在一个单例Manager里面添加了一个字典,将下载资源包的URL地址作为Key,对应的downloadTask作为Value,这样就可以在想要的地方获取到相应的下载进度了。

@property (nonatomic, strong, nonnull) NSMutableDictionary <NSString *, NSURLSessionDownloadTask *> *downloadTaskDict;

由于现在需要统一管理下载的资源,而且为了表示“用户至上”,比如节约流量需要使用断点下载,包括暂停和继续、应用重启时的断点下载;显示下载速度和比例等等。同时为了控制下载的速度也对最大下载数量进行了限制,默认3个等等。so.....

silence.jpg
先说下原理

这里使用了AFN的下载方法来完成下载功能,只不过需要对其进行一些扩展。因此我将每个下载任务封装成了一个模型<FKNetworkingDownloadModel>,以下为部分属性定义:


#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

NS_CLASS_AVAILABLE_IOS(7_0) @interface FKNetworkingProgressModel : NSObject 

/*
 * data length of the bytes written.
 */
@property (nonatomic, assign) int64_t totalBytesWritten;
/*
 * the total bytes of the resource data.
 */
@property (nonatomic, assign) int64_t totalBytesExpectedToWrite;
/*
 * download speed.
 */
@property (nonatomic, assign) int64_t downloadSpeed;
/*
 * download progress.
 */
@property (nonatomic, assign) float downloadProgress;
/*
 * download left time
 */
@property (nonatomic, assign) int32_t downloadLeft;
    
@end

NS_CLASS_AVAILABLE_IOS(7_0) @interface FKNetworkingDownloadModel : NSObject

/*
 * resource URLString 
 */
@property (nonatomic, copy) NSString *resourceURLString;
/*
 * fileName default is resourceURLString last component
 */
@property (nonatomic, copy) NSString *fileName;
/**
 *  file directory
 */
@property (nonatomic, copy) NSString *fileDirectory;
/**
 *  file path
 */
@property (nonatomic, copy, nullable) NSString *filePath;
/**
 *  the plist file path, the plist file include information of the download task mission.
 */
@property (nonatomic, copy, nullable) NSString *plistFilePath;
/**
 *  record the download time when receive the data from the serverce, used to calculate download speed
 */
@property (nonatomic, strong) NSDate *downloadDate;
/**
 *  check the download state when a model alloc. 
 */
//@property (nonatomic, assign) FKDownloadModelState modelState;
/**
 *  resume data, marked the position of the download mission.
 */
@property (nonatomic, strong, nullable) NSData *resumeData;
/*
 * download task
 */
@property (nonatomic, strong, nullable) NSURLSessionDownloadTask *downloadTask;
/*
 * progress
 */
@property (nonatomic, strong, nullable) FKNetworkingProgressModel *progressModel;
/**
 *  init method
 *
 *  @param URLString resourceURLString
 *
 *  @return 
 */
-(instancetype)initWithResourceURLString:(NSString *)URLString;

@end

NS_ASSUME_NONNULL_END

这样可以将每一个下载的资源封装成一个Model,这样在使用管理类来控制下载的时候就会显得比较轻松。

在使用AFN之前需要补充说明一下NSURLSessionTask的一些知识:
抽象类(父类):NSURLSessionTask 包含了一些属性,其中使用到的属性包括:

//请求信息,包括URL地址等。
@property (nullable, readonly, copy) NSURLRequest  *originalRequest; 
@property (nullable, readonly, copy) NSURLRequest  *currentRequest;  
//响应信息,包括响应数据的长度和地址等等
@property (nullable, readonly, copy) NSURLResponse *response;     
//下面两个属性是会使用到的,一个是已经接收到的数据长度和总共需要接受的数据长度,根据这个我们可以算出下载的进度信息   
@property (readonly) int64_t countOfBytesReceived;
@property (readonly) int64_t countOfBytesExpectedToReceive;
//任务描述,我这里用model对应的URL地址来赋值
@property (nullable, copy) NSString *taskDescription;
//状态(取消、完成、进行中等)
@property (readonly) NSURLSessionTaskState state;

同时父类也提供了几个方法

- (void)cancel;//取消下载任务
- (void)suspend;//挂起(这个一般用于程序在运行中的时候暂停下载任务)
- (void)resume;//恢复下载任务

理论上说,如果不做应用重启后的断点下载,通过上面的步骤已经可以完成大部分的下载操作了,但是在程序重启时并不能够获取到对应的已下载的进度(downloadTask会将下载的资源放入tmp路径下面,以'CFNetworking'开头),那这个时候执行下载任务那么会重新下载。考虑到用户的流量,就必须在重启后的进行断点下载,因此我们需要使用到子类NSURLSessionDownloadTask的一个方法

- (void)cancelByProducingResumeData:(void (^)(NSData * __nullable resumeData))completionHandler;

这个方法可以讲当前的task已下载的数据使用一个data保存起来, 然后使用NSURLSession的方法可以根据这个data从服务器获取后续的数据

- (NSURLSessionDownloadTask *)downloadTaskWithResumeData:(NSData *)resumeData;

断点下载的原理大概就是这样,接下来是考虑管理类的设计和封装。

管理类的设计和封装

思路:使用单例来管理下载任务,同时可以指定最大的下载数和单任务和多任务模式的切换。关于下载的控制(开始、暂停、恢复和断点下载)都可以通过AFN相应的方法来实现

ANF里面关于下载有几个方法,在<AFURLSessionManager>类里面可以找到
//这个方法是经常用到的一个,生成一个NSURLRequest,然后根据这个请求开始下载任务可以追踪到下载的进度,需要设置下载完成后的文件移动到的位置,最后是完成后的回调信息
-(NSURLSessionDownloadTask *)downloadTaskWithRequest:(NSURLRequest *)request
                                             progress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgressBlock
                                          destination:(nullable NSURL * (^)(NSURL *targetPath, NSURLResponse *response))destination
                                    completionHandler:(nullable void (^)(NSURLResponse *response, NSURL * _Nullable filePath, NSError * _Nullable error))completionHandler;

//该方法根据一个resumeData来进行断点下载,我这里断点下载的实现就是根据这个来的,resumeData已经在模型里面了。
在下载cancel或者suspend的时候只需要将model的resumeData赋值,然后写入本地plist文件,下次下载的时候根据plist信息获取下载的一些信息就OK了
-(NSURLSessionDownloadTask *)downloadTaskWithResumeData:(NSData *)resumeData
                                                progress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgressBlock
                                             destination:(nullable NSURL * (^)(NSURL *targetPath, NSURLResponse *response))destination
                                       completionHandler:(nullable void (^)(NSURLResponse *response, NSURL * _Nullable filePath, NSError * _Nullable error))completionHandler;

下面附上一头文件中的一些代码

#import <Foundation/Foundation.h>
#import "AFNetworking.h"
#import "FKNetworkingDownloadModel.h"

NS_ASSUME_NONNULL_BEGIN

NS_CLASS_AVAILABLE_IOS(7_0) @interface FKNetworkingManager : NSObject

/**
 *  download root file path. default is '~/cache/forkid.networking.manager1.0'
 */
@property (nonatomic, copy, readonly) NSString *downloadDirectory;

/**
 *  contains the models whose is downloading.   
 */
@property (nonatomic, strong, readonly) NSMutableArray <__kindof FKNetworkingDownloadModel *> *downloadingModels;

/**
 *  contains the models whose is waiting for download.   
 */
@property (nonatomic, strong, readonly) NSMutableArray <__kindof FKNetworkingDownloadModel *> *waitingModels;

/**
 *  max download mission number.
 */
@property (nonatomic, assign) NSInteger maxDownloadCount;

/**
 *  first in and fisrt out. 
 */
@property (nonatomic, assign) BOOL resumeTaskFIFO;

/**
 *  ignore maxDownloadCount. manager will resume all downloadTask.
 */
@property (nonatomic, assign, getter=isBatchDownload) BOOL batchDownload;

/**
  *  singleton
  */
+(FKNetworkingManager *)shareManager;

/**
 ===============> Description of download start <=============
 = when you wanna download a file from serverce, you can use next methods as you need. 
 = there have some methods such as start, resume, cancel and so on.
 = We add the 'cancel' method to pause a task instead of suspend, because we need to resume the task when the App restart.
 = all methods will use a download model who is subclass of the 'FKNetworkingDownloadModel' for convenience. 
 = and every model use a URL string as the primary key to mark a download mission or task.
 ===============> Description of download end. <=============
 */

/**
 *  this method used to start a download mission with a download model, notice that the download model can't be nil, or the download mission will not execute.
 *
 *  @param downloadModel     download model
 *  @param progress          progress of the download, track to refresh UI and ...
 *  @param completionHandler you can doing something after download mission completed, such as refresh your UI and move the file and so on.
 */
-(void)fk_startDownloadWithDownloadModel:(FKNetworkingDownloadModel *)downloadModel 
                                progress:(void (^)(FKNetworkingDownloadModel *downloadModel))progress
                       completionHandler:(void (^)(FKNetworkingDownloadModel *downloadModel, NSError * _Nullable error))completionHandler;

/**
 *  resume a download task with a download model, it will use the download model's 'resumeData'
 *
 *  @param downloadModel download model 
 */
-(void)fk_resumeDownloadWithDownloadModel:(FKNetworkingDownloadModel *)downloadModel;                          

/**
 *  suspend or cancel a download task
 *
 *  @param downloadModel download model
 */
-(void)fk_cancelDownloadTaskWithDownloadModel:(FKNetworkingDownloadModel *)downloadModel;

/**
 *  check the resourece has been downloaded or not from the download model resourceURL.
 *
 *  @param downloadModel download model
 *
 *  @return YES or NO.
 */
-(BOOL)fk_hasDownloadedFileWithDownloadModel:(FKNetworkingDownloadModel *)downloadModel; 

/**
 *  delete local file if exist.
 *
 *  @param downloadModel download model.
 */
-(void)fk_deleteDownloadedFileWithDownloadModel:(FKNetworkingDownloadModel *)downloadModel;

/**
 *  delete all downloaded files.
 */
-(void)fk_deleteAllDownloadedFiles;

/**
 *  get a download model, which is downloading with a URLString. if there is not exist a model, will return nil.
 *
 *  @param URLString URLString.
 *
 *  @return download model
 */
-(nullable FKNetworkingDownloadModel *)fk_getDownloadingModelWithURLString:(NSString *)URLString;

/**
 *  get download progress information. such as download speed, progress and so on.
 *
 *  @param downloadModel download model.
 *
 *  @return progress model.
 */
-(nullable FKNetworkingProgressModel *)fk_getDownloadProgressModelWithDownloadModel:(FKNetworkingDownloadModel *)downloadModel;
@end
NS_ASSUME_NONNULL_END

定义完上述的方法后,在实现文件中实现相应的方法即可,因为采用AFN,所以我们不用写太多的步骤,只需要关心流程控制,和线程控制就行了。

下面是部分的实现代码:

#import "FKNetworkingManager.h"
#import "NSObject+FKAdd.h"

NSString *const FKNetworkingManagerFileName = @"forkid.networking.manager1.0";

@interface FKNetworkingManager ()

/**
 *  AFNetworking manager.
 */
@property (nonatomic, strong) AFHTTPSessionManager *AFManager;

/**
 *  download root directory.
 */
@property (nonatomic, copy) NSString *downloadDirectory;

/**
 *  fileManager to manage download files
 */
@property (nonatomic, strong) NSFileManager *fileManager;

/*
 * the models for waiting for download, the elements should be FKDownloadModel and it's subClasses
 */
@property (nonatomic, strong) NSMutableArray <__kindof FKNetworkingDownloadModel *> *waitingModels;

/*
 * the models whose being downloaded, the elements should be FKDownloadModel and it's subClasses
 */
@property (nonatomic, strong) NSMutableArray <__kindof FKNetworkingDownloadModel *> *downloadingModels;

/*
 *  key-values dictionary of the downloadModels, format as '<NSString *key, FKDownloadModel *model>' to make constraints
 *  used to find a downloadModel from this container, 
 *  when the program will terminate, container will be clear
 */
@property (nonatomic, strong) NSMutableDictionary <NSString *, __kindof FKNetworkingDownloadModel *> *downloadModelsDict;

@end

NSInteger const fk_timeInterval = 5;

@implementation FKNetworkingManager

#pragma mark - download methods

-(void)fk_startDownloadWithDownloadModel:(FKNetworkingDownloadModel *)downloadModel 
                                progress:(void (^)(FKNetworkingDownloadModel * _Nonnull))progress 
                       completionHandler:(void (^)(FKNetworkingDownloadModel * _Nonnull, NSError * _Nullable))completionHandler{

    NSString *fileName = [downloadModel.fileName componentsSeparatedByString:@"."].firstObject;
    downloadModel.fileDirectory = [self.downloadDirectory stringByAppendingPathComponent:fileName];
    downloadModel.filePath = [[self.downloadDirectory stringByAppendingPathComponent:fileName] stringByAppendingPathComponent:downloadModel.fileName];
    downloadModel.plistFilePath = [downloadModel.fileDirectory stringByAppendingPathComponent:[NSString stringWithFormat:@"%@.plist", fileName]];
    
    if (![self canBeStartDownloadTaskWithDownloadModel:downloadModel]) return;
    
    downloadModel.resumeData = [NSData dataWithContentsOfFile:downloadModel.plistFilePath];
    
    if (downloadModel.resumeData.length == 0) {
        NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:downloadModel.resourceURLString]];
        downloadModel.downloadTask = [self.AFManager downloadTaskWithRequest:request progress:^(NSProgress * _Nonnull downloadProgress) {
            
            [self setValuesForDownloadModel:downloadModel withProgress:downloadProgress.fractionCompleted];
            progress(downloadModel);
            
        } destination:^NSURL * _Nonnull(NSURL * _Nonnull targetPath, NSURLResponse * _Nonnull response) {
            
            return [NSURL fileURLWithPath:downloadModel.filePath];
            
        } completionHandler:^(NSURLResponse * _Nonnull response, NSURL * _Nullable filePath, NSError * _Nullable error) {
            if (error) {
                [self fk_cancelDownloadTaskWithDownloadModel:downloadModel];
                completionHandler(downloadModel, error);
            }else{
                [self.downloadModelsDict removeObjectForKey:downloadModel.resourceURLString];
                completionHandler(downloadModel, nil);
                [self deletePlistFileWithDownloadModel:downloadModel];
            }
        }];
        
    }else{
        
        downloadModel.progressModel.totalBytesWritten = [self getResumeByteWithDownloadModel:downloadModel];
        downloadModel.downloadTask = [self.AFManager downloadTaskWithResumeData:downloadModel.resumeData progress:^(NSProgress * _Nonnull downloadProgress) {
            
            [self setValuesForDownloadModel:downloadModel withProgress:[self.AFManager downloadProgressForTask:downloadModel.downloadTask].fractionCompleted];
            progress(downloadModel);
            
        } destination:^NSURL * _Nonnull(NSURL * _Nonnull targetPath, NSURLResponse * _Nonnull response) {
            return [NSURL fileURLWithPath:downloadModel.filePath];
        } completionHandler:^(NSURLResponse * _Nonnull response, NSURL * _Nullable filePath, NSError * _Nullable error) {
            if (error) {
                [self fk_cancelDownloadTaskWithDownloadModel:downloadModel];
                completionHandler(downloadModel, error);
            }else{
                [self.downloadModelsDict removeObjectForKey:downloadModel.resourceURLString];
                completionHandler(downloadModel, nil);
                [self deletePlistFileWithDownloadModel:downloadModel];
            }
        }];
    }
    
    if (![self.fileManager fileExistsAtPath:self.downloadDirectory]) {
        [self.fileManager createDirectoryAtPath:self.downloadDirectory withIntermediateDirectories:YES attributes:nil error:nil];
    }
    
    [self createFolderAtPath:[self.downloadDirectory stringByAppendingPathComponent:fileName]];
    [self fk_resumeDownloadWithDownloadModel:downloadModel];
}

-(void)fk_resumeDownloadWithDownloadModel:(FKNetworkingDownloadModel *)downloadModel{
    if (downloadModel.downloadTask) {
        downloadModel.downloadDate = [NSDate date];
        [downloadModel.downloadTask resume];
        self.downloadModelsDict[downloadModel.resourceURLString] = downloadModel;
        [self.downloadingModels addObject:downloadModel];
    }
}

-(void)fk_cancelDownloadTaskWithDownloadModel:(FKNetworkingDownloadModel *)downloadModel{
    if (!downloadModel) return;
    NSURLSessionTaskState state = downloadModel.downloadTask.state;
    if (state == NSURLSessionTaskStateRunning) {
        [downloadModel.downloadTask cancelByProducingResumeData:^(NSData * _Nullable resumeData) {
            downloadModel.resumeData = resumeData;
            @synchronized (self) {
                BOOL isSuc = [downloadModel.resumeData writeToFile:downloadModel.plistFilePath atomically:YES];
                [self saveTotalBytesExpectedToWriteWithDownloadModel:downloadModel];
                if (isSuc) {
                    downloadModel.resumeData = nil;
                    [self.downloadModelsDict removeObjectForKey:downloadModel.resourceURLString];
                    [self.downloadingModels removeObject:downloadModel];
                }
            }
        }];
    }
}

-(void)fk_deleteDownloadedFileWithDownloadModel:(FKNetworkingDownloadModel *)downloadModel{
    if ([self.fileManager fileExistsAtPath:downloadModel.fileDirectory]) {
        [self.fileManager removeItemAtPath:downloadModel.fileDirectory error:nil];
    }
}

-(void)fk_deleteAllDownloadedFiles{
    if ([self.fileManager fileExistsAtPath:self.downloadDirectory]) {
        [self.fileManager removeItemAtPath:self.downloadDirectory error:nil];
    }
}

-(BOOL)fk_hasDownloadedFileWithDownloadModel:(FKNetworkingDownloadModel *)downloadModel{
    if ([self.fileManager fileExistsAtPath:downloadModel.filePath]) {
        NSLog(@"已下载的文件...");
        return YES;
    }
    return NO;
}

-(FKNetworkingDownloadModel *)fk_getDownloadingModelWithURLString:(NSString *)URLString{
    return self.downloadModelsDict[URLString];
}

-(FKNetworkingProgressModel *)fk_getDownloadProgressModelWithDownloadModel:(FKNetworkingDownloadModel *)downloadModel{
    FKNetworkingProgressModel *progressModel = downloadModel.progressModel;
    progressModel.downloadProgress = [self.AFManager downloadProgressForTask:downloadModel.downloadTask].fractionCompleted;
    return progressModel;
}

#pragma mark - private methods
-(BOOL)canBeStartDownloadTaskWithDownloadModel:(FKNetworkingDownloadModel *)downloadModel{
    if (!downloadModel) return NO;
    if (downloadModel.downloadTask && downloadModel.downloadTask.state == NSURLSessionTaskStateRunning) return NO;
    if ([self fk_hasDownloadedFileWithDownloadModel:downloadModel]) return NO;
    return YES;
}

-(void)setValuesForDownloadModel:(FKNetworkingDownloadModel *)downloadModel withProgress:(double)progress{
    NSTimeInterval interval = -1 * [downloadModel.downloadDate timeIntervalSinceNow];
    downloadModel.progressModel.totalBytesWritten = downloadModel.downloadTask.countOfBytesReceived;
    downloadModel.progressModel.totalBytesExpectedToWrite = downloadModel.downloadTask.countOfBytesExpectedToReceive;
    downloadModel.progressModel.downloadProgress = progress;
    downloadModel.progressModel.downloadSpeed = (int64_t)((downloadModel.progressModel.totalBytesWritten - [self getResumeByteWithDownloadModel:downloadModel]) / interval);
    if (downloadModel.progressModel.downloadSpeed != 0) {
        int64_t remainingContentLength = downloadModel.progressModel.totalBytesExpectedToWrite  - downloadModel.progressModel.totalBytesWritten;    
        int currentLeftTime = (int)(remainingContentLength / downloadModel.progressModel.downloadSpeed);
        downloadModel.progressModel.downloadLeft = currentLeftTime;
    }
}

-(int64_t)getResumeByteWithDownloadModel:(FKNetworkingDownloadModel *)downloadModel{
    int64_t resumeBytes = 0;
    NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile:downloadModel.plistFilePath];
    if (dict) {
        resumeBytes = [dict[@"NSURLSessionResumeBytesReceived"] longLongValue]; 
    }
    return resumeBytes;
}

-(NSString *)getTmpFileNameWithDownloadModel:(FKNetworkingDownloadModel *)downloadModel{
    NSString *fileName = nil;
    NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile:downloadModel.plistFilePath];
    if (dict) {
        fileName = dict[@"NSURLSessionResumeInfoTempFileName"]; 
    }
    return fileName;
}

-(void)createFolderAtPath:(NSString *)path{
    if ([self.fileManager fileExistsAtPath:path]) return;
    [self.fileManager createDirectoryAtPath:path withIntermediateDirectories:YES attributes:nil error:nil];
}

-(void)deletePlistFileWithDownloadModel:(FKNetworkingDownloadModel *)downloadModel{
    if (downloadModel.downloadTask.countOfBytesReceived == downloadModel.downloadTask.countOfBytesExpectedToReceive) {
        [self.fileManager removeItemAtPath:downloadModel.plistFilePath error:nil];
        [self removeTotalBytesExpectedToWriteWhenDownloadFinishedWithDownloadModel:downloadModel];
    }
}

-(NSString *)managerPlistFilePath{
    return [self.downloadDirectory stringByAppendingPathComponent:@"ForKidManager.plist"];
}

-(nullable NSMutableDictionary <NSString *, NSString *> *)managerPlistDict{
    NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithContentsOfFile:[self managerPlistFilePath]];
    return dict;
}

-(void)saveTotalBytesExpectedToWriteWithDownloadModel:(FKNetworkingDownloadModel *)downloadModel{
    NSMutableDictionary <NSString *, NSString *> *dict = [self managerPlistDict];
    [dict setValue:[NSString stringWithFormat:@"%lld", downloadModel.downloadTask.countOfBytesExpectedToReceive] forKey:downloadModel.resourceURLString];
    [dict writeToFile:[self managerPlistFilePath] atomically:YES];
}

-(void)removeTotalBytesExpectedToWriteWhenDownloadFinishedWithDownloadModel:(FKNetworkingDownloadModel *)downloadModel{
    NSMutableDictionary <NSString *, NSString *> *dict = [self managerPlistDict];
    [dict removeObjectForKey:downloadModel.resourceURLString];
    [dict writeToFile:[self managerPlistFilePath] atomically:YES];
}

#pragma mark - share instance
+(FKNetworkingManager *)shareManager{
    static FKNetworkingManager *manager = nil;
    static dispatch_once_t sigletonOnceToken;
    dispatch_once(&sigletonOnceToken, ^{
        manager = [[self alloc] init];
    });
    return manager;
}

- (instancetype)init{
    self = [super init];
    if (self) {
        _AFManager = [[AFHTTPSessionManager alloc]init];
        _AFManager.requestSerializer.timeoutInterval = 5;
        _AFManager.requestSerializer.cachePolicy = NSURLRequestReloadIgnoringLocalCacheData;//NSURLRequestUseProtocolCachePolicy;
        NSSet *typeSet = [NSSet setWithObjects:@"application/json", @"text/plain", @"text/javascript", @"text/json", @"text/html", nil];
        _AFManager.responseSerializer.acceptableContentTypes = typeSet;
        _AFManager.securityPolicy.allowInvalidCertificates = YES;
        
        _maxDownloadCount = 1;
        _resumeTaskFIFO = YES;
        _batchDownload = NO;
        _fileManager = [NSFileManager defaultManager];
        _waitingModels = [[NSMutableArray alloc] initWithCapacity:1];
        _downloadingModels = [[NSMutableArray alloc] initWithCapacity:1];
        _downloadModelsDict = [[NSMutableDictionary alloc] initWithCapacity:1];
        
        _downloadDirectory = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES).firstObject stringByAppendingPathComponent:FKNetworkingManagerFileName];
        [_fileManager createDirectoryAtPath:_downloadDirectory withIntermediateDirectories:YES attributes:nil error:nil];
        
        NSDictionary <NSString *, NSString *> *plistDict = [[NSDictionary alloc] init];
        NSString *managerPlistFilePath = [_downloadDirectory stringByAppendingPathComponent:@"ForKidManager.plist"];
        [plistDict writeToFile:managerPlistFilePath atomically:YES];
    }
    return self;
}
    
@end

如有问题或者BUG 欢迎指正

前段时间由于工作上的事情耽搁了,所以很多朋友问的Demo迟迟没有上传上去,这段时间我会完善当前的功能整理下传上去,简要的修复一点小bug和完善一下上传和POST方法以及网络请求管理。

未完待续...

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容