├── MCDownload.gif
├── MCDownloaderDemo.xcodeproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcuserdata
│ │ └── M.C.xcuserdatad
│ │ └── UserInterfaceState.xcuserstate
├── xcuserdata
│ └── M.C.xcuserdatad
│ │ └── xcschemes
│ │ ├── xcschememanagement.plist
│ │ └── MCDownloaderDemo.xcscheme
└── project.pbxproj
├── MCDownloaderDemo
├── ViewController.h
├── QKYDelayButton.h
├── AppDelegate.h
├── main.m
├── Assets.xcassets
│ └── AppIcon.appiconset
│ │ └── Contents.json
├── TableViewCell.h
├── Info.plist
├── QKYDelayButton.m
├── Base.lproj
│ ├── LaunchScreen.storyboard
│ └── Main.storyboard
├── AppDelegate.m
├── ViewController.m
└── TableViewCell.m
├── MCDownloaderDemoTests
├── Info.plist
└── MCDownloaderDemoTests.m
├── MCDownloaderDemoUITests
├── Info.plist
└── MCDownloaderDemoUITests.m
├── LICENSE
├── README.md
├── .gitignore
├── Source
├── MCDownloadOperation.h
├── MCDownloadReceipt.h
├── MCDownloader.h
├── MCDownloadReceipt.m
├── MCDownloadOperation.m
└── MCDownloader.m
└── MCDownloader.podspec
/MCDownload.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/agelessman/MCDownloader/HEAD/MCDownload.gif
--------------------------------------------------------------------------------
/MCDownloaderDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/MCDownloaderDemo.xcodeproj/project.xcworkspace/xcuserdata/M.C.xcuserdatad/UserInterfaceState.xcuserstate:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/agelessman/MCDownloader/HEAD/MCDownloaderDemo.xcodeproj/project.xcworkspace/xcuserdata/M.C.xcuserdatad/UserInterfaceState.xcuserstate
--------------------------------------------------------------------------------
/MCDownloaderDemo/ViewController.h:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.h
3 | // MCDownloadManager
4 | //
5 | // Created by 马超 on 16/9/5.
6 | // Copyright © 2016年 qikeyun. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | @interface ViewController : UITableViewController
12 |
13 |
14 | @end
15 |
16 |
--------------------------------------------------------------------------------
/MCDownloaderDemo/QKYDelayButton.h:
--------------------------------------------------------------------------------
1 | //
2 | // QKYDelayButton.h
3 | // qikeyun
4 | //
5 | // Created by 马超 on 16/6/4.
6 | // Copyright © 2016年 Jerome. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | @interface QKYDelayButton : UIButton
12 |
13 | @property (nonatomic,assign)NSTimeInterval clickDurationTime;
14 |
15 | @end
16 |
--------------------------------------------------------------------------------
/MCDownloaderDemo/AppDelegate.h:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.h
3 | // MCDownloaderDemo
4 | //
5 | // Created by M.C on 17/4/10.
6 | // Copyright © 2017年 M.C. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | @interface AppDelegate : UIResponder
12 |
13 | @property (strong, nonatomic) UIWindow *window;
14 |
15 |
16 | @end
17 |
18 |
--------------------------------------------------------------------------------
/MCDownloaderDemo/main.m:
--------------------------------------------------------------------------------
1 | //
2 | // main.m
3 | // MCDownloaderDemo
4 | //
5 | // Created by M.C on 17/4/10.
6 | // Copyright © 2017年 M.C. All rights reserved.
7 | //
8 |
9 | #import
10 | #import "AppDelegate.h"
11 |
12 | int main(int argc, char * argv[]) {
13 | @autoreleasepool {
14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/MCDownloaderDemoTests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | BNDL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 |
22 |
23 |
--------------------------------------------------------------------------------
/MCDownloaderDemoUITests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | BNDL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 |
22 |
23 |
--------------------------------------------------------------------------------
/MCDownloaderDemo/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "size" : "29x29",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "size" : "29x29",
11 | "scale" : "3x"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "size" : "40x40",
16 | "scale" : "2x"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "size" : "40x40",
21 | "scale" : "3x"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "size" : "60x60",
26 | "scale" : "2x"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "size" : "60x60",
31 | "scale" : "3x"
32 | }
33 | ],
34 | "info" : {
35 | "version" : 1,
36 | "author" : "xcode"
37 | }
38 | }
--------------------------------------------------------------------------------
/MCDownloaderDemo.xcodeproj/xcuserdata/M.C.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SchemeUserState
6 |
7 | MCDownloaderDemo.xcscheme
8 |
9 | orderHint
10 | 0
11 |
12 |
13 | SuppressBuildableAutocreation
14 |
15 | E71DCA1F1E9B1D2B00A058D2
16 |
17 | primary
18 |
19 |
20 | E71DCA381E9B1D2C00A058D2
21 |
22 | primary
23 |
24 |
25 | E71DCA431E9B1D2C00A058D2
26 |
27 | primary
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/MCDownloaderDemo/TableViewCell.h:
--------------------------------------------------------------------------------
1 | //
2 | // TableViewCell.h
3 | // MCDownloadManager
4 | //
5 | // Created by 马超 on 16/9/6.
6 | // Copyright © 2016年 qikeyun. All rights reserved.
7 | //
8 |
9 | #import
10 | #import "QKYDelayButton.h"
11 |
12 | @class TableViewCell;
13 | @protocol TableViewCellDelegate
14 |
15 | - (void)cell:(TableViewCell *)cell didClickedBtn:(UIButton *)btn;
16 |
17 | @end
18 |
19 | @interface TableViewCell : UITableViewCell
20 | @property (weak, nonatomic) IBOutlet UIProgressView *progressView;
21 | @property (weak, nonatomic) IBOutlet UILabel *nameLabel;
22 | @property (weak, nonatomic) IBOutlet QKYDelayButton *button;
23 | @property (weak, nonatomic) IBOutlet UILabel *bytesLable;
24 | @property (weak, nonatomic) IBOutlet UILabel *speedLable;
25 |
26 |
27 | @property (nonatomic, weak) id delegate;
28 | @property (nonatomic,copy)NSString *url;
29 | @end
30 |
--------------------------------------------------------------------------------
/MCDownloaderDemoTests/MCDownloaderDemoTests.m:
--------------------------------------------------------------------------------
1 | //
2 | // MCDownloaderDemoTests.m
3 | // MCDownloaderDemoTests
4 | //
5 | // Created by M.C on 17/4/10.
6 | // Copyright © 2017年 M.C. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | @interface MCDownloaderDemoTests : XCTestCase
12 |
13 | @end
14 |
15 | @implementation MCDownloaderDemoTests
16 |
17 | - (void)setUp {
18 | [super setUp];
19 | // Put setup code here. This method is called before the invocation of each test method in the class.
20 | }
21 |
22 | - (void)tearDown {
23 | // Put teardown code here. This method is called after the invocation of each test method in the class.
24 | [super tearDown];
25 | }
26 |
27 | - (void)testExample {
28 | // This is an example of a functional test case.
29 | // Use XCTAssert and related functions to verify your tests produce the correct results.
30 | }
31 |
32 | - (void)testPerformanceExample {
33 | // This is an example of a performance test case.
34 | [self measureBlock:^{
35 | // Put the code you want to measure the time of here.
36 | }];
37 | }
38 |
39 | @end
40 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 M.C
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/MCDownloaderDemo/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | APPL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 | LSRequiresIPhoneOS
22 |
23 | UILaunchStoryboardName
24 | LaunchScreen
25 | UIMainStoryboardFile
26 | Main
27 | UIRequiredDeviceCapabilities
28 |
29 | armv7
30 |
31 | UISupportedInterfaceOrientations
32 |
33 | UIInterfaceOrientationPortrait
34 | UIInterfaceOrientationLandscapeLeft
35 | UIInterfaceOrientationLandscapeRight
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/MCDownloaderDemo/QKYDelayButton.m:
--------------------------------------------------------------------------------
1 | //
2 | // QKYDelayButton.m
3 | // qikeyun
4 | //
5 | // Created by 马超 on 16/6/4.
6 | // Copyright © 2016年 Jerome. All rights reserved.
7 | //
8 |
9 | #import "QKYDelayButton.h"
10 |
11 | static NSTimeInterval defaultDuration = 1.0f;
12 |
13 | static BOOL _isIgnoreEvent = NO;
14 |
15 | static void resetState() {
16 |
17 | _isIgnoreEvent = NO;
18 | }
19 |
20 |
21 | @implementation QKYDelayButton
22 |
23 | - (void)sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event
24 | {
25 | if ([self isKindOfClass:[UIButton class]]) {
26 |
27 | self.clickDurationTime = self.clickDurationTime == 0 ? defaultDuration : self.clickDurationTime;
28 |
29 | if (_isIgnoreEvent) {
30 |
31 | return;
32 | }
33 | else if (self.clickDurationTime > 0) {
34 |
35 | _isIgnoreEvent = YES;
36 |
37 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(self.clickDurationTime * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
38 |
39 | resetState();
40 | });
41 |
42 | [super sendAction:action to:target forEvent:event];
43 | }
44 | }
45 | else {
46 |
47 | [super sendAction:action to:target forEvent:event];
48 | }
49 | }
50 |
51 | @end
52 |
--------------------------------------------------------------------------------
/MCDownloaderDemoUITests/MCDownloaderDemoUITests.m:
--------------------------------------------------------------------------------
1 | //
2 | // MCDownloaderDemoUITests.m
3 | // MCDownloaderDemoUITests
4 | //
5 | // Created by M.C on 17/4/10.
6 | // Copyright © 2017年 M.C. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | @interface MCDownloaderDemoUITests : XCTestCase
12 |
13 | @end
14 |
15 | @implementation MCDownloaderDemoUITests
16 |
17 | - (void)setUp {
18 | [super setUp];
19 |
20 | // Put setup code here. This method is called before the invocation of each test method in the class.
21 |
22 | // In UI tests it is usually best to stop immediately when a failure occurs.
23 | self.continueAfterFailure = NO;
24 | // UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method.
25 | [[[XCUIApplication alloc] init] launch];
26 |
27 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this.
28 | }
29 |
30 | - (void)tearDown {
31 | // Put teardown code here. This method is called after the invocation of each test method in the class.
32 | [super tearDown];
33 | }
34 |
35 | - (void)testExample {
36 | // Use recording to get started writing UI tests.
37 | // Use XCTAssert and related functions to verify your tests produce the correct results.
38 | }
39 |
40 | @end
41 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # MCDownloader
2 | A simple and powerful iOS downloader. [中文简介](http://www.jianshu.com/p/062327c5846a)
3 |
4 | 
5 |
6 |
7 | ## Installation
8 |
9 | Copy the source file to your project.
10 |
11 |
12 | ## Usage
13 | ### Start the download
14 |
15 | [[MCDownloader sharedDownloader] downloadDataWithURL:[NSURL URLWithString:url] progress:^(NSInteger receivedSize, NSInteger expectedSize, NSInteger speed, NSURL * _Nullable targetURL) {
16 |
17 | } completed:^(MCDownloadReceipt * _Nullable receipt, NSError * _Nullable error, BOOL finished) {
18 | NSLog(@"==%@", error.description);
19 | }];
20 |
21 | ### Stop the download
22 |
23 | [[MCDownloader sharedDownloader] cancel:receipt completed:^{
24 | [self.button setTitle:@"Start" forState:UIControlStateNormal];
25 | }];
26 |
27 | ### Remove the download
28 |
29 | [[MCDownloader sharedDownloader] remove:receipt completed:^{
30 | [self.tableView reloadData];
31 | }];
32 |
33 | ### Get the download information
34 |
35 | MCDownloadReceipt *receipt = [[MCDownloader sharedDownloader] downloadReceiptForURLString:url];
36 |
37 | ### Cancel and remove all downloads
38 |
39 | [[MCDownloader sharedDownloader] cancelAllDownloads];
40 |
41 | [[MCDownloader sharedDownloader] removeAndClearAll];
42 |
43 | ## License
44 | MCDownloader is released under an MIT license. See License.md for more information.
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | #
3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
4 |
5 | ## Build generated
6 | build/
7 | DerivedData/
8 |
9 | ## Various settings
10 | *.pbxuser
11 | !default.pbxuser
12 | *.mode1v3
13 | !default.mode1v3
14 | *.mode2v3
15 | !default.mode2v3
16 | *.perspectivev3
17 | !default.perspectivev3
18 | xcuserdata/
19 |
20 | ## Other
21 | *.moved-aside
22 | *.xcuserstate
23 |
24 | ## Obj-C/Swift specific
25 | *.hmap
26 | *.ipa
27 | *.dSYM.zip
28 | *.dSYM
29 |
30 | # CocoaPods
31 | #
32 | # We recommend against adding the Pods directory to your .gitignore. However
33 | # you should judge for yourself, the pros and cons are mentioned at:
34 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
35 | #
36 | # Pods/
37 |
38 | # Carthage
39 | #
40 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
41 | # Carthage/Checkouts
42 |
43 | Carthage/Build
44 |
45 | # fastlane
46 | #
47 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
48 | # screenshots whenever they are needed.
49 | # For more information about the recommended setup visit:
50 | # https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md
51 |
52 | fastlane/report.xml
53 | fastlane/screenshots
54 |
55 | #Code Injection
56 | #
57 | # After new code Injection tools there's a generated folder /iOSInjectionProject
58 | # https://github.com/johnno1962/injectionforxcode
59 |
60 | iOSInjectionProject/
61 |
--------------------------------------------------------------------------------
/MCDownloaderDemo/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/MCDownloaderDemo/AppDelegate.m:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.m
3 | // MCDownloaderDemo
4 | //
5 | // Created by M.C on 17/4/10.
6 | // Copyright © 2017年 M.C. All rights reserved.
7 | //
8 |
9 | #import "AppDelegate.h"
10 |
11 | @interface AppDelegate ()
12 |
13 | @end
14 |
15 | @implementation AppDelegate
16 |
17 |
18 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
19 | // Override point for customization after application launch.
20 | return YES;
21 | }
22 |
23 |
24 | - (void)applicationWillResignActive:(UIApplication *)application {
25 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
26 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
27 | }
28 |
29 |
30 | - (void)applicationDidEnterBackground:(UIApplication *)application {
31 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
32 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
33 | }
34 |
35 |
36 | - (void)applicationWillEnterForeground:(UIApplication *)application {
37 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
38 | }
39 |
40 |
41 | - (void)applicationDidBecomeActive:(UIApplication *)application {
42 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
43 | }
44 |
45 |
46 | - (void)applicationWillTerminate:(UIApplication *)application {
47 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
48 | }
49 |
50 |
51 | @end
52 |
--------------------------------------------------------------------------------
/MCDownloaderDemo/ViewController.m:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.m
3 | // MCDownloadManager
4 | //
5 | // Created by 马超 on 16/9/5.
6 | // Copyright © 2016年 qikeyun. All rights reserved.
7 | //
8 |
9 | #import "ViewController.h"
10 | #import "TableViewCell.h"
11 | #import
12 | #import "MCDownloader.h"
13 |
14 |
15 |
16 | @interface ViewController ()
17 | @property (weak, nonatomic) IBOutlet UILabel *label;
18 |
19 |
20 |
21 | @property (strong, nonatomic) NSMutableArray *urls;
22 | @end
23 |
24 | @implementation ViewController
25 |
26 | - (NSMutableArray *)urls
27 | {
28 | if (!_urls) {
29 | self.urls = [NSMutableArray array];
30 | for (int i = 1; i<=10; i++) {
31 | [self.urls addObject:[NSString stringWithFormat:@"http://120.25.226.186:32812/resources/videos/minion_%02d.mp4", i]];
32 |
33 | // [self.urls addObject:@"http://localhost/MJDownload-master.zip"];
34 | }
35 | }
36 | return _urls;
37 | }
38 | - (void)viewDidLoad {
39 | [super viewDidLoad];
40 | // Do any additional setup after loading the view, typically from a nib.
41 | self.view.backgroundColor = [UIColor whiteColor];
42 | [[MCDownloader sharedDownloader] removeAndClearAll];
43 | // [MCDownloader sharedDownloader].maxConcurrentDownloads = 1;
44 | }
45 |
46 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
47 | return [self urls].count;
48 | }
49 | - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
50 | return 100;
51 | }
52 |
53 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
54 | TableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell" forIndexPath:indexPath];
55 | cell.url = [self urls][indexPath.row];
56 | cell.delegate = self;
57 | return cell;
58 | }
59 | - (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath {
60 | return YES;
61 | }
62 | - (UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath {
63 | return UITableViewCellEditingStyleDelete;
64 | }
65 | - (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
66 | if (editingStyle == UITableViewCellEditingStyleDelete) {
67 | MCDownloadReceipt *receipt = [[MCDownloader sharedDownloader] downloadReceiptForURLString:[self urls][indexPath.row]];
68 | [[MCDownloader sharedDownloader] remove:receipt completed:^{
69 | [self.tableView reloadData];
70 | }];
71 |
72 | }
73 | }
74 |
75 | - (IBAction)nextAction:(UIBarButtonItem *)sender {
76 |
77 |
78 |
79 | NSArray *urls = [self urls];
80 |
81 | if ([sender.title isEqualToString:@"Start"]) {
82 |
83 | sender.enabled = NO;
84 |
85 | for (NSString *url in urls) {
86 | [[MCDownloader sharedDownloader] downloadDataWithURL:[NSURL URLWithString:url] progress:^(NSInteger receivedSize, NSInteger expectedSize, NSInteger speed, NSURL * _Nullable targetURL) {
87 |
88 | } completed:^(MCDownloadReceipt * _Nullable receipt, NSError * _Nullable error, BOOL finished) {
89 | NSLog(@"==%@", error.description);
90 | }];
91 |
92 | }
93 |
94 | sender.enabled = YES;
95 | sender.title = @"Stop";
96 | } else {
97 |
98 | sender.enabled = NO;
99 |
100 | [[MCDownloader sharedDownloader] cancelAllDownloads];
101 |
102 | sender.enabled = YES;
103 | sender.title = @"Start";
104 | }
105 | [self.tableView reloadData];
106 | }
107 |
108 |
109 | - (void)cell:(TableViewCell *)cell didClickedBtn:(UIButton *)btn {
110 | MCDownloadReceipt *receipt = [[MCDownloader sharedDownloader] downloadReceiptForURLString:cell.url];
111 | UIViewController *vc = [UIApplication sharedApplication].keyWindow.rootViewController;
112 | MPMoviePlayerViewController *mpc = [[MPMoviePlayerViewController alloc] initWithContentURL:[NSURL fileURLWithPath:receipt.filePath]];
113 | [vc presentViewController:mpc animated:YES completion:nil];
114 | }
115 |
116 |
117 | @end
118 |
--------------------------------------------------------------------------------
/Source/MCDownloadOperation.h:
--------------------------------------------------------------------------------
1 | //
2 | // MCDownloadOperation.h
3 | // MCDownloadManager
4 | //
5 | // Created by M.C on 17/4/6. (QQ:714080794 Gmail:chaoma0609@gmail.com)
6 | // Copyright © 2017年 qikeyun. All rights reserved.
7 | //
8 | // Permission is hereby granted, free of charge, to any person obtaining a copy
9 | // of this software and associated documentation files (the "Software"), to deal
10 | // in the Software without restriction, including without limitation the rights
11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | // copies of the Software, and to permit persons to whom the Software is
13 | // furnished to do so, subject to the following conditions:
14 | //
15 | // The above copyright notice and this permission notice shall be included in
16 | // all copies or substantial portions of the Software.
17 | //
18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24 | // THE SOFTWARE.
25 | //
26 | #import
27 | #import "MCDownloader.h"
28 |
29 |
30 | extern NSString * _Nonnull const MCDownloadStartNotification;
31 | extern NSString * _Nonnull const MCDownloadReceiveResponseNotification;
32 | extern NSString * _Nonnull const MCDownloadStopNotification;
33 | extern NSString * _Nonnull const MCDownloadFinishNotification;
34 |
35 | /**
36 | Describes a downloader operation. If one wants to use a custom downloader op, it needs to inherit from `NSOperation` and conform to this protocol
37 | */
38 | @protocol MCDownloaderOperationInterface
39 |
40 | - (nonnull instancetype)initWithRequest:(nullable NSURLRequest *)request
41 | inSession:(nullable NSURLSession *)session;
42 |
43 | - (nullable id)addHandlersForProgress:(nullable MCDownloaderProgressBlock)progressBlock
44 | completed:(nullable MCDownloaderCompletedBlock)completedBlock;
45 |
46 | @end
47 |
48 |
49 | @interface MCDownloadOperation : NSOperation
50 |
51 | /**
52 | * The request used by the operation's task.
53 | */
54 | @property (strong, nonatomic, nullable) NSURLRequest *request;
55 |
56 | /**
57 | * The operation's task
58 | */
59 | @property (strong, nonatomic, readonly, nullable) NSURLSessionTask *dataTask;
60 |
61 |
62 | /**
63 | * The expected size of data.
64 | */
65 | @property (assign, nonatomic) NSInteger expectedSize;
66 |
67 | /**
68 | * The response returned by the operation's connection.
69 | */
70 | @property (strong, nonatomic, nullable) NSURLResponse *response;
71 |
72 | /**
73 | * Initializes a `MCDownloadOperation` object
74 | *
75 | * @see MCDownloadOperation
76 | *
77 | * @param request the receipt
78 | * @param session the URL session in which this operation will run
79 | *
80 | * @return the initialized instance
81 | */
82 | - (nonnull instancetype)initWithRequest:(nullable NSURLRequest *)request
83 | inSession:(nullable NSURLSession *)session NS_DESIGNATED_INITIALIZER;
84 |
85 | /**
86 | * Adds handlers for progress and completion. Returns a tokent that can be passed to -cancel: to cancel this set of
87 | * callbacks.
88 | *
89 | * @param progressBlock the block executed when a new chunk of data arrives.
90 | * @note the progress block is executed on a background queue
91 | * @param completedBlock the block executed when the download is done.
92 | * @note the completed block is executed on the main queue for success. If errors are found, there is a chance the block will be executed on a background queue
93 | *
94 | * @return the token to use to cancel this set of handlers
95 | */
96 | - (nullable id)addHandlersForProgress:(nullable MCDownloaderProgressBlock)progressBlock
97 | completed:(nullable MCDownloaderCompletedBlock)completedBlock;
98 |
99 | /**
100 | * Cancels a set of callbacks. Once all callbacks are canceled, the operation is cancelled.
101 | *
102 | * @param token the token representing a set of callbacks to cancel
103 | *
104 | * @return YES if the operation was stopped because this was the last token to be canceled. NO otherwise.
105 | */
106 | - (BOOL)cancel:(nullable id)token;
107 | @end
108 |
--------------------------------------------------------------------------------
/MCDownloaderDemo.xcodeproj/xcuserdata/M.C.xcuserdatad/xcschemes/MCDownloaderDemo.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
33 |
39 |
40 |
41 |
43 |
49 |
50 |
51 |
52 |
53 |
59 |
60 |
61 |
62 |
63 |
64 |
74 |
76 |
82 |
83 |
84 |
85 |
86 |
87 |
93 |
95 |
101 |
102 |
103 |
104 |
106 |
107 |
110 |
111 |
112 |
--------------------------------------------------------------------------------
/MCDownloaderDemo/TableViewCell.m:
--------------------------------------------------------------------------------
1 | //
2 | // TableViewCell.m
3 | // MCDownloadManager
4 | //
5 | // Created by 马超 on 16/9/6.
6 | // Copyright © 2016年 qikeyun. All rights reserved.
7 | //
8 |
9 | #import "TableViewCell.h"
10 | #import "MCDownloader.h"
11 |
12 |
13 |
14 | @implementation TableViewCell
15 |
16 | - (void)awakeFromNib {
17 | [super awakeFromNib];
18 | // Initialization code
19 |
20 | self.button.clipsToBounds = YES;
21 | self.button.layer.cornerRadius = 10;
22 | self.button.layer.borderWidth = 1;
23 | self.button.layer.borderColor = [UIColor orangeColor].CGColor;
24 | [self.button setTitleColor:[UIColor orangeColor] forState:UIControlStateNormal];
25 |
26 | }
27 |
28 | - (void)setSelected:(BOOL)selected animated:(BOOL)animated {
29 | [super setSelected:selected animated:animated];
30 |
31 | // Configure the view for the selected state
32 | }
33 |
34 | - (void)setUrl:(NSString *)url {
35 | _url = url;
36 |
37 | MCDownloadReceipt *receipt = [[MCDownloader sharedDownloader] downloadReceiptForURLString:url];
38 |
39 | receipt.customFilePathBlock = ^NSString * _Nullable(MCDownloadReceipt * _Nullable receipt) {
40 | NSString *cacheDir = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES).firstObject;
41 | NSString *cacheFolderPath = [cacheDir stringByAppendingPathComponent:@"我自己写的"];
42 | return [cacheFolderPath stringByAppendingPathComponent:url.lastPathComponent];
43 | };
44 |
45 | // NSLog(@"%@", receipt.filePath);
46 | self.nameLabel.text = receipt.truename;
47 | self.speedLable.text = nil;
48 | self.bytesLable.text = nil;
49 | self.progressView.progress = 0;
50 | self.progressView.progress = receipt.progress.fractionCompleted;
51 |
52 | // self.imageView.image = [UIImage imageWithData:[NSData dataWithContentsOfFile:receipt.filePath]];
53 |
54 | if (receipt.state == MCDownloadStateDownloading || receipt.state == MCDownloadStateWillResume) {
55 | [self.button setTitle:@"Stop" forState:UIControlStateNormal];
56 | }else if (receipt.state == MCDownloadStateCompleted) {
57 | [self.button setTitle:@"Play" forState:UIControlStateNormal];
58 | self.nameLabel.text = @"Download Finished";
59 | }else {
60 | [self.button setTitle:@"Start" forState:UIControlStateNormal];
61 | }
62 |
63 | __weak typeof(receipt) weakReceipt = receipt;
64 | receipt.downloaderProgressBlock = ^(NSInteger receivedSize, NSInteger expectedSize, NSInteger speed, NSURL * _Nullable targetURL) {
65 | __strong typeof(weakReceipt) strongReceipt = weakReceipt;
66 | if ([targetURL.absoluteString isEqualToString:self.url]) {
67 | [self.button setTitle:@"Stop" forState:UIControlStateNormal];
68 | self.bytesLable.text = [NSString stringWithFormat:@"%0.1fm/%0.1fm", receivedSize/1024.0/1024,expectedSize/1024.0/1024];
69 | self.progressView.progress = (receivedSize/1024.0/1024) / (expectedSize/1024.0/1024);
70 | self.speedLable.text = [NSString stringWithFormat:@"%@/s", strongReceipt.speed ?: @"0"];
71 | }
72 |
73 | };
74 |
75 | receipt.downloaderCompletedBlock = ^(MCDownloadReceipt *receipt, NSError * _Nullable error, BOOL finished) {
76 | if (error) {
77 | [self.button setTitle:@"Start" forState:UIControlStateNormal];
78 | self.nameLabel.text = @"Download Failure";
79 | }else {
80 | [self.button setTitle:@"Play" forState:UIControlStateNormal];
81 | self.nameLabel.text = @"Download Finished";
82 | }
83 |
84 | };
85 | }
86 | - (IBAction)buttonAction:(UIButton *)sender {
87 |
88 | MCDownloadReceipt *receipt = [[MCDownloader sharedDownloader] downloadReceiptForURLString:self.url];
89 | if (receipt.state == MCDownloadStateDownloading || receipt.state == MCDownloadStateWillResume) {
90 |
91 | [[MCDownloader sharedDownloader] cancel:receipt completed:^{
92 | [self.button setTitle:@"Start" forState:UIControlStateNormal];
93 | }];
94 | }else if (receipt.state == MCDownloadStateCompleted) {
95 |
96 | if ([self.delegate respondsToSelector:@selector(cell:didClickedBtn:)]) {
97 | [self.delegate cell:self didClickedBtn:sender];
98 | }
99 | }else {
100 | [self.button setTitle:@"Stop" forState:UIControlStateNormal];
101 | [self download];
102 | }
103 |
104 | }
105 |
106 | - (void)download {
107 |
108 | [[MCDownloader sharedDownloader] downloadDataWithURL:[NSURL URLWithString:self.url] progress:^(NSInteger receivedSize, NSInteger expectedSize, NSInteger speed, NSURL * _Nullable targetURL) {
109 |
110 | } completed:^(MCDownloadReceipt *receipt, NSError * _Nullable error, BOOL finished) {
111 | NSLog(@"==%@", error.description);
112 | }];
113 |
114 |
115 | }
116 | @end
117 |
--------------------------------------------------------------------------------
/MCDownloader.podspec:
--------------------------------------------------------------------------------
1 | #
2 | # Be sure to run `pod spec lint MCDownloader.podspec' to ensure this is a
3 | # valid spec and to remove all comments including this before submitting the spec.
4 | #
5 | # To learn more about Podspec attributes see http://docs.cocoapods.org/specification.html
6 | # To see working Podspecs in the CocoaPods repo see https://github.com/CocoaPods/Specs/
7 | #
8 |
9 | Pod::Spec.new do |s|
10 |
11 | # ――― Spec Metadata ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
12 | #
13 | # These will help people to find your library, and whilst it
14 | # can feel like a chore to fill in it's definitely to your advantage. The
15 | # summary should be tweet-length, and the description more in depth.
16 | #
17 |
18 | s.name = "MCDownloader"
19 | s.version = "1.0.1"
20 | s.summary = "A simple and powerful iOS downloader."
21 |
22 | # This description is used to generate tags and improve search results.
23 | # * Think: What does it do? Why did you write it? What is the focus?
24 | # * Try to keep it short, snappy and to the point.
25 | # * Write the description between the DESC delimiters below.
26 | # * Finally, don't worry about the indent, CocoaPods strips it!
27 | # s.description = <<-DESC
28 | # DESC
29 |
30 | s.homepage = "https://github.com/agelessman/MCDownloader"
31 | # s.screenshots = "www.example.com/screenshots_1.gif", "www.example.com/screenshots_2.gif"
32 |
33 |
34 | # ――― Spec License ――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
35 | #
36 | # Licensing your code is important. See http://choosealicense.com for more info.
37 | # CocoaPods will detect a license file if there is a named LICENSE*
38 | # Popular ones are 'MIT', 'BSD' and 'Apache License, Version 2.0'.
39 | #
40 |
41 | s.license = "MIT"
42 | # s.license = { :type => "MIT", :file => "FILE_LICENSE" }
43 |
44 |
45 | # ――― Author Metadata ――――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
46 | #
47 | # Specify the authors of the library, with email addresses. Email addresses
48 | # of the authors are extracted from the SCM log. E.g. $ git log. CocoaPods also
49 | # accepts just a name if you'd rather not provide an email address.
50 | #
51 | # Specify a social_media_url where others can refer to, for example a twitter
52 | # profile URL.
53 | #
54 |
55 | s.author = { "MC" => "714080794@qq.com" }
56 | # Or just: s.author = "MC"
57 | # s.authors = { "MC" => "714080794@qq.com" }
58 | # s.social_media_url = "http://twitter.com/MC"
59 |
60 | # ――― Platform Specifics ――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
61 | #
62 | # If this Pod runs only on iOS or OS X, then specify the platform and
63 | # the deployment target. You can optionally include the target after the platform.
64 | #
65 |
66 | # s.platform = :ios
67 | s.platform = :ios, "8.0"
68 |
69 | # When using multiple platforms
70 | # s.ios.deployment_target = "5.0"
71 | # s.osx.deployment_target = "10.7"
72 | # s.watchos.deployment_target = "2.0"
73 | # s.tvos.deployment_target = "9.0"
74 |
75 |
76 | # ――― Source Location ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
77 | #
78 | # Specify the location from where the source should be retrieved.
79 | # Supports git, hg, bzr, svn and HTTP.
80 | #
81 |
82 | s.source = { :git => "https://github.com/agelessman/MCDownloader.git", :tag => "#{s.version}" }
83 |
84 |
85 | # ――― Source Code ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
86 | #
87 | # CocoaPods is smart about how it includes source code. For source files
88 | # giving a folder will include any swift, h, m, mm, c & cpp files.
89 | # For header files it will include any header in the folder.
90 | # Not including the public_header_files will make all headers public.
91 | #
92 |
93 | s.source_files = "Source/*.{h,m}"
94 | # s.exclude_files = "Classes/Exclude"
95 |
96 | # s.public_header_files = "Classes/**/*.h"
97 |
98 |
99 | # ――― Resources ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
100 | #
101 | # A list of resources included with the Pod. These are copied into the
102 | # target bundle with a build phase script. Anything else will be cleaned.
103 | # You can preserve files from being cleaned, please don't preserve
104 | # non-essential files like tests, examples and documentation.
105 | #
106 |
107 | # s.resource = "icon.png"
108 | # s.resources = "Resources/*.png"
109 |
110 | # s.preserve_paths = "FilesToSave", "MoreFilesToSave"
111 |
112 |
113 | # ――― Project Linking ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
114 | #
115 | # Link your library with frameworks, or libraries. Libraries do not include
116 | # the lib prefix of their name.
117 | #
118 |
119 | # s.framework = "SomeFramework"
120 | # s.frameworks = "SomeFramework", "AnotherFramework"
121 |
122 | # s.library = "iconv"
123 | # s.libraries = "iconv", "xml2"
124 |
125 |
126 | # ――― Project Settings ――――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
127 | #
128 | # If your library depends on compiler flags you can set them in the xcconfig hash
129 | # where they will only apply to your library. If you depend on other Podspecs
130 | # you can include multiple dependencies to ensure it works.
131 |
132 | # s.requires_arc = true
133 |
134 | # s.xcconfig = { "HEADER_SEARCH_PATHS" => "$(SDKROOT)/usr/include/libxml2" }
135 | # s.dependency "JSONKit", "~> 1.4"
136 |
137 | end
138 |
--------------------------------------------------------------------------------
/Source/MCDownloadReceipt.h:
--------------------------------------------------------------------------------
1 | //
2 | // MCDownloadReceipt.h
3 | // MCDownloadManager
4 | //
5 | // Created by M.C on 17/4/6. (QQ:714080794 Gmail:chaoma0609@gmail.com)
6 | // Copyright © 2017年 qikeyun. All rights reserved.
7 | //
8 | // Permission is hereby granted, free of charge, to any person obtaining a copy
9 | // of this software and associated documentation files (the "Software"), to deal
10 | // in the Software without restriction, including without limitation the rights
11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | // copies of the Software, and to permit persons to whom the Software is
13 | // furnished to do so, subject to the following conditions:
14 | //
15 | // The above copyright notice and this permission notice shall be included in
16 | // all copies or substantial portions of the Software.
17 | //
18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24 | // THE SOFTWARE.
25 | //
26 |
27 | #import
28 |
29 | @class MCDownloadReceipt;
30 |
31 | /** The download state */
32 | typedef NS_ENUM(NSUInteger, MCDownloadState) {
33 | MCDownloadStateNone, /** default */
34 | MCDownloadStateWillResume, /** waiting */
35 | MCDownloadStateDownloading, /** downloading */
36 | MCDownloadStateSuspened, /** suspened */
37 | MCDownloadStateCompleted, /** download completed */
38 | MCDownloadStateFailed /** download failed */
39 | };
40 |
41 | /** The download prioritization */
42 | typedef NS_ENUM(NSInteger, MCDownloadPrioritization) {
43 | MCDownloadPrioritizationFIFO, /** first in first out */
44 | MCDownloadPrioritizationLIFO /** last in first out */
45 | };
46 |
47 |
48 | typedef void (^MCDownloaderProgressBlock)(NSInteger receivedSize, NSInteger expectedSize, NSInteger speed, NSURL * _Nullable targetURL);
49 | typedef void (^MCDownloaderCompletedBlock)(MCDownloadReceipt * _Nullable receipt, NSError * _Nullable error, BOOL finished);
50 | typedef NSString *_Nullable (^MCDownloaderReceiptCustomFilePathBlock)(MCDownloadReceipt * _Nullable receipt);
51 |
52 | /**
53 | * The receipt of a downloader,we can get all the informations from the receipt.
54 | */
55 | @interface MCDownloadReceipt : NSObject
56 |
57 | /**
58 | * Download State
59 | */
60 | @property (nonatomic, assign, readonly) MCDownloadState state;
61 |
62 | /**
63 | The download source url
64 | */
65 | @property (nonatomic, copy, readonly, nonnull) NSString *url;
66 |
67 | /**
68 | The file path, you can use it to get the downloaded data.
69 | */
70 | @property (nonatomic, copy, readonly, nonnull) NSString *filePath;
71 |
72 | /**
73 | The url's pathExtension through the MD5 processing.
74 | */
75 | @property (nonatomic, copy, readonly, nullable) NSString *filename;
76 |
77 | /**
78 | The url's pathExtension without through the MD5 processing.
79 | */
80 | @property (nonatomic, copy, readonly, nullable) NSString *truename;
81 |
82 | /**
83 | The url's pathExtension without through the MD5 processing.
84 | */
85 | @property (nonatomic, copy, nullable) MCDownloaderReceiptCustomFilePathBlock customFilePathBlock;
86 |
87 | /**
88 | The current download speed,
89 | */
90 | @property (nonatomic, copy, readonly, nullable) NSString *speed; // KB/s
91 |
92 | @property (assign, nonatomic, readonly) long long totalBytesWritten;
93 | @property (assign, nonatomic, readonly) long long totalBytesExpectedToWrite;
94 |
95 | /**
96 | The current download progress object
97 | */
98 | @property (nonatomic, strong, readonly, nullable) NSProgress *progress;
99 |
100 | @property (nonatomic, strong, readonly, nullable) NSError *error;
101 |
102 |
103 | /**
104 | The call back block. When setting this block,the progress block will be called during downloading,the complete block will be called after download finished.
105 | */
106 | @property (nonatomic,copy, nullable, readonly)MCDownloaderProgressBlock downloaderProgressBlock;
107 | @property (nonatomic,copy, nullable, readonly)MCDownloaderCompletedBlock downloaderCompletedBlock;
108 |
109 |
110 |
111 |
112 |
113 | #pragma mark - Private Methods
114 | ///=============================================================================
115 | /// Method is at the bottom of the private method, do not need to use
116 | ///=============================================================================
117 |
118 | /**
119 | The `MCDowmloadReceipt` method of initialization. Generally don't need to use this method.
120 |
121 | use `MCDownloadReceipt *receipt = [[MCDownloader sharedDownloader] downloadReceiptForURLString:url];` to get the `MCDowmloadReceipt`.
122 |
123 | */
124 | - (nonnull instancetype)initWithURLString:(nonnull NSString *)URLString
125 | downloadOperationCancelToken:(nullable id)downloadOperationCancelToken
126 | downloaderProgressBlock:(nullable MCDownloaderProgressBlock)downloaderProgressBlock
127 | downloaderCompletedBlock:(nullable MCDownloaderCompletedBlock)downloaderCompletedBlock;
128 |
129 | - (void)setTotalBytesExpectedToWrite:(long long)totalBytesExpectedToWrite;
130 | - (void)setState:(MCDownloadState)state;
131 | - (void)setDownloadOperationCancelToken:(nullable id)downloadOperationCancelToken;
132 | - (void)setDownloaderProgressBlock:(nullable MCDownloaderProgressBlock)downloaderProgressBlock;
133 | - (void)setDownloaderCompletedBlock:(nullable MCDownloaderCompletedBlock)downloaderCompletedBlock;
134 | - (void)setSpeed:(NSString * _Nullable)speed;
135 |
136 |
137 |
138 | /**
139 | Auxiliary attributes and don't need to use
140 | */
141 | @property (nonatomic, assign) NSUInteger totalRead;
142 | @property (nonatomic, strong, nullable) NSDate *date;
143 | @property (nonatomic, strong, nullable) id downloadOperationCancelToken;
144 | @end
145 |
--------------------------------------------------------------------------------
/Source/MCDownloader.h:
--------------------------------------------------------------------------------
1 | //
2 | // MCDownloader.h
3 | // MCDownloadManager
4 | //
5 | // Created by M.C on 17/4/6. (QQ:714080794 Gmail:chaoma0609@gmail.com)
6 | // Copyright © 2017年 qikeyun. All rights reserved.
7 | //
8 | // Permission is hereby granted, free of charge, to any person obtaining a copy
9 | // of this software and associated documentation files (the "Software"), to deal
10 | // in the Software without restriction, including without limitation the rights
11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | // copies of the Software, and to permit persons to whom the Software is
13 | // furnished to do so, subject to the following conditions:
14 | //
15 | // The above copyright notice and this permission notice shall be included in
16 | // all copies or substantial portions of the Software.
17 | //
18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24 | // THE SOFTWARE.
25 | //
26 | #import
27 | #import "MCDownloadReceipt.h"
28 | #import
29 |
30 | NS_ASSUME_NONNULL_BEGIN
31 |
32 | // Use dispatch_main_async_safe instead of dispatch_async(dispatch_get_main_queue(), block)
33 | #ifndef dispatch_main_async_safe
34 | #define dispatch_main_async_safe(block)\
35 | if (strcmp(dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL), dispatch_queue_get_label(dispatch_get_main_queue())) == 0) {\
36 | block();\
37 | } else {\
38 | dispatch_async(dispatch_get_main_queue(), block);\
39 | }
40 | #endif
41 |
42 |
43 |
44 | FOUNDATION_EXPORT NSString * const MCDownloadCacheFolderName;
45 | FOUNDATION_EXPORT NSString * cacheFolder();
46 |
47 |
48 | extern NSString * _Nonnull const MCDownloadStartNotification;
49 | extern NSString * _Nonnull const MCDownloadStopNotification;
50 |
51 | typedef NSDictionary MCHTTPHeadersDictionary;
52 | typedef NSMutableDictionary MCHTTPHeadersMutableDictionary;
53 |
54 | typedef MCHTTPHeadersDictionary * _Nullable (^MCDownloaderHeadersFilterBlock)(NSURL * _Nullable url, MCHTTPHeadersDictionary * _Nullable headers);
55 |
56 |
57 | @interface MCDownloader : NSObject
58 |
59 | /**
60 | * The maximum number of concurrent downloads
61 | */
62 | @property (assign, nonatomic) NSInteger maxConcurrentDownloads;
63 |
64 | /**
65 | * Shows the current amount of downloads that still need to be downloaded
66 | */
67 | @property (readonly, nonatomic) NSUInteger currentDownloadCount;
68 |
69 |
70 | /**
71 | * The timeout value (in seconds) for the download operation. Default: 15.0.
72 | */
73 | @property (assign, nonatomic) NSTimeInterval downloadTimeout;
74 |
75 |
76 | /**
77 | Defines the order prioritization of incoming download requests being inserted into the queue. `MCDownloadPrioritizationFIFO` by default.
78 | */
79 | @property (nonatomic, assign) MCDownloadPrioritization downloadPrioritizaton;
80 | /**
81 | * Singleton method, returns the shared instance
82 | *
83 | * @return global shared instance of downloader class
84 | */
85 | + (nonnull instancetype)sharedDownloader;
86 |
87 |
88 | /**
89 | * Set filter to pick headers for downloading image HTTP request.
90 | *
91 | * This block will be invoked for each downloading image request, returned
92 | * NSDictionary will be used as headers in corresponding HTTP request.
93 | */
94 | @property (nonatomic, copy, nullable) MCDownloaderHeadersFilterBlock headersFilter;
95 |
96 | /**
97 | * Creates an instance of a downloader with specified session configuration.
98 | * *Note*: `timeoutIntervalForRequest` is going to be overwritten.
99 | * @return new instance of downloader class
100 | */
101 | - (nonnull instancetype)initWithSessionConfiguration:(nullable NSURLSessionConfiguration *)sessionConfiguration NS_DESIGNATED_INITIALIZER;
102 |
103 | /**
104 | * Set a value for a HTTP header to be appended to each download HTTP request.
105 | *
106 | * @param value The value for the header field. Use `nil` value to remove the header.
107 | * @param field The name of the header field to set.
108 | */
109 | - (void)setValue:(nullable NSString *)value forHTTPHeaderField:(nullable NSString *)field;
110 |
111 | /**
112 | * Returns the value of the specified HTTP header field.
113 | *
114 | * @return The value associated with the header field field, or `nil` if there is no corresponding header field.
115 | */
116 | - (nullable NSString *)valueForHTTPHeaderField:(nullable NSString *)field;
117 |
118 | /**
119 | * Sets a subclass of `MCDownloadOperation` as the default
120 | * `NSOperation` to be used each time MCDownload constructs a request
121 | * operation to download an data.
122 | *
123 | * @param operationClass The subclass of `MCDownloadOperation` to set
124 | * as default. Passing `nil` will revert to `MCDownloadOperation`.
125 | */
126 | - (void)setOperationClass:(nullable Class)operationClass;
127 |
128 | /**
129 | Creates an `MCDownloadReceipt` with the specified request.
130 |
131 | @param url The URL for the request.
132 | @param progressBlock A block object to be executed when the download progress is updated. Note this block is called on the main queue.
133 | */
134 | - (nullable MCDownloadReceipt *)downloadDataWithURL:(nullable NSURL *)url
135 | progress:(nullable MCDownloaderProgressBlock)progressBlock
136 | completed:(nullable MCDownloaderCompletedBlock)completedBlock;
137 |
138 | - (nullable MCDownloadReceipt *)downloadReceiptForURLString:(nullable NSString *)URLString;
139 |
140 | - (void)cancel:(nullable MCDownloadReceipt *)token completed:(nullable void (^)())completed;
141 |
142 | - (void)remove:(nullable MCDownloadReceipt *)token completed:(nullable void (^)())completed;
143 | /**
144 | * Sets the download queue suspension state
145 | */
146 | - (void)setSuspended:(BOOL)suspended;
147 |
148 | /**
149 | * Cancels all download operations in the queue
150 | */
151 | - (void)cancelAllDownloads;
152 |
153 | /**
154 | Romove All files in the cache folder.
155 | @Waring:
156 | This method is synchronized methods, you should be careful when using, will delete all the data in the cache folder
157 | */
158 | - (void)removeAndClearAll;
159 | @end
160 |
161 | NS_ASSUME_NONNULL_END
162 |
--------------------------------------------------------------------------------
/Source/MCDownloadReceipt.m:
--------------------------------------------------------------------------------
1 | //
2 | // MCDownloadReceipt.m
3 | // MCDownloadManager
4 | //
5 | // Created by M.C on 17/4/6. (QQ:714080794 Gmail:chaoma0609@gmail.com)
6 | // Copyright © 2017年 qikeyun. All rights reserved.
7 | //
8 | // Permission is hereby granted, free of charge, to any person obtaining a copy
9 | // of this software and associated documentation files (the "Software"), to deal
10 | // in the Software without restriction, including without limitation the rights
11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | // copies of the Software, and to permit persons to whom the Software is
13 | // furnished to do so, subject to the following conditions:
14 | //
15 | // The above copyright notice and this permission notice shall be included in
16 | // all copies or substantial portions of the Software.
17 | //
18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24 | // THE SOFTWARE.
25 | //
26 |
27 | #import "MCDownloadReceipt.h"
28 | #import
29 |
30 | extern NSString * cacheFolder();
31 |
32 | static unsigned long long fileSizeForPath(NSString *path) {
33 |
34 | signed long long fileSize = 0;
35 | NSFileManager *fileManager = [NSFileManager defaultManager];
36 | if ([fileManager fileExistsAtPath:path]) {
37 | NSError *error = nil;
38 | NSDictionary *fileDict = [fileManager attributesOfItemAtPath:path error:&error];
39 | if (!error && fileDict) {
40 | fileSize = [fileDict fileSize];
41 | }
42 | }
43 | return fileSize;
44 | }
45 |
46 | static NSString * getMD5String(NSString *str) {
47 |
48 | if (str == nil) return nil;
49 |
50 | const char *cstring = str.UTF8String;
51 | unsigned char bytes[CC_MD5_DIGEST_LENGTH];
52 | CC_MD5(cstring, (CC_LONG)strlen(cstring), bytes);
53 |
54 | NSMutableString *md5String = [NSMutableString string];
55 | for (int i = 0; i < CC_MD5_DIGEST_LENGTH; i++) {
56 | [md5String appendFormat:@"%02x", bytes[i]];
57 | }
58 | return md5String;
59 | }
60 |
61 | @interface MCDownloadReceipt()
62 |
63 | @property (nonatomic, assign) MCDownloadState state;
64 |
65 | @property (nonatomic, copy) NSString *url;
66 | @property (nonatomic, copy) NSString *filePath;
67 | @property (nonatomic, copy) NSString *filename;
68 | @property (nonatomic, copy) NSString *truename;
69 | @property (nonatomic, strong) NSProgress *progress;
70 |
71 | @property (assign, nonatomic) long long totalBytesWritten;
72 |
73 | @end
74 |
75 | @implementation MCDownloadReceipt
76 |
77 | - (NSString *)filePath {
78 | if (_filePath == nil) {
79 | NSString *path = [cacheFolder() stringByAppendingPathComponent:self.filename];
80 | if (![path isEqualToString:_filePath] ) {
81 | if (_filePath && ![[NSFileManager defaultManager] fileExistsAtPath:_filePath]) {
82 | NSString *dir = [_filePath stringByDeletingLastPathComponent];
83 | [[NSFileManager defaultManager] createDirectoryAtPath:dir withIntermediateDirectories:YES attributes:nil error:nil];
84 | }
85 | _filePath = path;
86 | }
87 | }
88 | return _filePath;
89 | }
90 |
91 | - (void)setCustomFilePathBlock:(nullable MCDownloaderReceiptCustomFilePathBlock)customFilePathBlock {
92 | _customFilePathBlock = customFilePathBlock;
93 | if (_customFilePathBlock) {
94 | NSString *path = customFilePathBlock(self);
95 | if (path && ![path isEqualToString:_filePath] ) {
96 | _filePath = path;
97 | if (_filePath && ![[NSFileManager defaultManager] fileExistsAtPath:_filePath]) {
98 | NSString *dir = [_filePath stringByDeletingLastPathComponent];
99 | [[NSFileManager defaultManager] createDirectoryAtPath:dir withIntermediateDirectories:YES attributes:nil error:nil];
100 | }
101 | }
102 | }
103 | }
104 |
105 | - (NSString *)filename {
106 | if (_filename == nil) {
107 | NSString *pathExtension = self.url.pathExtension;
108 | if (pathExtension.length) {
109 | _filename = [NSString stringWithFormat:@"%@.%@", getMD5String(self.url), pathExtension];
110 | } else {
111 | _filename = getMD5String(self.url);
112 | }
113 | }
114 | return _filename;
115 | }
116 |
117 | - (NSString *)truename {
118 | if (_truename == nil) {
119 | _truename = self.url.lastPathComponent;
120 | }
121 | return _truename;
122 | }
123 |
124 | - (NSProgress *)progress {
125 | if (_progress == nil) {
126 | _progress = [[NSProgress alloc] initWithParent:nil userInfo:nil];
127 | }
128 | @try {
129 | _progress.totalUnitCount = self.totalBytesExpectedToWrite;
130 | _progress.completedUnitCount = self.totalBytesWritten;
131 | } @catch (NSException *exception) {
132 |
133 | }
134 | return _progress;
135 | }
136 |
137 | - (long long)totalBytesWritten {
138 |
139 | return fileSizeForPath(self.filePath);
140 | }
141 |
142 |
143 | - (instancetype)initWithURL:(NSString *)url {
144 | if (self = [self init]) {
145 |
146 | self.url = url;
147 | }
148 | return self;
149 | }
150 |
151 | #pragma mark - NSCoding
152 | - (void)encodeWithCoder:(NSCoder *)aCoder
153 | {
154 | [aCoder encodeObject:self.url forKey:NSStringFromSelector(@selector(url))];
155 | [aCoder encodeObject:self.filePath forKey:NSStringFromSelector(@selector(filePath))];
156 | [aCoder encodeObject:@(self.state) forKey:NSStringFromSelector(@selector(state))];
157 | [aCoder encodeObject:self.filename forKey:NSStringFromSelector(@selector(filename))];
158 | [aCoder encodeObject:@(self.totalBytesWritten) forKey:NSStringFromSelector(@selector(totalBytesWritten))];
159 | [aCoder encodeObject:@(self.totalBytesExpectedToWrite) forKey:NSStringFromSelector(@selector(totalBytesExpectedToWrite))];
160 |
161 | }
162 |
163 | - (id)initWithCoder:(NSCoder *)aDecoder
164 | {
165 | self = [super init];
166 | if (self) {
167 | self.url = [aDecoder decodeObjectForKey:NSStringFromSelector(@selector(url))];
168 | self.filePath = [aDecoder decodeObjectForKey:NSStringFromSelector(@selector(filePath))];
169 | self.state = [[aDecoder decodeObjectOfClass:[NSNumber class] forKey:NSStringFromSelector(@selector(state))] unsignedIntegerValue];
170 | self.filename = [aDecoder decodeObjectForKey:NSStringFromSelector(@selector(filename))];
171 | self.totalBytesWritten = [[aDecoder decodeObjectOfClass:[NSNumber class] forKey:NSStringFromSelector(@selector(totalBytesWritten))] unsignedIntegerValue];
172 | self.totalBytesExpectedToWrite = [[aDecoder decodeObjectOfClass:[NSNumber class] forKey:NSStringFromSelector(@selector(totalBytesExpectedToWrite))] unsignedIntegerValue];
173 |
174 | }
175 | return self;
176 | }
177 |
178 |
179 | - (instancetype)initWithURLString:(NSString *)URLString
180 | downloadOperationCancelToken:(id)downloadOperationCancelToken
181 | downloaderProgressBlock:(MCDownloaderProgressBlock)downloaderProgressBlock
182 | downloaderCompletedBlock:(MCDownloaderCompletedBlock)downloaderCompletedBlock {
183 |
184 | if (self = [self init]) {
185 |
186 | self.url = URLString;
187 | self.totalBytesExpectedToWrite = 0;
188 | self.downloadOperationCancelToken = downloadOperationCancelToken;
189 | self.downloaderProgressBlock = downloaderProgressBlock;
190 | self.downloaderCompletedBlock = downloaderCompletedBlock;
191 | }
192 | return self;
193 | }
194 |
195 | - (void)setTotalBytesExpectedToWrite:(long long)totalBytesExpectedToWrite {
196 | _totalBytesExpectedToWrite = totalBytesExpectedToWrite;
197 | }
198 |
199 | - (void)setState:(MCDownloadState)state {
200 | _state = state;
201 | }
202 |
203 | - (void)setDownloadOperationCancelToken:(id)downloadOperationCancelToken {
204 | _downloadOperationCancelToken = downloadOperationCancelToken;
205 | }
206 |
207 | - (void)setDownloaderProgressBlock:(MCDownloaderProgressBlock)downloaderProgressBlock {
208 | _downloaderProgressBlock = downloaderProgressBlock;
209 | }
210 |
211 | - (void)setDownloaderCompletedBlock:(MCDownloaderCompletedBlock)downloaderCompletedBlock {
212 | _downloaderCompletedBlock = downloaderCompletedBlock;
213 | }
214 |
215 | - (void)setSpeed:(NSString *)speed {
216 | _speed = speed;
217 | }
218 |
219 |
220 |
221 | @end
222 |
--------------------------------------------------------------------------------
/MCDownloaderDemo/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
61 |
71 |
81 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
--------------------------------------------------------------------------------
/Source/MCDownloadOperation.m:
--------------------------------------------------------------------------------
1 | //
2 | // MCDownloadOperation.m
3 | // MCDownloadManager
4 | //
5 | // Created by M.C on 17/4/6. (QQ:714080794 Gmail:chaoma0609@gmail.com)
6 | // Copyright © 2017年 qikeyun. All rights reserved.
7 | //
8 | // Permission is hereby granted, free of charge, to any person obtaining a copy
9 | // of this software and associated documentation files (the "Software"), to deal
10 | // in the Software without restriction, including without limitation the rights
11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | // copies of the Software, and to permit persons to whom the Software is
13 | // furnished to do so, subject to the following conditions:
14 | //
15 | // The above copyright notice and this permission notice shall be included in
16 | // all copies or substantial portions of the Software.
17 | //
18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24 | // THE SOFTWARE.
25 | //
26 |
27 | #import "MCDownloadOperation.h"
28 |
29 |
30 | NS_ASSUME_NONNULL_BEGIN
31 |
32 | NSString *const MCDownloadStartNotification = @"MCDownloadStartNotification";
33 | NSString *const MCDownloadReceiveResponseNotification = @"MCDownloadReceiveResponseNotification";
34 | NSString *const MCDownloadStopNotification = @"MCDownloadStopNotification";
35 | NSString *const MCDownloadFinishNotification = @"MCDownloadFinishNotification";
36 |
37 | static NSString *const kProgressCallbackKey = @"progress";
38 | static NSString *const kCompletedCallbackKey = @"completed";
39 |
40 | typedef NSMutableDictionary MCCallbacksDictionary;
41 |
42 |
43 | @interface MCDownloadOperation ()
44 |
45 | @property (strong, nonatomic, nonnull) NSMutableArray *callbackBlocks;
46 |
47 | @property (assign, nonatomic, getter = isExecuting) BOOL executing;
48 | @property (assign, nonatomic, getter = isFinished) BOOL finished;
49 |
50 | // This is weak because it is injected by whoever manages this session. If this gets nil-ed out, we won't be able to run
51 | // the task associated with this operation
52 | @property (weak, nonatomic, nullable) NSURLSession *unownedSession;
53 | // This is set if we're using not using an injected NSURLSession. We're responsible of invalidating this one
54 | @property (strong, nonatomic, nullable) NSURLSession *ownedSession;
55 |
56 |
57 |
58 | @property (strong, nonatomic, readwrite, nullable) NSURLSessionTask *dataTask;
59 |
60 | @property (strong, nonatomic, nullable) dispatch_queue_t barrierQueue;
61 |
62 | @property (assign, nonatomic) UIBackgroundTaskIdentifier backgroundTaskId;
63 |
64 | @property (assign, nonatomic) long long totalBytesWritten;
65 | @property (assign, nonatomic) long long totalBytesExpectedToWrite;
66 |
67 | @property (strong, nonatomic) MCDownloadReceipt *receipt;
68 | @end
69 |
70 | @implementation MCDownloadOperation
71 | {
72 | BOOL responseFromCached;
73 | }
74 |
75 | @synthesize executing = _executing;
76 | @synthesize finished = _finished;
77 |
78 | - (MCDownloadReceipt *)receipt {
79 | if (_receipt == nil) {
80 | _receipt = [[MCDownloader sharedDownloader] downloadReceiptForURLString:self.request.URL.absoluteString];
81 | }
82 | return _receipt;
83 | }
84 | - (nonnull instancetype)init {
85 | return [self initWithRequest:nil inSession:nil];
86 | }
87 |
88 | - (nonnull instancetype)initWithRequest:(nullable NSURLRequest *)request inSession:(nullable NSURLSession *)session {
89 | if ((self = [super init])) {
90 | _request = [request copy];
91 | _callbackBlocks = [NSMutableArray new];
92 | _executing = NO;
93 | _finished = NO;
94 | _expectedSize = 0;
95 | _unownedSession = session;
96 | responseFromCached = YES; // Initially wrong until `- URLSession:dataTask:willCacheResponse:completionHandler: is called or not called
97 | _barrierQueue = dispatch_queue_create("com.machao.MCDownloaderOperationBarrierQueue", DISPATCH_QUEUE_CONCURRENT);
98 |
99 | [self.receipt setState:MCDownloadStateWillResume];
100 | }
101 | return self;
102 | }
103 |
104 | - (void)dealloc {
105 |
106 | }
107 |
108 | - (nullable id)addHandlersForProgress:(nullable MCDownloaderProgressBlock)progressBlock
109 | completed:(nullable MCDownloaderCompletedBlock)completedBlock {
110 | MCCallbacksDictionary *callbacks = [NSMutableDictionary new];
111 | if (progressBlock) callbacks[kProgressCallbackKey] = [progressBlock copy];
112 | if (completedBlock) callbacks[kCompletedCallbackKey] = [completedBlock copy];
113 | dispatch_barrier_async(self.barrierQueue, ^{
114 | [self.callbackBlocks addObject:callbacks];
115 | });
116 | return callbacks;
117 | }
118 |
119 | - (nullable NSArray *)callbacksForKey:(NSString *)key {
120 | __block NSMutableArray *callbacks = nil;
121 | dispatch_sync(self.barrierQueue, ^{
122 | // We need to remove [NSNull null] because there might not always be a progress block for each callback
123 | callbacks = [[self.callbackBlocks valueForKey:key] mutableCopy];
124 | [callbacks removeObjectIdenticalTo:[NSNull null]];
125 | });
126 | return [callbacks copy]; // strip mutability here
127 | }
128 |
129 | - (BOOL)cancel:(nullable id)token {
130 | __block BOOL shouldCancel = NO;
131 | dispatch_barrier_sync(self.barrierQueue, ^{
132 | [self.callbackBlocks removeAllObjects];
133 | if (self.callbackBlocks.count == 0) {
134 | shouldCancel = YES;
135 | }
136 | });
137 | if (shouldCancel) {
138 | [self cancel];
139 | }
140 | return shouldCancel;
141 | }
142 |
143 | - (void)start {
144 | @synchronized (self) {
145 | if (self.isCancelled) {
146 | self.finished = YES;
147 | [self reset];
148 | return;
149 | }
150 |
151 | #if TARGET_OS_IOS
152 | Class UIApplicationClass = NSClassFromString(@"UIApplication");
153 | BOOL hasApplication = UIApplicationClass && [UIApplicationClass respondsToSelector:@selector(sharedApplication)];
154 | if (hasApplication && [self shouldContinueWhenAppEntersBackground]) {
155 | __weak __typeof__ (self) wself = self;
156 | UIApplication * app = [UIApplicationClass performSelector:@selector(sharedApplication)];
157 | self.backgroundTaskId = [app beginBackgroundTaskWithExpirationHandler:^{
158 | __strong __typeof (wself) sself = wself;
159 |
160 | if (sself) {
161 | [sself cancel];
162 |
163 | [app endBackgroundTask:sself.backgroundTaskId];
164 | sself.backgroundTaskId = UIBackgroundTaskInvalid;
165 | }
166 | }];
167 | }
168 | #endif
169 | NSURLSession *session = self.unownedSession;
170 | if (!self.unownedSession) {
171 | NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
172 | sessionConfig.timeoutIntervalForRequest = 15;
173 |
174 | /**
175 | * Create the session for this task
176 | * We send nil as delegate queue so that the session creates a serial operation queue for performing all delegate
177 | * method calls and completion handler calls.
178 | */
179 | self.ownedSession = [NSURLSession sessionWithConfiguration:sessionConfig
180 | delegate:self
181 | delegateQueue:nil];
182 | session = self.ownedSession;
183 | }
184 |
185 | self.dataTask = [session dataTaskWithRequest:self.request];
186 | self.executing = YES;
187 | }
188 |
189 | [self.dataTask resume];
190 |
191 | if (self.dataTask) {
192 | for (MCDownloaderProgressBlock progressBlock in [self callbacksForKey:kProgressCallbackKey]) {
193 | progressBlock(0, NSURLResponseUnknownLength, 0, self.request.URL);
194 | }
195 | [self.receipt setState:MCDownloadStateDownloading];
196 | dispatch_async(dispatch_get_main_queue(), ^{
197 | [[NSNotificationCenter defaultCenter] postNotificationName:MCDownloadStartNotification object:self];
198 | });
199 | } else {
200 | [self callCompletionBlocksWithError:[NSError errorWithDomain:NSURLErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Connection can't be initialized"}]];
201 | }
202 |
203 | #if TARGET_OS_IOS
204 | Class UIApplicationClass = NSClassFromString(@"UIApplication");
205 | if(!UIApplicationClass || ![UIApplicationClass respondsToSelector:@selector(sharedApplication)]) {
206 | return;
207 | }
208 | if (self.backgroundTaskId != UIBackgroundTaskInvalid) {
209 | UIApplication * app = [UIApplication performSelector:@selector(sharedApplication)];
210 | [app endBackgroundTask:self.backgroundTaskId];
211 | self.backgroundTaskId = UIBackgroundTaskInvalid;
212 | }
213 | #endif
214 | }
215 |
216 | - (void)cancel {
217 | @synchronized (self) {
218 | [self cancelInternal];
219 | }
220 | }
221 |
222 | - (void)cancelInternal {
223 | if (self.isFinished) return;
224 | [super cancel];
225 |
226 | if (self.dataTask) {
227 | [self.dataTask cancel];
228 | [self.receipt setState:MCDownloadStateNone];
229 | dispatch_async(dispatch_get_main_queue(), ^{
230 | [[NSNotificationCenter defaultCenter] postNotificationName:MCDownloadStopNotification object:self];
231 | });
232 |
233 | // As we cancelled the connection, its callback won't be called and thus won't
234 | // maintain the isFinished and isExecuting flags.
235 | if (self.isExecuting) self.executing = NO;
236 | if (!self.isFinished) self.finished = YES;
237 | }
238 |
239 | [self reset];
240 | }
241 |
242 | - (void)done {
243 | self.finished = YES;
244 | self.executing = NO;
245 | [self reset];
246 | }
247 |
248 | - (void)reset {
249 | dispatch_barrier_async(self.barrierQueue, ^{
250 | [self.callbackBlocks removeAllObjects];
251 | });
252 | self.dataTask = nil;
253 | if (self.ownedSession) {
254 | [self.ownedSession invalidateAndCancel];
255 | self.ownedSession = nil;
256 | }
257 | }
258 |
259 | - (void)setFinished:(BOOL)finished {
260 | [self willChangeValueForKey:@"isFinished"];
261 | _finished = finished;
262 | [self didChangeValueForKey:@"isFinished"];
263 | }
264 |
265 | - (void)setExecuting:(BOOL)executing {
266 | [self willChangeValueForKey:@"isExecuting"];
267 | _executing = executing;
268 | [self didChangeValueForKey:@"isExecuting"];
269 | }
270 |
271 | - (BOOL)isConcurrent {
272 | return YES;
273 | }
274 |
275 | - (void)URLSession:(NSURLSession *)session
276 | dataTask:(NSURLSessionDataTask *)dataTask
277 | didReceiveResponse:(NSURLResponse *)response
278 | completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler {
279 |
280 | //'304 Not Modified' is an exceptional one
281 | if (![response respondsToSelector:@selector(statusCode)] || (((NSHTTPURLResponse *)response).statusCode < 400 && ((NSHTTPURLResponse *)response).statusCode != 304)) {
282 | NSInteger expected = response.expectedContentLength > 0 ? (NSInteger)response.expectedContentLength : 0;
283 |
284 | MCDownloadReceipt *receipt = [[MCDownloader sharedDownloader] downloadReceiptForURLString:self.request.URL.absoluteString];
285 | [receipt setTotalBytesExpectedToWrite:expected + receipt.totalBytesWritten];
286 | receipt.date = [NSDate date];
287 |
288 | self.expectedSize = expected;
289 | for (MCDownloaderProgressBlock progressBlock in [self callbacksForKey:kProgressCallbackKey]) {
290 | progressBlock(0, expected, 0,self.request.URL);
291 | }
292 |
293 | self.response = response;
294 | dispatch_async(dispatch_get_main_queue(), ^{
295 | [[NSNotificationCenter defaultCenter] postNotificationName:MCDownloadReceiveResponseNotification object:self];
296 | });
297 | }else if (![response respondsToSelector:@selector(statusCode)] || (((NSHTTPURLResponse *)response).statusCode == 416)) {
298 | [[NSNotificationCenter defaultCenter] postNotificationName:MCDownloadFinishNotification object:self];
299 |
300 | [self callCompletionBlocksWithFileURL:[NSURL fileURLWithPath:self.receipt.filePath] data:[NSData dataWithContentsOfFile:self.receipt.filePath] error:nil finished:YES];
301 | [self done];
302 | }
303 | else {
304 | NSUInteger code = ((NSHTTPURLResponse *)response).statusCode;
305 |
306 | //This is the case when server returns '304 Not Modified'. It means that remote image is not changed.
307 | //In case of 304 we need just cancel the operation and return cached image from the cache.
308 | if (code == 304) {
309 | [self cancelInternal];
310 | } else {
311 | [self.dataTask cancel];
312 | [self.receipt setState:MCDownloadStateNone];
313 | }
314 | dispatch_async(dispatch_get_main_queue(), ^{
315 | [[NSNotificationCenter defaultCenter] postNotificationName:MCDownloadStopNotification object:self];
316 | });
317 |
318 | [self callCompletionBlocksWithError:[NSError errorWithDomain:NSURLErrorDomain code:((NSHTTPURLResponse *)response).statusCode userInfo:nil]];
319 | [self.receipt setState:MCDownloadStateNone];
320 | [self done];
321 | }
322 |
323 | if (completionHandler) {
324 | completionHandler(NSURLSessionResponseAllow);
325 | }
326 | }
327 |
328 | - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
329 |
330 | __block NSError *error = nil;
331 | MCDownloadReceipt *receipt = [[MCDownloader sharedDownloader] downloadReceiptForURLString:self.request.URL.absoluteString];
332 |
333 | // Speed
334 | receipt.totalRead += data.length;
335 | NSDate *currentDate = [NSDate date];
336 | if ([currentDate timeIntervalSinceDate:receipt.date] >= 1) {
337 | double time = [currentDate timeIntervalSinceDate:receipt.date];
338 | long long speed = receipt.totalRead/time;
339 | receipt.speed = [self formatByteCount:speed];
340 | receipt.totalRead = 0.0;
341 | receipt.date = currentDate;
342 | }
343 |
344 | // Write Data
345 | NSInputStream *inputStream = [[NSInputStream alloc] initWithData:data];
346 | NSOutputStream *outputStream = [[NSOutputStream alloc] initWithURL:[NSURL fileURLWithPath:receipt.filePath] append:YES];
347 | [inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
348 | [outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
349 |
350 | [inputStream open];
351 | [outputStream open];
352 |
353 | while ([inputStream hasBytesAvailable] && [outputStream hasSpaceAvailable]) {
354 | uint8_t buffer[1024];
355 |
356 | NSInteger bytesRead = [inputStream read:buffer maxLength:1024];
357 | if (inputStream.streamError || bytesRead < 0) {
358 | error = inputStream.streamError;
359 | break;
360 | }
361 |
362 | NSInteger bytesWritten = [outputStream write:buffer maxLength:(NSUInteger)bytesRead];
363 | if (outputStream.streamError || bytesWritten < 0) {
364 | error = outputStream.streamError;
365 | break;
366 | }
367 |
368 | if (bytesRead == 0 && bytesWritten == 0) {
369 | break;
370 | }
371 | }
372 | [outputStream close];
373 | [inputStream close];
374 |
375 | receipt.progress.totalUnitCount = receipt.totalBytesExpectedToWrite;
376 | receipt.progress.completedUnitCount = receipt.totalBytesWritten;
377 |
378 | dispatch_main_async_safe(^{
379 | for (MCDownloaderProgressBlock progressBlock in [self callbacksForKey:kProgressCallbackKey]) {
380 | progressBlock(receipt.progress.completedUnitCount, receipt.progress.totalUnitCount, receipt.speed.integerValue, self.request.URL);
381 | }
382 | if (self.receipt.downloaderProgressBlock) {
383 | self.receipt.downloaderProgressBlock(receipt.progress.completedUnitCount, receipt.progress.totalUnitCount, receipt.speed.integerValue, self.request.URL);
384 | }
385 | });
386 | }
387 |
388 | - (void)URLSession:(NSURLSession *)session
389 | dataTask:(NSURLSessionDataTask *)dataTask
390 | willCacheResponse:(NSCachedURLResponse *)proposedResponse
391 | completionHandler:(void (^)(NSCachedURLResponse *cachedResponse))completionHandler {
392 |
393 | responseFromCached = NO; // If this method is called, it means the response wasn't read from cache
394 | NSCachedURLResponse *cachedResponse = proposedResponse;
395 |
396 | if (completionHandler) {
397 | completionHandler(cachedResponse);
398 | }
399 | }
400 |
401 | #pragma mark NSURLSessionTaskDelegate
402 |
403 | - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(nullable NSError *)error {
404 | @synchronized(self) {
405 | self.dataTask = nil;
406 | dispatch_async(dispatch_get_main_queue(), ^{
407 | [[NSNotificationCenter defaultCenter] postNotificationName:MCDownloadStopNotification object:self];
408 | if (!error) {
409 | [[NSNotificationCenter defaultCenter] postNotificationName:MCDownloadFinishNotification object:self];
410 | }
411 | });
412 | }
413 |
414 | if (error) {
415 | [self callCompletionBlocksWithError:error];
416 | } else {
417 | MCDownloadReceipt *receipt = self.receipt;
418 | [receipt setState:MCDownloadStateCompleted];
419 | if ([self callbacksForKey:kCompletedCallbackKey].count > 0) {
420 |
421 | [self callCompletionBlocksWithFileURL:[NSURL fileURLWithPath:receipt.filePath] data:[NSData dataWithContentsOfFile:receipt.filePath] error:nil finished:YES];
422 |
423 | }
424 | dispatch_main_async_safe(^{
425 | if (self.receipt.downloaderCompletedBlock) {
426 | self.receipt.downloaderCompletedBlock(receipt, nil, YES);
427 | }
428 | });
429 | }
430 | [self done];
431 | }
432 |
433 |
434 |
435 |
436 | - (BOOL)shouldContinueWhenAppEntersBackground {
437 | return YES;
438 | }
439 |
440 | - (void)callCompletionBlocksWithError:(nullable NSError *)error {
441 | [self callCompletionBlocksWithFileURL:nil data:nil error:error finished:YES];
442 | }
443 |
444 | - (void)callCompletionBlocksWithFileURL:(nullable NSURL *)fileURL
445 | data:(nullable NSData *)data
446 | error:(nullable NSError *)error
447 | finished:(BOOL)finished {
448 |
449 | if (error) {
450 | [self.receipt setState:MCDownloadStateFailed];
451 | }else {
452 | [self.receipt setState:MCDownloadStateCompleted];
453 | }
454 | NSArray *completionBlocks = [self callbacksForKey:kCompletedCallbackKey];
455 | dispatch_main_async_safe(^{
456 | for (MCDownloaderCompletedBlock completedBlock in completionBlocks) {
457 | completedBlock(self.receipt, error, finished);
458 | }
459 |
460 | if (self.receipt.downloaderCompletedBlock) {
461 | self.receipt.downloaderCompletedBlock(self.receipt, error, YES);
462 | }
463 | });
464 | }
465 |
466 | - (NSString*)formatByteCount:(long long)size
467 | {
468 | return [NSByteCountFormatter stringFromByteCount:size countStyle:NSByteCountFormatterCountStyleFile];
469 | }
470 | @end
471 |
472 |
473 | NS_ASSUME_NONNULL_END
474 |
--------------------------------------------------------------------------------
/Source/MCDownloader.m:
--------------------------------------------------------------------------------
1 | //
2 | // MCDownloader.m
3 | // MCDownloadManager
4 | //
5 | // Created by M.C on 17/4/6. (QQ:714080794 Gmail:chaoma0609@gmail.com)
6 | // Copyright © 2017年 qikeyun. All rights reserved.
7 | //
8 | // Permission is hereby granted, free of charge, to any person obtaining a copy
9 | // of this software and associated documentation files (the "Software"), to deal
10 | // in the Software without restriction, including without limitation the rights
11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | // copies of the Software, and to permit persons to whom the Software is
13 | // furnished to do so, subject to the following conditions:
14 | //
15 | // The above copyright notice and this permission notice shall be included in
16 | // all copies or substantial portions of the Software.
17 | //
18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24 | // THE SOFTWARE.
25 | //
26 |
27 | #import "MCDownloader.h"
28 | #import "MCDownloadOperation.h"
29 | #import "MCDownloadReceipt.h"
30 |
31 | NSString * const MCDownloadCacheFolderName = @"MCDownloadCache";
32 |
33 | static NSString *cacheFolderPath;
34 |
35 | NSString * cacheFolder() {
36 | if (!cacheFolderPath) {
37 | NSString *cacheDir = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES).firstObject;
38 | cacheFolderPath = [cacheDir stringByAppendingPathComponent:MCDownloadCacheFolderName];
39 | NSFileManager *filemgr = [NSFileManager defaultManager];
40 | NSError *error = nil;
41 | if(![filemgr createDirectoryAtPath:cacheFolderPath withIntermediateDirectories:YES attributes:nil error:&error]) {
42 | NSLog(@"Failed to create cache directory at %@", cacheFolderPath);
43 | cacheFolderPath = nil;
44 | }
45 | }
46 | return cacheFolderPath;
47 | }
48 |
49 | static void clearCacheFolder() {
50 | cacheFolderPath = nil;
51 | }
52 |
53 | static NSString * LocalReceiptsPath() {
54 | return [cacheFolder() stringByAppendingPathComponent:@"receipts.data"];
55 | }
56 |
57 | @interface MCDownloader()
58 |
59 | @property (strong, nonatomic, nonnull) NSOperationQueue *downloadQueue;
60 | @property (weak, nonatomic, nullable) NSOperation *lastAddedOperation;
61 | @property (assign, nonatomic, nullable) Class operationClass;
62 | @property (strong, nonatomic, nonnull) NSMutableDictionary *URLOperations;
63 | @property (strong, nonatomic, nullable) MCHTTPHeadersMutableDictionary *HTTPHeaders;
64 | // This queue is used to serialize the handling of the network responses of all the download operation in a single queue
65 | @property (strong, nonatomic, nullable) dispatch_queue_t barrierQueue;
66 |
67 | // The session in which data tasks will run
68 | @property (strong, nonatomic) NSURLSession *session;
69 |
70 | @property (nonatomic, strong) NSMutableDictionary *allDownloadReceipts;
71 | @property (assign, nonatomic) UIBackgroundTaskIdentifier backgroundTaskId;
72 | @end
73 | @implementation MCDownloader
74 |
75 | - (NSMutableDictionary *)allDownloadReceipts {
76 | if (_allDownloadReceipts == nil) {
77 | NSDictionary *receipts = [NSKeyedUnarchiver unarchiveObjectWithFile:LocalReceiptsPath()];
78 | _allDownloadReceipts = receipts != nil ? receipts.mutableCopy : [NSMutableDictionary dictionary];
79 | }
80 | return _allDownloadReceipts;
81 | }
82 |
83 | + (nonnull instancetype)sharedDownloader {
84 | static dispatch_once_t once;
85 | static id instance;
86 | dispatch_once(&once, ^{
87 | instance = [self new];
88 | });
89 | return instance;
90 | }
91 |
92 | - (nonnull instancetype)init {
93 | return [self initWithSessionConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
94 | }
95 |
96 | - (nonnull instancetype)initWithSessionConfiguration:(nullable NSURLSessionConfiguration *)sessionConfiguration {
97 | if ((self = [super init])) {
98 | _operationClass = [MCDownloadOperation class];
99 | _downloadPrioritizaton = MCDownloadPrioritizationFIFO;
100 | _downloadQueue = [NSOperationQueue new];
101 | _downloadQueue.maxConcurrentOperationCount = 3;
102 | _downloadQueue.name = @"com.machao.MCDownloader";
103 | _URLOperations = [NSMutableDictionary new];
104 | _barrierQueue = dispatch_queue_create("com.machao.MCDownloaderBarrierQueue", DISPATCH_QUEUE_CONCURRENT);
105 | _downloadTimeout = 15.0;
106 |
107 | sessionConfiguration.timeoutIntervalForRequest = _downloadTimeout;
108 | sessionConfiguration.HTTPMaximumConnectionsPerHost = 10;
109 | /**
110 | * Create the session for this task
111 | * We send nil as delegate queue so that the session creates a serial operation queue for performing all delegate
112 | * method calls and completion handler calls.
113 | */
114 | self.session = [NSURLSession sessionWithConfiguration:sessionConfiguration
115 | delegate:self
116 | delegateQueue:nil];
117 |
118 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationWillTerminate:) name:UIApplicationWillTerminateNotification object:nil];
119 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidReceiveMemoryWarning:) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
120 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationWillResignActive:) name:UIApplicationWillResignActiveNotification object:nil];
121 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidBecomeActive:) name:UIApplicationDidBecomeActiveNotification object:nil];
122 | }
123 | return self;
124 | }
125 |
126 | #pragma mark - NSNotification
127 | - (void)applicationWillTerminate:(NSNotification *)not {
128 | [self setAllStateToNone];
129 | [self saveAllDownloadReceipts];
130 | }
131 |
132 | - (void)applicationDidReceiveMemoryWarning:(NSNotification *)not {
133 | [self saveAllDownloadReceipts];
134 | }
135 |
136 | - (void)applicationWillResignActive:(NSNotification *)not {
137 | [self saveAllDownloadReceipts];
138 | /// 捕获到失去激活状态后
139 | Class UIApplicationClass = NSClassFromString(@"UIApplication");
140 | BOOL hasApplication = UIApplicationClass && [UIApplicationClass respondsToSelector:@selector(sharedApplication)];
141 | if (hasApplication ) {
142 | __weak __typeof__ (self) wself = self;
143 | UIApplication * app = [UIApplicationClass performSelector:@selector(sharedApplication)];
144 | self.backgroundTaskId = [app beginBackgroundTaskWithExpirationHandler:^{
145 | __strong __typeof (wself) sself = wself;
146 |
147 | if (sself) {
148 | [sself setAllStateToNone];
149 | [sself saveAllDownloadReceipts];
150 |
151 | [app endBackgroundTask:sself.backgroundTaskId];
152 | sself.backgroundTaskId = UIBackgroundTaskInvalid;
153 | }
154 | }];
155 | }
156 | }
157 |
158 | - (void)applicationDidBecomeActive:(NSNotification *)not {
159 |
160 | Class UIApplicationClass = NSClassFromString(@"UIApplication");
161 | if(!UIApplicationClass || ![UIApplicationClass respondsToSelector:@selector(sharedApplication)]) {
162 | return;
163 | }
164 | if (self.backgroundTaskId != UIBackgroundTaskInvalid) {
165 | UIApplication * app = [UIApplication performSelector:@selector(sharedApplication)];
166 | [app endBackgroundTask:self.backgroundTaskId];
167 | self.backgroundTaskId = UIBackgroundTaskInvalid;
168 | }
169 |
170 | NSString *cacheDir = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES).firstObject;
171 | NSString *cachePath = [cacheDir stringByAppendingPathComponent:MCDownloadCacheFolderName];
172 | NSString *existedCacheFolderPath = cacheFolder();
173 | if (existedCacheFolderPath && ![existedCacheFolderPath isEqualToString:cachePath]) {
174 | clearCacheFolder();
175 | [self.allDownloadReceipts enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) {
176 | if ([obj isKindOfClass:[MCDownloadReceipt class]]) {
177 | MCDownloadReceipt *receipt = obj;
178 | [receipt setValue:nil forKey:@"filePath"];
179 | }
180 | }];
181 | }
182 | }
183 |
184 | - (void)setAllStateToNone {
185 | [self.allDownloadReceipts enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) {
186 | if ([obj isKindOfClass:[MCDownloadReceipt class]]) {
187 | MCDownloadReceipt *receipt = obj;
188 | if (receipt.state != MCDownloadStateCompleted) {
189 | [receipt setState:MCDownloadStateNone];
190 | }
191 | }
192 | }];
193 | }
194 |
195 | - (void)saveAllDownloadReceipts {
196 | [NSKeyedArchiver archiveRootObject:self.allDownloadReceipts toFile:LocalReceiptsPath()];
197 | }
198 |
199 | - (void)dealloc {
200 | [self.session invalidateAndCancel];
201 | self.session = nil;
202 |
203 | [self.downloadQueue cancelAllOperations];
204 |
205 | }
206 |
207 | - (void)setValue:(nullable NSString *)value forHTTPHeaderField:(nullable NSString *)field {
208 | if (value) {
209 | self.HTTPHeaders[field] = value;
210 | }
211 | else {
212 | [self.HTTPHeaders removeObjectForKey:field];
213 | }
214 | }
215 |
216 | - (nullable NSString *)valueForHTTPHeaderField:(nullable NSString *)field {
217 | return self.HTTPHeaders[field];
218 | }
219 |
220 | - (void)setMaxConcurrentDownloads:(NSInteger)maxConcurrentDownloads {
221 | _downloadQueue.maxConcurrentOperationCount = maxConcurrentDownloads;
222 | }
223 |
224 | - (NSUInteger)currentDownloadCount {
225 | return _downloadQueue.operationCount;
226 | }
227 |
228 | - (NSInteger)maxConcurrentDownloads {
229 | return _downloadQueue.maxConcurrentOperationCount;
230 | }
231 |
232 | - (void)setOperationClass:(nullable Class)operationClass {
233 | if (operationClass && [operationClass isSubclassOfClass:[NSOperation class]] && [operationClass conformsToProtocol:@protocol(MCDownloaderOperationInterface)]) {
234 | _operationClass = operationClass;
235 | } else {
236 | _operationClass = [MCDownloadOperation class];
237 | }
238 | }
239 |
240 | - (nullable MCDownloadReceipt *)downloadDataWithURL:(nullable NSURL *)url
241 | progress:(nullable MCDownloaderProgressBlock)progressBlock
242 | completed:(nullable MCDownloaderCompletedBlock)completedBlock {
243 | __weak MCDownloader *wself = self;
244 |
245 | MCDownloadReceipt *receipt = [self downloadReceiptForURLString:url.absoluteString];
246 | if (receipt.state == MCDownloadStateCompleted) {
247 | dispatch_main_async_safe(^{
248 |
249 | [[NSNotificationCenter defaultCenter] postNotificationName:MCDownloadFinishNotification object:self];
250 | if (completedBlock) {
251 | completedBlock(receipt ,nil ,YES);
252 | }
253 | if (receipt.downloaderCompletedBlock) {
254 | receipt.downloaderCompletedBlock(receipt, nil, YES);
255 | }
256 |
257 | });
258 | return receipt;
259 | }
260 |
261 | return [self addProgressCallback:progressBlock completedBlock:completedBlock forURL:url createCallback:^MCDownloadOperation *{
262 | __strong __typeof (wself) sself = wself;
263 |
264 | NSTimeInterval timeoutInterval = sself.downloadTimeout;
265 | if (timeoutInterval == 0.0) {
266 | timeoutInterval = 15.0;
267 | }
268 |
269 | NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url];
270 | MCDownloadReceipt *receipt = [sself downloadReceiptForURLString:url.absoluteString];
271 | if (receipt.totalBytesWritten > 0) {
272 | NSString *range = [NSString stringWithFormat:@"bytes=%zd-", receipt.totalBytesWritten];
273 | [request setValue:range forHTTPHeaderField:@"Range"];
274 | }
275 |
276 | request.HTTPShouldUsePipelining = YES;
277 |
278 | if (sself.headersFilter) {
279 | request.allHTTPHeaderFields = sself.headersFilter(url, [sself.HTTPHeaders copy]);
280 | }
281 | else {
282 | request.allHTTPHeaderFields = sself.HTTPHeaders;
283 | }
284 |
285 |
286 | MCDownloadOperation *operation = [[sself.operationClass alloc] initWithRequest:request inSession:sself.session];
287 |
288 | [sself.downloadQueue addOperation:operation];
289 |
290 |
291 | if (sself.downloadPrioritizaton == MCDownloadPrioritizationLIFO) {
292 | // Emulate LIFO execution order by systematically adding new operations as last operation's dependency
293 | [sself.lastAddedOperation addDependency:operation];
294 | sself.lastAddedOperation = operation;
295 | }
296 |
297 | return operation;
298 | }];
299 | }
300 |
301 | - (MCDownloadReceipt *)downloadReceiptForURLString:(NSString *)URLString {
302 | if (URLString == nil) {
303 | return nil;
304 | }
305 | if (self.allDownloadReceipts[URLString]) {
306 | return self.allDownloadReceipts[URLString];
307 | }else {
308 | MCDownloadReceipt *receipt = [[MCDownloadReceipt alloc] initWithURLString:URLString downloadOperationCancelToken:nil downloaderProgressBlock:nil downloaderCompletedBlock:nil];
309 | self.allDownloadReceipts[URLString] = receipt;
310 | return receipt;
311 | }
312 |
313 | return nil;
314 | }
315 |
316 |
317 |
318 | - (nullable MCDownloadReceipt *)addProgressCallback:(MCDownloaderProgressBlock)progressBlock
319 | completedBlock:(MCDownloaderCompletedBlock)completedBlock
320 | forURL:(nullable NSURL *)url
321 | createCallback:(MCDownloadOperation *(^)())createCallback {
322 | // The URL will be used as the key to the callbacks dictionary so it cannot be nil. If it is nil immediately call the completed block with no image or data.
323 | if (url == nil) {
324 | if (completedBlock != nil) {
325 | completedBlock(nil, nil, NO);
326 | }
327 | return nil;
328 | }
329 |
330 | __block MCDownloadReceipt *token = nil;
331 |
332 | dispatch_barrier_sync(self.barrierQueue, ^{
333 | MCDownloadOperation *operation = self.URLOperations[url];
334 | if (!operation) {
335 | operation = createCallback();
336 | self.URLOperations[url] = operation;
337 |
338 | __weak MCDownloadOperation *woperation = operation;
339 | operation.completionBlock = ^{
340 | MCDownloadOperation *soperation = woperation;
341 | if (!soperation) return;
342 | if (self.URLOperations[url] == soperation) {
343 | [self.URLOperations removeObjectForKey:url];
344 | };
345 | };
346 | }
347 | id downloadOperationCancelToken = [operation addHandlersForProgress:progressBlock completed:completedBlock];
348 |
349 | if (!self.allDownloadReceipts[url.absoluteString]) {
350 | token = [[MCDownloadReceipt alloc] initWithURLString:url.absoluteString
351 | downloadOperationCancelToken:downloadOperationCancelToken
352 | downloaderProgressBlock:nil
353 | downloaderCompletedBlock:nil];
354 | self.allDownloadReceipts[url.absoluteString] = token;
355 | }else {
356 | token = self.allDownloadReceipts[url.absoluteString];
357 |
358 | if (!token.downloadOperationCancelToken) {
359 | [token setDownloadOperationCancelToken:downloadOperationCancelToken];
360 | }
361 | }
362 |
363 | });
364 |
365 | return token;
366 | }
367 |
368 | #pragma mark - Control Methods
369 |
370 | - (void)cancel:(nullable MCDownloadReceipt *)token completed:(nullable void (^)())completed {
371 | dispatch_barrier_async(self.barrierQueue, ^{
372 | MCDownloadOperation *operation = self.URLOperations[[NSURL URLWithString:token.url]];
373 | BOOL canceled = [operation cancel:token.downloadOperationCancelToken];
374 | if (canceled) {
375 | [self.URLOperations removeObjectForKey:[NSURL URLWithString:token.url]];
376 | [token setState:MCDownloadStateNone];
377 | // [self.allDownloadReceipts removeObjectForKey:token.url];
378 |
379 | }
380 | dispatch_main_async_safe(^{
381 | if (completed) {
382 | completed();
383 | }
384 | });
385 | });
386 | }
387 |
388 | - (void)remove:(MCDownloadReceipt *)token completed:(nullable void (^)())completed{
389 | [token setState:MCDownloadStateNone];
390 | [self cancel:token completed:^{
391 | NSFileManager *fileManager = [NSFileManager defaultManager];
392 |
393 | [fileManager removeItemAtPath:token.filePath error:nil];
394 |
395 | dispatch_main_async_safe(^{
396 | if (completed) {
397 | completed();
398 | }
399 | });
400 | }];
401 |
402 | }
403 |
404 | - (void)setSuspended:(BOOL)suspended {
405 | (self.downloadQueue).suspended = suspended;
406 | }
407 |
408 | - (void)cancelAllDownloads {
409 | [self.downloadQueue cancelAllOperations];
410 | [self setAllStateToNone];
411 | [self saveAllDownloadReceipts];
412 | }
413 |
414 | - (void)removeAndClearAll {
415 | [self cancelAllDownloads];
416 | NSFileManager *fileManager = [NSFileManager defaultManager];
417 | [fileManager removeItemAtPath:cacheFolder() error:nil];
418 | clearCacheFolder();
419 | }
420 |
421 | #pragma mark Helper methods
422 |
423 | - (MCDownloadOperation *)operationWithTask:(NSURLSessionTask *)task {
424 | MCDownloadOperation *returnOperation = nil;
425 | for (MCDownloadOperation *operation in self.downloadQueue.operations) {
426 | if (operation.dataTask.taskIdentifier == task.taskIdentifier) {
427 | returnOperation = operation;
428 | break;
429 | }
430 | }
431 | return returnOperation;
432 | }
433 |
434 | #pragma mark NSURLSessionDataDelegate
435 |
436 | - (void)URLSession:(NSURLSession *)session
437 | dataTask:(NSURLSessionDataTask *)dataTask
438 | didReceiveResponse:(NSURLResponse *)response
439 | completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler {
440 |
441 | // Identify the operation that runs this task and pass it the delegate method
442 | MCDownloadOperation *dataOperation = [self operationWithTask:dataTask];
443 |
444 | [dataOperation URLSession:session dataTask:dataTask didReceiveResponse:response completionHandler:completionHandler];
445 | }
446 |
447 | - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
448 |
449 | // Identify the operation that runs this task and pass it the delegate method
450 | MCDownloadOperation *dataOperation = [self operationWithTask:dataTask];
451 |
452 | [dataOperation URLSession:session dataTask:dataTask didReceiveData:data];
453 | }
454 |
455 | - (void)URLSession:(NSURLSession *)session
456 | dataTask:(NSURLSessionDataTask *)dataTask
457 | willCacheResponse:(NSCachedURLResponse *)proposedResponse
458 | completionHandler:(void (^)(NSCachedURLResponse *cachedResponse))completionHandler {
459 |
460 | // Identify the operation that runs this task and pass it the delegate method
461 | MCDownloadOperation *dataOperation = [self operationWithTask:dataTask];
462 |
463 | [dataOperation URLSession:session dataTask:dataTask willCacheResponse:proposedResponse completionHandler:completionHandler];
464 | }
465 |
466 | #pragma mark NSURLSessionTaskDelegate
467 |
468 | - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
469 | // Identify the operation that runs this task and pass it the delegate method
470 | MCDownloadOperation *dataOperation = [self operationWithTask:task];
471 |
472 | [dataOperation URLSession:session task:task didCompleteWithError:error];
473 | }
474 |
475 |
476 | @end
477 |
--------------------------------------------------------------------------------
/MCDownloaderDemo.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 46;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | E71DCA251E9B1D2B00A058D2 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = E71DCA241E9B1D2B00A058D2 /* main.m */; };
11 | E71DCA281E9B1D2B00A058D2 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = E71DCA271E9B1D2B00A058D2 /* AppDelegate.m */; };
12 | E71DCA2B1E9B1D2B00A058D2 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = E71DCA2A1E9B1D2B00A058D2 /* ViewController.m */; };
13 | E71DCA2E1E9B1D2B00A058D2 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = E71DCA2C1E9B1D2B00A058D2 /* Main.storyboard */; };
14 | E71DCA301E9B1D2B00A058D2 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E71DCA2F1E9B1D2B00A058D2 /* Assets.xcassets */; };
15 | E71DCA331E9B1D2B00A058D2 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = E71DCA311E9B1D2B00A058D2 /* LaunchScreen.storyboard */; };
16 | E71DCA3E1E9B1D2C00A058D2 /* MCDownloaderDemoTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E71DCA3D1E9B1D2C00A058D2 /* MCDownloaderDemoTests.m */; };
17 | E71DCA491E9B1D2C00A058D2 /* MCDownloaderDemoUITests.m in Sources */ = {isa = PBXBuildFile; fileRef = E71DCA481E9B1D2C00A058D2 /* MCDownloaderDemoUITests.m */; };
18 | E71DCA5D1E9B1D5F00A058D2 /* MCDownloader.m in Sources */ = {isa = PBXBuildFile; fileRef = E71DCA581E9B1D5F00A058D2 /* MCDownloader.m */; };
19 | E71DCA5E1E9B1D5F00A058D2 /* MCDownloadOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = E71DCA5A1E9B1D5F00A058D2 /* MCDownloadOperation.m */; };
20 | E71DCA5F1E9B1D5F00A058D2 /* MCDownloadReceipt.m in Sources */ = {isa = PBXBuildFile; fileRef = E71DCA5C1E9B1D5F00A058D2 /* MCDownloadReceipt.m */; };
21 | E71DCA641E9B1DD000A058D2 /* QKYDelayButton.m in Sources */ = {isa = PBXBuildFile; fileRef = E71DCA611E9B1DD000A058D2 /* QKYDelayButton.m */; };
22 | E71DCA651E9B1DD000A058D2 /* TableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = E71DCA631E9B1DD000A058D2 /* TableViewCell.m */; };
23 | /* End PBXBuildFile section */
24 |
25 | /* Begin PBXContainerItemProxy section */
26 | E71DCA3A1E9B1D2C00A058D2 /* PBXContainerItemProxy */ = {
27 | isa = PBXContainerItemProxy;
28 | containerPortal = E71DCA181E9B1D2B00A058D2 /* Project object */;
29 | proxyType = 1;
30 | remoteGlobalIDString = E71DCA1F1E9B1D2B00A058D2;
31 | remoteInfo = MCDownloaderDemo;
32 | };
33 | E71DCA451E9B1D2C00A058D2 /* PBXContainerItemProxy */ = {
34 | isa = PBXContainerItemProxy;
35 | containerPortal = E71DCA181E9B1D2B00A058D2 /* Project object */;
36 | proxyType = 1;
37 | remoteGlobalIDString = E71DCA1F1E9B1D2B00A058D2;
38 | remoteInfo = MCDownloaderDemo;
39 | };
40 | /* End PBXContainerItemProxy section */
41 |
42 | /* Begin PBXFileReference section */
43 | E71DCA201E9B1D2B00A058D2 /* MCDownloaderDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MCDownloaderDemo.app; sourceTree = BUILT_PRODUCTS_DIR; };
44 | E71DCA241E9B1D2B00A058D2 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; };
45 | E71DCA261E9B1D2B00A058D2 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; };
46 | E71DCA271E9B1D2B00A058D2 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; };
47 | E71DCA291E9B1D2B00A058D2 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; };
48 | E71DCA2A1E9B1D2B00A058D2 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; };
49 | E71DCA2D1E9B1D2B00A058D2 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
50 | E71DCA2F1E9B1D2B00A058D2 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
51 | E71DCA321E9B1D2B00A058D2 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
52 | E71DCA341E9B1D2B00A058D2 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
53 | E71DCA391E9B1D2C00A058D2 /* MCDownloaderDemoTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MCDownloaderDemoTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
54 | E71DCA3D1E9B1D2C00A058D2 /* MCDownloaderDemoTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MCDownloaderDemoTests.m; sourceTree = ""; };
55 | E71DCA3F1E9B1D2C00A058D2 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
56 | E71DCA441E9B1D2C00A058D2 /* MCDownloaderDemoUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MCDownloaderDemoUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
57 | E71DCA481E9B1D2C00A058D2 /* MCDownloaderDemoUITests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MCDownloaderDemoUITests.m; sourceTree = ""; };
58 | E71DCA4A1E9B1D2C00A058D2 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
59 | E71DCA571E9B1D5F00A058D2 /* MCDownloader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MCDownloader.h; sourceTree = ""; };
60 | E71DCA581E9B1D5F00A058D2 /* MCDownloader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MCDownloader.m; sourceTree = ""; };
61 | E71DCA591E9B1D5F00A058D2 /* MCDownloadOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MCDownloadOperation.h; sourceTree = ""; };
62 | E71DCA5A1E9B1D5F00A058D2 /* MCDownloadOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MCDownloadOperation.m; sourceTree = ""; };
63 | E71DCA5B1E9B1D5F00A058D2 /* MCDownloadReceipt.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MCDownloadReceipt.h; sourceTree = ""; };
64 | E71DCA5C1E9B1D5F00A058D2 /* MCDownloadReceipt.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MCDownloadReceipt.m; sourceTree = ""; };
65 | E71DCA601E9B1DD000A058D2 /* QKYDelayButton.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = QKYDelayButton.h; sourceTree = ""; };
66 | E71DCA611E9B1DD000A058D2 /* QKYDelayButton.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = QKYDelayButton.m; sourceTree = ""; };
67 | E71DCA621E9B1DD000A058D2 /* TableViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TableViewCell.h; sourceTree = ""; };
68 | E71DCA631E9B1DD000A058D2 /* TableViewCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TableViewCell.m; sourceTree = ""; };
69 | /* End PBXFileReference section */
70 |
71 | /* Begin PBXFrameworksBuildPhase section */
72 | E71DCA1D1E9B1D2B00A058D2 /* Frameworks */ = {
73 | isa = PBXFrameworksBuildPhase;
74 | buildActionMask = 2147483647;
75 | files = (
76 | );
77 | runOnlyForDeploymentPostprocessing = 0;
78 | };
79 | E71DCA361E9B1D2C00A058D2 /* Frameworks */ = {
80 | isa = PBXFrameworksBuildPhase;
81 | buildActionMask = 2147483647;
82 | files = (
83 | );
84 | runOnlyForDeploymentPostprocessing = 0;
85 | };
86 | E71DCA411E9B1D2C00A058D2 /* Frameworks */ = {
87 | isa = PBXFrameworksBuildPhase;
88 | buildActionMask = 2147483647;
89 | files = (
90 | );
91 | runOnlyForDeploymentPostprocessing = 0;
92 | };
93 | /* End PBXFrameworksBuildPhase section */
94 |
95 | /* Begin PBXGroup section */
96 | E71DCA171E9B1D2B00A058D2 = {
97 | isa = PBXGroup;
98 | children = (
99 | E71DCA561E9B1D5F00A058D2 /* Source */,
100 | E71DCA221E9B1D2B00A058D2 /* MCDownloaderDemo */,
101 | E71DCA3C1E9B1D2C00A058D2 /* MCDownloaderDemoTests */,
102 | E71DCA471E9B1D2C00A058D2 /* MCDownloaderDemoUITests */,
103 | E71DCA211E9B1D2B00A058D2 /* Products */,
104 | );
105 | sourceTree = "";
106 | };
107 | E71DCA211E9B1D2B00A058D2 /* Products */ = {
108 | isa = PBXGroup;
109 | children = (
110 | E71DCA201E9B1D2B00A058D2 /* MCDownloaderDemo.app */,
111 | E71DCA391E9B1D2C00A058D2 /* MCDownloaderDemoTests.xctest */,
112 | E71DCA441E9B1D2C00A058D2 /* MCDownloaderDemoUITests.xctest */,
113 | );
114 | name = Products;
115 | sourceTree = "";
116 | };
117 | E71DCA221E9B1D2B00A058D2 /* MCDownloaderDemo */ = {
118 | isa = PBXGroup;
119 | children = (
120 | E71DCA601E9B1DD000A058D2 /* QKYDelayButton.h */,
121 | E71DCA611E9B1DD000A058D2 /* QKYDelayButton.m */,
122 | E71DCA621E9B1DD000A058D2 /* TableViewCell.h */,
123 | E71DCA631E9B1DD000A058D2 /* TableViewCell.m */,
124 | E71DCA261E9B1D2B00A058D2 /* AppDelegate.h */,
125 | E71DCA271E9B1D2B00A058D2 /* AppDelegate.m */,
126 | E71DCA291E9B1D2B00A058D2 /* ViewController.h */,
127 | E71DCA2A1E9B1D2B00A058D2 /* ViewController.m */,
128 | E71DCA2C1E9B1D2B00A058D2 /* Main.storyboard */,
129 | E71DCA2F1E9B1D2B00A058D2 /* Assets.xcassets */,
130 | E71DCA311E9B1D2B00A058D2 /* LaunchScreen.storyboard */,
131 | E71DCA341E9B1D2B00A058D2 /* Info.plist */,
132 | E71DCA231E9B1D2B00A058D2 /* Supporting Files */,
133 | );
134 | path = MCDownloaderDemo;
135 | sourceTree = "";
136 | };
137 | E71DCA231E9B1D2B00A058D2 /* Supporting Files */ = {
138 | isa = PBXGroup;
139 | children = (
140 | E71DCA241E9B1D2B00A058D2 /* main.m */,
141 | );
142 | name = "Supporting Files";
143 | sourceTree = "";
144 | };
145 | E71DCA3C1E9B1D2C00A058D2 /* MCDownloaderDemoTests */ = {
146 | isa = PBXGroup;
147 | children = (
148 | E71DCA3D1E9B1D2C00A058D2 /* MCDownloaderDemoTests.m */,
149 | E71DCA3F1E9B1D2C00A058D2 /* Info.plist */,
150 | );
151 | path = MCDownloaderDemoTests;
152 | sourceTree = "";
153 | };
154 | E71DCA471E9B1D2C00A058D2 /* MCDownloaderDemoUITests */ = {
155 | isa = PBXGroup;
156 | children = (
157 | E71DCA481E9B1D2C00A058D2 /* MCDownloaderDemoUITests.m */,
158 | E71DCA4A1E9B1D2C00A058D2 /* Info.plist */,
159 | );
160 | path = MCDownloaderDemoUITests;
161 | sourceTree = "";
162 | };
163 | E71DCA561E9B1D5F00A058D2 /* Source */ = {
164 | isa = PBXGroup;
165 | children = (
166 | E71DCA571E9B1D5F00A058D2 /* MCDownloader.h */,
167 | E71DCA581E9B1D5F00A058D2 /* MCDownloader.m */,
168 | E71DCA591E9B1D5F00A058D2 /* MCDownloadOperation.h */,
169 | E71DCA5A1E9B1D5F00A058D2 /* MCDownloadOperation.m */,
170 | E71DCA5B1E9B1D5F00A058D2 /* MCDownloadReceipt.h */,
171 | E71DCA5C1E9B1D5F00A058D2 /* MCDownloadReceipt.m */,
172 | );
173 | path = Source;
174 | sourceTree = "";
175 | };
176 | /* End PBXGroup section */
177 |
178 | /* Begin PBXNativeTarget section */
179 | E71DCA1F1E9B1D2B00A058D2 /* MCDownloaderDemo */ = {
180 | isa = PBXNativeTarget;
181 | buildConfigurationList = E71DCA4D1E9B1D2C00A058D2 /* Build configuration list for PBXNativeTarget "MCDownloaderDemo" */;
182 | buildPhases = (
183 | E71DCA1C1E9B1D2B00A058D2 /* Sources */,
184 | E71DCA1D1E9B1D2B00A058D2 /* Frameworks */,
185 | E71DCA1E1E9B1D2B00A058D2 /* Resources */,
186 | );
187 | buildRules = (
188 | );
189 | dependencies = (
190 | );
191 | name = MCDownloaderDemo;
192 | productName = MCDownloaderDemo;
193 | productReference = E71DCA201E9B1D2B00A058D2 /* MCDownloaderDemo.app */;
194 | productType = "com.apple.product-type.application";
195 | };
196 | E71DCA381E9B1D2C00A058D2 /* MCDownloaderDemoTests */ = {
197 | isa = PBXNativeTarget;
198 | buildConfigurationList = E71DCA501E9B1D2C00A058D2 /* Build configuration list for PBXNativeTarget "MCDownloaderDemoTests" */;
199 | buildPhases = (
200 | E71DCA351E9B1D2C00A058D2 /* Sources */,
201 | E71DCA361E9B1D2C00A058D2 /* Frameworks */,
202 | E71DCA371E9B1D2C00A058D2 /* Resources */,
203 | );
204 | buildRules = (
205 | );
206 | dependencies = (
207 | E71DCA3B1E9B1D2C00A058D2 /* PBXTargetDependency */,
208 | );
209 | name = MCDownloaderDemoTests;
210 | productName = MCDownloaderDemoTests;
211 | productReference = E71DCA391E9B1D2C00A058D2 /* MCDownloaderDemoTests.xctest */;
212 | productType = "com.apple.product-type.bundle.unit-test";
213 | };
214 | E71DCA431E9B1D2C00A058D2 /* MCDownloaderDemoUITests */ = {
215 | isa = PBXNativeTarget;
216 | buildConfigurationList = E71DCA531E9B1D2C00A058D2 /* Build configuration list for PBXNativeTarget "MCDownloaderDemoUITests" */;
217 | buildPhases = (
218 | E71DCA401E9B1D2C00A058D2 /* Sources */,
219 | E71DCA411E9B1D2C00A058D2 /* Frameworks */,
220 | E71DCA421E9B1D2C00A058D2 /* Resources */,
221 | );
222 | buildRules = (
223 | );
224 | dependencies = (
225 | E71DCA461E9B1D2C00A058D2 /* PBXTargetDependency */,
226 | );
227 | name = MCDownloaderDemoUITests;
228 | productName = MCDownloaderDemoUITests;
229 | productReference = E71DCA441E9B1D2C00A058D2 /* MCDownloaderDemoUITests.xctest */;
230 | productType = "com.apple.product-type.bundle.ui-testing";
231 | };
232 | /* End PBXNativeTarget section */
233 |
234 | /* Begin PBXProject section */
235 | E71DCA181E9B1D2B00A058D2 /* Project object */ = {
236 | isa = PBXProject;
237 | attributes = {
238 | LastUpgradeCheck = 0820;
239 | ORGANIZATIONNAME = M.C;
240 | TargetAttributes = {
241 | E71DCA1F1E9B1D2B00A058D2 = {
242 | CreatedOnToolsVersion = 8.2.1;
243 | DevelopmentTeam = XC34BSW465;
244 | ProvisioningStyle = Automatic;
245 | };
246 | E71DCA381E9B1D2C00A058D2 = {
247 | CreatedOnToolsVersion = 8.2.1;
248 | ProvisioningStyle = Automatic;
249 | TestTargetID = E71DCA1F1E9B1D2B00A058D2;
250 | };
251 | E71DCA431E9B1D2C00A058D2 = {
252 | CreatedOnToolsVersion = 8.2.1;
253 | ProvisioningStyle = Automatic;
254 | TestTargetID = E71DCA1F1E9B1D2B00A058D2;
255 | };
256 | };
257 | };
258 | buildConfigurationList = E71DCA1B1E9B1D2B00A058D2 /* Build configuration list for PBXProject "MCDownloaderDemo" */;
259 | compatibilityVersion = "Xcode 3.2";
260 | developmentRegion = English;
261 | hasScannedForEncodings = 0;
262 | knownRegions = (
263 | en,
264 | Base,
265 | );
266 | mainGroup = E71DCA171E9B1D2B00A058D2;
267 | productRefGroup = E71DCA211E9B1D2B00A058D2 /* Products */;
268 | projectDirPath = "";
269 | projectRoot = "";
270 | targets = (
271 | E71DCA1F1E9B1D2B00A058D2 /* MCDownloaderDemo */,
272 | E71DCA381E9B1D2C00A058D2 /* MCDownloaderDemoTests */,
273 | E71DCA431E9B1D2C00A058D2 /* MCDownloaderDemoUITests */,
274 | );
275 | };
276 | /* End PBXProject section */
277 |
278 | /* Begin PBXResourcesBuildPhase section */
279 | E71DCA1E1E9B1D2B00A058D2 /* Resources */ = {
280 | isa = PBXResourcesBuildPhase;
281 | buildActionMask = 2147483647;
282 | files = (
283 | E71DCA331E9B1D2B00A058D2 /* LaunchScreen.storyboard in Resources */,
284 | E71DCA301E9B1D2B00A058D2 /* Assets.xcassets in Resources */,
285 | E71DCA2E1E9B1D2B00A058D2 /* Main.storyboard in Resources */,
286 | );
287 | runOnlyForDeploymentPostprocessing = 0;
288 | };
289 | E71DCA371E9B1D2C00A058D2 /* Resources */ = {
290 | isa = PBXResourcesBuildPhase;
291 | buildActionMask = 2147483647;
292 | files = (
293 | );
294 | runOnlyForDeploymentPostprocessing = 0;
295 | };
296 | E71DCA421E9B1D2C00A058D2 /* Resources */ = {
297 | isa = PBXResourcesBuildPhase;
298 | buildActionMask = 2147483647;
299 | files = (
300 | );
301 | runOnlyForDeploymentPostprocessing = 0;
302 | };
303 | /* End PBXResourcesBuildPhase section */
304 |
305 | /* Begin PBXSourcesBuildPhase section */
306 | E71DCA1C1E9B1D2B00A058D2 /* Sources */ = {
307 | isa = PBXSourcesBuildPhase;
308 | buildActionMask = 2147483647;
309 | files = (
310 | E71DCA5D1E9B1D5F00A058D2 /* MCDownloader.m in Sources */,
311 | E71DCA2B1E9B1D2B00A058D2 /* ViewController.m in Sources */,
312 | E71DCA5E1E9B1D5F00A058D2 /* MCDownloadOperation.m in Sources */,
313 | E71DCA281E9B1D2B00A058D2 /* AppDelegate.m in Sources */,
314 | E71DCA5F1E9B1D5F00A058D2 /* MCDownloadReceipt.m in Sources */,
315 | E71DCA651E9B1DD000A058D2 /* TableViewCell.m in Sources */,
316 | E71DCA251E9B1D2B00A058D2 /* main.m in Sources */,
317 | E71DCA641E9B1DD000A058D2 /* QKYDelayButton.m in Sources */,
318 | );
319 | runOnlyForDeploymentPostprocessing = 0;
320 | };
321 | E71DCA351E9B1D2C00A058D2 /* Sources */ = {
322 | isa = PBXSourcesBuildPhase;
323 | buildActionMask = 2147483647;
324 | files = (
325 | E71DCA3E1E9B1D2C00A058D2 /* MCDownloaderDemoTests.m in Sources */,
326 | );
327 | runOnlyForDeploymentPostprocessing = 0;
328 | };
329 | E71DCA401E9B1D2C00A058D2 /* Sources */ = {
330 | isa = PBXSourcesBuildPhase;
331 | buildActionMask = 2147483647;
332 | files = (
333 | E71DCA491E9B1D2C00A058D2 /* MCDownloaderDemoUITests.m in Sources */,
334 | );
335 | runOnlyForDeploymentPostprocessing = 0;
336 | };
337 | /* End PBXSourcesBuildPhase section */
338 |
339 | /* Begin PBXTargetDependency section */
340 | E71DCA3B1E9B1D2C00A058D2 /* PBXTargetDependency */ = {
341 | isa = PBXTargetDependency;
342 | target = E71DCA1F1E9B1D2B00A058D2 /* MCDownloaderDemo */;
343 | targetProxy = E71DCA3A1E9B1D2C00A058D2 /* PBXContainerItemProxy */;
344 | };
345 | E71DCA461E9B1D2C00A058D2 /* PBXTargetDependency */ = {
346 | isa = PBXTargetDependency;
347 | target = E71DCA1F1E9B1D2B00A058D2 /* MCDownloaderDemo */;
348 | targetProxy = E71DCA451E9B1D2C00A058D2 /* PBXContainerItemProxy */;
349 | };
350 | /* End PBXTargetDependency section */
351 |
352 | /* Begin PBXVariantGroup section */
353 | E71DCA2C1E9B1D2B00A058D2 /* Main.storyboard */ = {
354 | isa = PBXVariantGroup;
355 | children = (
356 | E71DCA2D1E9B1D2B00A058D2 /* Base */,
357 | );
358 | name = Main.storyboard;
359 | sourceTree = "";
360 | };
361 | E71DCA311E9B1D2B00A058D2 /* LaunchScreen.storyboard */ = {
362 | isa = PBXVariantGroup;
363 | children = (
364 | E71DCA321E9B1D2B00A058D2 /* Base */,
365 | );
366 | name = LaunchScreen.storyboard;
367 | sourceTree = "";
368 | };
369 | /* End PBXVariantGroup section */
370 |
371 | /* Begin XCBuildConfiguration section */
372 | E71DCA4B1E9B1D2C00A058D2 /* Debug */ = {
373 | isa = XCBuildConfiguration;
374 | buildSettings = {
375 | ALWAYS_SEARCH_USER_PATHS = NO;
376 | CLANG_ANALYZER_NONNULL = YES;
377 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
378 | CLANG_CXX_LIBRARY = "libc++";
379 | CLANG_ENABLE_MODULES = YES;
380 | CLANG_ENABLE_OBJC_ARC = YES;
381 | CLANG_WARN_BOOL_CONVERSION = YES;
382 | CLANG_WARN_CONSTANT_CONVERSION = YES;
383 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
384 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
385 | CLANG_WARN_EMPTY_BODY = YES;
386 | CLANG_WARN_ENUM_CONVERSION = YES;
387 | CLANG_WARN_INFINITE_RECURSION = YES;
388 | CLANG_WARN_INT_CONVERSION = YES;
389 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
390 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
391 | CLANG_WARN_UNREACHABLE_CODE = YES;
392 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
393 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
394 | COPY_PHASE_STRIP = NO;
395 | DEBUG_INFORMATION_FORMAT = dwarf;
396 | ENABLE_STRICT_OBJC_MSGSEND = YES;
397 | ENABLE_TESTABILITY = YES;
398 | GCC_C_LANGUAGE_STANDARD = gnu99;
399 | GCC_DYNAMIC_NO_PIC = NO;
400 | GCC_NO_COMMON_BLOCKS = YES;
401 | GCC_OPTIMIZATION_LEVEL = 0;
402 | GCC_PREPROCESSOR_DEFINITIONS = (
403 | "DEBUG=1",
404 | "$(inherited)",
405 | );
406 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
407 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
408 | GCC_WARN_UNDECLARED_SELECTOR = YES;
409 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
410 | GCC_WARN_UNUSED_FUNCTION = YES;
411 | GCC_WARN_UNUSED_VARIABLE = YES;
412 | IPHONEOS_DEPLOYMENT_TARGET = 10.2;
413 | MTL_ENABLE_DEBUG_INFO = YES;
414 | ONLY_ACTIVE_ARCH = YES;
415 | SDKROOT = iphoneos;
416 | };
417 | name = Debug;
418 | };
419 | E71DCA4C1E9B1D2C00A058D2 /* Release */ = {
420 | isa = XCBuildConfiguration;
421 | buildSettings = {
422 | ALWAYS_SEARCH_USER_PATHS = NO;
423 | CLANG_ANALYZER_NONNULL = YES;
424 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
425 | CLANG_CXX_LIBRARY = "libc++";
426 | CLANG_ENABLE_MODULES = YES;
427 | CLANG_ENABLE_OBJC_ARC = YES;
428 | CLANG_WARN_BOOL_CONVERSION = YES;
429 | CLANG_WARN_CONSTANT_CONVERSION = YES;
430 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
431 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
432 | CLANG_WARN_EMPTY_BODY = YES;
433 | CLANG_WARN_ENUM_CONVERSION = YES;
434 | CLANG_WARN_INFINITE_RECURSION = YES;
435 | CLANG_WARN_INT_CONVERSION = YES;
436 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
437 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
438 | CLANG_WARN_UNREACHABLE_CODE = YES;
439 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
440 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
441 | COPY_PHASE_STRIP = NO;
442 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
443 | ENABLE_NS_ASSERTIONS = NO;
444 | ENABLE_STRICT_OBJC_MSGSEND = YES;
445 | GCC_C_LANGUAGE_STANDARD = gnu99;
446 | GCC_NO_COMMON_BLOCKS = YES;
447 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
448 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
449 | GCC_WARN_UNDECLARED_SELECTOR = YES;
450 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
451 | GCC_WARN_UNUSED_FUNCTION = YES;
452 | GCC_WARN_UNUSED_VARIABLE = YES;
453 | IPHONEOS_DEPLOYMENT_TARGET = 10.2;
454 | MTL_ENABLE_DEBUG_INFO = NO;
455 | SDKROOT = iphoneos;
456 | VALIDATE_PRODUCT = YES;
457 | };
458 | name = Release;
459 | };
460 | E71DCA4E1E9B1D2C00A058D2 /* Debug */ = {
461 | isa = XCBuildConfiguration;
462 | buildSettings = {
463 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
464 | DEVELOPMENT_TEAM = XC34BSW465;
465 | INFOPLIST_FILE = MCDownloaderDemo/Info.plist;
466 | IPHONEOS_DEPLOYMENT_TARGET = 8.0;
467 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
468 | PRODUCT_BUNDLE_IDENTIFIER = M.C.MCDownloaderDemo;
469 | PRODUCT_NAME = "$(TARGET_NAME)";
470 | };
471 | name = Debug;
472 | };
473 | E71DCA4F1E9B1D2C00A058D2 /* Release */ = {
474 | isa = XCBuildConfiguration;
475 | buildSettings = {
476 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
477 | DEVELOPMENT_TEAM = XC34BSW465;
478 | INFOPLIST_FILE = MCDownloaderDemo/Info.plist;
479 | IPHONEOS_DEPLOYMENT_TARGET = 8.0;
480 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
481 | PRODUCT_BUNDLE_IDENTIFIER = M.C.MCDownloaderDemo;
482 | PRODUCT_NAME = "$(TARGET_NAME)";
483 | };
484 | name = Release;
485 | };
486 | E71DCA511E9B1D2C00A058D2 /* Debug */ = {
487 | isa = XCBuildConfiguration;
488 | buildSettings = {
489 | BUNDLE_LOADER = "$(TEST_HOST)";
490 | INFOPLIST_FILE = MCDownloaderDemoTests/Info.plist;
491 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
492 | PRODUCT_BUNDLE_IDENTIFIER = M.C.MCDownloaderDemoTests;
493 | PRODUCT_NAME = "$(TARGET_NAME)";
494 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/MCDownloaderDemo.app/MCDownloaderDemo";
495 | };
496 | name = Debug;
497 | };
498 | E71DCA521E9B1D2C00A058D2 /* Release */ = {
499 | isa = XCBuildConfiguration;
500 | buildSettings = {
501 | BUNDLE_LOADER = "$(TEST_HOST)";
502 | INFOPLIST_FILE = MCDownloaderDemoTests/Info.plist;
503 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
504 | PRODUCT_BUNDLE_IDENTIFIER = M.C.MCDownloaderDemoTests;
505 | PRODUCT_NAME = "$(TARGET_NAME)";
506 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/MCDownloaderDemo.app/MCDownloaderDemo";
507 | };
508 | name = Release;
509 | };
510 | E71DCA541E9B1D2C00A058D2 /* Debug */ = {
511 | isa = XCBuildConfiguration;
512 | buildSettings = {
513 | INFOPLIST_FILE = MCDownloaderDemoUITests/Info.plist;
514 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
515 | PRODUCT_BUNDLE_IDENTIFIER = M.C.MCDownloaderDemoUITests;
516 | PRODUCT_NAME = "$(TARGET_NAME)";
517 | TEST_TARGET_NAME = MCDownloaderDemo;
518 | };
519 | name = Debug;
520 | };
521 | E71DCA551E9B1D2C00A058D2 /* Release */ = {
522 | isa = XCBuildConfiguration;
523 | buildSettings = {
524 | INFOPLIST_FILE = MCDownloaderDemoUITests/Info.plist;
525 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
526 | PRODUCT_BUNDLE_IDENTIFIER = M.C.MCDownloaderDemoUITests;
527 | PRODUCT_NAME = "$(TARGET_NAME)";
528 | TEST_TARGET_NAME = MCDownloaderDemo;
529 | };
530 | name = Release;
531 | };
532 | /* End XCBuildConfiguration section */
533 |
534 | /* Begin XCConfigurationList section */
535 | E71DCA1B1E9B1D2B00A058D2 /* Build configuration list for PBXProject "MCDownloaderDemo" */ = {
536 | isa = XCConfigurationList;
537 | buildConfigurations = (
538 | E71DCA4B1E9B1D2C00A058D2 /* Debug */,
539 | E71DCA4C1E9B1D2C00A058D2 /* Release */,
540 | );
541 | defaultConfigurationIsVisible = 0;
542 | defaultConfigurationName = Release;
543 | };
544 | E71DCA4D1E9B1D2C00A058D2 /* Build configuration list for PBXNativeTarget "MCDownloaderDemo" */ = {
545 | isa = XCConfigurationList;
546 | buildConfigurations = (
547 | E71DCA4E1E9B1D2C00A058D2 /* Debug */,
548 | E71DCA4F1E9B1D2C00A058D2 /* Release */,
549 | );
550 | defaultConfigurationIsVisible = 0;
551 | defaultConfigurationName = Release;
552 | };
553 | E71DCA501E9B1D2C00A058D2 /* Build configuration list for PBXNativeTarget "MCDownloaderDemoTests" */ = {
554 | isa = XCConfigurationList;
555 | buildConfigurations = (
556 | E71DCA511E9B1D2C00A058D2 /* Debug */,
557 | E71DCA521E9B1D2C00A058D2 /* Release */,
558 | );
559 | defaultConfigurationIsVisible = 0;
560 | defaultConfigurationName = Release;
561 | };
562 | E71DCA531E9B1D2C00A058D2 /* Build configuration list for PBXNativeTarget "MCDownloaderDemoUITests" */ = {
563 | isa = XCConfigurationList;
564 | buildConfigurations = (
565 | E71DCA541E9B1D2C00A058D2 /* Debug */,
566 | E71DCA551E9B1D2C00A058D2 /* Release */,
567 | );
568 | defaultConfigurationIsVisible = 0;
569 | defaultConfigurationName = Release;
570 | };
571 | /* End XCConfigurationList section */
572 | };
573 | rootObject = E71DCA181E9B1D2B00A058D2 /* Project object */;
574 | }
575 |
--------------------------------------------------------------------------------