iOS笔记22
来源:互联网 发布:淘宝网页版电脑版登录 编辑:程序博客网 时间:2024/06/07 23:10
1
//NSURLSessionTask的子类
NSURLSessionTask
/ \
NSURLSessionDataTask NSURLSessionDownloadTask
|
NSUrlSessionUploadTask
2
//NSURLConnection和Runloop(面试)
(1)两种为NSURLConnection设置代理方式的区别//第一种设置方式://通过该方法设置代理,会自动的发送请求// [[NSURLConnection alloc]initWithRequest:request delegate:self];//第二种设置方式://设置代理,startImmediately为NO的时候,该方法不会自动发送请求NSURLConnection *connect = [[NSURLConnection alloc]initWithRequest:request delegate:self startImmediately:NO];//手动通过代码的方式来发送请求//注意该方法内部会自动的把connect添加到当前线程的RunLoop中在默认模式下执行[connect start];(2)如何控制代理方法在哪个线程调用//说明:默认情况下,代理方法会在主线程中进行调用(为了方便开发者拿到数据后处理一些刷新UI的操作不需要考虑到线程间通信)//设置代理方法的执行队列[connect setDelegateQueue:[[NSOperationQueue alloc]init]];(3)开子线程发送网络请求的注意点,适用于自动发送网络请求模式//使用GCD开启一个子线程来发送网络请求dispatch_async(dispatch_get_global_queue(0, 0), ^{ //使用非自动发送网络请求模式,发送请求OK /* //创建NSURLConnection对象,设置代理,暂不发送 NSURLConnection *connect = [[NSURLConnection alloc]initWithRequest:request delegate:self startImmediately:NO]; //设置代理方法的执行队列 [connect setDelegateQueue:[[NSOperationQueue alloc]init]]; //调用start发送网络请求 [connect start]; */ //使用自动发送网络请求模式,发送请求失败(需要改造代码) //WHY? /*01 网络请求发送和数据接收是否成功,和一些因素相关,比如客户端的网速、服务器端的查询速度等等。 02 而在子线程中创建的NSURLConnection对象是一个临时变量,当请求发送完成之后就被释放了,所以这个时候它的代理方法不会调用用。 03 为什么使用非自动发送网络请求模式是OK的。 因为在该模式中,调用了start来开始发送网络请求,该方法内部会自动将当前的connect作为一个Source添加到当前线程所在的Runloop中 如果当前线程是子线程(即当前线程的runloop并未创建),那么该方法内部会默认先创建当前线程的Runloop,设置在runloop的默认模式下运行。 此时runloop会对这个Connect对象进行强引用,保证了代理方法被调用的前提 */ NSURLConnection *connect = [[NSURLConnection alloc]initWithRequest:request delegate:self]; [connect setDelegateQueue:[[NSOperationQueue alloc]init]]; //创建当前线程的runloop,并开启runloop [[NSRunLoop currentRunLoop] run];});
3
//NSURLConnection和Runloop + dispatch_async…. 总结(自己)
在子线程中发送网络请求
1> (1)NSURLConnection *conn = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:YES];
(2)NSURLConnection *conn = [[NSURLConnection alloc] initWithRequest:request delegate:self];
(3)NSURLConnection *conn = [[NSURLConnection alloc] initWithRequest:request delegate:self];
这三种方法不会调用代理方法
若想调用代理方法需要写这样的代码:[[NSRunLoop currentRunLoop] run];
2> NSURLConnection *conn = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO]; //手动通过代码的方式来发送请求 //注意该方法内部会自动的把connect添加到当前线程的RunLoop中在默认模式下执行 [connect start];
4
//NSURLConnection–如何控制代理方法在哪个线程调用
//说明:默认情况下,代理方法会在主线程中进行调用(为了方便开发者拿到数据后处理一些刷新UI的操作不需要考虑到线程间通信)//设置代理方法的执行队列[connect setDelegateQueue:[[NSOperationQueue alloc]init]];
5
//NSURLSession—NSURLSessionDataTask(block)(block里面是自动在子线程中) 的基本使用 (NSHTTPURLResponse) 执行Task [dataTask resume]
//get请求方式1:dataTaskWithRequest get请求方式2:dataTaskWithURL 发送POST请求
//———————————————————————————————
(1)使用步骤
//———————————————————————————————
使用NSURLSession创建task,然后执行task
(2)关于task
//———————————————————————————————
a.NSURLSessionTask是一个抽象类,本身不能使用,只能使用它的子类
b.NSURLSessionDataTask\NSURLSessionUploadTask\NSURLSessionDownloadTask
(3)发送get请求方式1 (dataTaskWithRequest)
//1.创建NSURLSession对象(可以获取单例对象)NSURLSession *session = [NSURLSession sharedSession];//2.根据NSURLSession对象创建一个TaskNSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/login?username=ss&pwd=ss&type=JSON"];NSURLRequest *request = [NSURLRequest requestWithURL:url];//方法参数说明/* 注意:该block是在子线程中调用的,如果拿到数据之后要做一些UI刷新操作,那么需要回到主线程刷新 第一个参数:需要发送的请求对象 block:当请求结束拿到服务器响应的数据时调用block block-NSData:该请求的响应体 block-NSURLResponse:存放本次请求的响应信息,响应头,真实类型为NSHTTPURLResponse block-NSErroe:请求错误信息 */NSURLSessionDataTask * dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { //拿到响应头信息 NSHTTPURLResponse *res = (NSHTTPURLResponse *)response; //4.解析拿到的响应数据 NSLog(@"%@\n%@",[[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding],res.allHeaderFields);}];//3.执行Task//注意:刚创建出来的task默认是挂起状态的,需要调用该方法来启动任务(执行任务)[dataTask resume];
//———————————————————————————————
(4)发送get请求方式2(dataTaskWithURL)
//注意:该方法内部默认会把URL对象包装成一个NSURLRequest对象(默认是GET请求)//方法参数说明/* //第一个参数:发送请求的URL地址 //block:当请求结束拿到服务器响应的数据时调用block //block-NSData:该请求的响应体 //block-NSURLResponse:存放本次请求的响应信息,响应头,真实类型为NSHTTPURLResponse //block-NSErroe:请求错误信息 */- ( NSURLSessionDataTask *)dataTaskWithURL:(NSURL *)url completionHandler:( (^)(NSData *data, NSURLResponse *response, NSError *error))completionHandler;
//———————————————————————————————
(5)发送POST请求
//1.创建NSURLSession对象(可以获取单例对象)NSURLSession *session = [NSURLSession sharedSession];//2.根据NSURLSession对象创建一个TaskNSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/login"];//创建一个请求对象,并这是请求方法为POST,把参数放在请求体中传递NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];request.HTTPMethod = @"POST";request.HTTPBody = [@"username=520it&pwd=520it&type=JSON" dataUsingEncoding:NSUTF8StringEncoding];NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData * __nullable data, NSURLResponse * __nullable response, NSError * __nullable error) { //拿到响应头信息 NSHTTPURLResponse *res = (NSHTTPURLResponse *)response; //解析拿到的响应数据 NSLog(@"%@\n%@",[[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding],res.allHeaderFields);}];//3.执行Task//注意:刚创建出来的task默认是挂起状态的,需要调用该方法来启动任务(执行任务)[dataTask resume];
6
//NSURLSession—-NSURLSessionDataTask 代理方法 (通过创建session的时候可以设置代理方法在哪个线程中执行)
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{ //1.创建NSURLSession,并设置代理 /* 第一个参数:session对象的全局配置设置,一般使用默认配置就可以 第二个参数:谁成为session对象的代理 第三个参数:代理方法在哪个队列中执行(在哪个线程中调用),如果是主队列那么在主线程中执行,如果是非主队列,那么在子线程中执行 */ NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[NSOperationQueue mainQueue]]; //2.创建task //2.1 确定请求路径 NSURL *url =[NSURL URLWithString:@"http://120.25.226.186:32812/login?username=ewrw&pwd=werwr&type=JSON"]; //2.2 创建请求对象 NSURLRequest *request =[NSURLRequest requestWithURL:url]; //2.3 创建task NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request]; //3.启动task [dataTask resume];
// 执行任务(其它方法,如暂停、取消等)
//启动task //[dataTask resume]; //其它方法,如取消任务,暂停任务等 //[dataTask cancel]; //[dataTask suspend];}#pragma mark ----------------------#pragma mark NSURLSessionDataDelegate/* 1.当接收到服务器响应的时候调用 session:发送请求的session对象 dataTask:根据NSURLSession创建的task任务 response:服务器响应信息(响应头) completionHandler:通过该block回调,告诉服务器端是否接收返回的数据 */-(void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler{ self.fileData = [NSMutableData data]; //需要在该方法中告诉服务器是否接收数据 /* NSURLSessionResponseCancel = 0, 取消任务 NSURLSessionResponseAllow = 1, 接收 NSURLSessionResponseBecomeDownload = 2,变成下载任务 NSURLSessionResponseBecomeStream 变成流 */ completionHandler(NSURLSessionResponseAllow); //若不实现didReceiveResponse这个方法,默认调用了completionHandler(NSURLSessionResponseAllow);}//2.接收到服务器返回给我们的数据,而且可能会调用多次-(void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data{ [self.fileData appendData:data];}//3.当请求结束之后会调用该方法,如果失败那么error有值-(void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error{ //4.解析数据 NSDictionary *dictM = [NSJSONSerialization JSONObjectWithData:self.fileData options:kNilOptions error:nil]; NSLog(@"%@",dictM);}
7
//NSURLSession—NSURLSessionDownloadTask block (自动在子线程中执行) (NSFileManager将一个文件剪切到另一个文件夹)
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{ //1.创建session NSURLSession *session =[NSURLSession sharedSession]; //2.创建task NSURL *url =[NSURL URLWithString:@"http://120.25.226.186:32812/resources/images/minion_07.png"]; /* 第一个参数:请求路径 第二个参数:completionHandler location: 注意:session内部已经实现了边下载边写入沙盒的操作,不必再关心内存的问题 */ NSURLSessionDownloadTask *downloadTask =[session downloadTaskWithURL:url completionHandler:^(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error) { NSLog(@"%@",location); //4.剪切文件 //4.1 拼接文件全路径 NSString *fullpath =[[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:response.suggestedFilename]; //4.2 创建文件管理者 NSFileManager *manager =[NSFileManager defaultManager]; //4.3 执行剪切 [manager moveItemAtURL:location toURL:[NSURL fileURLWithPath:fullpath] error:nil]; NSLog(@"%@",[NSThread currentThread]); //在子线程中执行 }]; //3.执行任务 [downloadTask resume];}
8
//NSURLSession—-NSURLSessionDownloadTask 代理方法下载文件(didWriteData、didResumeAtOffset、didFinishDownloadingToURL、didCompleteWithError)(通过创建session的时候可以设置代理方法在哪个线程中执行)
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{ NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[[NSOperationQueue alloc]init]]; NSURL *url =[NSURL URLWithString:@"http://120.25.226.186:32812/resources/videos/minion_02.mp4"]; NSURLRequest *request =[NSURLRequest requestWithURL:url]; NSURLSessionDownloadTask *downloadTask =[session downloadTaskWithRequest:request]; [downloadTask resume];}//----------------------------------------------------------------------#pragma mark NSURLSessionDownloadDelegate//1.didWriteData 接收到数据之后写数据到沙盒中/* bytesWritten:本次写入的数据大小 totalBytesWritten:已经下载的文件数据的大小 totalBytesExpectedToWrite:文件的总大小 */-(void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite{ NSLog(@"%f---%@",1.0 * totalBytesWritten/totalBytesExpectedToWrite,[NSThread currentThread]); self.progressView.progress = 1.0 * totalBytesWritten/totalBytesExpectedToWrite;}//2.didResumeAtOffset 恢复下载的时候会调用这个方法-(void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didResumeAtOffset:(int64_t)fileOffset expectedTotalBytes:(int64_t)expectedTotalBytes{ NSLog(@"didResumeAtOffset");}//3.didFinishDownloadingToURL 下载完成-(void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location{ NSLog(@"didFinishDownloadingToURL--%@",location); //2.1 拼接文件路径 NSString *fullpath =[[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:downloadTask.response.suggestedFilename]; //2.2 创建文件管理者 NSFileManager *manager =[NSFileManager defaultManager]; //2.3 剪切 [manager moveItemAtURL:location toURL:[NSURL fileURLWithPath:fullpath] error:nil];}//4.didCompleteWithError 整个请求结束-(void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error{}
9
//暂停任务、取消任务(取消任务不可以恢复下载、暂停任务可以恢复下载)
//暂停任务
[self.downloadTask suspend];
//取消任务方式1[self.downloadTask cancel];//取消任务方式2[self.downloadTask cancelByProducingResumeData:^(NSData * _Nullable resumeData) { //resumeData 恢复下载必须得数据 self.data =resumeData;}];
10
//downloadTask中有response
downloadTask.response.suggestedFilename
11
//通过resumeData取消下载任务和新建下载任务
- (IBAction)cancelBtnClick:(id)sender {
[self.downloadTask cancelByProducingResumeData:^(NSData * _Nullable resumeData) {
//esumeData 恢复下载必须得数据
self.resumeData =resumeData;
}];
}
- (IBAction)goONBtnClick:(id)sender { //根据之前保存的信息,重新创建一个task,开启新的任务 self.downloadTask = [self.session downloadTaskWithResumeData:self.resumeData]; [self.downloadTask resume];}
12
//暂停和恢复的具体代码
- (IBAction)suspendBtnClick:(id)sender {
[self.downloadTask suspend];
}
- (IBAction)goONBtnClick:(id)sender { [self.downloadTask resume];}
13
//request中设置Range
NSMutableURLRequest *request =[NSMutableURLRequest requestWithURL:url];
NSString *range =[NSString stringWithFormat:@”bytes=%zd-“,self.currentLength];
[request setValue:range forHTTPHeaderField:@”Range”];
NSLog(@”%@”,range);
_dataTask = [self.session dataTaskWithRequest:request];
14
//通过NSFileManager来获得文件目前的大小(attributesOfItemAtPath、NSFileSize)
NSFileManager *manager = [NSFileManager defaultManager];
NSDictionary *dictM = [manager attributesOfItemAtPath:fullpath error:nil];
NSLog(@”%@”,dictM);
NSInteger length = [dictM[@”NSFileSize”] integerValue];
15
//输出流的使用 NSOutputStream
//创建输出流
NSOutputStream *stream = [[NSOutputStream alloc]initToFileAtPath:fullpath append:YES];
//打开输出流[stream open];self.stream = stream;................................[self.stream write:data.bytes maxLength:data.length];//注意:文件写完要关闭输出流[self.stream close];self.stream = nil;
16
//在dealloc里释放session
-(void)dealloc
{
// [self.session resetWithCompletionHandler:nil];
[self.session invalidateAndCancel];
}
17
//NSURLSessionDataTask离线断点下载文件(NSURLSessionDataDelegate)
#import “ViewController.h”
#define FileName @”minion_02.mp4”
#define LengthFileName @”xmg.txt”
@interface ViewController ()<NSURLSessionDataDelegate>@property (weak, nonatomic) IBOutlet UIProgressView *progressView;@property (nonatomic ,strong) NSURLSession *session;@property (nonatomic ,strong) NSURLSessionDataTask *dataTask;@property (nonatomic ,strong) NSOutputStream *stream;@property(nonatomic, assign) NSInteger totalLength;@property(nonatomic, assign) NSInteger currentLength;@end@implementation ViewController#pragma mark ----------------------#pragma mark Lzay loading-(NSURLSession *)session{ if (_session == nil) { //1.创建session _session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[NSOperationQueue mainQueue]]; } return _session;}-(NSURLSessionDataTask *)dataTask{ if (_dataTask == nil) { //2.创建task //2.1 确定请求路径 NSURL *url =[NSURL URLWithString:@"http://120.25.226.186:32812/resources/videos/minion_02.mp4"]; //2.2 创建请求对象 NSMutableURLRequest *request =[NSMutableURLRequest requestWithURL:url]; NSString *range =[NSString stringWithFormat:@"bytes=%zd-",self.currentLength]; [request setValue:range forHTTPHeaderField:@"Range"]; NSLog(@"%@",range); //2.3 创建task _dataTask = [self.session dataTaskWithRequest:request]; } return _dataTask;}#pragma mark ----------------------#pragma mark Events-(void)viewDidLoad{ [super viewDidLoad]; //拿到文件的总大小 self.totalLength = [self getLengthFile]; self.currentLength = [self getCurrentLength]; if (self.totalLength !=0) { self.progressView.progress = 1.0 * self.currentLength/self.totalLength; }}- (IBAction)StartBtnClick:(id)sender{ [self.dataTask resume];}- (IBAction)suspendBtnClick:(id)sender{ [self.dataTask suspend];}- (IBAction)goONBtnClick:(id)sender{ [self.dataTask resume];}//获得当前已经下载的文件大小-(NSInteger)getCurrentLength{ NSString *caches = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject]; NSString *fullpath =[caches stringByAppendingPathComponent:FileName]; NSFileManager *manager = [NSFileManager defaultManager]; NSDictionary *dictM = [manager attributesOfItemAtPath:fullpath error:nil]; NSLog(@"%@",dictM); NSInteger length = [dictM[@"NSFileSize"] integerValue]; return length;}//保存文件的总大小-(void)saveLengthFile:(NSInteger)length{ NSString *caches = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject]; //创建一个新的文件,用来保存文件对应的下载数据 NSString *lengthFile = [caches stringByAppendingPathComponent:LengthFileName]; NSFileManager *manger = [NSFileManager defaultManager]; [manger createFileAtPath:lengthFile contents:nil attributes:nil]; NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithContentsOfFile:lengthFile]; if (dict == nil) { dict =[NSMutableDictionary dictionary]; } [dict setObject:@(length) forKey:FileName]; [dict writeToFile:lengthFile atomically:YES];}//获得文件的总大小-(NSInteger )getLengthFile{ NSString *caches = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject]; //创建一个新的文件,用来保存文件对应的下载数据 NSString *lengthFile = [caches stringByAppendingPathComponent:LengthFileName]; NSMutableDictionary *dictM =[NSMutableDictionary dictionaryWithContentsOfFile:lengthFile]; NSLog(@"dictM----%@",dictM); if (dictM) { NSInteger totalLength = [[dictM objectForKey:FileName] integerValue]; return totalLength; }else { return 0; }}#pragma mark ----------------------#pragma mark NSURLSessionDataDelegate//1.当接收到服务器响应的时候会调用-(void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler{ NSLog(@"didReceiveResponse--%@",[NSThread currentThread]); NSString *caches = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject]; self.totalLength = response.expectedContentLength + self.currentLength; [self saveLengthFile:self.totalLength]; //1.拼接文件全路径 NSString *fullpath =[caches stringByAppendingPathComponent:FileName]; NSLog(@"%@",fullpath); //2.创建输出流 NSOutputStream *stream = [[NSOutputStream alloc]initToFileAtPath:fullpath append:YES]; //3.打开输出流 [stream open]; self.stream = stream; completionHandler(NSURLSessionResponseAllow);}//2.接收到服务器返回给我们的数据,而且可能会调用多次-(void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data{ [self.stream write:data.bytes maxLength:data.length]; self.currentLength +=data.length; NSLog(@"%f",1.0 * self.currentLength/self.totalLength); self.progressView.progress = 1.0 * self.currentLength/self.totalLength; // NSLog(@"didReceiveData--%@",[NSThread currentThread]);}//3.当请求结束之后会调用该方法,如果失败那么error有值-(void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error{ //关闭流 [self.stream close]; self.stream = nil;}#warning 释放session-(void)dealloc{ // [self.session resetWithCompletionHandler:nil]; [self.session invalidateAndCancel];}@end
18
//获取本地文件的大小([manager attributesOfItemAtPath:fullpath error:nil]、NSFileSize)
-(NSInteger)getCurrentLength
{
NSString *caches = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
NSString *fullpath =[caches stringByAppendingPathComponent:FileName];
NSFileManager *manager = [NSFileManager defaultManager];
NSDictionary *dictM = [manager attributesOfItemAtPath:fullpath error:nil];
NSLog(@”%@”,dictM);
NSInteger length = [dictM[@”NSFileSize”] integerValue];
return length;
}
19
//NSURLSessionDownloadTask实现大文件下载(block形式)
涉及知识点(1)使用NSURLSession和NSURLSessionDownload可以很方便的实现文件下载操作/* 第一个参数:要下载文件的url路径 第二个参数:当接收完服务器返回的数据之后调用该block location:下载的文件的保存地址(默认是存储在沙盒中tmp文件夹下面,随时会被删除) response:服务器响应信息,响应头 error:该请求的错误信息 *///说明:downloadTaskWithURL方法已经实现了在下载文件数据的过程中边下载文件数据,边写入到沙盒文件的操作NSURLSessionDownloadTask *downloadTask = [session downloadTaskWithURL:url completionHandler:^(NSURL * __nullable location, NSURLResponse * __nullable response, NSError * __nullable error)]
(2)downloadTaskWithURL内部默认已经实现了变下载边写入操作,所以不用开发人员担心内存问题
(3)文件下载后默认保存在tmp文件目录,需要开发人员手动的剪切到合适的沙盒目录
(4)缺点:没有办法监控下载进度
20
//NSURLSessionDownloadTask实现大文件下载-监听下载进度
5.1 涉及知识点(1)创建NSURLSession并设置代理,通过NSURLSessionDownloadTask并以代理的方式来完成大文件的下载//1.创建NSULRSession,设置代理self.session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[NSOperationQueue mainQueue]];//2.创建taskNSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/resources/videos/minion_01.mp4"];self.downloadTask = [self.session downloadTaskWithURL:url];//3.执行task[self.downloadTask resume];(2)常用代理方法的说明/* 1.当接收到下载数据的时候调用,可以在该方法中监听文件下载的进度 该方法会被调用多次 totalBytesWritten:已经写入到文件中的数据大小 totalBytesExpectedToWrite:目前文件的总大小 bytesWritten:本次下载的文件数据大小 */-(void)URLSession:(nonnull NSURLSession *)session downloadTask:(nonnull NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite/* 2.恢复下载的时候调用该方法 fileOffset:恢复之后,要从文件的什么地方开发下载 expectedTotalBytes:该文件数据的总大小 */-(void)URLSession:(nonnull NSURLSession *)session downloadTask:(nonnull NSURLSessionDownloadTask *)downloadTask didResumeAtOffset:(int64_t)fileOffset expectedTotalBytes:(int64_t)expectedTotalBytes/* 3.下载完成之后调用该方法 */-(void)URLSession:(nonnull NSURLSession *)session downloadTask:(nonnull NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(nonnull NSURL *)location/* 4.请求完成之后调用 如果请求失败,那么error有值 */-(void)URLSession:(nonnull NSURLSession *)session task:(nonnull NSURLSessionTask *)task didCompleteWithError:(nullable NSError *)error(3)实现断点下载相关代码//如果任务,取消了那么以后就不能恢复了// [self.downloadTask cancel];//如果采取这种方式来取消任务,那么该方法会通过resumeData保存当前文件的下载信息//只要有了这份信息,以后就可以通过这些信息来恢复下载[self.downloadTask cancelByProducingResumeData:^(NSData * __nullable resumeData) { self.resumeData = resumeData;}];-----------//继续下载//首先通过之前保存的resumeData信息,创建一个下载任务self.downloadTask = [self.session downloadTaskWithResumeData:self.resumeData];[self.downloadTask resume];(4)计算当前下载进度//获取文件下载进度self.progress.progress = 1.0 * totalBytesWritten/totalBytesExpectedToWrite;(5)局限性01 如果用户点击暂停之后退出程序,那么需要把恢复下载的数据写一份到沙盒,代码复杂度更02 如果用户在下载中途未保存恢复下载数据即退出程序,则不具备可操作性
21
//NSURLSessionDataTask实现大文件离线断点下载(完整思路)
6.1 涉及知识点(1)关于NSOutputStream的使用//1. 创建一个输入流,数据追加到文件的屁股上//把数据写入到指定的文件地址,如果当前文件不存在,则会自动创建NSOutputStream *stream = [[NSOutputStream alloc]initWithURL:[NSURL fileURLWithPath:[self fullPath]] append:YES];//2. 打开流[stream open];//3. 写入流数据[stream write:data.bytes maxLength:data.length];//4.当不需要的时候应该关闭流[stream close];(2)关于网络请求请求头的设置(可以设置请求下载文件的某一部分)//1. 设置请求对象//1.1 创建请求路径NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/resources/videos/minion_01.mp4"];//1.2 创建可变请求对象NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];//1.3 拿到当前文件的残留数据大小self.currentContentLength = [self FileSize];//1.4 告诉服务器从哪个地方开始下载文件数据NSString *range = [NSString stringWithFormat:@"bytes=%zd-",self.currentContentLength];NSLog(@"%@",range);//1.5 设置请求头[request setValue:range forHTTPHeaderField:@"Range"];(3)NSURLSession对象的释放-(void)dealloc{ //在最后的时候应该把session释放,以免造成内存泄露 // NSURLSession设置过代理后,需要在最后(比如控制器销毁的时候)调用session的invalidateAndCancel或者resetWithCompletionHandler,才不会有内存泄露 // [self.session invalidateAndCancel]; [self.session resetWithCompletionHandler:^{ NSLog(@"释放---"); }];}
22
//NSURLSessionDataTask实现大文件离线断点下载(具体代码实现)
@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIProgressView *progressView;
- (IBAction)startBtnClick:(id)sender;
- (IBAction)suspendBtnClick:(id)sender;
- (IBAction)goONBtnClick:(id)sender;
@property (nonatomic ,strong) NSURLSessionDownloadTask *downloadTask;@property (nonatomic ,strong) NSData *data;@property (nonatomic ,strong) NSURLSession *session;@end@implementation ViewController#pragma mark -------------------------------#pragma mark Events- (IBAction)startBtnClick:(id)sender { self.session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[NSOperationQueue mainQueue]]; NSURL *url =[NSURL URLWithString:@"http://120.25.226.186:32812/resources/videos/minion_02.mp4"]; NSURLRequest *request =[NSURLRequest requestWithURL:url]; self.downloadTask =[self.session downloadTaskWithRequest:request]; [self.downloadTask resume];}- (IBAction)suspendBtnClick:(id)sender { // [self.downloadTask suspend]; //取消任务 // [self.downloadTask cancel]; [self.downloadTask cancelByProducingResumeData:^(NSData * _Nullable resumeData) { // resumeData 恢复下载必须得数据 self.data =resumeData; }];}- (IBAction)goONBtnClick:(id)sender { // [self.downloadTask resume]; //根据之前保存的信息,重新创建一个task,开启新的任务 self.downloadTask = [self.session downloadTaskWithResumeData:self.data]; [self.downloadTask resume];}#pragma mark -------------------------------#pragma mark NSURLSessionDownloadDelegate//1.接收到数据之后写数据到沙盒中/* bytesWritten:本次写入的数据大小 totalBytesWritten:已经下载的文件数据的大小 totalBytesExpectedToWrite:文件的总大小 */-(void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite{ // NSLog(@"didWriteData"); NSLog(@"%f---%@",1.0 * totalBytesWritten/totalBytesExpectedToWrite,[NSThread currentThread]); self.progressView.progress = 1.0 * totalBytesWritten/totalBytesExpectedToWrite;}//恢复下载的时候会调用这个方法/* fileOffset:从文件的什么地方开始下载 expectedTotalBytes:文件的总打小 */-(void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didResumeAtOffset:(int64_t)fileOffset expectedTotalBytes:(int64_t)expectedTotalBytes{ NSLog(@"didResumeAtOffset---%zd--%zd",fileOffset,expectedTotalBytes);}//2.下载完成-(void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location{ NSLog(@"didFinishDownloadingToURL--%@",location); //2.1 拼接文件路径 NSString *fullpath =[[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:downloadTask.response.suggestedFilename]; //2.2 创建文件管理者 NSFileManager *manager =[NSFileManager defaultManager]; //2.3 剪切 [manager moveItemAtURL:location toURL:[NSURL fileURLWithPath:fullpath] error:nil]; NSLog(@"%@",fullpath);}//3.整个请求结束-(void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error{ NSLog(@"didCompleteWithError");}
23
//NSURLSession实现文件上传(关于NSURLSessionConfiguration相关)
涉及知识点
//————————————————————————————————-
(1)实现文件上传的方法
/* 第一个参数:请求对象 第二个参数:请求体(要上传的文件数据) block回调: NSData:响应体 NSURLResponse:响应头 NSError:请求的错误信息 */NSURLSessionUploadTask *uploadTask = [session uploadTaskWithRequest:request fromData:data completionHandler:^(NSData * __nullable data, NSURLResponse * __nullable response, NSError * __nullable error)]
//————————————————————————————————-
(2)设置代理,在代理方法中监听文件上传进度
/* 调用该方法上传文件数据 如果文件数据很大,那么该方法会被调用多次 参数说明: totalBytesSent:已经上传的文件数据的大小 totalBytesExpectedToSend:文件的总大小 */
-(void)URLSession:(nonnull NSURLSession )session task:(nonnull NSURLSessionTask )task didSendBodyData:(int64_t)bytesSent totalBytesSent:(int64_t)totalBytesSent totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend
{
NSLog(@”%.2f”,1.0 * totalBytesSent/totalBytesExpectedToSend);
}
//————————————————————————————————-
(3)关于NSURLSessionConfiguration相关
01 作用:可以统一配置NSURLSession,如请求超时等
02 创建的方式和使用
//创建配置的三种方式
+ (NSURLSessionConfiguration *)defaultSessionConfiguration;
+ (NSURLSessionConfiguration *)ephemeralSessionConfiguration;
+ (NSURLSessionConfiguration )backgroundSessionConfigurationWithIdentifier:(NSString )identifier NS_AVAILABLE(10_10, 8_0);
//统一配置NSURLSession
-(NSURLSession *)session
{
if (_session == nil) {
//创建NSURLSessionConfiguration NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration]; //设置请求超时为10秒钟 config.timeoutIntervalForRequest = 10; //在蜂窝网络情况下是否继续请求(上传或下载) config.allowsCellularAccess = NO; _session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:[NSOperationQueue mainQueue]]; } return _session;}
24
//NSURLSession实现文件上传(没有认真看,有空去研究下)
#define kboundary @"----WebKitFormBoundarySDAq9OBmwcDpce5Z"#define KNewLine [@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]#import "ViewController.h" @interface ViewController ()<NSURLSessionDataDelegate> @property (nonatomic ,strong) NSMutableData *fileData; @end @implementation ViewController -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{ [self upload2];} -(void)upload1{ //1.确定session NSURLSession *session =[NSURLSession sharedSession]; //2.确定请求路径 NSURL *url =[NSURL URLWithString:@"http://120.25.226.186:32812/upload"]; //3.创建可变的请求对象 NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; //4.设置请求方法 request.HTTPMethod = @"POST"; //5.设置请求头 NSString *header = [NSString stringWithFormat:@"multipart/form-data; boundary=%@",kboundary]; [request setValue:header forHTTPHeaderField:@"Content-Type"]; //6.拼接请求体 NSData *data =[self bodyData]; // request.HTTPBody = data; //7.发送请求上传 //注意:这个方法是采用put // [session uploadTaskWithRequest:<#(nonnull NSURLRequest *)#> fromFile:<#(nonnull NSURL *)#>] /* 第一个参数:请求对象 第二个参数:要上传的数据 第三个参数:completionHandler */ NSURLSessionUploadTask *uploadTask = [session uploadTaskWithRequest:request fromData:data completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { //8.解析数据 NSLog(@"%@",[[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding]); }]; //9.执行task [uploadTask resume];} -(void)upload2{ //1.确定session NSURLSession *session =[NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[NSOperationQueue mainQueue]]; //2.确定请求路径 NSURL *url =[NSURL URLWithString:@"http://120.25.226.186:32812/upload"]; //3.创建可变的请求对象 NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; //4.设置请求方法 request.HTTPMethod = @"POST"; //5.设置请求头 NSString *header = [NSString stringWithFormat:@"multipart/form-data; boundary=%@",kboundary]; [request setValue:header forHTTPHeaderField:@"Content-Type"]; //6.拼接请求体 NSData *data =[self bodyData]; // //7.发送请求上传 // NSURLSessionUploadTask *uploadTask = [session uploadTaskWithRequest:request fromData:data]; // //7.发送请求上传 NSURLSessionUploadTask *uploadTask = [session uploadTaskWithRequest:request fromData:data completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { NSLog(@"%@",[[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding]); }]; //8.开启 [uploadTask resume];} -(NSData *)bodyData{ //5.设置请求体 /* 5.1拼接文件参数 --分隔符 Content-Disposition: form-data; name="file"; filename="123.png" Content-Type: image/png 空行 文件数据 */ NSMutableData *fileData = [NSMutableData data]; [fileData appendData:[[NSString stringWithFormat:@"--%@",kboundary] dataUsingEncoding:NSUTF8StringEncoding]]; [fileData appendData:KNewLine]; [fileData appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"file\"; filename=\"123.png\""] dataUsingEncoding:NSUTF8StringEncoding]]; [fileData appendData:KNewLine]; [fileData appendData:[@"Content-Type: image/png" dataUsingEncoding:NSUTF8StringEncoding]]; [fileData appendData:KNewLine]; [fileData appendData:KNewLine]; UIImage *image =[UIImage imageNamed:@"Snip20151021_148"]; NSData *imageData = UIImagePNGRepresentation(image); [fileData appendData:imageData]; [fileData appendData:KNewLine]; /* 5.2拼接非文件参数 --分隔符 Content-Disposition: form-data; name="username" 空行 非文件的参数 */ [fileData appendData:[[NSString stringWithFormat:@"--%@",kboundary] dataUsingEncoding:NSUTF8StringEncoding]]; [fileData appendData:KNewLine]; [fileData appendData:[@"Content-Disposition: form-data; name=\"username\"" dataUsingEncoding:NSUTF8StringEncoding]]; [fileData appendData:KNewLine]; [fileData appendData:KNewLine]; [fileData appendData:[@"werwrwr" dataUsingEncoding:NSUTF8StringEncoding]]; [fileData appendData:KNewLine]; /* 5.3结尾标示 --分隔符-- */ [fileData appendData:[[NSString stringWithFormat:@"--%@--",kboundary] dataUsingEncoding:NSUTF8StringEncoding]]; [fileData appendData:KNewLine]; return fileData;}#pragma mark NSURLSessionDataDelegate/* 当发送数据到服务器的时候会调用该方法 bytesSent:本次发送的数据大小 totalBytesSent:总共发送了多少 totalBytesExpectedToSend:文件的总大小 */ -(void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didSendBodyData:(int64_t)bytesSent totalBytesSent:(int64_t)totalBytesSent totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend{ NSLog(@"%f",1.0 *totalBytesSent/totalBytesExpectedToSend);}////-(void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data//{// [self.fileData appendData:data];//}////-(void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler//{// self.fileData = [NSMutableData data];// completionHandler(NSURLSessionResponseAllow);//}//-(void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error//{// NSLog(@"%@",[[NSString alloc]initWithData:self.fileData encoding:NSUTF8StringEncoding]);//}
25
//AFN框架基本使用(未看)
8.1 AFN内部结构AFN结构体- NSURLConnection+ AFURLConnectionOperation+ AFHTTPRequestOperation+ AFHTTPRequestOperationManager(封装了常用的 HTTP 方法)* 属性* baseURL :AFN建议开发者针对 AFHTTPRequestOperationManager 自定义个一个单例子类,设置 baseURL, 所有的网络访问,都只使用相对路径即可* requestSerializer :请求数据格式/默认是二进制的 HTTP* responseSerializer :响应的数据格式/默认是 JSON 格式* operationQueue* reachabilityManager :网络连接管理器* 方法* manager :方便创建管理器的类方法* HTTPRequestOperationWithRequest :在访问服务器时,如果要告诉服务器一些附加信息,都需要在 Request 中设置* GET* POST- NSURLSession+ AFURLSessionManager+ AFHTTPSessionManager(封装了常用的 HTTP 方法)* GET* POST* UIKit + AFNetworking 分类* NSProgress :利用KVO- 半自动的序列化&反序列化的功能+ AFURLRequestSerialization :请求的数据格式/默认是二进制的+ AFURLResponseSerialization :响应的数据格式/默认是JSON格式- 附加功能+ 安全策略* HTTPS* AFSecurityPolicy+ 网络检测* 对苹果的网络连接检测做了一个封装* AFNetworkReachabilityManager建议:可以学习下AFN对 UIKit 做了一些分类, 对自己能力提升是非常有帮助的
26
// AFN的基本使用(未看)
(1)发送GET请求的两种方式(POST同)
-(void)get1{ //1.创建AFHTTPRequestOperationManager管理者 //AFHTTPRequestOperationManager内部是基于NSURLConnection实现的 AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager]; //2.发送请求 /* http://120.25.226.186:32812/login?username=ee&pwd=ee&type=JSON 第一个参数:NSString类型的请求路径,AFN内部会自动将该路径包装为一个url并创建请求对象 第二个参数:请求参数,以字典的方式传递,AFN内部会判断当前是POST请求还是GET请求,以选择直接拼接还是转换为NSData放到请求体中传递 第三个参数:请求成功之后回调Block 第四个参数:请求失败回调Block */ NSDictionary *param = @{ @"username":@"520it", @"pwd":@"520it" }; //注意:字符串中不能包含空格 [manager GET:@"http://120.25.226.186:32812/login" parameters:param success:^(AFHTTPRequestOperation * _Nonnull operation, id _Nonnull responseObject) { NSLog(@"请求成功---%@",responseObject); } failure:^(AFHTTPRequestOperation * _Nonnull operation, NSError * _Nonnull error) { NSLog(@"失败---%@",error); }];}-(void)get2{ //1.创建AFHTTPSessionManager管理者 //AFHTTPSessionManager内部是基于NSURLSession实现的 AFHTTPSessionManager *manager = [AFHTTPSessionManager manager]; //2.发送请求 NSDictionary *param = @{ @"username":@"520it", @"pwd":@"520it" }; //注意:responseObject:请求成功返回的响应结果(AFN内部已经把响应体转换为OC对象,通常是字典或数组) [manager GET:@"http://120.25.226.186:32812/login" parameters:param success:^(NSURLSessionDataTask * _Nonnull task, id _Nonnull responseObject) { NSLog(@"请求成功---%@",[responseObject class]); } failure:^(NSURLSessionDataTask * _Nonnull task, NSError * _Nonnull error) { NSLog(@"失败---%@",error); }];}(2)使用AFN下载文件-(void)download{ //1.创建一个管理者 AFHTTPSessionManager *manage = [AFHTTPSessionManager manager]; //2.下载文件 /* 第一个参数:请求对象 第二个参数:下载进度 第三个参数:block回调,需要返回一个url地址,用来告诉AFN下载文件的目标地址 targetPath:AFN内部下载文件存储的地址,tmp文件夹下 response:请求的响应头 返回值:文件应该剪切到什么地方 第四个参数:block回调,当文件下载完成之后调用 response:响应头 filePath:文件存储在沙盒的地址 == 第三个参数中block的返回值 error:错误信息 */ //2.1 创建请求对象 NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://120.25.226.186:32812/resources/images/minion_02.png"]]; //2.2 创建下载进度,并监听 NSProgress *progress = nil; NSURLSessionDownloadTask *downloadTask = [manage downloadTaskWithRequest:request progress:&progress destination:^NSURL * _Nonnull(NSURL * _Nonnull targetPath, NSURLResponse * _Nonnull response) { NSString *caches = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject]; //拼接文件全路径 NSString *fullpath = [caches stringByAppendingPathComponent:response.suggestedFilename]; NSURL *filePathUrl = [NSURL fileURLWithPath:fullpath]; return filePathUrl; } completionHandler:^(NSURLResponse * _Nonnull response, NSURL * _Nonnull filePath, NSError * _Nonnull error) { NSLog(@"文件下载完毕---%@",filePath); }]; //2.3 使用KVO监听下载进度 [progress addObserver:self forKeyPath:@"completedUnitCount" options:NSKeyValueObservingOptionNew context:nil]; //3.启动任务 [downloadTask resume];}//获取并计算当前文件的下载进度-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(NSProgress *)progress change:(NSDictionary<NSString *,id> *)change context:(void *)context{ NSLog(@"%zd--%zd--%f",progress.completedUnitCount,progress.totalUnitCount,1.0 * progress.completedUnitCount/progress.totalUnitCount);}
27
//Cocoapods的安装(未看)
1.先升级Gemsudo gem update --system2.切换cocoapods的数据源【先删除,再添加,查看】gem sources --remove https://rubygems.org/gem sources -a https://ruby.taobao.org/ //http已经改成httpsgem sources -l3.安装cocoapodssudo gem install cocoapods4.将Podspec文件托管地址从github切换到国内的oschina【先删除,再添加,再更新】pod repo remove master//pod repo add master http://git.oschina.net/akuandev/Specs.gitpod repo add master https://gitcafe.com/akuandev/Specs.gitpod repo update5.设置pod仓库pod setup6.测试【如果有版本号,则说明已经安装成功】pod --version7.利用cocoapods来安装第三方框架01 进入要安装框架的项目的.xcodeproj同级文件夹02 在该文件夹中新建一个文件podfile03 在文件中告诉cocoapods需要安装的框架信息a.该框架支持的平台b.适用的iOS版本c.框架的名称d.框架的版本8.安装pod install --no-repo-updatepod update --no-repo-update
//————————————————————————-
28
//使用Alcatraz来管理Xcode插件([ˈælkətræz])
//1、先删除掉本地的Alcatraz,命令行如下:
rm -rf ~/Library/Application Support/Developer/Shared/Xcode/Plug-ins/Alcatraz.xcplugin
2、再关闭掉Xcode3、继续输入以下命令行:curl -fsSL https://raw.github.com/supermarin/Alcatraz/master/Scripts/install.sh | sh4、显示安装成功后,打开xcode5、如果有弹出load bundles的时候,选择load bundles(注意:不要选择skip bundles)
//————————————————————————-
这样就安装好了Alcatraz,打开Xcode–>Window–>Package Manager
- iOS笔记22
- iOS笔记22
- iOS笔记
- iOS笔记
- ios 笔记
- IOS笔记
- IOS笔记
- ios -笔记
- ios 笔记
- IOS笔记
- iOS 笔记
- IOS笔记
- ios 笔记
- IOS 笔记
- ios笔记
- iOS笔记
- iOS 笔记
- ios笔记
- 黑马程序员—Java基础—类的加载和反射
- JAVA常见的异常类型
- 设计师配色宝典:教你从零开始学配色
- SAP销售订单关联的生产成本核算
- Linux系统下安装ngnix
- iOS笔记22
- mac 安装了vmware fushion 误删文件后说文件找不到
- hist()直方图
- Android Studio学习笔记1安装
- 开机自动启动VM中的虚拟机
- VMware无法打开内核设备“\\.\Global\vmx86”: 系统找不到指定的文件。
- Leetcode Basic Calculator 递归解法
- 网页中的相对路径概述
- 编程的智慧