zoukankan      html  css  js  c++  java
  • NSURLSession与AFNetworking3.0

    下面是用GET方式请求一个页面数据的示例:

    AFNetworking 2.x

    NSString *siteUrl = @"http://webinar.ofweek.com/readDemoFile.action";
    NSDictionary *parameters = @{@"activity.id":@"9866033",@"user.id":@"2"};
    
    AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
    manager.responseSerializer = [AFHTTPResponseSerializer serializer];
    manager.responseSerializer.acceptableContentTypes = [NSSet setWithObject:@"text/html"];
    //    manager.responseSerializer.acceptableContentTypes = [manager.responseSerializer.acceptableContentTypes setByAddingObject:@"text/html; charset=GBK"];
    
    [manager GET:siteUrl parameters:parameters
         success:^(AFHTTPRequestOperation * _Nonnull operation, id  _Nonnull responseObject) {
             NSLog(@"responseString:%@",operation.responseString);
             
             NSData *responseData = operation.responseData;
             NSStringEncoding enc = CFStringConvertEncodingToNSStringEncoding(kCFStringEncodingGB_18030_2000);
             NSString *strResponseData = [[NSString alloc] initWithData:responseData encoding:enc];
             NSLog(@"responseData:%@",strResponseData);
             
             NSLog(@"responseObject:%@",responseObject);
         }
         failure:^(AFHTTPRequestOperation * _Nonnull operation, NSError * _Nonnull error) {
             NSLog(@"failed,%@",error);
         }
    ];
    Code

    AFNetworking 3.x

    NSString *siteUrl = @"https://www.shopbop.com/actions/viewSearchResultsAction.action";
    NSDictionary *parameters = @{@"query":@"bag",@"baseIndex":@"80"};
    
    AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
    
    manager.responseSerializer = [AFHTTPResponseSerializer serializer];
    
    [manager GET:siteUrl parameters:parameters
         success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
            NSLog(@"success,%@",task.response.MIMEType);
    
            NSString *responseString = [[NSString alloc] initWithData:responseObject encoding:NSUTF8StringEncoding];
            NSLog(@"%@", responseString);
        }
         failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
        NSLog(@"failed");
        }
     ];
    Code

    iOS9中引入了新特性App Transport Security (ATS),该特性要求App内访问网站必须使用HTTPS协议。下面的方法简单的关闭了这个限制:

    1. Info.plist中添加新项NSAppTransportSecurity类型为Dictionary

    2. NSAppTransportSecurity下添加子项NSAllowsArbitraryLoads,类型为Boolean,值设为YES

    <key>NSAppTransportSecurity</key>
    <dict>
        <key>NSAllowsArbitraryLoads</key>
        <true/>
    </dict>

    NSURLSession

    NSURLSession是iOS7新推出的网络接口,它与以前的NSURLConnection是并列的。如果用户强制关闭应用,NSURLSesssion会断掉。它支持三种类型的任务:加载数据,下载和上传。

    利用NSURLSession进行数据传输需要以下流程:

    创建一个NSURLSessionConfiguration,用于接下来创建NSSession时需要的工作模式

    NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];

    工作模式分为:

    默认会话模式(default) — 工作模式类似于原来的NSURLConnection,使用的是基于磁盘缓存的持久化策略,使用用户keychain中保存的证书进行认证授权。

    瞬时会话模式(ephemeral) — 该模式不适用磁盘保存任何数据。所有和会话相关的caches,证书,cookies等都被保存在RAM中,因此当程序使会话无效,这些缓存的数据就会被自动清空。

    后台会话模式(background) — 该模式在后台完成上传和下载,在创建configuration对象的时候需要提供一个NSString类型的ID用于标识完成工作的后台会话。

    NSURLConnection实际上是由一系列组件组成,包括有:NSURLRequest、NSURLResponse、NSURLProtocol、NSURLCache、NSHTTPCookieStorage、NSURLCredentialStorage以及同名的NSURLConnection。

    在WWDC2013中,Apple的开发团队对NSURLConnection进行了重构,并推出了NSURLSession作为替代。NSURLSession的大部分组件与NSURLConnection中的组件相同,不同在处在于它将NSURLConnection替换为NSURLSession和NSURLSessionConfiguration,以及三个NSURLSessionTask的子类:NSURLSessionDataTask、NSURLSessionUploadTask和NSURLSessionDownloadTask。

    下面是NSURLSession新推出的类:

    NSURLSessionConfiguration类

    可以通过该类配置会话的工作模式,三种模式的代码如下:

    + (NSURLSessionConfiguration *)defaultSessionConfiguration;
    + (NSURLSessionConfiguration *)ephemeralSessionConfiguration;
    + (NSURLSessionConfiguration *)backgroundSessionConfiguration:(NSString *)identifier;

    在backgroundSessionConfiguration:方法中的identifier参数指定了会话ID,用于标记后台的session。

    该类还有两个重要的属性:

    /* allow request to route over cellular. */
    @property BOOL allowsCellularAccess;
    
    /* allows background tasks to be scheduled at the discretion of the system for optimal performance. */
    @property (getter=isDiscretionary) BOOL discretionary NS_AVAILABLE(NA, 7_0);

    allowsCellularAccess属性指定是否允许使用蜂窝连接,discretionary属性为YES时表示当程序在后台运作时由系统自己选择最佳的网络连接配置,在使用后台传输数据的时候,建议使用discretionary属性而不是allowsCellularAccess属性,因为discretionary属性会综合考虑WiFi可用性和电池电量。(当设备有足够电量时,设备才通过WiFi进行数据传输。如果电量低,或者只打开了蜂窝连接,传输任务将不会执行)

    NSURLSession类

    获取该类的实例有下面几种方式:

    /*
     * The shared session uses the currently set global NSURLCache,
     * NSHTTPCookieStorage and NSURLCredentialStorage objects.
     */
    + (NSURLSession *)sharedSession;
    
    /*
     * Customization of NSURLSession occurs during creation of a new session.
     * If you only need to use the convenience routines with custom
     * configuration options it is not necessary to specify a delegate.
     * If you do specify a delegate, the delegate will be retained until after
     * the delegate has been sent the URLSession:didBecomeInvalidWithError: message.
     */
    + (NSURLSession *)sessionWithConfiguration:(NSURLSessionConfiguration *)configuration;
    + (NSURLSession *)sessionWithConfiguration:(NSURLSessionConfiguration *)configuration delegate:(id <NSURLSessionDelegate>)delegate delegateQueue:(NSOperationQueue *)queue;

    第一种方式是使用静态的sharedSession方法,将返回共享的会话,该会话使用全局的Cache、Cookie和证书。

    第二种方式是通过sessionWithConfiguration:方法根据NSURLSessionConfiguration的值创建对应工作模式下的会话。

    第三种方式是通过sessionWithConfiguration:delegate:delegateQueue方法创建对象,在第二种方式的基础上增加了session的委托和委托所处的队列。当不再需要连接时,可以调用NSURLSession的invalidateAndCancel方法直接关闭,或者调用finishTasksAndInvalidate等待当前Task结束后关闭。这两种关闭方法都会触发delegate类的URLSession:didBecomeInvalidWithError:事件。

    NSURLSessionTask类

    这是一个抽象类,它包含三个子类:NSURLSessionDataTask,NSURLSessionUploadTask和NSURLSessionDownloadTask。这三个类封装了获取数据,上传和下载任务。下面是继承关系图:

    (1)NSURLSessionDataTask

    利用NSURLRequest对象或NSURL对象来创建该类的实例:

    /* Creates a data task with the given request.  The request may have a body stream. */
    - (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request;
    
    /* Creates a data task to retrieve the contents of the given URL. */
    - (NSURLSessionDataTask *)dataTaskWithURL:(NSURL *)url;

    利用NSURLRequest对象或NSURL对象来创建该类的实例,当任务完成后,通过completionHandler指定回调的代码块:

    /*
     * data task convenience methods.  These methods create tasks that
     * bypass the normal delegate calls for response and data delivery,
     * and provide a simple cancelable asynchronous interface to receiving
     * data.  Errors will be returned in the NSURLErrorDomain,
     * see <Foundation/NSURLError.h>.  The delegate, if any, will still be
     * called for authentication challenges.
     */
    - (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request completionHandler:(void (^)(NSData *data, NSURLResponse *response, NSError *error))completionHandler;
    - (NSURLSessionDataTask *)dataTaskWithURL:(NSURL *)url completionHandler:(void (^)(NSData *data, NSURLResponse *response, NSError *error))completionHandler;

    读取数据示例:

    #import "ViewController.h"
    
    @interface ViewController ()
    @property (weak, nonatomic) IBOutlet UIActivityIndicatorView *progressCircle;
    @property (weak, nonatomic) IBOutlet UIWebView *webView;
    - (IBAction)loadButtonClicked:(id)sender;
    
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        // Do any additional setup after loading the view, typically from a nib.
    
    }
    
    - (void)didReceiveMemoryWarning {
        [super didReceiveMemoryWarning];
        // Dispose of any resources that can be recreated.
    }
    
    - (IBAction)loadButtonClicked:(id)sender {
        // 开始加载数据,开始转动
        [self.progressCircle startAnimating];
        
        NSURL *url = [NSURL URLWithString:@"http://tech.qq.com/it.htm"];
        NSURLRequest *request = [NSURLRequest requestWithURL:url];
        NSURLSession *session = [NSURLSession sharedSession];
        NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request
                                                    completionHandler:
                                          ^(NSData *data, NSURLResponse *response, NSError *error) {
                                              //打印出返回的状态码,请求成功返回200
                                              NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
                                              NSInteger responseStatusCode = [httpResponse statusCode];
                                              NSLog(@"%ld", (long)responseStatusCode);
                                              
                                              //打印出返回的代码
                                              NSStringEncoding enc = CFStringConvertEncodingToNSStringEncoding(kCFStringEncodingGB_18030_2000);
                                              NSString *strResponseData = [[NSString alloc] initWithData:data encoding:enc];
                                              NSLog(@"ResponseData String:%@",strResponseData);
                                              
                                             
    
                                              // 在UIWebView中加载数据
                                              [self.webView loadData:data
                                                            MIMEType:@"text/html"
                                                    textEncodingName :@"utf-8"
                                                             baseURL:[NSURL URLWithString:@"http://tech.qq.com"]];
                                              
                                              //加载数据完毕,停止转动
                                              if ([NSThread isMainThread])
                                              {
                                                  [self.progressCircle stopAnimating];
                                              }
                                              else
                                              {
                                                  dispatch_sync(dispatch_get_main_queue(), ^{
                                                      [self.progressCircle stopAnimating];
                                                  });
                                              }
                                          }];
        //使用resume方法启动任务
        [dataTask resume];
    }
    @end
    Code

    (2)NSURLSessionUploadTask

    利用NSURLRequest对象创建该类的实例,在上传时指定文件源或数据源

    /* Creates an upload task with the given request.  The body of the request will be created from the file referenced by fileURL */
    - (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request fromFile:(NSURL *)fileURL;
    
    /* Creates an upload task with the given request.  The body of the request is provided from the bodyData. */
    - (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request fromData:(NSData *)bodyData;
    
    /* Creates an upload task with the given request.  The previously set body stream of the request (if any) is ignored and the URLSession:task:needNewBodyStream: delegate will be called when the body payload is required. */
    - (NSURLSessionUploadTask *)uploadTaskWithStreamedRequest:(NSURLRequest *)request;

    同上,当任务完成后,通过completionHandler指定回调的代码块:

    /*
     * upload convenience method.
     */
    - (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request fromFile:(NSURL *)fileURL completionHandler:(void (^)(NSData *data, NSURLResponse *response, NSError *error))completionHandler;
    - (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request fromData:(NSData *)bodyData completionHandler:(void (^)(NSData *data, NSURLResponse *response, NSError *error))completionHandler;

    上传示例:

    #import <UIKit/UIKit.h>
    
    @interface ViewController : UIViewController <NSURLSessionDataDelegate>
    @end
    
    #import "ViewController.h"
    
    @interface ViewController ()
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        // Do any additional setup after loading the view, typically from a nib.
    
    }
    
    - (IBAction)testClicked:(id)sender {
        NSURL *uploadURL = [NSURL URLWithString:@"http://www.synchemical.com/temp/UploadImage4.ashx?param2=iphone6"];
        NSString *boundary = @"----------V2ymHFg03ehbqgZCaKO6jy";
        
        NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
        NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:nil];
        
        NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
        NSString *documentsDirectory = [paths objectAtIndex:0];
        NSString *uploadFilePath = [documentsDirectory stringByAppendingPathComponent:@"ad6.png"];
        NSLog(@"file path: %@",uploadFilePath);
        NSString *fileName = [uploadFilePath lastPathComponent];
        
        NSMutableData *mutableData = [NSMutableData data];
        NSData *fileData = [[NSData alloc] initWithContentsOfFile:uploadFilePath];
    //    NSData *dataOfFile = UIImageJPEGRepresentation(self.imageView.image,1.0);
        
        if (fileData) {
            //Body part for "textContent" parameter. This is a string.
            NSString *textContent = @"This is a parameter string";
            [mutableData appendData:[[NSString stringWithFormat:@"--%@
    ", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
            [mutableData appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name="%@"
    
    ", @"textContent"] dataUsingEncoding:NSUTF8StringEncoding]];
            [mutableData appendData:[[NSString stringWithFormat:@"%@
    ", textContent] dataUsingEncoding:NSUTF8StringEncoding]];
            //end
            
            
            [mutableData appendData:[[NSString stringWithFormat:@"--%@
    ", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
            [mutableData appendData:[[NSString stringWithFormat:@"Content-Disposition:form-data; name="myfile"; filename="%@"
    ",fileName] dataUsingEncoding:NSUTF8StringEncoding]];
            [mutableData appendData:[@"Content-Type:application/zip
    
    " dataUsingEncoding:NSUTF8StringEncoding]];
            [mutableData appendData:fileData];
            [mutableData appendData:[[NSString stringWithFormat:@"
    "] dataUsingEncoding:NSUTF8StringEncoding]];
            [mutableData appendData:[[NSString stringWithFormat:@"--%@--
    ", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
        }
    
        NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:uploadURL];
        [request setHTTPMethod:@"POST"];
        NSString *contentType = [NSString stringWithFormat:@"multipart/form-data; boundary=%@",boundary];
        [request setValue:contentType forHTTPHeaderField:@"Content-Type"];
        
        NSURLSessionUploadTask *uploadTask = [session uploadTaskWithRequest:request fromData:mutableData completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
            NSString *message = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
            NSLog(@"message:%@", message);
            
            [session invalidateAndCancel]; //当不再需要连接时,可以调用Session的invalidateAndCancel直接关闭
        }];
        
        [uploadTask resume];
    }
    
    - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didSendBodyData:(int64_t)bytesSent totalBytesSent:(int64_t)totalBytesSent totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend {
        NSLog(@"already sent:%lld",bytesSent);
        NSLog(@"totoal to send:%lld",totalBytesSent);
        NSLog(@"expected send:%lld",totalBytesExpectedToSend);
    }
    
    - (void)didReceiveMemoryWarning {
        [super didReceiveMemoryWarning];
        // Dispose of any resources that can be recreated.
    }
    
    @end
    
    <%@ WebHandler Language="C#" Class="UploadImage" %>
    using System;
    using System.Web;
    using System.IO;
    public class UploadImage : IHttpHandler
    {
        public void ProcessRequest(HttpContext context) {         
            HttpPostedFile myFile = context.Request.Files["myfile"];
            string strFolder = HttpContext.Current.Server.MapPath(context.Request["folder"]);
            myFile.SaveAs(strFolder +"/uploadFolder/" + myFile.FileName);
            if (!Directory.Exists(strFolder)) 
            {
                Directory.CreateDirectory(strFolder);
            }
    
            context.Response.Write("param: "+context.Request["textContent"]);
       }
        
        public bool IsReusable {
            get {
                return false;
            }
        }
    }
    Code

    (3)NSURLSessionDownloadTask

    利用NSURLRequest对象或NSURL对象来创建该类的实例:

    /* Creates a download task with the given request. */
    - (NSURLSessionDownloadTask *)downloadTaskWithRequest:(NSURLRequest *)request;
    
    /* Creates a download task to download the contents of the given URL. */
    - (NSURLSessionDownloadTask *)downloadTaskWithURL:(NSURL *)url;
    
    /* Creates a download task with the resume data.  If the download cannot be successfully resumed, URLSession:task:didCompleteWithError: will be called. */
    - (NSURLSessionDownloadTask *)downloadTaskWithResumeData:(NSData *)resumeData;

    利用NSURLRequest对象或NSURL对象来创建该类的实例,当任务完成后,通过completionHandler指定回调的代码块,最后一个方法支持通过之前未下载完成的数据继续下载

    /*
     * download task convenience methods.  When a download successfully
     * completes, the NSURL will point to a file that must be read or
     * copied during the invocation of the completion routine.  The file
     * will be removed automatically.
     */
    - (NSURLSessionDownloadTask *)downloadTaskWithRequest:(NSURLRequest *)request completionHandler:(void (^)(NSURL *location, NSURLResponse *response, NSError *error))completionHandler;
    - (NSURLSessionDownloadTask *)downloadTaskWithURL:(NSURL *)url completionHandler:(void (^)(NSURL *location, NSURLResponse *response, NSError *error))completionHandler;
    - (NSURLSessionDownloadTask *)downloadTaskWithResumeData:(NSData *)resumeData completionHandler:(void (^)(NSURL *location, NSURLResponse *response, NSError *error))completionHandler;

    下载示例:

    #import "ViewController.h"
    
    @interface ViewController () <NSURLSessionDelegate> {
        NSData *partialData;
    }
    @property (strong, nonatomic) NSURLSessionDownloadTask *downloadTask;
    @property (strong, nonatomic) NSURLSession *session;
    @property (weak, nonatomic) IBOutlet UIActivityIndicatorView *progressCircle;
    @property (weak, nonatomic) IBOutlet UIWebView *webView;
    - (IBAction)downloadButtonClicked:(id)sender;
    
    @end
    
    @implementation ViewController
    - (IBAction)downloadButtonClicked:(id)sender {
        [self.progressCircle startAnimating];
        
        NSURL *URL = [NSURL URLWithString:@"http://www.synchemical.com/temp/2800kb.jpg"];
        NSURLRequest *request = [NSURLRequest requestWithURL:URL];
        NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
        self.session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:[NSOperationQueue mainQueue]];
    
        self.downloadTask = [self.session downloadTaskWithRequest:request];
        [self.downloadTask resume];
    }
    
    - (IBAction)resumeButtonClicked:(id)sender {
        // 传入上次暂停下载返回的数据,就可以恢复下载
        self.downloadTask = [self.session downloadTaskWithResumeData:partialData];
        
        // 开始任务
        [self.downloadTask resume];
        
        // 清空
        partialData = nil;
    }
    
    - (IBAction)pauseButtonClicked:(id)sender {
        [self.downloadTask cancelByProducingResumeData:^(NSData *resumeData) {
            partialData = resumeData;
            self.downloadTask = nil;
        }];
    }
    
    - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite {
        NSLog(@"=========下载进度=========");
        // 获得下载进度
        NSLog(@"totalBytesWritten:%lld",totalBytesWritten);
        NSLog(@"totalBytesExpectedToWrite:%lld",totalBytesExpectedToWrite);
        NSLog(@"download percent:%f",(double)totalBytesWritten / totalBytesExpectedToWrite);
    }
    
    - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location {
        NSLog(@"finish download");
        //打印出返回的状态码,请求成功返回200
        NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)downloadTask.response;
        NSInteger responseStatusCode = [httpResponse statusCode];
        NSLog(@"hxw: %ld", (long)responseStatusCode);
        
        //输出下载文件原来的存放目录
        NSLog(@"file tmp path: %@", location);
        //设置文件的存放目录
        NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
        NSString *documentsPath = [paths objectAtIndex:0];
        NSURL *documentsURL = [NSURL fileURLWithPath:documentsPath];
        NSString *filePath = [[downloadTask.response URL] lastPathComponent];
        NSURL *fileURL = [documentsURL URLByAppendingPathComponent:filePath];
        NSLog(@"file save path:%@",fileURL.path);
        //如果路径下文件已经存在,先将其删除
        NSFileManager *fileManager = [NSFileManager defaultManager];
        BOOL isDir;
        if ([fileManager fileExistsAtPath:fileURL.path isDirectory:&isDir]) {
            NSLog(@"存在文件");
            if (isDir) {
                NSLog(@"它是个目录");
            }
            else {
                NSLog(@"它是个普通文件");
            }
            [fileManager removeItemAtURL:fileURL error:nil];
        }
        //移动文件
        BOOL success = [fileManager moveItemAtURL:location toURL:fileURL error:nil];
        if (success)
        {
            dispatch_async(dispatch_get_main_queue(), ^{
                //UIImage *image = [UIImage imageWithContentsOfFile:[fileURL path]];
                //self.imageView.image = image;
                //self.imageView.contentMode = UIViewContentModeScaleAspectFill;
                //在webView中加载图片文件
                NSURLRequest *showImage_request = [NSURLRequest requestWithURL:fileURL];
                [self.webView loadRequest:showImage_request];
                
                //下载完毕,停止转动
                if ([NSThread isMainThread]) {
                    [self.progressCircle stopAnimating];
                }
                else {
                    dispatch_sync(dispatch_get_main_queue(), ^{
                        [self.progressCircle stopAnimating];
                    });
                }
            });
        }
        else
        {
            NSLog(@"Couldn't copy the downloaded file");
        }
    }
    
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        // Do any additional setup after loading the view, typically from a nib.
    }
    
    - (void)didReceiveMemoryWarning {
        [super didReceiveMemoryWarning];
        // Dispose of any resources that can be recreated.
    }
    @end
    Code
  • 相关阅读:
    梅小雨20191010-2 每周例行报告
    梅小雨20190919-1 每周例行报告
    梅小雨20190919-4 单元测试,结对
    王可非 20191128-1 总结
    20191121-1 每周例行报告
    20191114-1 每周例行报告
    对“都是为了生活”小组成员帮助的感谢
    20191107-1 每周例行报告
    20191031-1 每周例行报告
    20191024-1 每周例行报告
  • 原文地址:https://www.cnblogs.com/CoderWayne/p/5427667.html
Copyright © 2011-2022 走看看