├── .github └── Bug_report.md ├── .gitignore ├── .travis.yml ├── CONTRIBUTING.md ├── Gemfile ├── Gemfile.lock ├── LICENSE ├── Podfile ├── Podfile.lock ├── README.md ├── SwiftBridgeDemo ├── SwiftBridgeDemo.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ └── contents.xcworkspacedata └── SwiftBridgeDemo │ ├── AppDelegate.swift │ ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json │ ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard │ ├── Bridge-header.h │ ├── Info.plist │ └── ViewController.swift ├── YCDownloadSession.podspec ├── YCDownloadSession ├── Core │ ├── YCDownloadDB.h │ ├── YCDownloadDB.m │ ├── YCDownloadSession.h │ ├── YCDownloadTask.h │ ├── YCDownloadTask.m │ ├── YCDownloadUtils.h │ ├── YCDownloadUtils.m │ ├── YCDownloader.h │ └── YCDownloader.m ├── Info.plist ├── YCDownloadItem.h ├── YCDownloadItem.m ├── YCDownloadManager.h └── YCDownloadManager.m ├── YCDownloadSessionDemo.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── xcshareddata │ └── xcschemes │ ├── YCDownloadSession.xcscheme │ └── YCDownloadSessionDemo.xcscheme ├── YCDownloadSessionDemo.xcworkspace ├── contents.xcworkspacedata └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── YCDownloadSessionDemo ├── YCDownloadSession │ ├── AppDelegate.h │ ├── AppDelegate.m │ ├── Assets.xcassets │ │ └── Contents.json │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ ├── Class │ │ ├── Others │ │ │ ├── TestSwiftController.swift │ │ │ └── YCDownloadSessionDemo-Bridging-Header.h │ │ ├── PlayerVC │ │ │ ├── PlayerViewController.h │ │ │ └── PlayerViewController.m │ │ ├── RootVc │ │ │ ├── DownloadViewController.h │ │ │ ├── DownloadViewController.m │ │ │ ├── MainTableViewController.h │ │ │ └── MainTableViewController.m │ │ ├── VideoCache │ │ │ ├── VideoCacheController.h │ │ │ ├── VideoCacheController.m │ │ │ ├── VideoCacheListCell.h │ │ │ ├── VideoCacheListCell.m │ │ │ └── VideoCacheListCell.xib │ │ └── VideoList │ │ │ ├── VideoListInfoCell.h │ │ │ ├── VideoListInfoCell.m │ │ │ ├── VideoListInfoCell.xib │ │ │ ├── VideoListInfoController.h │ │ │ ├── VideoListInfoController.m │ │ │ ├── VideoListInfoModel.h │ │ │ └── VideoListInfoModel.m │ ├── Info.plist │ ├── YCDownloadSwift.h │ ├── main.m │ └── video.json └── YCDownloadSessionTests │ ├── Info.plist │ └── YCDownloadSessionTests.m └── _config.yml /.github/Bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 问题反馈 3 | about: Create a report to help us improve 4 | 5 | --- 6 | 7 | **手机信息 (please complete the following information):** 8 | - Device: [e.g. iPhone6] 9 | - OS: [e.g. iOS8.1] 10 | - YCDownloadSession Version [e.g. 22] 11 | 12 | **如何复现** 13 | 详细的描述如何复现返回的问题?并且参照demo,在demo里同样的操作是否会复现? 14 | 1. 到xx页面 15 | 2. 点击xx 16 | 3. 出现xx错误 17 | 18 | **错误或者奔溃信息** 19 | 复制粘贴相关信息或者截图 20 | 21 | 22 | **友情提示** 23 | 发生问题,或者有疑问时,**请仔细参照demo的实现逻辑**。为了能够快速解决,请严格按照本模板描述,并且将你的问题在demo的复现过程详细描述出来。 24 | 25 | 有能力的同学,请自行debug问题的原因,然后进行反馈。或者找到解决办法后发起pull request。 26 | 27 | 28 | -------------------------------------------------------------------------------- /.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 | Pods 62 | .bundle 63 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: objective-c 2 | osx_image: xcode12 3 | xcode_project: YCDownloadSessionDemo.xcworkspace 4 | xcode_scheme: YCDownloadSessionDemo 5 | 6 | before_install: 7 | - env 8 | - xcodebuild -version 9 | - xcodebuild -showsdks 10 | - xcpretty --version 11 | - ruby -v 12 | - gem source -l 13 | 14 | script: 15 | - set -o pipefail 16 | - bundle install 17 | - bundle exec pod install 18 | - xcodebuild clean build -workspace "$TRAVIS_XCODE_PROJECT" -scheme "$TRAVIS_XCODE_SCHEME" -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 8,OS=latest' | xcpretty 19 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org/' do 2 | gem 'cocoapods', '~> 1.5.3' 3 | end 4 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | CFPropertyList (3.0.3) 5 | activesupport (4.2.11.3) 6 | i18n (~> 0.7) 7 | minitest (~> 5.1) 8 | thread_safe (~> 0.3, >= 0.3.4) 9 | tzinfo (~> 1.1) 10 | atomos (0.1.3) 11 | claide (1.0.3) 12 | cocoapods (1.5.3) 13 | activesupport (>= 4.0.2, < 5) 14 | claide (>= 1.0.2, < 2.0) 15 | cocoapods-core (= 1.5.3) 16 | cocoapods-deintegrate (>= 1.0.2, < 2.0) 17 | cocoapods-downloader (>= 1.2.0, < 2.0) 18 | cocoapods-plugins (>= 1.0.0, < 2.0) 19 | cocoapods-search (>= 1.0.0, < 2.0) 20 | cocoapods-stats (>= 1.0.0, < 2.0) 21 | cocoapods-trunk (>= 1.3.0, < 2.0) 22 | cocoapods-try (>= 1.1.0, < 2.0) 23 | colored2 (~> 3.1) 24 | escape (~> 0.0.4) 25 | fourflusher (~> 2.0.1) 26 | gh_inspector (~> 1.0) 27 | molinillo (~> 0.6.5) 28 | nap (~> 1.0) 29 | ruby-macho (~> 1.1) 30 | xcodeproj (>= 1.5.7, < 2.0) 31 | cocoapods-core (1.5.3) 32 | activesupport (>= 4.0.2, < 6) 33 | fuzzy_match (~> 2.0.4) 34 | nap (~> 1.0) 35 | cocoapods-deintegrate (1.0.5) 36 | cocoapods-downloader (1.5.0) 37 | cocoapods-plugins (1.0.0) 38 | nap 39 | cocoapods-search (1.0.1) 40 | cocoapods-stats (1.1.0) 41 | cocoapods-trunk (1.6.0) 42 | nap (>= 0.8, < 2.0) 43 | netrc (~> 0.11) 44 | cocoapods-try (1.2.0) 45 | colored2 (3.1.2) 46 | concurrent-ruby (1.1.9) 47 | escape (0.0.4) 48 | fourflusher (2.0.1) 49 | fuzzy_match (2.0.4) 50 | gh_inspector (1.1.3) 51 | i18n (0.9.5) 52 | concurrent-ruby (~> 1.0) 53 | minitest (5.14.4) 54 | molinillo (0.6.6) 55 | nanaimo (0.3.0) 56 | nap (1.1.0) 57 | netrc (0.11.0) 58 | rexml (3.2.5) 59 | ruby-macho (1.4.0) 60 | thread_safe (0.3.6) 61 | tzinfo (1.2.9) 62 | thread_safe (~> 0.1) 63 | xcodeproj (1.21.0) 64 | CFPropertyList (>= 2.3.3, < 4.0) 65 | atomos (~> 0.1.3) 66 | claide (>= 1.0.2, < 2.0) 67 | colored2 (~> 3.1) 68 | nanaimo (~> 0.3.0) 69 | rexml (~> 3.2.4) 70 | 71 | PLATFORMS 72 | ruby 73 | 74 | DEPENDENCIES 75 | cocoapods (~> 1.5.3)! 76 | 77 | BUNDLED WITH 78 | 1.17.3 79 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 WangZhen 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 | -------------------------------------------------------------------------------- /Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment the next line to define a global platform for your project 2 | target 'YCDownloadSessionDemo' do 3 | pod 'YCDownloadSession', :path=>'./' 4 | pod 'SDWebImage' 5 | pod 'WMPlayer' 6 | pod 'Masonry' 7 | pod 'AFNetworking' 8 | pod 'MJRefresh' 9 | pod 'Bugly' 10 | 11 | target 'YCDownloadSessionDemoTests' do 12 | inherit! :search_paths 13 | # Pods for testing 14 | end 15 | 16 | end 17 | 18 | target 'YCDownloadSession' do 19 | use_frameworks! 20 | pod 'YCDownloadSession', :path=>'./' 21 | end 22 | 23 | post_install do |installer| 24 | installer.pods_project.targets.each do |target| 25 | target.build_configurations.each do |config| 26 | if target.name != "YCDownloadSession" 27 | config.build_settings['GCC_WARN_INHIBIT_ALL_WARNINGS'] = "YES" 28 | config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '9.0' 29 | end 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - AFNetworking (3.2.1): 3 | - AFNetworking/NSURLSession (= 3.2.1) 4 | - AFNetworking/Reachability (= 3.2.1) 5 | - AFNetworking/Security (= 3.2.1) 6 | - AFNetworking/Serialization (= 3.2.1) 7 | - AFNetworking/UIKit (= 3.2.1) 8 | - AFNetworking/NSURLSession (3.2.1): 9 | - AFNetworking/Reachability 10 | - AFNetworking/Security 11 | - AFNetworking/Serialization 12 | - AFNetworking/Reachability (3.2.1) 13 | - AFNetworking/Security (3.2.1) 14 | - AFNetworking/Serialization (3.2.1) 15 | - AFNetworking/UIKit (3.2.1): 16 | - AFNetworking/NSURLSession 17 | - Bugly (2.5.71) 18 | - Masonry (1.1.0) 19 | - MJRefresh (3.5.0) 20 | - SDWebImage (5.9.5): 21 | - SDWebImage/Core (= 5.9.5) 22 | - SDWebImage/Core (5.9.5) 23 | - WMPlayer (5.0.0): 24 | - Masonry 25 | - YCDownloadSession (2.0.3): 26 | - YCDownloadSession/Core (= 2.0.3) 27 | - YCDownloadSession/Mgr (= 2.0.3) 28 | - YCDownloadSession/Core (2.0.3) 29 | - YCDownloadSession/Mgr (2.0.3): 30 | - YCDownloadSession/Core 31 | 32 | DEPENDENCIES: 33 | - AFNetworking 34 | - Bugly 35 | - Masonry 36 | - MJRefresh 37 | - SDWebImage 38 | - WMPlayer 39 | - YCDownloadSession (from `./`) 40 | 41 | SPEC REPOS: 42 | https://github.com/CocoaPods/Specs.git: 43 | - AFNetworking 44 | - Bugly 45 | - Masonry 46 | - MJRefresh 47 | - SDWebImage 48 | - WMPlayer 49 | 50 | EXTERNAL SOURCES: 51 | YCDownloadSession: 52 | :path: "./" 53 | 54 | SPEC CHECKSUMS: 55 | AFNetworking: cb604b1c2bded0871f5f61f5d53653739e841d6b 56 | Bugly: fd066c75c4a0eca1440f9b6a84bd37d51bfc85ac 57 | Masonry: 678fab65091a9290e40e2832a55e7ab731aad201 58 | MJRefresh: 6afc955813966afb08305477dd7a0d9ad5e79a16 59 | SDWebImage: 0b2ba0d56479bf6a45ecddbfd5558bea93150d25 60 | WMPlayer: a914f80ed5dca7cd9e3a94ec07a2b02eec8c15c0 61 | YCDownloadSession: a95e2449c6d01af8ba0d5977ce79a9f596f4245d 62 | 63 | PODFILE CHECKSUM: 13c872c3d303ef29916c6f2ec84f06d5ccbf4713 64 | 65 | COCOAPODS: 1.11.3 66 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [YCDownloadSession](https://onezens.github.io/YCDownloadSession/) 2 | 3 | [![Platform](https://img.shields.io/badge/platform-iOS-yellowgreen.svg)](https://github.com/onezens/YCDownloadSession) 4 | [![Support](https://img.shields.io/badge/support-iOS%208%2B%20-blue.svg?style=flat)](https://www.apple.com/nl/ios/) 5 | [![CocoaPods](http://img.shields.io/cocoapods/v/YCDownloadSession.svg?style=flat)](https://cocoapods.org/pods/YCDownloadSession) 6 | [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) 7 | [![Build Status](https://travis-ci.com/onezens/YCDownloadSession.svg?branch=master)](https://travis-ci.com/onezens/YCDownloadSession) 8 | 9 | 10 | 11 | ## 通过Cocoapods安装 12 | 13 | 安装Cocoapods 14 | 15 | ``` 16 | $ brew install ruby 17 | $ sudo gem install cocoapods 18 | ``` 19 | 20 | **Podfile** 21 | 22 | 分成主要两个包: 23 | 24 | - `Core` : YCDownloader 只有下载器 25 | - `Mgr` : YCDownloader , YCDownloadManager 所有 26 | 27 | ``` 28 | source 'https://github.com/CocoaPods/Specs.git' 29 | platform :ios, '8.0' 30 | 31 | target 'TargetName' do 32 | pod 'YCDownloadSession', '~> 2.0.2', :subspecs => ['Core', 'Mgr'] 33 | end 34 | ``` 35 | 36 | 然后安装依赖库: 37 | 38 | ``` 39 | $ pod install 40 | ``` 41 | 42 | 提示错误 `[!] Unable to find a specification for YCDownloadSession ` 解决办法: 43 | 44 | ``` 45 | $ pod repo update master 46 | ``` 47 | ## 通过Carthage安装 48 | 安装carthage: 49 | 50 | ``` 51 | brew install carthage 52 | ``` 53 | 添加下面配置到`Cartfile`里: 54 | 55 | ``` 56 | github "onezens/YCDownloadSession" 57 | ``` 58 | 安装, 然后添加Framework到项目: 59 | 60 | ``` 61 | carthage update --platform ios 62 | ``` 63 | 64 | ## 用法 65 | 66 | **注意事项** 67 | 68 | 1、在真机上测试,用户想要完整使用后台下载功能,需要在系统设置页,检查是否开启`后台 App 刷新`;如果没有开启,则第一个任务下载完成后,无法后台唤醒,开启下一个下载任务 69 | 70 | 71 | 72 | **引用头文件** 73 | 74 | ``` 75 | #import 76 | ``` 77 | 78 | 79 | **AppDelegate设置后台下载成功回调方法** 80 | 81 | 82 | ``` 83 | -(void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)(void))completionHandler{ 84 | [[YCDownloader downloader] addCompletionHandler:completionHandler identifier:identifier]; 85 | } 86 | ``` 87 | 88 | ### 下载器 `YCDownloader` 89 | 90 | 创建下载任务 91 | 92 | ``` 93 | YCDownloadTask *task = [[YCDownloader downloader] downloadWithUrl:@"download_url" progress:^(NSProgress * _Nonnull progress, YCDownloadTask * _Nonnull task) { 94 | NSLog(@"progress: %f", progress.fractionCompleted); 95 | } completion:^(NSString * _Nullable localPath, NSError * _Nullable error) { 96 | // handler download task completed callback 97 | }]; 98 | ``` 99 | 100 | 开始下载任务: 101 | 102 | ``` 103 | [[YCDownloader downloader] resumeTask:self.downloadTask]; 104 | ``` 105 | 106 | 暂停下载任务: 107 | 108 | ``` 109 | [[YCDownloader downloader] pauseTask:self.downloadTask]; 110 | ``` 111 | 112 | 删除下载任务: 113 | 114 | ``` 115 | [[YCDownloader downloader] cancelTask:self.downloadTask]; 116 | ``` 117 | 118 | 异常退出应用后,恢复之前正在进行的任务的回调 119 | 120 | ``` 121 | /** 122 | 恢复下载任务,继续下载任务,主要用于app异常退出状态恢复,继续下载任务的回调设置 123 | 124 | @param tid 下载任务的taskId 125 | @param progress 下载进度回调 126 | @param completion 下载成功失败回调 127 | @return 下载任务task 128 | */ 129 | - (nullable YCDownloadTask *)resumeDownloadTaskWithTid:(NSString *)tid progress:(YCProgressHandler)progress completion:(YCCompletionHandler)completion; 130 | ``` 131 | 132 | ### 下载任务管理器`YCDownloadManager` 133 | 134 | 设置任务管理器配置 135 | 136 | ``` 137 | NSString *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, true).firstObject; 138 | path = [path stringByAppendingPathComponent:@"download"]; 139 | YCDConfig *config = [YCDConfig new]; 140 | config.saveRootPath = path; 141 | config.uid = @"100006"; 142 | config.maxTaskCount = 3; 143 | config.taskCachekMode = YCDownloadTaskCacheModeKeep; 144 | config.launchAutoResumeDownload = true; 145 | [YCDownloadManager mgrWithConfig:config]; 146 | ``` 147 | 148 | 下载任务相关通知 149 | 150 | ``` 151 | //某一个YCDownloadItem下载成功通知 152 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(downloadTaskFinishedNoti:) name:kDownloadTaskFinishedNoti object:nil]; 153 | //mgr 管理的所有任务完成通知 154 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(downloadAllTaskFinished) name:kDownloadTaskAllFinishedNoti object:nil]; 155 | ``` 156 | 157 | 开始下载任务 158 | 159 | ``` 160 | YCDownloadItem *item = [YCDownloadItem itemWithUrl:model.mp4_url fileId:model.file_id]; 161 | item.extraData = ...; 162 | [YCDownloadManager startDownloadWithItem:item]; 163 | ``` 164 | 下载相关控制 165 | 166 | ``` 167 | /** 168 | 暂停一个后台下载任务 169 | 170 | @param item 创建的下载任务item 171 | */ 172 | + (void)pauseDownloadWithItem:(nonnull YCDownloadItem *)item; 173 | 174 | /** 175 | 继续开始一个后台下载任务 176 | 177 | @param item 创建的下载任务item 178 | */ 179 | + (void)resumeDownloadWithItem:(nonnull YCDownloadItem *)item; 180 | 181 | /** 182 | 删除一个后台下载任务,同时会删除当前任务下载的缓存数据 183 | 184 | @param item 创建的下载任务item 185 | */ 186 | + (void)stopDownloadWithItem:(nonnull YCDownloadItem *)item; 187 | ``` 188 | 蜂窝煤网络访问控制 189 | 190 | ``` 191 | /** 192 | 是否允许蜂窝煤网络下载,以及网络状态变为蜂窝煤是否允许下载,必须把所有的downloadTask全部暂停,然后重新创建。否则,原先创建的 193 | 下载task依旧在网络切换为蜂窝煤网络时会继续下载 194 | 195 | @param isAllow 是否允许蜂窝煤网络下载 196 | */ 197 | + (void)allowsCellularAccess:(BOOL)isAllow; 198 | 199 | /** 200 | 获取是否允许蜂窝煤访问 201 | */ 202 | + (BOOL)isAllowsCellularAccess; 203 | ``` 204 | 205 | ## 使用效果图 206 | 207 | 单文件下载测试 208 | 209 | ![单文件下载测试](http://src.onezen.cc/demo/download/1.gif) 210 | 211 | 多视频下载测试 212 | 213 | ![多视频下载测试](http://src.onezen.cc/demo/download/2.gif) 214 | 215 | 下载通知 216 | 217 | ![下载通知](http://src.onezen.cc/demo/download/4.png) 218 | 219 | 220 | -------------------------------------------------------------------------------- /SwiftBridgeDemo/SwiftBridgeDemo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 48; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 357B35271FCCF6AD007CE251 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 357B35261FCCF6AD007CE251 /* AppDelegate.swift */; }; 11 | 357B35291FCCF6AD007CE251 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 357B35281FCCF6AD007CE251 /* ViewController.swift */; }; 12 | 357B352C1FCCF6AD007CE251 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 357B352A1FCCF6AD007CE251 /* Main.storyboard */; }; 13 | 357B352E1FCCF6AD007CE251 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 357B352D1FCCF6AD007CE251 /* Assets.xcassets */; }; 14 | 357B35311FCCF6AD007CE251 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 357B352F1FCCF6AD007CE251 /* LaunchScreen.storyboard */; }; 15 | 35894A92214A586A00D5E6D6 /* YCDownloader.m in Sources */ = {isa = PBXBuildFile; fileRef = 35894A8D214A586A00D5E6D6 /* YCDownloader.m */; }; 16 | 35894A93214A586A00D5E6D6 /* YCDownloadUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 35894A8F214A586A00D5E6D6 /* YCDownloadUtils.m */; }; 17 | 35894A94214A586A00D5E6D6 /* YCDownloadTask.m in Sources */ = {isa = PBXBuildFile; fileRef = 35894A91214A586A00D5E6D6 /* YCDownloadTask.m */; }; 18 | /* End PBXBuildFile section */ 19 | 20 | /* Begin PBXFileReference section */ 21 | 357B35231FCCF6AD007CE251 /* SwiftBridgeDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SwiftBridgeDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 22 | 357B35261FCCF6AD007CE251 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 23 | 357B35281FCCF6AD007CE251 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 24 | 357B352B1FCCF6AD007CE251 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 25 | 357B352D1FCCF6AD007CE251 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 26 | 357B35301FCCF6AD007CE251 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 27 | 357B35321FCCF6AD007CE251 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 28 | 357B35381FCCF6C3007CE251 /* Bridge-header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Bridge-header.h"; sourceTree = ""; }; 29 | 35894A8B214A586A00D5E6D6 /* YCDownloadUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = YCDownloadUtils.h; sourceTree = ""; }; 30 | 35894A8C214A586A00D5E6D6 /* YCDownloadSession.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = YCDownloadSession.h; sourceTree = ""; }; 31 | 35894A8D214A586A00D5E6D6 /* YCDownloader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = YCDownloader.m; sourceTree = ""; }; 32 | 35894A8E214A586A00D5E6D6 /* YCDownloadTask.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = YCDownloadTask.h; sourceTree = ""; }; 33 | 35894A8F214A586A00D5E6D6 /* YCDownloadUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = YCDownloadUtils.m; sourceTree = ""; }; 34 | 35894A90214A586A00D5E6D6 /* YCDownloader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = YCDownloader.h; sourceTree = ""; }; 35 | 35894A91214A586A00D5E6D6 /* YCDownloadTask.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = YCDownloadTask.m; sourceTree = ""; }; 36 | /* End PBXFileReference section */ 37 | 38 | /* Begin PBXFrameworksBuildPhase section */ 39 | 357B35201FCCF6AD007CE251 /* Frameworks */ = { 40 | isa = PBXFrameworksBuildPhase; 41 | buildActionMask = 2147483647; 42 | files = ( 43 | ); 44 | runOnlyForDeploymentPostprocessing = 0; 45 | }; 46 | /* End PBXFrameworksBuildPhase section */ 47 | 48 | /* Begin PBXGroup section */ 49 | 357B351A1FCCF6AD007CE251 = { 50 | isa = PBXGroup; 51 | children = ( 52 | 357B35251FCCF6AD007CE251 /* SwiftBridgeDemo */, 53 | 357B35241FCCF6AD007CE251 /* Products */, 54 | ); 55 | sourceTree = ""; 56 | }; 57 | 357B35241FCCF6AD007CE251 /* Products */ = { 58 | isa = PBXGroup; 59 | children = ( 60 | 357B35231FCCF6AD007CE251 /* SwiftBridgeDemo.app */, 61 | ); 62 | name = Products; 63 | sourceTree = ""; 64 | }; 65 | 357B35251FCCF6AD007CE251 /* SwiftBridgeDemo */ = { 66 | isa = PBXGroup; 67 | children = ( 68 | 35894A8A214A586A00D5E6D6 /* core */, 69 | 357B35261FCCF6AD007CE251 /* AppDelegate.swift */, 70 | 357B35281FCCF6AD007CE251 /* ViewController.swift */, 71 | 357B352A1FCCF6AD007CE251 /* Main.storyboard */, 72 | 357B352D1FCCF6AD007CE251 /* Assets.xcassets */, 73 | 357B352F1FCCF6AD007CE251 /* LaunchScreen.storyboard */, 74 | 357B35321FCCF6AD007CE251 /* Info.plist */, 75 | 357B35381FCCF6C3007CE251 /* Bridge-header.h */, 76 | ); 77 | path = SwiftBridgeDemo; 78 | sourceTree = ""; 79 | }; 80 | 35894A8A214A586A00D5E6D6 /* core */ = { 81 | isa = PBXGroup; 82 | children = ( 83 | 35894A8C214A586A00D5E6D6 /* YCDownloadSession.h */, 84 | 35894A8B214A586A00D5E6D6 /* YCDownloadUtils.h */, 85 | 35894A8F214A586A00D5E6D6 /* YCDownloadUtils.m */, 86 | 35894A90214A586A00D5E6D6 /* YCDownloader.h */, 87 | 35894A8D214A586A00D5E6D6 /* YCDownloader.m */, 88 | 35894A8E214A586A00D5E6D6 /* YCDownloadTask.h */, 89 | 35894A91214A586A00D5E6D6 /* YCDownloadTask.m */, 90 | ); 91 | name = core; 92 | path = ../../YCDownloadSession/core; 93 | sourceTree = ""; 94 | }; 95 | /* End PBXGroup section */ 96 | 97 | /* Begin PBXNativeTarget section */ 98 | 357B35221FCCF6AD007CE251 /* SwiftBridgeDemo */ = { 99 | isa = PBXNativeTarget; 100 | buildConfigurationList = 357B35351FCCF6AD007CE251 /* Build configuration list for PBXNativeTarget "SwiftBridgeDemo" */; 101 | buildPhases = ( 102 | 357B351F1FCCF6AD007CE251 /* Sources */, 103 | 357B35201FCCF6AD007CE251 /* Frameworks */, 104 | 357B35211FCCF6AD007CE251 /* Resources */, 105 | ); 106 | buildRules = ( 107 | ); 108 | dependencies = ( 109 | ); 110 | name = SwiftBridgeDemo; 111 | productName = SwiftBridgeDemo; 112 | productReference = 357B35231FCCF6AD007CE251 /* SwiftBridgeDemo.app */; 113 | productType = "com.apple.product-type.application"; 114 | }; 115 | /* End PBXNativeTarget section */ 116 | 117 | /* Begin PBXProject section */ 118 | 357B351B1FCCF6AD007CE251 /* Project object */ = { 119 | isa = PBXProject; 120 | attributes = { 121 | LastSwiftUpdateCheck = 0910; 122 | LastUpgradeCheck = 0910; 123 | ORGANIZATIONNAME = wz; 124 | TargetAttributes = { 125 | 357B35221FCCF6AD007CE251 = { 126 | CreatedOnToolsVersion = 9.1; 127 | ProvisioningStyle = Automatic; 128 | }; 129 | }; 130 | }; 131 | buildConfigurationList = 357B351E1FCCF6AD007CE251 /* Build configuration list for PBXProject "SwiftBridgeDemo" */; 132 | compatibilityVersion = "Xcode 8.0"; 133 | developmentRegion = en; 134 | hasScannedForEncodings = 0; 135 | knownRegions = ( 136 | en, 137 | Base, 138 | ); 139 | mainGroup = 357B351A1FCCF6AD007CE251; 140 | productRefGroup = 357B35241FCCF6AD007CE251 /* Products */; 141 | projectDirPath = ""; 142 | projectRoot = ""; 143 | targets = ( 144 | 357B35221FCCF6AD007CE251 /* SwiftBridgeDemo */, 145 | ); 146 | }; 147 | /* End PBXProject section */ 148 | 149 | /* Begin PBXResourcesBuildPhase section */ 150 | 357B35211FCCF6AD007CE251 /* Resources */ = { 151 | isa = PBXResourcesBuildPhase; 152 | buildActionMask = 2147483647; 153 | files = ( 154 | 357B35311FCCF6AD007CE251 /* LaunchScreen.storyboard in Resources */, 155 | 357B352E1FCCF6AD007CE251 /* Assets.xcassets in Resources */, 156 | 357B352C1FCCF6AD007CE251 /* Main.storyboard in Resources */, 157 | ); 158 | runOnlyForDeploymentPostprocessing = 0; 159 | }; 160 | /* End PBXResourcesBuildPhase section */ 161 | 162 | /* Begin PBXSourcesBuildPhase section */ 163 | 357B351F1FCCF6AD007CE251 /* Sources */ = { 164 | isa = PBXSourcesBuildPhase; 165 | buildActionMask = 2147483647; 166 | files = ( 167 | 357B35291FCCF6AD007CE251 /* ViewController.swift in Sources */, 168 | 35894A93214A586A00D5E6D6 /* YCDownloadUtils.m in Sources */, 169 | 35894A94214A586A00D5E6D6 /* YCDownloadTask.m in Sources */, 170 | 357B35271FCCF6AD007CE251 /* AppDelegate.swift in Sources */, 171 | 35894A92214A586A00D5E6D6 /* YCDownloader.m in Sources */, 172 | ); 173 | runOnlyForDeploymentPostprocessing = 0; 174 | }; 175 | /* End PBXSourcesBuildPhase section */ 176 | 177 | /* Begin PBXVariantGroup section */ 178 | 357B352A1FCCF6AD007CE251 /* Main.storyboard */ = { 179 | isa = PBXVariantGroup; 180 | children = ( 181 | 357B352B1FCCF6AD007CE251 /* Base */, 182 | ); 183 | name = Main.storyboard; 184 | sourceTree = ""; 185 | }; 186 | 357B352F1FCCF6AD007CE251 /* LaunchScreen.storyboard */ = { 187 | isa = PBXVariantGroup; 188 | children = ( 189 | 357B35301FCCF6AD007CE251 /* Base */, 190 | ); 191 | name = LaunchScreen.storyboard; 192 | sourceTree = ""; 193 | }; 194 | /* End PBXVariantGroup section */ 195 | 196 | /* Begin XCBuildConfiguration section */ 197 | 357B35331FCCF6AD007CE251 /* Debug */ = { 198 | isa = XCBuildConfiguration; 199 | buildSettings = { 200 | ALWAYS_SEARCH_USER_PATHS = NO; 201 | CLANG_ANALYZER_NONNULL = YES; 202 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 203 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 204 | CLANG_CXX_LIBRARY = "libc++"; 205 | CLANG_ENABLE_MODULES = YES; 206 | CLANG_ENABLE_OBJC_ARC = YES; 207 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 208 | CLANG_WARN_BOOL_CONVERSION = YES; 209 | CLANG_WARN_COMMA = YES; 210 | CLANG_WARN_CONSTANT_CONVERSION = YES; 211 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 212 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 213 | CLANG_WARN_EMPTY_BODY = YES; 214 | CLANG_WARN_ENUM_CONVERSION = YES; 215 | CLANG_WARN_INFINITE_RECURSION = YES; 216 | CLANG_WARN_INT_CONVERSION = YES; 217 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 218 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 219 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 220 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 221 | CLANG_WARN_STRICT_PROTOTYPES = YES; 222 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 223 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 224 | CLANG_WARN_UNREACHABLE_CODE = YES; 225 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 226 | CODE_SIGN_IDENTITY = "iPhone Developer"; 227 | COPY_PHASE_STRIP = NO; 228 | DEBUG_INFORMATION_FORMAT = dwarf; 229 | ENABLE_STRICT_OBJC_MSGSEND = YES; 230 | ENABLE_TESTABILITY = YES; 231 | GCC_C_LANGUAGE_STANDARD = gnu11; 232 | GCC_DYNAMIC_NO_PIC = NO; 233 | GCC_NO_COMMON_BLOCKS = YES; 234 | GCC_OPTIMIZATION_LEVEL = 0; 235 | GCC_PREPROCESSOR_DEFINITIONS = ( 236 | "DEBUG=1", 237 | "$(inherited)", 238 | ); 239 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 240 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 241 | GCC_WARN_UNDECLARED_SELECTOR = YES; 242 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 243 | GCC_WARN_UNUSED_FUNCTION = YES; 244 | GCC_WARN_UNUSED_VARIABLE = YES; 245 | IPHONEOS_DEPLOYMENT_TARGET = 11.1; 246 | MTL_ENABLE_DEBUG_INFO = YES; 247 | ONLY_ACTIVE_ARCH = YES; 248 | SDKROOT = iphoneos; 249 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 250 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 251 | }; 252 | name = Debug; 253 | }; 254 | 357B35341FCCF6AD007CE251 /* Release */ = { 255 | isa = XCBuildConfiguration; 256 | buildSettings = { 257 | ALWAYS_SEARCH_USER_PATHS = NO; 258 | CLANG_ANALYZER_NONNULL = YES; 259 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 260 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 261 | CLANG_CXX_LIBRARY = "libc++"; 262 | CLANG_ENABLE_MODULES = YES; 263 | CLANG_ENABLE_OBJC_ARC = YES; 264 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 265 | CLANG_WARN_BOOL_CONVERSION = YES; 266 | CLANG_WARN_COMMA = YES; 267 | CLANG_WARN_CONSTANT_CONVERSION = YES; 268 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 269 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 270 | CLANG_WARN_EMPTY_BODY = YES; 271 | CLANG_WARN_ENUM_CONVERSION = YES; 272 | CLANG_WARN_INFINITE_RECURSION = YES; 273 | CLANG_WARN_INT_CONVERSION = YES; 274 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 275 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 276 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 277 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 278 | CLANG_WARN_STRICT_PROTOTYPES = YES; 279 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 280 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 281 | CLANG_WARN_UNREACHABLE_CODE = YES; 282 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 283 | CODE_SIGN_IDENTITY = "iPhone Developer"; 284 | COPY_PHASE_STRIP = NO; 285 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 286 | ENABLE_NS_ASSERTIONS = NO; 287 | ENABLE_STRICT_OBJC_MSGSEND = YES; 288 | GCC_C_LANGUAGE_STANDARD = gnu11; 289 | GCC_NO_COMMON_BLOCKS = YES; 290 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 291 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 292 | GCC_WARN_UNDECLARED_SELECTOR = YES; 293 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 294 | GCC_WARN_UNUSED_FUNCTION = YES; 295 | GCC_WARN_UNUSED_VARIABLE = YES; 296 | IPHONEOS_DEPLOYMENT_TARGET = 11.1; 297 | MTL_ENABLE_DEBUG_INFO = NO; 298 | SDKROOT = iphoneos; 299 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 300 | VALIDATE_PRODUCT = YES; 301 | }; 302 | name = Release; 303 | }; 304 | 357B35361FCCF6AD007CE251 /* Debug */ = { 305 | isa = XCBuildConfiguration; 306 | buildSettings = { 307 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 308 | CODE_SIGN_STYLE = Automatic; 309 | DEVELOPMENT_TEAM = J4N8RS9Y8V; 310 | INFOPLIST_FILE = SwiftBridgeDemo/Info.plist; 311 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 312 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 313 | PRODUCT_BUNDLE_IDENTIFIER = cc.onezen.SwiftBridgeDemo; 314 | PRODUCT_NAME = "$(TARGET_NAME)"; 315 | SWIFT_OBJC_BRIDGING_HEADER = "SwiftBridgeDemo/Bridge-header.h"; 316 | SWIFT_VERSION = 4.0; 317 | TARGETED_DEVICE_FAMILY = "1,2"; 318 | USER_HEADER_SEARCH_PATHS = "${SRCROOT}/**"; 319 | }; 320 | name = Debug; 321 | }; 322 | 357B35371FCCF6AD007CE251 /* Release */ = { 323 | isa = XCBuildConfiguration; 324 | buildSettings = { 325 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 326 | CODE_SIGN_STYLE = Automatic; 327 | DEVELOPMENT_TEAM = J4N8RS9Y8V; 328 | INFOPLIST_FILE = SwiftBridgeDemo/Info.plist; 329 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 330 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 331 | PRODUCT_BUNDLE_IDENTIFIER = cc.onezen.SwiftBridgeDemo; 332 | PRODUCT_NAME = "$(TARGET_NAME)"; 333 | SWIFT_OBJC_BRIDGING_HEADER = "SwiftBridgeDemo/Bridge-header.h"; 334 | SWIFT_VERSION = 4.0; 335 | TARGETED_DEVICE_FAMILY = "1,2"; 336 | USER_HEADER_SEARCH_PATHS = "${SRCROOT}/**"; 337 | }; 338 | name = Release; 339 | }; 340 | /* End XCBuildConfiguration section */ 341 | 342 | /* Begin XCConfigurationList section */ 343 | 357B351E1FCCF6AD007CE251 /* Build configuration list for PBXProject "SwiftBridgeDemo" */ = { 344 | isa = XCConfigurationList; 345 | buildConfigurations = ( 346 | 357B35331FCCF6AD007CE251 /* Debug */, 347 | 357B35341FCCF6AD007CE251 /* Release */, 348 | ); 349 | defaultConfigurationIsVisible = 0; 350 | defaultConfigurationName = Release; 351 | }; 352 | 357B35351FCCF6AD007CE251 /* Build configuration list for PBXNativeTarget "SwiftBridgeDemo" */ = { 353 | isa = XCConfigurationList; 354 | buildConfigurations = ( 355 | 357B35361FCCF6AD007CE251 /* Debug */, 356 | 357B35371FCCF6AD007CE251 /* Release */, 357 | ); 358 | defaultConfigurationIsVisible = 0; 359 | defaultConfigurationName = Release; 360 | }; 361 | /* End XCConfigurationList section */ 362 | }; 363 | rootObject = 357B351B1FCCF6AD007CE251 /* Project object */; 364 | } 365 | -------------------------------------------------------------------------------- /SwiftBridgeDemo/SwiftBridgeDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /SwiftBridgeDemo/SwiftBridgeDemo/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // SwiftBridgeDemo 4 | // 5 | // Created by wz on 2017/11/28. 6 | // Copyright © 2017年 wz. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 18 | // Override point for customization after application launch. 19 | return true 20 | } 21 | 22 | func applicationWillResignActive(_ application: UIApplication) { 23 | // 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. 24 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 25 | } 26 | 27 | func applicationDidEnterBackground(_ application: UIApplication) { 28 | // 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. 29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 30 | } 31 | 32 | func applicationWillEnterForeground(_ application: UIApplication) { 33 | // 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. 34 | } 35 | 36 | func applicationDidBecomeActive(_ application: UIApplication) { 37 | // 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. 38 | } 39 | 40 | func applicationWillTerminate(_ application: UIApplication) { 41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 42 | } 43 | 44 | 45 | } 46 | 47 | -------------------------------------------------------------------------------- /SwiftBridgeDemo/SwiftBridgeDemo/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /SwiftBridgeDemo/SwiftBridgeDemo/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 | -------------------------------------------------------------------------------- /SwiftBridgeDemo/SwiftBridgeDemo/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 | 27 | 35 | 43 | 51 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /SwiftBridgeDemo/SwiftBridgeDemo/Bridge-header.h: -------------------------------------------------------------------------------- 1 | // 2 | // Bridge-header.h 3 | // SwiftBridgeDemo 4 | // 5 | // Created by wz on 2017/11/28. 6 | // Copyright © 2017年 wz. All rights reserved. 7 | // 8 | 9 | #ifndef Bridge_header_h 10 | #define Bridge_header_h 11 | 12 | #import "YCDownloadSession.h" 13 | 14 | 15 | #endif /* Bridge_header_h */ 16 | -------------------------------------------------------------------------------- /SwiftBridgeDemo/SwiftBridgeDemo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSAppTransportSecurity 6 | 7 | NSAllowsArbitraryLoads 8 | 9 | 10 | CFBundleDevelopmentRegion 11 | $(DEVELOPMENT_LANGUAGE) 12 | CFBundleExecutable 13 | $(EXECUTABLE_NAME) 14 | CFBundleIdentifier 15 | $(PRODUCT_BUNDLE_IDENTIFIER) 16 | CFBundleInfoDictionaryVersion 17 | 6.0 18 | CFBundleName 19 | $(PRODUCT_NAME) 20 | CFBundlePackageType 21 | APPL 22 | CFBundleShortVersionString 23 | 1.0 24 | CFBundleVersion 25 | 1 26 | LSRequiresIPhoneOS 27 | 28 | UILaunchStoryboardName 29 | LaunchScreen 30 | UIMainStoryboardFile 31 | Main 32 | UIRequiredDeviceCapabilities 33 | 34 | armv7 35 | 36 | UISupportedInterfaceOrientations 37 | 38 | UIInterfaceOrientationPortrait 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UISupportedInterfaceOrientations~ipad 43 | 44 | UIInterfaceOrientationPortrait 45 | UIInterfaceOrientationPortraitUpsideDown 46 | UIInterfaceOrientationLandscapeLeft 47 | UIInterfaceOrientationLandscapeRight 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /SwiftBridgeDemo/SwiftBridgeDemo/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // SwiftBridgeDemo 4 | // 5 | // Created by wz on 2017/11/28. 6 | // Copyright © 2017年 wz. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ViewController: UIViewController { 12 | 13 | 14 | let downloadUrl = "http://dldir1.qq.com/qqfile/QQforMac/QQ_V6.0.1.dmg" 15 | var task:YCDownloadTask? = nil 16 | 17 | @IBAction func start(_ sender: Any) { 18 | task = YCDownloader.downloader().download(withUrl: downloadUrl, progress: { (progress, task) in 19 | 20 | print(progress.fractionCompleted) 21 | 22 | }, completion: { (path, error) in 23 | if (error != nil) { 24 | print(error!) 25 | }else{ 26 | print(path!) 27 | } 28 | }) 29 | 30 | 31 | } 32 | @IBAction func pause(_ sender: Any) { 33 | if let dTask = task { 34 | YCDownloader.downloader().pause(dTask) 35 | } 36 | 37 | } 38 | 39 | @IBAction func resume(_ sender: Any) { 40 | if let dTask = task { 41 | YCDownloader.downloader().resumeTask(dTask) 42 | } 43 | } 44 | @IBAction func stop(_ sender: Any) { 45 | if let dTask = task { 46 | YCDownloader.downloader().cancel(dTask) 47 | } 48 | } 49 | } 50 | 51 | 52 | -------------------------------------------------------------------------------- /YCDownloadSession.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # Be sure to run `pod spec lint YCDownloadSession.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 | # :tag => "#{s.version}" 8 | 9 | Pod::Spec.new do |s| 10 | s.name = "YCDownloadSession" 11 | s.version = "2.0.6" 12 | s.summary = "iOS background download video or file" 13 | s.homepage = "https://github.com/onezens/YCDownloadSession" 14 | s.license = "MIT" 15 | s.author = { "onezens" => "mail@onezen.cc" } 16 | s.platform = :ios, "8.0" 17 | s.source = { :git => "https://github.com/onezens/YCDownloadSession.git", :tag => "#{s.version}" } 18 | 19 | s.subspec 'Core' do |c| 20 | c.source_files = "YCDownloadSession/Core/*.{h,m}" 21 | c.public_header_files = "YCDownloadSession/Core/*.h" 22 | end 23 | 24 | s.subspec 'Mgr' do |m| 25 | m.dependency 'YCDownloadSession/Core' 26 | m.source_files = "YCDownloadSession/*.{h,m}" 27 | m.public_header_files = "YCDownloadSession/*.h" 28 | end 29 | 30 | s.default_subspec = 'Core','Mgr' 31 | s.requires_arc = true 32 | s.frameworks = 'CFNetwork' 33 | 34 | end 35 | -------------------------------------------------------------------------------- /YCDownloadSession/Core/YCDownloadDB.h: -------------------------------------------------------------------------------- 1 | // 2 | // YCDownloadDB.h 3 | // YCDownloadSession 4 | // 5 | // Created by wz on 2019/4/3. 6 | // Copyright © 2019 onezen.cc. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "YCDownloadTask.h" 11 | 12 | #ifndef YCDownload_Mgr_Item 13 | #if __has_include() 14 | #define YCDownload_Mgr_Item 1 15 | #import 16 | #elif __has_include("YCDownloadItem.h") 17 | #define YCDownload_Mgr_Item 1 18 | #import "YCDownloadItem.h" 19 | #else 20 | #define YCDownload_Mgr_Item 0 21 | #endif 22 | #endif 23 | 24 | @interface YCDownloadDB : NSObject 25 | 26 | + (NSArray *)fetchAllDownloadTasks; 27 | + (YCDownloadTask *)taskWithTid:(NSString *)tid; 28 | + (NSArray *)taskWithUrl:(NSString *)url; 29 | + (NSArray *)taskWithStid:(NSInteger)stid; //TODO: add url 30 | + (void)removeAllTasks; 31 | + (BOOL)removeTask:(YCDownloadTask *)task; 32 | + (BOOL)saveTask:(YCDownloadTask *)task; 33 | + (void)saveAllData; 34 | 35 | @end 36 | 37 | #if YCDownload_Mgr_Item 38 | @interface YCDownloadDB(item) 39 | + (NSArray *)fetchAllDownloadItemWithUid:(NSString *)uid; 40 | + (NSArray *)fetchAllDownloadedItemWithUid:(NSString *)uid; 41 | + (NSArray *)fetchAllDownloadingItemWithUid:(NSString *)uid; 42 | + (NSArray *)itemsWithUrl:(NSString *)downloadUrl uid:(NSString *)uid; 43 | + (YCDownloadItem *)itemWithTaskId:(NSString *)taskId; 44 | + (YCDownloadItem *)itemWithFid:(NSString *)fid uid:(NSString *)uid; 45 | + (void)removeAllItemsWithUid:(NSString *)uid; 46 | + (BOOL)removeItemWithTaskId:(NSString *)taskId; 47 | + (BOOL)saveItem:(YCDownloadItem *)item; 48 | @end 49 | #endif 50 | -------------------------------------------------------------------------------- /YCDownloadSession/Core/YCDownloadSession.h: -------------------------------------------------------------------------------- 1 | // 2 | // YCDownloadSession.h 3 | // YCDownloadSession 4 | // 5 | // Created by wz on 17/3/14. 6 | // Copyright © 2017年 onezen.cc. All rights reserved. 7 | // Contact me: http://www.onezen.cc/about/ 8 | // Github: https://github.com/onezens/YCDownloadSession 9 | // 10 | // 11 | 12 | #ifndef YCDownload_H 13 | #define YCDownload_H 14 | 15 | #import "YCDownloader.h" 16 | #import "YCDownloadDB.h" 17 | #import "YCDownloadUtils.h" 18 | 19 | #ifndef YCDownload_Manager 20 | #if __has_include() && __has_include() 21 | #define YCDownload_Manager 1 22 | #import 23 | #elif __has_include("YCDownloadManager.h") && __has_include("YCDownloadItem.h") 24 | #define YCDownload_Manager 1 25 | #import "YCDownloadManager.h" 26 | #else 27 | #define YCDownload_Manager 0 28 | #endif 29 | #endif 30 | 31 | #endif 32 | -------------------------------------------------------------------------------- /YCDownloadSession/Core/YCDownloadTask.h: -------------------------------------------------------------------------------- 1 | // 2 | // YCDownloadTask.h 3 | // YCDownloadSession 4 | // 5 | // Created by wz on 17/3/15. 6 | // Copyright © 2017年 onezen.cc. All rights reserved. 7 | // Contact me: http://www.onezen.cc/about/ 8 | // Github: https://github.com/onezens/YCDownloadSession 9 | // 10 | 11 | #import 12 | @class YCDownloadTask; 13 | 14 | typedef void (^YCCompletionHandler)(NSString * _Nullable localPath, NSError * _Nullable error); 15 | typedef void (^YCProgressHandler)(NSProgress * _Nonnull progress,YCDownloadTask * _Nonnull task); 16 | 17 | #pragma mark - YCDownloadTask 18 | 19 | @interface YCDownloadTask : NSObject 20 | 21 | @property (nonatomic, strong, nullable) NSData *resumeData; 22 | @property (nonatomic, copy, readonly, nonnull) NSString *taskId; 23 | @property (nonatomic, copy, readonly, nonnull) NSString *downloadURL; 24 | @property (nonatomic, assign, readonly) int64_t fileSize; 25 | @property (nonatomic, assign) int64_t downloadedSize; 26 | @property (nonatomic, copy, nonnull) NSString *version; 27 | /** 28 | default value: NSURLSessionTaskPriorityDefault 29 | option: NSURLSessionTaskPriorityDefault NSURLSessionTaskPriorityLow NSURLSessionTaskPriorityHigh 30 | poiority float value range: 0.0 - 1.0 31 | */ 32 | @property (nonatomic, assign, readonly) float priority; 33 | @property (nonatomic, assign, readonly) BOOL isRunning; 34 | @property (nonatomic, strong, readonly, nonnull) NSProgress *progress; 35 | @property (nonatomic, copy, nullable) YCProgressHandler progressHandler; 36 | @property (nonatomic, copy, nullable) YCCompletionHandler completionHandler; 37 | @property (nonatomic, strong, nonnull) NSData *extraData; 38 | 39 | /** 40 | if no downloadTask, state = -1 41 | */ 42 | @property (nonatomic, assign, readonly) NSURLSessionTaskState state; 43 | 44 | #pragma mark - method 45 | - (void)updateTask; 46 | 47 | + (nonnull instancetype)taskWithRequest:(nonnull NSURLRequest *)request progress:(YCProgressHandler)progress completion:(YCCompletionHandler)completion; 48 | 49 | + (nonnull instancetype)taskWithRequest:(nonnull NSURLRequest *)request progress:(YCProgressHandler)progress completion:(YCCompletionHandler)completion priority:(float)priority; 50 | 51 | + (nonnull NSString *)downloaderVerison; 52 | 53 | @end 54 | 55 | 56 | #pragma mark - YCResumeData 57 | @interface YCResumeData: NSObject 58 | 59 | @property (nonatomic, copy) NSString *downloadUrl; 60 | @property (nonatomic, strong) NSMutableURLRequest *currentRequest; 61 | @property (nonatomic, strong) NSMutableURLRequest *originalRequest; 62 | @property (nonatomic, assign) NSInteger downloadSize; 63 | @property (nonatomic, copy) NSString *resumeTag; 64 | @property (nonatomic, assign) NSInteger resumeInfoVersion; 65 | @property (nonatomic, strong) NSDate *downloadDate; 66 | @property (nonatomic, copy) NSString *tempName; 67 | @property (nonatomic, copy) NSString *resumeRange; 68 | 69 | - (instancetype)initWithResumeData:(NSData *)resumeData; 70 | 71 | + (NSURLSessionDownloadTask *)downloadTaskWithCorrectResumeData:(NSData *)resumeData urlSession:(NSURLSession *)urlSession; 72 | 73 | /** 74 | 清除 NSURLSessionResumeByteRange 字段 75 | 修正iOS11.0 iOS11.1 多次暂停继续 文件大小不对的问题(iOS11.2官方已经修复) 76 | 77 | @param resumeData 原始resumeData 78 | @return 清除后resumeData 79 | */ 80 | + (NSData *)cleanResumeData:(NSData *)resumeData; 81 | @end 82 | 83 | -------------------------------------------------------------------------------- /YCDownloadSession/Core/YCDownloadTask.m: -------------------------------------------------------------------------------- 1 | // 2 | // YCDownloadTask.m 3 | // YCDownloadSession 4 | // 5 | // Created by wz on 17/3/15. 6 | // Copyright © 2017年 onezen.cc. All rights reserved. 7 | // Contact me: http://www.onezen.cc/about/ 8 | // Github: https://github.com/onezens/YCDownloadSession 9 | // 10 | 11 | #import "YCDownloadTask.h" 12 | #import "YCDownloadUtils.h" 13 | 14 | @interface YCDownloadTask() 15 | @property (nonatomic, assign) NSInteger pid; 16 | @property (nonatomic, assign) NSInteger stid; 17 | @property (nonatomic, copy) NSString *tmpName; 18 | @property (nonatomic, assign) BOOL needToRestart; 19 | @property (nonatomic, strong) NSURLRequest *request; 20 | @property (nonatomic, assign, readonly) BOOL isFinished; 21 | @property (nonatomic, assign, readonly) BOOL isSupportRange; 22 | @property (nonatomic, assign, readonly) NSUInteger createTime; 23 | @property (nonatomic, strong) NSURLSessionDownloadTask *downloadTask; 24 | @property (nonatomic, assign) BOOL isDeleted; 25 | @end 26 | 27 | 28 | @implementation YCDownloadTask 29 | 30 | @synthesize progress = _progress; 31 | 32 | - (instancetype)init { 33 | NSAssert(false, @"use - (instancetype)initWithRequest:(NSURLRequest *)request progress:(YCProgressHandler)progress completion:(YCCompletionHandler)completion"); 34 | return nil; 35 | } 36 | 37 | - (instancetype)initWithPrivate{ 38 | if (self = [super init]) { 39 | _createTime = [YCDownloadUtils sec_timestamp]; 40 | _version = [YCDownloadTask downloaderVerison]; 41 | } 42 | return self; 43 | } 44 | 45 | - (instancetype)initWithRequest:(NSURLRequest *)request progress:(YCProgressHandler)progress completion:(YCCompletionHandler)completion priority:(float)priority{ 46 | if (self = [self initWithPrivate]) { 47 | NSString *url = request.URL.absoluteString; 48 | _request = request; 49 | _downloadURL = url; 50 | _taskId = [YCDownloadTask taskIdForUrl:url fileId:[NSUUID UUID].UUIDString]; 51 | _priority = priority ? priority : NSURLSessionTaskPriorityDefault; 52 | _progressHandler = progress; 53 | _completionHandler = completion; 54 | } 55 | return self; 56 | } 57 | 58 | + (instancetype)taskWithDict:(NSMutableDictionary *)dict { 59 | YCDownloadTask *task = [[self alloc] initWithPrivate]; 60 | [task setValuesForKeysWithDictionary:dict]; 61 | return task; 62 | } 63 | 64 | + (instancetype)taskWithRequest:(NSURLRequest *)request progress:(YCProgressHandler)progress completion:(YCCompletionHandler)completion { 65 | return [[self alloc] initWithRequest:request progress:progress completion:completion priority:0]; 66 | } 67 | 68 | + (instancetype)taskWithRequest:(NSURLRequest *)request progress:(YCProgressHandler)progress completion:(YCCompletionHandler)completion priority:(float)priority { 69 | return [[self alloc] initWithRequest:request progress:progress completion:completion priority:priority]; 70 | } 71 | 72 | #pragma mark - public 73 | 74 | - (void)updateTask { 75 | _fileSize = [_downloadTask.response expectedContentLength]; 76 | } 77 | 78 | #pragma mark - setter 79 | 80 | - (void)setDownloadTask:(NSURLSessionDownloadTask *)downloadTask { 81 | NSAssert(downloadTask==nil || [downloadTask isKindOfClass:[NSURLSessionDownloadTask class]], @"downloadTask class", downloadTask.class); 82 | _downloadTask = downloadTask; 83 | downloadTask.priority = self.priority; 84 | } 85 | 86 | 87 | #pragma mark - getter 88 | 89 | - (NSURLSessionTaskState)state { 90 | return self.downloadTask ? self.downloadTask.state : -1; 91 | } 92 | 93 | - (NSProgress *)progress { 94 | if (!_progress) { 95 | _progress = [NSProgress progressWithTotalUnitCount:NSURLSessionTransferSizeUnknown]; 96 | } 97 | return _progress; 98 | } 99 | 100 | - (BOOL)isSupportRange { 101 | if([self.downloadTask.response isKindOfClass:[NSHTTPURLResponse class]]){ 102 | NSHTTPURLResponse *response = (NSHTTPURLResponse *)self.downloadTask.response; 103 | NSString *rangeHeader = [response.allHeaderFields valueForKey:@"Accept-Ranges"]; 104 | NSString *etag = [response.allHeaderFields valueForKey:@"ETag"]; 105 | return rangeHeader.length>0 && etag.length>0; 106 | } 107 | return true; 108 | } 109 | 110 | - (BOOL)isRunning { 111 | return self.downloadTask && self.downloadTask.state == NSURLSessionTaskStateRunning; 112 | } 113 | 114 | - (NSInteger)stid { 115 | return self.downloadTask ? self.downloadTask.taskIdentifier : _stid; 116 | } 117 | 118 | - (BOOL)isFinished { 119 | return self.fileSize != 0 && self.fileSize == self.downloadedSize; 120 | } 121 | 122 | - (NSString *)description { 123 | return [NSString stringWithFormat:@"{ taskId: %@, url: %@, stid: %ld}", self, self.taskId, self.downloadURL, (long)self.stid]; 124 | } 125 | 126 | + (NSString *)taskIdForUrl:(NSString *)url fileId:(NSString *)fileId { 127 | NSString *name = [YCDownloadUtils md5ForString:fileId.length>0 ? [NSString stringWithFormat:@"%@-%@",url, fileId] : url]; 128 | return name; 129 | } 130 | 131 | + (NSString *)downloaderVerison { 132 | return @"2.0.3"; 133 | } 134 | 135 | @end 136 | 137 | 138 | #pragma mark -- YCResumeData implementation 139 | 140 | static NSString * const kNSURLSessionDownloadURL = @"NSURLSessionDownloadURL"; 141 | static NSString * const kNSURLSessionResumeInfoTempFileName = @"NSURLSessionResumeInfoTempFileName"; 142 | static NSString * const kNSURLSessionResumeBytesReceived = @"NSURLSessionResumeBytesReceived"; 143 | static NSString * const kNSURLSessionResumeCurrentRequest = @"NSURLSessionResumeCurrentRequest"; 144 | static NSString * const kNSURLSessionResumeOriginalRequest = @"NSURLSessionResumeOriginalRequest"; 145 | static NSString * const kNSURLSessionResumeEntityTag = @"NSURLSessionResumeEntityTag"; 146 | static NSString * const kNSURLSessionResumeByteRange = @"NSURLSessionResumeByteRange"; 147 | static NSString * const kNSURLSessionResumeInfoVersion = @"NSURLSessionResumeInfoVersion"; 148 | static NSString * const kNSURLSessionResumeServerDownloadDate = @"NSURLSessionResumeServerDownloadDate"; 149 | 150 | 151 | @interface YCResumeData() 152 | 153 | @end 154 | 155 | @implementation YCResumeData 156 | 157 | - (instancetype)initWithResumeData:(NSData *)resumeData { 158 | if (self = [super init]) { 159 | [self decodeResumeData:resumeData]; 160 | } 161 | return self; 162 | } 163 | 164 | 165 | - (void)decodeResumeData:(NSData *)resumeData { 166 | 167 | id resumeDataObj = [NSPropertyListSerialization propertyListWithData:resumeData options:0 format:0 error:nil]; 168 | if ([resumeDataObj isKindOfClass:[NSDictionary class]]) { 169 | NSDictionary *resumeDict = resumeDataObj; 170 | NSString *downloadUrl = [resumeDict valueForKey:kNSURLSessionDownloadURL]; 171 | NSString *tempName = [resumeDict valueForKey:kNSURLSessionResumeInfoTempFileName]; 172 | NSNumber *downloadSize = [resumeDict valueForKey:kNSURLSessionResumeBytesReceived]; 173 | NSData *currentReqData = [resumeDict valueForKey:kNSURLSessionResumeCurrentRequest]; 174 | NSData *originalReqData = [resumeDict valueForKey:kNSURLSessionResumeOriginalRequest]; 175 | NSString *resumeTag = [resumeDict valueForKey:kNSURLSessionResumeEntityTag]; 176 | NSString *resumeRange = [resumeDict valueForKey:kNSURLSessionResumeByteRange]; 177 | NSNumber *resumeInfoVersion = [resumeDict valueForKey:kNSURLSessionResumeInfoVersion]; 178 | NSString *downloadDate = [resumeDict valueForKey:kNSURLSessionResumeServerDownloadDate]; 179 | 180 | 181 | _downloadUrl = downloadUrl; 182 | _tempName = tempName; 183 | _downloadSize = downloadSize.integerValue; 184 | _resumeTag = resumeTag; 185 | _resumeRange = resumeRange; 186 | _resumeInfoVersion = resumeInfoVersion.integerValue; 187 | NSDateFormatter *formatter = [[NSDateFormatter alloc] init]; 188 | _downloadDate = [formatter dateFromString:downloadDate]; 189 | 190 | [NSKeyedUnarchiver unarchiveObjectWithData:currentReqData]; 191 | [NSKeyedUnarchiver unarchiveObjectWithData:originalReqData]; 192 | } 193 | } 194 | 195 | 196 | + (NSData *)correctRequestData:(NSData *)data 197 | { 198 | if (!data) { 199 | return nil; 200 | } 201 | // return the same data if it's correct 202 | if ([NSKeyedUnarchiver unarchiveObjectWithData:data] != nil) { 203 | return data; 204 | } 205 | NSMutableDictionary *archive = [[NSPropertyListSerialization propertyListWithData:data options:NSPropertyListMutableContainersAndLeaves format:nil error:nil] mutableCopy]; 206 | 207 | if (!archive) { 208 | return nil; 209 | } 210 | NSInteger k = 0; 211 | id objectss = archive[@"$objects"]; 212 | while ([objectss[1] objectForKey:[NSString stringWithFormat:@"$%ld",(long)k]] != nil) { 213 | k += 1; 214 | } 215 | NSInteger i = 0; 216 | while ([archive[@"$objects"][1] objectForKey:[NSString stringWithFormat:@"__nsurlrequest_proto_prop_obj_%ld",(long)i]] != nil) { 217 | NSMutableArray *arr = archive[@"$objects"]; 218 | NSMutableDictionary *dic = arr[1]; 219 | id obj = [dic objectForKey:[NSString stringWithFormat:@"__nsurlrequest_proto_prop_obj_%ld",(long)i]]; 220 | if (obj) { 221 | [dic setValue:obj forKey:[NSString stringWithFormat:@"$%ld",(long)(i+k)]]; 222 | [dic removeObjectForKey:[NSString stringWithFormat:@"__nsurlrequest_proto_prop_obj_%ld",(long)i]]; 223 | [arr replaceObjectAtIndex:1 withObject:dic]; 224 | archive[@"$objects"] = arr; 225 | } 226 | i++; 227 | } 228 | if ([archive[@"$objects"][1] objectForKey:@"__nsurlrequest_proto_props"] != nil) { 229 | NSMutableArray *arr = archive[@"$objects"]; 230 | NSMutableDictionary *dic = arr[1]; 231 | id obj = [dic objectForKey:@"__nsurlrequest_proto_props"]; 232 | if (obj) { 233 | [dic setValue:obj forKey:[NSString stringWithFormat:@"$%ld",(long)(i+k)]]; 234 | [dic removeObjectForKey:@"__nsurlrequest_proto_props"]; 235 | [arr replaceObjectAtIndex:1 withObject:dic]; 236 | archive[@"$objects"] = arr; 237 | } 238 | } 239 | // Rectify weird "NSKeyedArchiveRootObjectKey" top key to NSKeyedArchiveRootObjectKey = "root" 240 | if ([archive[@"$top"] objectForKey:@"NSKeyedArchiveRootObjectKey"] != nil) { 241 | [archive[@"$top"] setObject:archive[@"$top"][@"NSKeyedArchiveRootObjectKey"] forKey: NSKeyedArchiveRootObjectKey]; 242 | [archive[@"$top"] removeObjectForKey:@"NSKeyedArchiveRootObjectKey"]; 243 | } 244 | // Reencode archived object 245 | NSData *result = [NSPropertyListSerialization dataWithPropertyList:archive format:NSPropertyListBinaryFormat_v1_0 options:0 error:nil]; 246 | return result; 247 | } 248 | 249 | + (NSMutableDictionary *)getResumeDictionary:(NSData *)data 250 | { 251 | NSMutableDictionary *iresumeDictionary = nil; 252 | if (YC_DEVICE_VERSION >= 10) { 253 | id root = nil; 254 | id keyedUnarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data]; 255 | @try { 256 | if (@available(iOS 9.0, *)) { 257 | root = [keyedUnarchiver decodeTopLevelObjectForKey:@"NSKeyedArchiveRootObjectKey" error:nil]; 258 | } else { 259 | root = [keyedUnarchiver decodeObjectForKey:@"NSKeyedArchiveRootObjectKey"]; 260 | } 261 | if (root == nil) { 262 | if (@available(iOS 9.0, *)) { 263 | root = [keyedUnarchiver decodeTopLevelObjectForKey:NSKeyedArchiveRootObjectKey error:nil]; 264 | } else { 265 | root = [keyedUnarchiver decodeObjectForKey:NSKeyedArchiveRootObjectKey]; 266 | } 267 | } 268 | } @catch(NSException *exception) { 269 | 270 | } 271 | [keyedUnarchiver finishDecoding]; 272 | iresumeDictionary = [root mutableCopy]; 273 | } 274 | 275 | if (iresumeDictionary == nil) { 276 | iresumeDictionary = [NSPropertyListSerialization propertyListWithData:data options:NSPropertyListMutableContainersAndLeaves format:nil error:nil]; 277 | } 278 | return iresumeDictionary; 279 | } 280 | 281 | + (NSData *)correctResumeData:(NSData *)data 282 | { 283 | if (YC_DEVICE_VERSION >= 11.2) { 284 | return data; 285 | } 286 | NSString *kResumeCurrentRequest = kNSURLSessionResumeCurrentRequest; 287 | NSString *kResumeOriginalRequest = kNSURLSessionResumeOriginalRequest; 288 | if (data == nil) { 289 | return nil; 290 | } 291 | NSMutableDictionary *resumeDictionary = [YCResumeData getResumeDictionary:data]; 292 | if (resumeDictionary == nil) { 293 | return nil; 294 | } 295 | resumeDictionary[kResumeCurrentRequest] = [YCResumeData correctRequestData: resumeDictionary[kResumeCurrentRequest]]; 296 | resumeDictionary[kResumeOriginalRequest] = [YCResumeData correctRequestData:resumeDictionary[kResumeOriginalRequest]]; 297 | NSData *result = [NSPropertyListSerialization dataWithPropertyList:resumeDictionary format:NSPropertyListXMLFormat_v1_0 options:0 error:nil]; 298 | return result; 299 | } 300 | 301 | 302 | + (NSURLSessionDownloadTask *)downloadTaskWithCorrectResumeData:(NSData *)resumeData urlSession:(NSURLSession *)urlSession { 303 | NSString *kResumeCurrentRequest = kNSURLSessionResumeCurrentRequest; 304 | NSString *kResumeOriginalRequest = kNSURLSessionResumeOriginalRequest; 305 | 306 | NSData *cData = [YCResumeData correctResumeData:resumeData]; 307 | cData = cData ? cData:resumeData; 308 | NSURLSessionDownloadTask *task = [urlSession downloadTaskWithResumeData:cData]; 309 | NSMutableDictionary *resumeDic = [YCResumeData getResumeDictionary:cData]; 310 | if (resumeDic) { 311 | if (task.originalRequest == nil) { 312 | NSData *originalReqData = resumeDic[kResumeOriginalRequest]; 313 | NSURLRequest *originalRequest = [NSKeyedUnarchiver unarchiveObjectWithData:originalReqData ]; 314 | if (originalRequest) { 315 | [task setValue:originalRequest forKey:@"originalRequest"]; 316 | } 317 | } 318 | if (task.currentRequest == nil) { 319 | NSData *currentReqData = resumeDic[kResumeCurrentRequest]; 320 | NSURLRequest *currentRequest = [NSKeyedUnarchiver unarchiveObjectWithData:currentReqData]; 321 | if (currentRequest) { 322 | [task setValue:currentRequest forKey:@"currentRequest"]; 323 | } 324 | } 325 | } 326 | return task; 327 | } 328 | 329 | + (NSData *)cleanResumeData:(NSData *)resumeData { 330 | NSString *dataString = [[NSString alloc] initWithData:resumeData encoding:NSUTF8StringEncoding]; 331 | if ([dataString containsString:@"NSURLSessionResumeByteRange"]) { 332 | NSRange rangeKey = [dataString rangeOfString:@"NSURLSessionResumeByteRange"]; 333 | NSString *headStr = [dataString substringToIndex:rangeKey.location]; 334 | NSString *backStr = [dataString substringFromIndex:rangeKey.location]; 335 | 336 | NSRange rangeValue = [backStr rangeOfString:@"\n\t"]; 337 | NSString *tailStr = [backStr substringFromIndex:rangeValue.location + rangeValue.length]; 338 | dataString = [headStr stringByAppendingString:tailStr]; 339 | } 340 | return [dataString dataUsingEncoding:NSUTF8StringEncoding]; 341 | } 342 | 343 | @end 344 | -------------------------------------------------------------------------------- /YCDownloadSession/Core/YCDownloadUtils.h: -------------------------------------------------------------------------------- 1 | // 2 | // YCDownloadUtils.h 3 | // YCDownloadSession 4 | // 5 | // Created by wz on 2018/6/22. 6 | // Copyright © 2018年 onezen.cc. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #define YC_DEVICE_VERSION [[[UIDevice currentDevice] systemVersion] floatValue] 12 | 13 | @interface YCDownloadUtils : NSObject 14 | 15 | /** 16 | 获取当前手机的空闲磁盘空间 17 | */ 18 | + (int64_t)fileSystemFreeSize; 19 | 20 | /** 21 | 将文件的字节大小,转换成更加容易识别的大小KB,MB,GB 22 | */ 23 | + (NSString *)fileSizeStringFromBytes:(int64_t)byteSize; 24 | 25 | /** 26 | 字符串md5加密 27 | 28 | @param string 需要MD5加密的字符串 29 | @return MD5后的值 30 | */ 31 | + (NSString *)md5ForString:(NSString *)string; 32 | 33 | /** 34 | 创建路径 35 | */ 36 | + (void)createPathIfNotExist:(NSString *)path; 37 | 38 | + (int64_t)fileSizeWithPath:(NSString *)path; 39 | 40 | + (NSString *)urlStrWithDownloadTask:(NSURLSessionDownloadTask *)downloadTask; 41 | 42 | + (NSUInteger)sec_timestamp; 43 | 44 | @end 45 | 46 | 47 | -------------------------------------------------------------------------------- /YCDownloadSession/Core/YCDownloadUtils.m: -------------------------------------------------------------------------------- 1 | // 2 | // YCDownloadUtils.m 3 | // YCDownloadSession 4 | // 5 | // Created by wz on 2018/6/22. 6 | // Copyright © 2018年 onezen.cc. All rights reserved. 7 | // 8 | 9 | #import "YCDownloadUtils.h" 10 | #import 11 | 12 | #define kCommonUtilsGigabyte (1024 * 1024 * 1024) 13 | #define kCommonUtilsMegabyte (1024 * 1024) 14 | #define kCommonUtilsKilobyte 1024 15 | 16 | @implementation YCDownloadUtils 17 | 18 | + (int64_t)fileSystemFreeSize { 19 | int64_t totalFreeSpace = 0; 20 | NSError *error = nil; 21 | NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); 22 | NSDictionary *dictionary = [[NSFileManager defaultManager] attributesOfFileSystemForPath:[paths lastObject] error: &error]; 23 | 24 | if (dictionary) { 25 | NSNumber *freeFileSystemSizeInBytes = [dictionary objectForKey:NSFileSystemFreeSize]; 26 | totalFreeSpace = [freeFileSystemSizeInBytes longLongValue]; 27 | } 28 | return totalFreeSpace; 29 | } 30 | 31 | + (NSString *)fileSizeStringFromBytes:(int64_t)byteSize { 32 | if (kCommonUtilsGigabyte <= byteSize) { 33 | return [NSString stringWithFormat:@"%@GB", [self numberStringFromDouble:(double)byteSize / kCommonUtilsGigabyte]]; 34 | } 35 | if (kCommonUtilsMegabyte <= byteSize) { 36 | return [NSString stringWithFormat:@"%@MB", [self numberStringFromDouble:(double)byteSize / kCommonUtilsMegabyte]]; 37 | } 38 | if (kCommonUtilsKilobyte <= byteSize) { 39 | return [NSString stringWithFormat:@"%@KB", [self numberStringFromDouble:(double)byteSize / kCommonUtilsKilobyte]]; 40 | } 41 | return [NSString stringWithFormat:@"%luB", (unsigned long)byteSize]; 42 | } 43 | 44 | + (NSString *)numberStringFromDouble:(const double)num { 45 | NSInteger section = round((num - (NSInteger)num) * 100); 46 | if (section % 10) { 47 | return [NSString stringWithFormat:@"%.2f", num]; 48 | } 49 | if (section > 0) { 50 | return [NSString stringWithFormat:@"%.1f", num]; 51 | } 52 | return [NSString stringWithFormat:@"%.0f", num]; 53 | } 54 | 55 | + (NSString *)md5ForString:(NSString *)string { 56 | const char *str = [string UTF8String]; 57 | if (str == NULL) str = ""; 58 | unsigned char r[CC_MD5_DIGEST_LENGTH]; 59 | CC_MD5(str, (CC_LONG)strlen(str), r); 60 | NSString *md5Result = [NSString stringWithFormat:@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", 61 | r[0], r[1], r[2], r[3], r[4], r[5], r[6], r[7], r[8], r[9], r[10], r[11], r[12], r[13], r[14], r[15]]; 62 | return md5Result; 63 | } 64 | 65 | + (void)createPathIfNotExist:(NSString *)path { 66 | if(![[NSFileManager defaultManager] fileExistsAtPath:path]){ 67 | [[NSFileManager defaultManager] createDirectoryAtPath:path withIntermediateDirectories:true attributes:nil error:nil]; 68 | } 69 | } 70 | + (int64_t)fileSizeWithPath:(NSString *)path { 71 | if(![[NSFileManager defaultManager] fileExistsAtPath:path]) return 0; 72 | NSDictionary *dic = [[NSFileManager defaultManager] attributesOfItemAtPath:path error:nil]; 73 | return dic ? (int64_t)[dic fileSize] : 0; 74 | } 75 | 76 | + (NSString *)urlStrWithDownloadTask:(NSURLSessionDownloadTask *)downloadTask { 77 | return downloadTask.originalRequest.URL.absoluteString ? : downloadTask.currentRequest.URL.absoluteString; 78 | } 79 | 80 | + (NSUInteger)sec_timestamp { 81 | return (NSUInteger)[[NSDate date] timeIntervalSince1970]; 82 | } 83 | 84 | @end 85 | 86 | -------------------------------------------------------------------------------- /YCDownloadSession/Core/YCDownloader.h: -------------------------------------------------------------------------------- 1 | // 2 | // YCDownload.h 3 | // YCDownloadSession 4 | // 5 | // Created by wz on 2018/8/27. 6 | // Copyright © 2018 onezen.cc. All rights reserved. 7 | // Contact me: http://www.onezen.cc/about/ 8 | // Github: https://github.com/onezens/YCDownloadSession 9 | // 10 | 11 | #import 12 | #import "YCDownloadTask.h" 13 | typedef void (^BGCompletedHandler)(void); 14 | 15 | /** 16 | 下载完成后的数据处理行为 17 | - YCDownloadTaskCacheModeDefault: 下载完成后,删除库中的下载数据 18 | - YCDownloadTaskCacheModeKeep: 下载完成后,不删除库中的下载数据 19 | */ 20 | typedef NS_ENUM(NSUInteger, YCDownloadTaskCacheMode) { 21 | YCDownloadTaskCacheModeDefault, 22 | YCDownloadTaskCacheModeKeep 23 | }; 24 | 25 | @protocol YCDownloader 26 | /** 27 | 单利downloader 28 | */ 29 | + (nonnull instancetype)downloader; 30 | @end 31 | 32 | @interface YCDownloader : NSObject 33 | /** 34 | 是否允许蜂窝煤网络下载 35 | */ 36 | @property (nonatomic, assign) BOOL allowsCellularAccess; 37 | 38 | /** 39 | 下载完成后的数据处理行为 40 | */ 41 | @property (nonatomic, assign) YCDownloadTaskCacheMode taskCachekMode; 42 | 43 | /** 44 | 单利downloader 45 | */ 46 | + (nonnull instancetype)downloader; 47 | 48 | /** 49 | 通过url开始创建下载任务,手动调用resumeTask:开始下载 50 | 51 | @param url 下载url 52 | @param progress 下载进度 53 | @param completion 下载成功或者失败回调 54 | @return 下载任务的task 55 | */ 56 | - (nonnull YCDownloadTask *)downloadWithUrl:(nonnull NSString *)url progress:(YCProgressHandler)progress completion:(YCCompletionHandler)completion; 57 | 58 | /** 59 | 通过request对象创建下载任务,可以自定义header等请求信息,手动调用resumeTask:开始下载 60 | 61 | @param request request 对象 62 | @param progress 下载进度回调 63 | @param completion 下载成功失败回调 64 | @return 下载任务task 65 | */ 66 | - (nonnull YCDownloadTask *)downloadWithRequest:(nonnull NSURLRequest *)request progress:(YCProgressHandler)progress completion:(YCCompletionHandler)completion; 67 | 68 | /** 69 | 通过request对象进行创建下载任务,可以自定义header等请求信息,手动调用resumeTask:开始下载 70 | 71 | @param request request 对象 72 | @param progress 下载进度回调 73 | @param completion 下载成功失败回调 74 | @param priority 下载任务优先级,默认是 NSURLSessionTaskPriorityDefault, 取值范围0~1 75 | @return 下载任务task 76 | */ 77 | - (nonnull YCDownloadTask *)downloadWithRequest:(nonnull NSURLRequest *)request progress:(YCProgressHandler)progress completion:(YCCompletionHandler)completion priority:(float)priority; 78 | 79 | 80 | /** 81 | 恢复下载任务,继续下载任务,主要用于app异常退出状态恢复,继续下载任务的回调设置 82 | 83 | @param tid 下载任务的taskId 84 | @param progress 下载进度回调 85 | @param completion 下载成功失败回调 86 | @return 下载任务task 87 | */ 88 | - (nullable YCDownloadTask *)resumeDownloadTaskWithTid:(NSString *)tid progress:(YCProgressHandler)progress completion:(YCCompletionHandler)completion; 89 | 90 | /** 91 | 继续下载任务 92 | 93 | @param task 需要继续的task 94 | @return 是否继续下载成功,失败后可冲洗下载 95 | */ 96 | - (BOOL)resumeTask:(nonnull YCDownloadTask *)task; 97 | 98 | 99 | /** 100 | 暂停下载任务 101 | 102 | @param task 需要暂停的task 103 | */ 104 | - (void)pauseTask:(nonnull YCDownloadTask *)task; 105 | 106 | /** 107 | 暂停下载任务 108 | 109 | @param task 需要删除的task 110 | */ 111 | - (void)cancelTask:(nonnull YCDownloadTask *)task; 112 | 113 | /** 114 | 后台某一下载任务完成时,第一次在AppDelegate中的 -(void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)(void))completionHandler 115 | 回调方法中调用该方法。多个task,一个session,只调用一次AppDelegate的回调方法。 116 | completionHandler 回调执行后,app被系统唤醒的状态会变为休眠状态。 117 | 118 | @param handler 后台任务结束后的调用的处理方法 119 | @param identifier background session 的标识 120 | */ 121 | -(void)addCompletionHandler:(BGCompletedHandler)handler identifier:(nonnull NSString *)identifier; 122 | 123 | @end 124 | -------------------------------------------------------------------------------- /YCDownloadSession/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 2.0.3 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | NSPrincipalClass 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /YCDownloadSession/YCDownloadItem.h: -------------------------------------------------------------------------------- 1 | // 2 | // YCDownloadItem.h 3 | // YCDownloadSession 4 | // 5 | // Created by wz on 17/7/28. 6 | // Copyright © 2017年 onezen.cc. All rights reserved. 7 | // Contact me: http://www.onezen.cc/about/ 8 | // Github: https://github.com/onezens/YCDownloadSession 9 | // 10 | 11 | #import 12 | #import "YCDownloadTask.h" 13 | @class YCDownloadItem; 14 | 15 | extern NSString * const kDownloadTaskFinishedNoti; 16 | 17 | typedef NS_ENUM(NSUInteger, YCDownloadStatus) { 18 | YCDownloadStatusUnknow, 19 | YCDownloadStatusWaiting, 20 | YCDownloadStatusDownloading, 21 | YCDownloadStatusPaused, 22 | YCDownloadStatusFailed, 23 | YCDownloadStatusFinished 24 | }; 25 | 26 | @protocol YCDownloadItemDelegate 27 | 28 | @optional 29 | - (void)downloadItemStatusChanged:(nonnull YCDownloadItem *)item; 30 | - (void)downloadItem:(nonnull YCDownloadItem *)item downloadedSize:(int64_t)downloadedSize totalSize:(int64_t)totalSize; 31 | - (void)downloadItem:(nonnull YCDownloadItem *)item speed:(NSUInteger)speed speedDesc:(NSString *)speedDesc; 32 | @end 33 | 34 | @interface YCDownloadItem : NSObject 35 | 36 | -(nonnull instancetype)initWithUrl:(nonnull NSString *)url fileId:(nullable NSString *)fileId; 37 | +(nonnull instancetype)itemWithUrl:(nonnull NSString *)url fileId:(nullable NSString *)fileId; 38 | 39 | @property (nonatomic, copy, nonnull) NSString *taskId; 40 | @property (nonatomic, copy, readonly, nullable) NSString *fileId; 41 | @property (nonatomic, copy, readonly, nonnull) NSString *downloadURL; 42 | @property (nonatomic, copy, readonly, nonnull) NSString *version; 43 | @property (nonatomic, assign, readonly) int64_t fileSize; 44 | @property (nonatomic, assign, readonly) int64_t downloadedSize; 45 | @property (nonatomic, weak, nullable) id delegate; 46 | @property (nonatomic, assign) BOOL enableSpeed; 47 | @property (nonatomic, strong, nullable) NSData *extraData; 48 | @property (nonatomic, assign, readwrite) YCDownloadStatus downloadStatus; 49 | @property (nonatomic, copy, readonly, nullable) YCProgressHandler progressHandler; 50 | @property (nonatomic, copy, readonly, nullable) YCCompletionHandler completionHandler; 51 | /** 52 | 下载的文件在沙盒保存的类型,默认为video.可指定为pdf,image,等自定义类型 53 | */ 54 | @property (nonatomic, copy, nullable) NSString *fileType; 55 | @property (nonatomic, copy, nullable) NSString *uid; 56 | @property (nonatomic, copy, nonnull) NSString *saveRootPath; 57 | /**文件沙盒保存路径*/ 58 | @property (nonatomic, copy, readonly, nonnull) NSString *savePath; 59 | 60 | @end 61 | 62 | -------------------------------------------------------------------------------- /YCDownloadSession/YCDownloadItem.m: -------------------------------------------------------------------------------- 1 | // 2 | // YCDownloadItem.m 3 | // YCDownloadSession 4 | // 5 | // Created by wz on 17/7/28. 6 | // Copyright © 2017年 onezen.cc. All rights reserved. 7 | // Contact me: http://www.onezen.cc/about/ 8 | // Github: https://github.com/onezens/YCDownloadSession 9 | // 10 | 11 | #import "YCDownloadItem.h" 12 | #import "YCDownloadUtils.h" 13 | #import "YCDownloadDB.h" 14 | 15 | NSString * const kDownloadTaskFinishedNoti = @"kDownloadTaskFinishedNoti"; 16 | 17 | @interface YCDownloadTask(Downloader) 18 | @property (nonatomic, strong) NSURLSessionDownloadTask *downloadTask; 19 | @end 20 | 21 | @interface YCDownloadItem() 22 | @property (nonatomic, copy) NSString *rootPath; 23 | @property (nonatomic, assign) NSInteger pid; 24 | @property (nonatomic, assign) BOOL isRemoved; 25 | @property (nonatomic, assign) BOOL noNeedStartNext; 26 | @property (nonatomic, copy) NSString *fileExtension; 27 | @property (nonatomic, assign, readonly) NSUInteger createTime; 28 | @property (nonatomic, assign) uint64_t preDSize; 29 | @property (nonatomic, strong) NSTimer *speedTimer; 30 | @end 31 | 32 | @implementation YCDownloadItem 33 | 34 | #pragma mark - init 35 | 36 | - (instancetype)initWithPrivate{ 37 | if (self = [super init]) { 38 | _createTime = [YCDownloadUtils sec_timestamp]; 39 | _version = [YCDownloadTask downloaderVerison]; 40 | } 41 | return self; 42 | } 43 | 44 | - (instancetype)initWithUrl:(NSString *)url fileId:(NSString *)fileId { 45 | if (self = [self initWithPrivate]) { 46 | _downloadURL = url; 47 | _fileId = fileId; 48 | } 49 | return self; 50 | } 51 | + (instancetype)itemWithDict:(NSDictionary *)dict { 52 | YCDownloadItem *item = [[YCDownloadItem alloc] initWithPrivate]; 53 | [item setValuesForKeysWithDictionary:dict]; 54 | return item; 55 | } 56 | + (instancetype)itemWithUrl:(NSString *)url fileId:(NSString *)fileId { 57 | return [[YCDownloadItem alloc] initWithUrl:url fileId:fileId]; 58 | } 59 | 60 | #pragma mark - Handler 61 | - (void)downloadProgress:(YCDownloadTask *)task downloadedSize:(int64_t)downloadedSize fileSize:(int64_t)fileSize { 62 | if (self.fileSize==0) _fileSize = fileSize; 63 | if (!self.fileExtension) [self setFileExtensionWithTask:task]; 64 | _downloadedSize = downloadedSize; 65 | if ([self.delegate respondsToSelector:@selector(downloadItem:downloadedSize:totalSize:)]) { 66 | [self.delegate downloadItem:self downloadedSize:downloadedSize totalSize:fileSize]; 67 | } 68 | } 69 | 70 | - (void)downloadStatusChanged:(YCDownloadStatus)status downloadTask:(YCDownloadTask *)task { 71 | _downloadStatus = status; 72 | if ([self.delegate respondsToSelector:@selector(downloadItemStatusChanged:)]) { 73 | [self.delegate downloadItemStatusChanged:self]; 74 | } 75 | //通知优先级最后,不与上面的finished重合 76 | if (status == YCDownloadStatusFinished || status == YCDownloadStatusFailed) { 77 | [YCDownloadDB saveItem:self]; 78 | [[NSNotificationCenter defaultCenter] postNotificationName:kDownloadTaskFinishedNoti object:self]; 79 | } 80 | [self calculaterSpeedWithStatus:status]; 81 | } 82 | 83 | - (void)speedTimerRun { 84 | uint64_t size = self.downloadedSize> self.preDSize ? self.downloadedSize - self.preDSize : 0; 85 | if (size == 0) { 86 | [self.delegate downloadItem:self speed:0 speedDesc:@"0KB/s"]; 87 | }else{ 88 | NSString *ss = [NSString stringWithFormat:@"%@/s",[YCDownloadUtils fileSizeStringFromBytes:size]]; 89 | [self.delegate downloadItem:self speed:size speedDesc:ss]; 90 | } 91 | self.preDSize = self.downloadedSize; 92 | //NSLog(@"[YCDownload] [speedTimerRun] %@ dsize: %llu pdsize: %llu", ss, self.downloadedSize, self.preDownloadedSize); 93 | } 94 | 95 | - (void)invalidateSpeedTimer { 96 | [self.speedTimer invalidate]; 97 | self.speedTimer = nil; 98 | } 99 | 100 | - (void)calculaterSpeedWithStatus:(YCDownloadStatus)status { 101 | //计算下载速度 102 | if (!self.enableSpeed) return; 103 | if (status != YCDownloadStatusDownloading) { 104 | [self invalidateSpeedTimer]; 105 | [self.delegate downloadItem:self speed:0 speedDesc:@"0KB/s"]; 106 | }else{ 107 | [self.speedTimer fire]; 108 | } 109 | } 110 | 111 | #pragma mark - getter & setter 112 | 113 | - (void)setDownloadStatus:(YCDownloadStatus)downloadStatus { 114 | _downloadStatus = downloadStatus; 115 | if ([self.delegate respondsToSelector:@selector(downloadItemStatusChanged:)]) { 116 | [self.delegate downloadItemStatusChanged:self]; 117 | } 118 | [self calculaterSpeedWithStatus:downloadStatus]; 119 | } 120 | 121 | - (void)setSaveRootPath:(NSString *)saveRootPath { 122 | NSString *path = [saveRootPath stringByReplacingOccurrencesOfString:NSHomeDirectory() withString:@""]; 123 | _rootPath = path; 124 | } 125 | 126 | - (NSString *)saveRootPath { 127 | NSString *rootPath = self.rootPath; 128 | if(!rootPath){ 129 | rootPath = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, true).firstObject; 130 | rootPath = [rootPath stringByAppendingPathComponent:@"YCDownload"]; 131 | }else{ 132 | rootPath = [NSHomeDirectory() stringByAppendingPathComponent:rootPath]; 133 | } 134 | return rootPath; 135 | } 136 | 137 | 138 | - (void)setFileExtensionWithTask:(YCDownloadTask *)task { 139 | NSURLResponse *oriResponse =task.downloadTask.response; 140 | if ([oriResponse isKindOfClass:[NSHTTPURLResponse class]]) { 141 | NSHTTPURLResponse *response = (NSHTTPURLResponse *)oriResponse; 142 | NSString *extension = [[response.allHeaderFields valueForKey:@"Content-Type"] componentsSeparatedByString:@"/"].lastObject; 143 | if ([extension containsString:@";"]) { 144 | extension = [extension componentsSeparatedByString:@";"].firstObject; 145 | } 146 | if(extension.length==0) extension = response.suggestedFilename.pathExtension; 147 | _fileExtension = extension; 148 | }else{ 149 | NSLog(@"[YCDownload] [warning] downloadTask response class type error: %@", oriResponse); 150 | } 151 | } 152 | 153 | - (YCProgressHandler)progressHandler { 154 | __weak typeof(self) weakSelf = self; 155 | return ^(NSProgress *progress, YCDownloadTask *task){ 156 | if(weakSelf.downloadStatus == YCDownloadStatusWaiting){ 157 | [weakSelf downloadStatusChanged:YCDownloadStatusDownloading downloadTask:nil]; 158 | } 159 | [weakSelf downloadProgress:task downloadedSize:progress.completedUnitCount fileSize:(progress.totalUnitCount>0 ? progress.totalUnitCount : 0)]; 160 | }; 161 | } 162 | 163 | - (YCCompletionHandler)completionHandler { 164 | __weak typeof(self) weakSelf = self; 165 | return ^(NSString *localPath, NSError *error){ 166 | YCDownloadTask *task = [YCDownloadDB taskWithTid:self.taskId]; 167 | if (error) { 168 | NSLog(@"[YCDownload] [Item completionHandler] error : %@", error); 169 | [weakSelf downloadStatusChanged:YCDownloadStatusFailed downloadTask:nil]; 170 | if(!weakSelf.isRemoved) [YCDownloadDB saveItem:weakSelf]; 171 | return ; 172 | } 173 | 174 | // bg completion ,maybe had no extension 175 | if (!self.fileExtension) [self setFileExtensionWithTask:task]; 176 | NSError *saveError = nil; 177 | if([[NSFileManager defaultManager] fileExistsAtPath:self.savePath]){ 178 | NSLog(@"[YCDownload] [Item completionHandler] Warning file Exist at path: %@ and replaced it!", weakSelf.savePath); 179 | [[NSFileManager defaultManager] removeItemAtPath:self.savePath error:nil]; 180 | } 181 | 182 | if([[NSFileManager defaultManager] moveItemAtPath:localPath toPath:self.savePath error:&saveError]){ 183 | NSAssert(self.fileExtension, @"file extension can not nil!"); 184 | int64_t fileSize = [YCDownloadUtils fileSizeWithPath:weakSelf.savePath]; 185 | self->_downloadedSize = fileSize; 186 | self->_fileSize = fileSize; 187 | [weakSelf downloadStatusChanged:YCDownloadStatusFinished downloadTask:nil]; 188 | }else{ 189 | [weakSelf downloadStatusChanged:YCDownloadStatusFailed downloadTask:nil]; 190 | NSLog(@"[YCDownload] [Item completionHandler] move file failed error: %@ \nlocalPath: %@ \nsavePath:%@", saveError,localPath,self.savePath); 191 | } 192 | 193 | }; 194 | } 195 | 196 | - (NSTimer *)speedTimer { 197 | if (!_speedTimer) { 198 | _speedTimer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(speedTimerRun) userInfo:nil repeats:true]; 199 | } 200 | return _speedTimer; 201 | } 202 | 203 | #pragma mark - public 204 | 205 | - (NSString *)compatibleKey { 206 | return [YCDownloadTask downloaderVerison]; 207 | } 208 | 209 | - (NSString *)saveUidDirectory { 210 | return [[self saveRootPath] stringByAppendingPathComponent:self.uid]; 211 | } 212 | 213 | - (NSString *)saveDirectory { 214 | NSString *path = [self saveUidDirectory]; 215 | path = [path stringByAppendingPathComponent:(self.fileType ? self.fileType : @"data")]; 216 | [YCDownloadUtils createPathIfNotExist:path]; 217 | return path; 218 | } 219 | 220 | - (NSString *)saveName { 221 | NSString *saveName = self.fileId ? self.fileId : self.taskId; 222 | return [saveName stringByAppendingPathExtension: self.fileExtension.length>0 ? self.fileExtension : @"data"]; 223 | } 224 | 225 | - (NSString *)savePath { 226 | return [[self saveDirectory] stringByAppendingPathComponent:[self saveName]]; 227 | } 228 | 229 | - (NSString *)description { 230 | return [NSString stringWithFormat:@"{taskId: %@, url: %@ fileId: %@}", self, self.taskId, self.downloadURL, self.fileId]; 231 | } 232 | 233 | -(void)dealloc { 234 | [self invalidateSpeedTimer]; 235 | } 236 | 237 | @end 238 | -------------------------------------------------------------------------------- /YCDownloadSession/YCDownloadManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // YCDownloadManager.h 3 | // YCDownloadSession 4 | // 5 | // Created by wz on 17/3/24. 6 | // Copyright © 2017年 onezen.cc. All rights reserved. 7 | // Contact me: http://www.onezen.cc/about/ 8 | // Github: https://github.com/onezens/YCDownloadSession 9 | // 10 | 11 | 12 | #import 13 | #import "YCDownloadItem.h" 14 | #import "YCDownloader.h" 15 | 16 | @interface YCDConfig: NSObject 17 | /** 18 | 设置用户标识 19 | */ 20 | @property (nonatomic, copy, nullable) NSString *uid; 21 | 22 | /** 23 | 文件保存根路径,默认是Library/Cache/YCDownload目录,系统磁盘不足时,会被系统清理 24 | 更多信息:https://developer.apple.com/library/content/documentation/FileManagement/Conceptual/FileSystemProgrammingGuide/FileSystemOverview/FileSystemOverview.html#//apple_ref/doc/uid/TP40010672-CH2-SW2 25 | */ 26 | @property (nonatomic, copy, nullable) NSString *saveRootPath; 27 | 28 | /** 29 | 最大下载任务个数 30 | */ 31 | @property (nonatomic, assign) NSUInteger maxTaskCount; 32 | 33 | 34 | @property (nonatomic, assign) YCDownloadTaskCacheMode taskCachekMode; 35 | 36 | /** 37 | 冷启动是否自动恢复下载中的任务,否则会暂停所有任务 38 | */ 39 | @property (nonatomic, assign) BOOL launchAutoResumeDownload; 40 | 41 | @end 42 | 43 | 44 | @interface YCDownloadManager : NSObject 45 | 46 | /** 47 | 下载管理配置 48 | */ 49 | + (void)mgrWithConfig:(nonnull YCDConfig *)config; 50 | 51 | 52 | /** 53 | 切换用户,更新uid 54 | */ 55 | + (void)updateUid:(NSString *)uid; 56 | 57 | /** 58 | 开始/创建一个后台下载任务。 59 | 60 | @param item 下载信息的item 61 | */ 62 | + (void)startDownloadWithItem:(nonnull YCDownloadItem *)item; 63 | 64 | /** 65 | 开始/创建一个后台下载任务。 66 | 67 | @param item 下载信息的item 68 | @param priority 下载任务的task,默认:NSURLSessionTaskPriorityDefault 可选参数:NSURLSessionTaskPriorityLow NSURLSessionTaskPriorityHigh NSURLSessionTaskPriorityDefault 范围:0.0-1.1 69 | */ 70 | + (void)startDownloadWithItem:(nonnull YCDownloadItem *)item priority:(float)priority; 71 | 72 | 73 | /** 74 | 开始/创建一个后台下载任务。 75 | 76 | @param downloadURLString 下载的资源的url 77 | */ 78 | + (void)startDownloadWithUrl:(nonnull NSString *)downloadURLString; 79 | 80 | /** 81 | 开始/创建一个后台下载任务。 82 | 83 | @param downloadURLString 下载的资源的url, 不可以为空 84 | @param fileId 非资源的标识,可以为空,用作下载文件保存的名称 85 | @param priority 下载任务的task,默认:NSURLSessionTaskPriorityDefault 可选参数:NSURLSessionTaskPriorityLow NSURLSessionTaskPriorityHigh NSURLSessionTaskPriorityDefault 范围:0.0-1.1 86 | @param extraData item对应的需要存储在本地数据库中的信息 87 | */ 88 | + (void)startDownloadWithUrl:(nonnull NSString *)downloadURLString fileId:(nullable NSString *)fileId priority:(float)priority extraData:(nullable NSData *)extraData; 89 | 90 | /** 91 | 暂停一个后台下载任务 92 | 93 | @param item 创建的下载任务item 94 | */ 95 | + (void)pauseDownloadWithItem:(nonnull YCDownloadItem *)item; 96 | 97 | /** 98 | 继续开始一个后台下载任务 99 | 100 | @param item 创建的下载任务item 101 | */ 102 | + (void)resumeDownloadWithItem:(nonnull YCDownloadItem *)item; 103 | 104 | /** 105 | 删除一个后台下载任务,同时会删除当前任务下载的缓存数据 106 | 107 | @param item 创建的下载任务item 108 | */ 109 | + (void)stopDownloadWithItem:(nonnull YCDownloadItem *)item; 110 | 111 | /** 112 | 暂停所有的下载 113 | */ 114 | + (void)pauseAllDownloadTask; 115 | 116 | /** 117 | 开始所有的下载 118 | */ 119 | + (void)resumeAllDownloadTask; 120 | 121 | /** 122 | 清空所有的下载文件缓存,YCDownloadManager所管理的所有文件,不包括YCDownloadSession单独下载的文件 123 | */ 124 | + (void)removeAllCache; 125 | 126 | /** 127 | 获取所有的未完成的下载item 128 | */ 129 | + (nonnull NSArray *)downloadList; 130 | 131 | /** 132 | 获取所有已完成的下载item 133 | */ 134 | + (nonnull NSArray *)finishList; 135 | 136 | /** 137 | 根据fileId获取item 138 | */ 139 | + (nullable YCDownloadItem *)itemWithFileId:(NSString *)fid; 140 | 141 | /** 142 | 根据downloadUrl获取item 143 | */ 144 | + (nonnull NSArray *)itemsWithDownloadUrl:(nonnull NSString *)downloadUrl; 145 | 146 | /** 147 | 获取所有下载数据所占用的磁盘空间,不包括YCDownloadSession单独下载的文件 148 | */ 149 | + (int64_t)videoCacheSize; 150 | 151 | /** 152 | 是否允许蜂窝煤网络下载,以及网络状态变为蜂窝煤是否允许下载,必须把所有的downloadTask全部暂停,然后重新创建。否则,原先创建的 153 | 下载task依旧在网络切换为蜂窝煤网络时会继续下载 154 | 155 | @param isAllow 是否允许蜂窝煤网络下载 156 | */ 157 | + (void)allowsCellularAccess:(BOOL)isAllow; 158 | 159 | /** 160 | 获取是否允许蜂窝煤访问 161 | */ 162 | + (BOOL)isAllowsCellularAccess; 163 | 164 | @end 165 | -------------------------------------------------------------------------------- /YCDownloadSession/YCDownloadManager.m: -------------------------------------------------------------------------------- 1 | // 2 | // YCDownloadManager.m 3 | // YCDownloadSession 4 | // 5 | // Created by wz on 17/3/24. 6 | // Copyright © 2017年 onezen.cc. All rights reserved. 7 | // Contact me: http://www.onezen.cc/about/ 8 | // Github: https://github.com/onezens/YCDownloadSession 9 | // 10 | 11 | #import "YCDownloadManager.h" 12 | #import "YCDownloadUtils.h" 13 | #import "YCDownloader.h" 14 | #import "YCDownloadDB.h" 15 | 16 | #define YCDownloadMgr [YCDownloadManager manager] 17 | 18 | @interface YCDownloader(Mgr) 19 | - (void)endBGCompletedHandler; 20 | @end 21 | 22 | 23 | @interface YCDownloadItem(Mgr) 24 | @property (nonatomic, assign) BOOL isRemoved; 25 | @property (nonatomic, assign) BOOL noNeedStartNext; 26 | @end 27 | 28 | @interface YCDownloadManager () 29 | { 30 | NSString *_uniqueId; 31 | } 32 | @property (nonatomic, strong) NSMutableArray *waitItems; 33 | @property (nonatomic, strong) NSMutableArray *runItems; 34 | @property (nonatomic, strong) YCDConfig *config; 35 | @end 36 | 37 | @implementation YCDownloadManager 38 | 39 | static id _instance; 40 | 41 | #pragma mark - init 42 | 43 | + (void)mgrWithConfig:(YCDConfig *)config { 44 | static dispatch_once_t onceToken; 45 | dispatch_once(&onceToken, ^{ 46 | _instance = [[self alloc] init]; 47 | YCDownloadMgr.config = config; 48 | [YCDownloadMgr initManager]; 49 | }); 50 | } 51 | 52 | + (instancetype)manager { 53 | NSAssert(_instance, @"please set config: [YCDownloadManager mgrWithConfig:config];"); 54 | return _instance; 55 | } 56 | 57 | - (instancetype)init { 58 | if (self = [super init]) { 59 | [self addNotification]; 60 | _runItems = [NSMutableArray array]; 61 | _waitItems = [NSMutableArray array]; 62 | } 63 | return self; 64 | } 65 | 66 | - (void)initManager{ 67 | [self setUid:self.config.uid]; 68 | [YCDownloader downloader].taskCachekMode = self.config.taskCachekMode; 69 | [self restoreItems]; 70 | } 71 | 72 | - (void)addNotification { 73 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(downloadTaskFinishNoti:) name:kDownloadTaskFinishedNoti object:nil]; 74 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(appWillTerminate) name:UIApplicationWillTerminateNotification object:nil]; 75 | } 76 | 77 | - (void)restoreItems { 78 | [[YCDownloadDB fetchAllDownloadItemWithUid:self.uid] enumerateObjectsUsingBlock:^(YCDownloadItem * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { 79 | [self downloadFinishedWithItem:obj]; 80 | YCDownloadTask *task = [self taskWithItem:obj]; 81 | if (obj.downloadStatus == YCDownloadStatusDownloading || ( task.isRunning && self.config.launchAutoResumeDownload)) { 82 | obj.downloadStatus = YCDownloadStatusDownloading; 83 | task.completionHandler = obj.completionHandler; 84 | task.progressHandler = obj.progressHandler; 85 | if (task.state != NSURLSessionTaskStateRunning) { 86 | obj.downloadStatus = YCDownloadStatusPaused; 87 | } 88 | [self.runItems addObject:obj]; 89 | } 90 | if(self.config.launchAutoResumeDownload){ 91 | if(obj.downloadStatus == YCDownloadStatusWaiting){ 92 | [self.waitItems addObject:obj]; 93 | } 94 | }else{ 95 | if (obj.downloadStatus == YCDownloadStatusWaiting || obj.downloadStatus==YCDownloadStatusDownloading) { 96 | [self pauseDownloadWithItem:obj]; 97 | } 98 | } 99 | }]; 100 | if (self.config.launchAutoResumeDownload && self.waitItems.count>0) { 101 | [self resumeDownloadWithItem:self.waitItems.firstObject]; 102 | } 103 | [YCDownloadDB saveAllData]; 104 | } 105 | 106 | #pragma mark - public 107 | 108 | + (void)updateUid:(NSString *)uid { 109 | [YCDownloadMgr setUid:uid]; 110 | } 111 | 112 | + (void)startDownloadWithUrl:(NSString *)downloadURLString{ 113 | [self startDownloadWithUrl:downloadURLString fileId:nil priority:NSURLSessionTaskPriorityDefault extraData:nil]; 114 | } 115 | 116 | + (void)startDownloadWithUrl:(NSString *)downloadURLString fileId:(NSString *)fileId priority:(float)priority extraData:(NSData *)extraData { 117 | [YCDownloadMgr startDownloadWithUrl:downloadURLString fileId:fileId priority:priority extraData:extraData]; 118 | } 119 | 120 | + (void)startDownloadWithItem:(YCDownloadItem *)item { 121 | [YCDownloadMgr startDownloadWithItem:item priority:NSURLSessionTaskPriorityDefault]; 122 | } 123 | 124 | + (void)startDownloadWithItem:(YCDownloadItem *)item priority:(float)priority { 125 | [YCDownloadMgr startDownloadWithItem:item priority:priority]; 126 | } 127 | 128 | 129 | + (void)pauseDownloadWithItem:(YCDownloadItem *)item { 130 | [YCDownloadMgr pauseDownloadWithItem:item]; 131 | } 132 | 133 | + (void)resumeDownloadWithItem:(YCDownloadItem *)item { 134 | [YCDownloadMgr resumeDownloadWithItem:item]; 135 | } 136 | 137 | + (void)stopDownloadWithItem:(YCDownloadItem *)item { 138 | [YCDownloadMgr stopDownloadWithItem:item]; 139 | } 140 | 141 | + (void)pauseAllDownloadTask { 142 | [YCDownloadMgr pauseAllDownloadTask]; 143 | } 144 | 145 | + (void)resumeAllDownloadTask { 146 | [YCDownloadMgr resumeAllDownloadTask]; 147 | } 148 | 149 | + (void)removeAllCache { 150 | [YCDownloadMgr removeAllCache]; 151 | } 152 | 153 | + (YCDownloadItem *)itemWithFileId:(NSString *)fid { 154 | return [YCDownloadMgr itemWithFileId:fid]; 155 | } 156 | 157 | + (NSArray *)itemsWithDownloadUrl:(NSString *)downloadUrl { 158 | return [YCDownloadMgr itemsWithDownloadUrl:downloadUrl]; 159 | } 160 | 161 | + (NSArray *)downloadList { 162 | return [YCDownloadDB fetchAllDownloadingItemWithUid:YCDownloadMgr.uid]; 163 | } 164 | + (NSArray *)finishList { 165 | return [YCDownloadDB fetchAllDownloadedItemWithUid:YCDownloadMgr.uid]; 166 | } 167 | 168 | #pragma mark - setter or getter 169 | 170 | +(BOOL)isAllowsCellularAccess{ 171 | return [YCDownloadMgr isAllowsCellularAccess]; 172 | } 173 | +(void)allowsCellularAccess:(BOOL)isAllow { 174 | [YCDownloadMgr allowsCellularAccess:isAllow]; 175 | } 176 | 177 | - (void)setUid:(NSString *)uid { 178 | if ([_uniqueId isEqualToString:uid]) return; 179 | [self pauseAllDownloadTask]; 180 | _uniqueId = uid; 181 | } 182 | 183 | - (NSString *)uid { 184 | return _uniqueId ? : @"YCDownloadUID"; 185 | } 186 | 187 | #pragma mark - Handler 188 | 189 | - (void)appWillTerminate { 190 | [self pauseAllDownloadTask]; 191 | } 192 | 193 | - (void)saveDownloadItem:(YCDownloadItem *)item { 194 | [YCDownloadDB saveItem:item]; 195 | } 196 | 197 | - (void)downloadTaskFinishNoti:(NSNotification *)noti { 198 | YCDownloadItem *item = noti.object; 199 | [self.runItems removeObject:item]; 200 | [self startNextDownload]; 201 | if (self.runItems.count==0 && self.waitItems.count==0) { 202 | NSLog(@"[YCDownload] [startNextDownload] all download task finished"); 203 | [[YCDownloader downloader] endBGCompletedHandler]; 204 | } 205 | } 206 | - (void)startNextDownload { 207 | YCDownloadItem *item = self.waitItems.firstObject; 208 | if (!item) return; 209 | [self.waitItems removeObject:item]; 210 | [self resumeDownloadWithItem:item]; 211 | } 212 | 213 | + (int64_t)videoCacheSize { 214 | int64_t size = 0; 215 | NSArray *downloadList = [self downloadList]; 216 | NSArray *finishList = [self finishList]; 217 | for (YCDownloadTask *task in downloadList) { 218 | size += task.downloadedSize; 219 | } 220 | for (YCDownloadTask *task in finishList) { 221 | size += task.fileSize; 222 | } 223 | return size; 224 | } 225 | 226 | - (BOOL)canResumeDownload { 227 | return self.runItems.count0 && localFileSize == item.fileSize; 260 | if (fileFinished) { 261 | [item setValue:@(localFileSize) forKey:@"_downloadedSize"]; 262 | item.downloadStatus = YCDownloadStatusFinished; 263 | return true; 264 | } 265 | if (item.downloadStatus == YCDownloadStatusFinished){ 266 | NSLog(@"[YCDownload] [downloadFinishedWithItem] status finished to failed, reason: savePath error! %@", item.savePath); 267 | item.downloadStatus = YCDownloadStatusFailed; 268 | } 269 | if ([[NSFileManager defaultManager] fileExistsAtPath:item.savePath]) { 270 | [[NSFileManager defaultManager] removeItemAtPath:item.savePath error:nil]; 271 | } 272 | return false; 273 | } 274 | 275 | - (YCDownloadItem *)itemWithTaskId:(NSString *)taskId { 276 | return [YCDownloadDB itemWithTaskId:taskId]; 277 | } 278 | 279 | - (void)removeItemWithTaskId:(NSString *)taskId { 280 | [YCDownloadDB removeItemWithTaskId:taskId]; 281 | } 282 | 283 | - (YCDownloadTask *)taskWithItem:(YCDownloadItem *)item { 284 | NSAssert(item.taskId, @"item taskid not nil"); 285 | YCDownloadTask *task = nil; 286 | task = [YCDownloadDB taskWithTid:item.taskId]; 287 | return task; 288 | } 289 | 290 | - (void)resumeDownloadWithItem:(YCDownloadItem *)item{ 291 | if ([self downloadFinishedWithItem:item]) { 292 | NSLog(@"[YCDownload] [resumeDownloadWithItem] detect item finished : %@", item); 293 | [self startNextDownload]; 294 | return; 295 | } 296 | if (![self canResumeDownload]) { 297 | item.downloadStatus = YCDownloadStatusWaiting; 298 | [self.waitItems addObject:item]; 299 | return; 300 | } 301 | item.downloadStatus = YCDownloadStatusDownloading; 302 | YCDownloadTask *task = [self taskWithItem:item]; 303 | task.completionHandler = item.completionHandler; 304 | task.progressHandler = item.progressHandler; 305 | if([[YCDownloader downloader] resumeTask:task]) { 306 | [self.runItems addObject:item]; 307 | return; 308 | } 309 | //[self startDownloadWithItem:item priority:task.priority]; 310 | } 311 | 312 | 313 | - (void)pauseDownloadWithItem:(YCDownloadItem *)item { 314 | item.downloadStatus = YCDownloadStatusPaused; 315 | YCDownloadTask *task = [self taskWithItem:item]; 316 | [[YCDownloader downloader] pauseTask:task]; 317 | [self saveDownloadItem:item]; 318 | [self.runItems removeObject:item]; 319 | [self.waitItems removeObject:item]; 320 | if(!item.noNeedStartNext) [self startNextDownload]; 321 | } 322 | 323 | - (void)stopDownloadWithItem:(YCDownloadItem *)item { 324 | if (item == nil) return; 325 | item.isRemoved = true; 326 | YCDownloadTask *task = [self taskWithItem:item]; 327 | [[YCDownloader downloader] cancelTask:task]; 328 | BOOL isExist = [[NSFileManager defaultManager] fileExistsAtPath:item.savePath]; 329 | NSLog(@"[YCDownload] [remove item] isExist : %d path: %@", isExist, item.savePath); 330 | [[NSFileManager defaultManager] removeItemAtPath:item.savePath error:nil]; 331 | [self removeItemWithTaskId:item.taskId]; 332 | [YCDownloadDB removeTask:task]; 333 | [self.runItems removeObject:item]; 334 | [self.waitItems removeObject:item]; 335 | if(!item.noNeedStartNext) [self startNextDownload]; 336 | } 337 | 338 | - (void)pauseAllDownloadTask { 339 | [[YCDownloadDB fetchAllDownloadingItemWithUid:self.uid] enumerateObjectsUsingBlock:^(YCDownloadItem * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { 340 | if (obj.downloadStatus == YCDownloadStatusWaiting || obj.downloadStatus == YCDownloadStatusDownloading) { 341 | obj.noNeedStartNext = true; 342 | [self pauseDownloadWithItem:obj]; 343 | } 344 | }]; 345 | } 346 | 347 | - (void)removeAllCache { 348 | [[YCDownloadDB fetchAllDownloadItemWithUid:self.uid] enumerateObjectsUsingBlock:^(YCDownloadItem * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { 349 | obj.noNeedStartNext = true; 350 | [self stopDownloadWithItem:obj]; 351 | }]; 352 | } 353 | 354 | - (void)resumeAllDownloadTask{ 355 | NSArray *downloading = [YCDownloadDB fetchAllDownloadingItemWithUid:self.uid]; 356 | [downloading enumerateObjectsUsingBlock:^(YCDownloadItem *item, NSUInteger idx, BOOL * _Nonnull stop) { 357 | if (item.downloadStatus == YCDownloadStatusPaused || item.downloadStatus == YCDownloadStatusFailed) { 358 | [self resumeDownloadWithItem:item]; 359 | } 360 | }]; 361 | [YCDownloadDB saveAllData]; 362 | } 363 | 364 | -(void)allowsCellularAccess:(BOOL)isAllow { 365 | [YCDownloader downloader].allowsCellularAccess = isAllow; 366 | } 367 | 368 | - (BOOL)isAllowsCellularAccess { 369 | return [YCDownloader downloader].allowsCellularAccess; 370 | } 371 | 372 | - (YCDownloadItem *)itemWithFileId:(NSString *)fid { 373 | return [YCDownloadDB itemWithFid:fid uid:self.uid]; 374 | } 375 | 376 | - (NSArray *)itemsWithDownloadUrl:(NSString *)downloadUrl { 377 | return [YCDownloadDB itemsWithUrl:downloadUrl uid:self.uid]; 378 | } 379 | 380 | -(void)dealloc { 381 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 382 | } 383 | 384 | @end 385 | 386 | 387 | @implementation YCDConfig 388 | 389 | - (NSUInteger)maxTaskCount { 390 | return _maxTaskCount ? _maxTaskCount : 1; 391 | } 392 | 393 | @end 394 | -------------------------------------------------------------------------------- /YCDownloadSessionDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /YCDownloadSessionDemo.xcodeproj/xcshareddata/xcschemes/YCDownloadSession.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 34 | 35 | 45 | 46 | 52 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 70 | 71 | 72 | 73 | 75 | 76 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /YCDownloadSessionDemo.xcodeproj/xcshareddata/xcschemes/YCDownloadSessionDemo.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 42 | 48 | 49 | 50 | 51 | 52 | 64 | 66 | 72 | 73 | 74 | 75 | 81 | 83 | 89 | 90 | 91 | 92 | 94 | 95 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /YCDownloadSessionDemo.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /YCDownloadSessionDemo.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /YCDownloadSessionDemo/YCDownloadSession/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // YCDownloadSession 4 | // 5 | // Created by wz on 17/3/14. 6 | // Copyright © 2017年 onezen.cc. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | @end 16 | 17 | -------------------------------------------------------------------------------- /YCDownloadSessionDemo/YCDownloadSession/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // YCDownloadSession 4 | // 5 | // Created by wz on 17/3/14. 6 | // Copyright © 2017年 onezen.cc. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | #import "MainTableViewController.h" 11 | #import 12 | #import "YCDownloadSession.h" 13 | #import "VideoListInfoModel.h" 14 | #import "YCDownloadSwift.h" 15 | 16 | @interface AppDelegate () 17 | 18 | @property (nonatomic, strong) NSTimer *testTimer; 19 | 20 | @property (nonatomic, assign) NSInteger duration; 21 | 22 | @end 23 | 24 | @implementation AppDelegate 25 | 26 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 27 | 28 | //root vc 29 | self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; 30 | MainTableViewController *vc = [[MainTableViewController alloc] init]; 31 | UIViewController *rootVc = [[UINavigationController alloc] initWithRootViewController:vc]; 32 | self.window.rootViewController = rootVc; 33 | [self.window makeKeyAndVisible]; 34 | 35 | //注册通知 36 | if ([[UIDevice currentDevice].systemVersion doubleValue] >= 8.0) { 37 | UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:UIUserNotificationTypeAlert | UIUserNotificationTypeBadge | UIUserNotificationTypeSound categories:nil]; 38 | [application registerUserNotificationSettings:settings]; 39 | } 40 | 41 | //setup bugly 42 | // [self setUpBugly]; 43 | 44 | //setup downloadsession 45 | [self setUpDownload]; 46 | 47 | [self testSwift]; 48 | 49 | return YES; 50 | } 51 | 52 | - (void)startTestTimer { 53 | if(self.testTimer) { 54 | [self cancelTestTimer]; 55 | } 56 | self.duration = 0; 57 | self.testTimer = [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) { 58 | self.duration += 1; 59 | NSLog(@"[YCDownload] testTimer run duration: %zd", self.duration); 60 | }]; 61 | [self.testTimer fire]; 62 | } 63 | 64 | - (void)cancelTestTimer { 65 | NSLog(@"[YCDownload] cancelTestTimer"); 66 | [self.testTimer invalidate]; 67 | self.testTimer = nil; 68 | self.duration = 0; 69 | } 70 | 71 | - (void)setUpDownload { 72 | NSString *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, true).firstObject; 73 | path = [path stringByAppendingPathComponent:@"download"]; 74 | YCDConfig *config = [YCDConfig new]; 75 | config.saveRootPath = path; 76 | config.uid = @"100006"; 77 | config.maxTaskCount = 1; 78 | config.taskCachekMode = YCDownloadTaskCacheModeKeep; 79 | config.launchAutoResumeDownload = true; 80 | [YCDownloadManager mgrWithConfig:config]; 81 | 82 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(downloadTaskFinishedNoti:) name:kDownloadTaskFinishedNoti object:nil]; 83 | } 84 | 85 | - (void)setUpBugly { 86 | BuglyConfig *config = [BuglyConfig new]; 87 | config.blockMonitorEnable = true; 88 | config.channel = @"git"; 89 | config.unexpectedTerminatingDetectionEnable = true; 90 | config.symbolicateInProcessEnable = true; 91 | [Bugly startWithAppId:@"900036376" config:config]; 92 | } 93 | 94 | - (void)testSwift { 95 | TestSwiftController *testVc = [TestSwiftController new]; 96 | testVc.title = @"test"; 97 | [testVc logInfo]; 98 | } 99 | 100 | #pragma mark notificaton 101 | 102 | - (void)downloadTaskFinishedNoti:(NSNotification *)noti{ 103 | YCDownloadItem *item = noti.object; 104 | if (item.downloadStatus == YCDownloadStatusFinished) { 105 | VideoListInfoModel *mo = [VideoListInfoModel infoWithData:item.extraData]; 106 | NSString *detail = [NSString stringWithFormat:@"%@ 视频,已经下载完成!", mo.title]; 107 | [self localPushWithTitle:@"YCDownloadSession" detail:detail]; 108 | } 109 | } 110 | 111 | #pragma mark local push 112 | 113 | - (void)localPushWithTitle:(NSString *)title detail:(NSString *)body { 114 | 115 | if (title.length == 0) return; 116 | UILocalNotification *localNote = [[UILocalNotification alloc] init]; 117 | localNote.fireDate = [NSDate dateWithTimeIntervalSinceNow:3.0]; 118 | localNote.alertBody = body; 119 | localNote.alertAction = @"滑动来解锁"; 120 | localNote.hasAction = NO; 121 | localNote.soundName = @"default"; 122 | localNote.userInfo = @{@"type" : @1}; 123 | [[UIApplication sharedApplication] scheduleLocalNotification:localNote]; 124 | } 125 | 126 | 127 | -(void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)(void))completionHandler{ 128 | NSLog(@"[YCDownload] handleEventsForBackgroundURLSession: %@", identifier); 129 | [[YCDownloader downloader] addCompletionHandler:completionHandler identifier:identifier]; 130 | } 131 | 132 | - (void)applicationDidBecomeActive:(UIApplication *)application { 133 | [self cancelTestTimer]; 134 | } 135 | 136 | - (void)applicationWillResignActive:(UIApplication *)application { 137 | NSLog(@"%s", __func__); 138 | //[YCDownloadManager updateUid:@"100002"]; 139 | [self startTestTimer]; 140 | } 141 | 142 | - (void)applicationWillTerminate:(UIApplication *)application { 143 | NSLog(@"%s", __func__); 144 | } 145 | 146 | @end 147 | -------------------------------------------------------------------------------- /YCDownloadSessionDemo/YCDownloadSession/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /YCDownloadSessionDemo/YCDownloadSession/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 | -------------------------------------------------------------------------------- /YCDownloadSessionDemo/YCDownloadSession/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 | -------------------------------------------------------------------------------- /YCDownloadSessionDemo/YCDownloadSession/Class/Others/TestSwiftController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TestSwiftController.swift 3 | // YCDownloadSessionDemo 4 | // 5 | // Created by wz on 2018/10/10. 6 | // Copyright © 2018年 onezen.cc. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class TestSwiftController: UIViewController { 12 | 13 | override func viewDidLoad() { 14 | super.viewDidLoad() 15 | YCDownloader.downloader().download(withUrl: "", progress: { (progress, task) in 16 | print(progress.completedUnitCount) 17 | }) { (localPath, err) in 18 | if err != nil { 19 | print(err!) 20 | }else{ 21 | print(localPath ?? "localPath nil") 22 | } 23 | } 24 | } 25 | 26 | @objc func logInfo() { 27 | print("Hello, here is Swift log") 28 | } 29 | 30 | override func didReceiveMemoryWarning() { 31 | super.didReceiveMemoryWarning() 32 | // Dispose of any resources that can be recreated. 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /YCDownloadSessionDemo/YCDownloadSession/Class/Others/YCDownloadSessionDemo-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // Use this file to import your target's public headers that you would like to expose to Swift. 3 | // 4 | 5 | #import 6 | -------------------------------------------------------------------------------- /YCDownloadSessionDemo/YCDownloadSession/Class/PlayerVC/PlayerViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // PlayerViewController.h 3 | // YCDownloadSession 4 | // 5 | // Created by wz on 2017/9/30. 6 | // Copyright © 2017年 onezen.cc. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "YCDownloadItem.h" 11 | 12 | @interface PlayerViewController : UIViewController 13 | 14 | @property (nonatomic, strong) YCDownloadItem *playerItem; 15 | @end 16 | -------------------------------------------------------------------------------- /YCDownloadSessionDemo/YCDownloadSession/Class/PlayerVC/PlayerViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // PlayerViewController.m 3 | // YCDownloadSession 4 | // 5 | // Created by wz on 2017/9/30. 6 | // Copyright © 2017年 onezen.cc. All rights reserved. 7 | // 8 | 9 | #import "PlayerViewController.h" 10 | #import "WMPlayer.h" 11 | #import "VideoListInfoModel.h" 12 | 13 | @interface PlayerViewController () 14 | 15 | @property (nonatomic, strong) WMPlayer *player; 16 | @property (nonatomic, assign) CGRect originalFrame; 17 | @property (nonatomic, assign) BOOL isFullScreen; 18 | 19 | @end 20 | 21 | @implementation PlayerViewController 22 | 23 | - (void)viewDidLoad { 24 | [super viewDidLoad]; 25 | 26 | self.view.backgroundColor = [UIColor whiteColor]; 27 | self.originalFrame = CGRectMake(0, 64, self.view.bounds.size.width, 200); 28 | self.player = [[WMPlayer alloc] init]; 29 | self.player.delegate = self; 30 | [self.view addSubview:_player]; 31 | VideoListInfoModel *infoMo = [VideoListInfoModel infoWithData:self.playerItem.extraData]; 32 | self.title = infoMo.title; 33 | 34 | //保存路径需要转换为url路径,才能播放 35 | NSURL *url = [NSURL fileURLWithPath:self.playerItem.savePath]; 36 | NSLog(@"[YCDownload] [playViewVC] videoUrl:%@", url); 37 | 38 | WMPlayerModel *model = [[WMPlayerModel alloc] init]; 39 | model.videoURL = url; 40 | _player.playerModel = model; 41 | [_player play]; 42 | } 43 | 44 | - (void)dealloc { 45 | [_player pause]; 46 | [_player removeFromSuperview]; 47 | } 48 | 49 | 50 | #pragma mark rotate 51 | 52 | /** 53 | * 旋转屏幕的时候,是否自动旋转子视图,NO的话不会旋转控制器的子控件 54 | * 55 | */ 56 | - (BOOL)shouldAutorotate 57 | { 58 | return true; 59 | } 60 | 61 | /** 62 | * 当前控制器支持的旋转方向 63 | */ 64 | - (UIInterfaceOrientationMask)supportedInterfaceOrientations 65 | { 66 | return UIInterfaceOrientationMaskPortrait | UIInterfaceOrientationMaskLandscapeRight | UIInterfaceOrientationMaskLandscapeLeft ; 67 | } 68 | 69 | - (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation 70 | { 71 | if (self.player.isFullscreen) 72 | return UIInterfaceOrientationPortrait; 73 | return UIInterfaceOrientationLandscapeRight ; 74 | } 75 | 76 | /** 77 | 需要切换的屏幕方向,手动转屏 78 | */ 79 | - (void)setFullScreen:(BOOL)isFullScreen { 80 | 81 | if (isFullScreen) { 82 | [self rotateOrientation:UIInterfaceOrientationLandscapeRight]; 83 | }else{ 84 | [self rotateOrientation:UIInterfaceOrientationPortrait]; 85 | } 86 | } 87 | 88 | - (void)rotateOrientation:(UIInterfaceOrientation)orientation { 89 | 90 | [[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications]; 91 | [[UIApplication sharedApplication] setStatusBarOrientation:orientation animated:YES]; 92 | [[UIDevice currentDevice] setValue:[NSNumber numberWithInteger:orientation] forKey:@"orientation"]; 93 | } 94 | 95 | //自动转屏或者手动调用 96 | - (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id)coordinator { 97 | 98 | self.isFullScreen = size.width > size.height; 99 | } 100 | 101 | 102 | - (void)viewWillLayoutSubviews { 103 | [super viewWillLayoutSubviews]; 104 | if (self.isFullScreen){ 105 | 106 | [self.navigationController setNavigationBarHidden:true]; 107 | self.player.frame = self.view.bounds; 108 | self.player.isFullscreen = true; 109 | }else{ 110 | [self.navigationController setNavigationBarHidden:false]; 111 | self.player.frame = self.originalFrame; 112 | self.player.isFullscreen = false; 113 | } 114 | } 115 | 116 | #pragma mark - player view delegate 117 | 118 | 119 | //点击播放暂停按钮代理方法 120 | -(void)wmplayer:(WMPlayer *)wmplayer clickedPlayOrPauseButton:(UIButton *)playOrPauseBtn{ 121 | NSLog(@"%s", __func__); 122 | } 123 | //点击关闭按钮代理方法 124 | -(void)wmplayer:(WMPlayer *)wmplayer clickedCloseButton:(UIButton *)closeBtn{ 125 | 126 | if (self.player.isFullscreen) { 127 | [self setFullScreen:false]; 128 | }else{ 129 | [self.navigationController popViewControllerAnimated:true]; 130 | } 131 | } 132 | //点击全屏按钮代理方法 133 | -(void)wmplayer:(WMPlayer *)wmplayer clickedFullScreenButton:(UIButton *)fullScreenBtn{ 134 | [self setFullScreen:!self.player.isFullscreen]; 135 | } 136 | //单击WMPlayer的代理方法 137 | -(void)wmplayer:(WMPlayer *)wmplayer singleTaped:(UITapGestureRecognizer *)singleTap{ 138 | NSLog(@"%s", __func__); 139 | } 140 | //双击WMPlayer的代理方法 141 | -(void)wmplayer:(WMPlayer *)wmplayer doubleTaped:(UITapGestureRecognizer *)doubleTap{ 142 | NSLog(@"%s", __func__); 143 | } 144 | //WMPlayer的的操作栏隐藏和显示 145 | -(void)wmplayer:(WMPlayer *)wmplayer isHiddenTopAndBottomView:(BOOL )isHidden{ 146 | NSLog(@"%s", __func__); 147 | } 148 | ///播放状态 149 | //播放失败的代理方法 150 | -(void)wmplayerFailedPlay:(WMPlayer *)wmplayer WMPlayerStatus:(WMPlayerState)state{ 151 | NSLog(@"%s", __func__); 152 | } 153 | //准备播放的代理方法 154 | -(void)wmplayerReadyToPlay:(WMPlayer *)wmplayer WMPlayerStatus:(WMPlayerState)state{ 155 | NSLog(@"%s", __func__); 156 | } 157 | //播放完毕的代理方法 158 | -(void)wmplayerFinishedPlay:(WMPlayer *)wmplayer{ 159 | NSLog(@"%s", __func__); 160 | } 161 | 162 | 163 | @end 164 | 165 | 166 | 167 | -------------------------------------------------------------------------------- /YCDownloadSessionDemo/YCDownloadSession/Class/RootVc/DownloadViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // YCDownloadSession 4 | // 5 | // Created by wz on 17/3/14. 6 | // Copyright © 2017年 onezen.cc. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface DownloadViewController : UIViewController 12 | 13 | 14 | @end 15 | 16 | -------------------------------------------------------------------------------- /YCDownloadSessionDemo/YCDownloadSession/Class/RootVc/DownloadViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // YCDownloadSession 4 | // 5 | // Created by wz on 17/3/14. 6 | // Copyright © 2017年 onezen.cc. All rights reserved. 7 | // 8 | 9 | #import "DownloadViewController.h" 10 | #import 11 | 12 | static NSString * const kDownloadTaskIdKey = @"kDownloadTaskIdKey"; 13 | @interface DownloadViewController () 14 | 15 | @property (nonatomic, copy) NSString *downloadURL; 16 | @property (nonatomic, weak) UILabel *progressLbl; 17 | @property (nonatomic, weak) YCDownloadTask *downloadTask; 18 | @property (nonatomic, copy) YCCompletionHandler completion; 19 | @property (nonatomic, copy) YCProgressHandler progress; 20 | 21 | @end 22 | 23 | @implementation DownloadViewController 24 | 25 | - (void)viewDidLoad { 26 | [super viewDidLoad]; 27 | 28 | self.view.backgroundColor = [UIColor whiteColor]; 29 | 30 | UIButton *btn = [[UIButton alloc] initWithFrame:CGRectMake(100, 100, 100, 36)]; 31 | [btn setTitle:@"start" forState:UIControlStateNormal]; 32 | [btn setTitleColor:[UIColor blueColor] forState:UIControlStateNormal]; 33 | [btn setTitleColor:[UIColor cyanColor] forState:UIControlStateHighlighted]; 34 | [btn addTarget:self action:@selector(start) forControlEvents:UIControlEventTouchUpInside]; 35 | [self.view addSubview:btn]; 36 | 37 | UIButton *resumeBtn = [[UIButton alloc] initWithFrame:CGRectMake(100, 150, 100, 36)]; 38 | [resumeBtn setTitle:@"resume" forState:UIControlStateNormal]; 39 | [resumeBtn setTitleColor:[UIColor blueColor] forState:UIControlStateNormal]; 40 | [resumeBtn setTitleColor:[UIColor cyanColor] forState:UIControlStateHighlighted]; 41 | [resumeBtn addTarget:self action:@selector(resume) forControlEvents:UIControlEventTouchUpInside]; 42 | [self.view addSubview:resumeBtn]; 43 | 44 | UIButton *stopBtn = [[UIButton alloc] initWithFrame:CGRectMake(100, 200, 100, 36)]; 45 | [stopBtn setTitle:@"stop" forState:UIControlStateNormal]; 46 | [stopBtn setTitleColor:[UIColor blueColor] forState:UIControlStateNormal]; 47 | [stopBtn addTarget:self action:@selector(stop) forControlEvents:UIControlEventTouchUpInside]; 48 | [stopBtn setTitleColor:[UIColor cyanColor] forState:UIControlStateHighlighted]; 49 | [self.view addSubview:stopBtn]; 50 | 51 | UIButton *pauseBtn = [[UIButton alloc] initWithFrame:CGRectMake(100, 250, 100, 36)]; 52 | [pauseBtn setTitle:@"pause" forState:UIControlStateNormal]; 53 | [pauseBtn setTitleColor:[UIColor blueColor] forState:UIControlStateNormal]; 54 | [pauseBtn addTarget:self action:@selector(pause) forControlEvents:UIControlEventTouchUpInside]; 55 | [pauseBtn setTitleColor:[UIColor cyanColor] forState:UIControlStateHighlighted]; 56 | [self.view addSubview:pauseBtn]; 57 | 58 | self.downloadURL = @"http://dldir1.qq.com/qqfile/QQforMac/QQ_V6.0.1.dmg"; 59 | 60 | UILabel *lbl = [[UILabel alloc] init]; 61 | lbl.text = @"0%"; 62 | lbl.frame = CGRectMake(100, 300, 200, 30); 63 | lbl.textAlignment = NSTextAlignmentCenter; 64 | self.progressLbl = lbl; 65 | [self.view addSubview:lbl]; 66 | 67 | __weak typeof(self) weakSelf = self; 68 | self.completion = ^(NSString *localPath, NSError *error) { 69 | if (error) { 70 | [weakSelf downloadStatusChanged:YCDownloadStatusFailed downloadTask:nil]; 71 | }else{ 72 | [weakSelf downloadStatusChanged:YCDownloadStatusFinished downloadTask:nil]; 73 | NSLog(@"[YCDownload] %@", localPath); 74 | } 75 | weakSelf.downloadTask = nil; 76 | }; 77 | self.progress = ^(NSProgress *progress, YCDownloadTask *task) { 78 | weakSelf.progressLbl.text = [NSString stringWithFormat:@"%f",progress.fractionCompleted]; 79 | }; 80 | [YCDownloader downloader]; 81 | NSString *tid = [[NSUserDefaults standardUserDefaults] valueForKey:kDownloadTaskIdKey]; 82 | YCDownloadTask *task = [YCDownloadDB taskWithTid:tid]; 83 | if (task.isRunning) [self resume]; 84 | } 85 | 86 | - (void)downloadProgress:(YCDownloadTask *)task downloadedSize:(NSUInteger)downloadedSize fileSize:(NSUInteger)fileSize { 87 | self.progressLbl.text = [NSString stringWithFormat:@"%f",(float)downloadedSize / fileSize * 100]; 88 | } 89 | 90 | - (void)downloadStatusChanged:(YCDownloadStatus)status downloadTask:(YCDownloadTask *)task { 91 | if (status == YCDownloadStatusFinished) { 92 | self.progressLbl.text = @"download success!"; 93 | }else if (status == YCDownloadStatusFailed){ 94 | self.progressLbl.text = @"download failed!"; 95 | } 96 | } 97 | 98 | - (void)start { 99 | if (self.downloadTask) { 100 | [self resume]; 101 | return; 102 | } 103 | self.downloadTask = [[YCDownloader downloader] downloadWithUrl:self.downloadURL progress:self.progress completion:self.completion]; 104 | self.downloadTask.extraData = [@"dfasdfasdfa" dataUsingEncoding:NSUTF8StringEncoding]; 105 | [[NSUserDefaults standardUserDefaults] setValue:self.downloadTask.taskId forKey:kDownloadTaskIdKey]; 106 | [self resume]; 107 | 108 | } 109 | - (void)resume { 110 | if (self.downloadTask) { 111 | [[YCDownloader downloader] resumeTask:self.downloadTask]; 112 | }else{ 113 | //recovery download 114 | NSString *tid = [[NSUserDefaults standardUserDefaults] valueForKey:kDownloadTaskIdKey]; 115 | if (tid) self.downloadTask = [[YCDownloader downloader] resumeDownloadTaskWithTid:tid progress:self.progress completion:self.completion]; 116 | } 117 | } 118 | 119 | - (void)pause { 120 | [[YCDownloader downloader] pauseTask:self.downloadTask]; 121 | } 122 | 123 | - (void)stop { 124 | [[YCDownloader downloader] cancelTask:self.downloadTask]; 125 | self.downloadTask = nil; 126 | [[NSUserDefaults standardUserDefaults] removeObjectForKey:kDownloadTaskIdKey]; 127 | self.progressLbl.text = @"stop"; 128 | } 129 | 130 | 131 | @end 132 | -------------------------------------------------------------------------------- /YCDownloadSessionDemo/YCDownloadSession/Class/RootVc/MainTableViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // MainTableViewController.h 3 | // YCDownloadSession 4 | // 5 | // Created by wz on 2017/9/17. 6 | // Copyright © 2017年 onezen.cc. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface MainTableViewController : UITableViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /YCDownloadSessionDemo/YCDownloadSession/Class/RootVc/MainTableViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // MainTableViewController.m 3 | // YCDownloadSession 4 | // 5 | // Created by wz on 2017/9/17. 6 | // Copyright © 2017年 onezen.cc. All rights reserved. 7 | // 8 | 9 | #import "MainTableViewController.h" 10 | #import "DownloadViewController.h" 11 | #import "VideoListInfoController.h" 12 | #import 13 | 14 | @interface MainTableViewController () 15 | 16 | @property (nonatomic, strong) UISwitch *cellarSwitch; 17 | 18 | @end 19 | 20 | @implementation MainTableViewController 21 | 22 | - (void)viewDidLoad { 23 | [super viewDidLoad]; 24 | self.title = @"后台视频下载"; 25 | } 26 | 27 | - (void)viewWillAppear:(BOOL)animated { 28 | [super viewWillAppear:animated]; 29 | [self.tableView reloadData]; 30 | } 31 | 32 | - (void)cellarSwitch:(UISwitch *)sender { 33 | 34 | [YCDownloadManager allowsCellularAccess:sender.isOn]; 35 | } 36 | 37 | #pragma mark - tableView datasource & delegate 38 | 39 | 40 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { 41 | 42 | static NSString *cellID = @"MainTableViewControllerCell"; 43 | 44 | UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellID]; 45 | cell.selectionStyle = UITableViewCellSelectionStyleNone; 46 | 47 | if (!cell) { 48 | cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:cellID]; 49 | } 50 | 51 | 52 | if (indexPath.row == 0) { 53 | cell.textLabel.text = @"单个文件下载"; 54 | cell.accessoryView = nil; 55 | }else if (indexPath.row == 1){ 56 | cell.textLabel.text = @"多视频下载"; 57 | cell.accessoryView = nil; 58 | }else if(indexPath.row==2){ 59 | cell.textLabel.text = @"是否允许4G下载"; 60 | cell.accessoryView = self.cellarSwitch; 61 | }else if (indexPath.row==3){ 62 | cell.textLabel.text = @"磁盘剩余空间"; 63 | cell.detailTextLabel.text = [NSString stringWithFormat:@"%@", [YCDownloadUtils fileSizeStringFromBytes:[YCDownloadUtils fileSystemFreeSize]]]; 64 | }else if (indexPath.row==4){ 65 | cell.textLabel.text = @"当前缓存大小"; 66 | cell.detailTextLabel.text = [NSString stringWithFormat:@"%@", [YCDownloadUtils fileSizeStringFromBytes:[YCDownloadManager videoCacheSize]]]; 67 | }else if (indexPath.row==5){ 68 | cell.textLabel.text = @"清空所有视频缓存"; 69 | } 70 | 71 | return cell; 72 | } 73 | 74 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { 75 | 76 | return 6; 77 | } 78 | 79 | 80 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { 81 | 82 | if (indexPath.row == 0) { 83 | DownloadViewController *vc = [[DownloadViewController alloc] init]; 84 | [self.navigationController pushViewController:vc animated:true]; 85 | return; 86 | }else if (indexPath.row == 1){ 87 | VideoListInfoController *vc = [[VideoListInfoController alloc] init]; 88 | [self.navigationController pushViewController:vc animated:true]; 89 | }else if (indexPath.row==5){ 90 | 91 | UIAlertController *alertVc = [UIAlertController alertControllerWithTitle:@"是否清空所有下载文件缓存?" message:nil preferredStyle:UIAlertControllerStyleAlert]; 92 | UIAlertAction *confirm = [UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { 93 | //注意清空缓存的逻辑,YCDownloadSession单独下载的文件单独清理,这里的大小由YCDownloadManager控制 94 | [YCDownloadManager removeAllCache]; 95 | [self.tableView reloadData]; 96 | }]; 97 | UIAlertAction *cancel = [UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) { 98 | 99 | }]; 100 | [alertVc addAction:confirm]; 101 | [alertVc addAction:cancel]; 102 | [self presentViewController:alertVc animated:true completion:nil]; 103 | } 104 | } 105 | 106 | 107 | #pragma mark - lazy loading 108 | 109 | - (UISwitch *)cellarSwitch { 110 | 111 | if (!_cellarSwitch) { 112 | _cellarSwitch = [[UISwitch alloc] init]; 113 | [_cellarSwitch setOn:[YCDownloadManager isAllowsCellularAccess]]; 114 | [_cellarSwitch addTarget:self action:@selector(cellarSwitch:) forControlEvents:UIControlEventTouchUpInside]; 115 | } 116 | return _cellarSwitch; 117 | } 118 | 119 | 120 | @end 121 | -------------------------------------------------------------------------------- /YCDownloadSessionDemo/YCDownloadSession/Class/VideoCache/VideoCacheController.h: -------------------------------------------------------------------------------- 1 | // 2 | // VideoCacheController.h 3 | // YCDownloadSession 4 | // 5 | // Created by wz on 17/3/23. 6 | // Copyright © 2017年 onezen.cc. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | typedef void(^VideoCacheStartAll)(void); 12 | @interface VideoCacheController : UIViewController 13 | 14 | @property (nonatomic, copy) VideoCacheStartAll startAllBlk; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /YCDownloadSessionDemo/YCDownloadSession/Class/VideoCache/VideoCacheController.m: -------------------------------------------------------------------------------- 1 | // 2 | // VideoCacheController.m 3 | // YCDownloadSession 4 | // 5 | // Created by wz on 17/3/23. 6 | // Copyright © 2017年 onezen.cc. All rights reserved. 7 | // 8 | 9 | #import "VideoCacheController.h" 10 | #import "VideoCacheListCell.h" 11 | #import 12 | #import "PlayerViewController.h" 13 | 14 | static NSString * const kDefinePauseAllTitle = @"暂停所有"; 15 | static NSString * const kDefineStartAllTitle = @"开始所有"; 16 | 17 | @interface VideoCacheController () 18 | @property (nonatomic, strong) UITableView *tableView; 19 | @property (nonatomic, strong) NSMutableArray *cacheVideoList; 20 | @end 21 | 22 | @implementation VideoCacheController 23 | 24 | - (void)viewDidLoad { 25 | [super viewDidLoad]; 26 | [self setupTableView]; 27 | self.title = @"缓存"; 28 | self.view.backgroundColor = [UIColor whiteColor]; 29 | self.cacheVideoList = [NSMutableArray array]; 30 | [self getCacheVideoList]; 31 | self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:self.cacheVideoList.count > 0 ? kDefinePauseAllTitle : kDefineStartAllTitle style:UIBarButtonItemStyleDone target:self action:@selector(pauseAll)]; 32 | } 33 | 34 | - (void)getCacheVideoList { 35 | 36 | [self.cacheVideoList removeAllObjects]; 37 | [self.cacheVideoList addObjectsFromArray:[YCDownloadManager downloadList]]; 38 | [self.cacheVideoList addObjectsFromArray:[YCDownloadManager finishList]]; 39 | [self.tableView reloadData]; 40 | self.navigationItem.rightBarButtonItem.enabled = self.cacheVideoList.count>0; 41 | } 42 | 43 | - (void)setupTableView { 44 | _tableView = [[UITableView alloc] init]; 45 | _tableView.delegate = self; 46 | _tableView.dataSource = self; 47 | _tableView.frame = self.view.bounds; 48 | _tableView.separatorStyle = UITableViewCellSeparatorStyleNone; 49 | [self.view addSubview:_tableView]; 50 | } 51 | 52 | - (void)pauseAll { 53 | if (self.navigationItem.rightBarButtonItem.title == kDefinePauseAllTitle) { 54 | [YCDownloadManager pauseAllDownloadTask]; 55 | self.navigationItem.rightBarButtonItem.title = kDefineStartAllTitle; 56 | }else{ 57 | if (self.startAllBlk && self.cacheVideoList.count==0) { 58 | self.startAllBlk(); 59 | [self getCacheVideoList]; 60 | }else{ 61 | [YCDownloadManager resumeAllDownloadTask]; 62 | self.navigationItem.rightBarButtonItem.title = kDefinePauseAllTitle; 63 | } 64 | } 65 | } 66 | 67 | #pragma mark - uitableview datasource & delegate 68 | 69 | - (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath { 70 | return YES; 71 | } 72 | 73 | - (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath { 74 | 75 | if (editingStyle == UITableViewCellEditingStyleDelete) { 76 | 77 | YCDownloadItem *item = _cacheVideoList[indexPath.row]; 78 | [YCDownloadManager stopDownloadWithItem:item]; 79 | 80 | [self.cacheVideoList removeObjectAtIndex:indexPath.row]; 81 | // Delete the row from the data source. 82 | [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade]; 83 | 84 | } 85 | else if (editingStyle == UITableViewCellEditingStyleInsert) { 86 | // Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view. 87 | } 88 | } 89 | 90 | 91 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { 92 | 93 | VideoCacheListCell *cell = [VideoCacheListCell videoCacheListCellWithTableView:tableView]; 94 | YCDownloadItem *item = self.cacheVideoList[indexPath.row]; 95 | cell.item = item; 96 | item.delegate = cell; 97 | return cell; 98 | } 99 | 100 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { 101 | return self.cacheVideoList.count; 102 | } 103 | 104 | - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { 105 | return [VideoCacheListCell rowHeight]; 106 | } 107 | 108 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { 109 | 110 | YCDownloadItem *item = self.cacheVideoList[indexPath.row]; 111 | if (item.downloadStatus == YCDownloadStatusDownloading) { 112 | [YCDownloadManager pauseDownloadWithItem:item]; 113 | }else if (item.downloadStatus == YCDownloadStatusPaused){ 114 | [YCDownloadManager resumeDownloadWithItem:item]; 115 | }else if (item.downloadStatus == YCDownloadStatusFailed){ 116 | [YCDownloadManager resumeDownloadWithItem:item]; 117 | }else if (item.downloadStatus == YCDownloadStatusWaiting){ 118 | [YCDownloadManager pauseDownloadWithItem:item]; 119 | }else if (item.downloadStatus == YCDownloadStatusFinished){ 120 | PlayerViewController *playerVC = [[PlayerViewController alloc] init]; 121 | playerVC.playerItem = item; 122 | [self.navigationController pushViewController:playerVC animated:true]; 123 | } 124 | [self.tableView reloadData]; 125 | } 126 | 127 | 128 | @end 129 | -------------------------------------------------------------------------------- /YCDownloadSessionDemo/YCDownloadSession/Class/VideoCache/VideoCacheListCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // VideoCacheListCell.h 3 | // YCDownloadSession 4 | // 5 | // Created by wz on 17/3/23. 6 | // Copyright © 2017年 onezen.cc. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "YCDownloadSession.h" 11 | 12 | @interface VideoCacheListCell : UITableViewCell 13 | 14 | @property (weak, nonatomic) IBOutlet UILabel *speedLbl; 15 | @property (nonatomic, strong) YCDownloadItem *item; 16 | 17 | +(instancetype)videoCacheListCellWithTableView:(UITableView *)tableView; 18 | 19 | + (CGFloat)rowHeight; 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /YCDownloadSessionDemo/YCDownloadSession/Class/VideoCache/VideoCacheListCell.m: -------------------------------------------------------------------------------- 1 | 2 | // 3 | // VideoCacheListCell.m 4 | // YCDownloadSession 5 | // 6 | // Created by wz on 17/3/23. 7 | // Copyright © 2017年 onezen.cc. All rights reserved. 8 | // 9 | 10 | #import "VideoCacheListCell.h" 11 | #import "UIImageView+WebCache.h" 12 | #import 13 | #import "VideoListInfoModel.h" 14 | 15 | 16 | @interface VideoCacheListCell () 17 | 18 | @property (weak, nonatomic) IBOutlet UIProgressView *progressView; 19 | @property (weak, nonatomic) IBOutlet UILabel *titleLbl; 20 | @property (weak, nonatomic) IBOutlet UILabel *sizeLbl; 21 | @property (weak, nonatomic) IBOutlet UILabel *statusLbl; 22 | @property (weak, nonatomic) IBOutlet UIImageView *coverImgView; 23 | 24 | 25 | @end 26 | 27 | 28 | @implementation VideoCacheListCell 29 | 30 | + (instancetype)videoCacheListCellWithTableView:(UITableView *)tableView { 31 | static NSString *cellId = @"VideoCacheListCell"; 32 | VideoCacheListCell *cell = [tableView dequeueReusableCellWithIdentifier:cellId]; 33 | if (!cell) { 34 | cell = [[NSBundle mainBundle] loadNibNamed:@"VideoCacheListCell" owner:nil options:nil].firstObject; 35 | } 36 | return cell; 37 | 38 | } 39 | 40 | + (CGFloat)rowHeight { 41 | return 84.0f; 42 | } 43 | 44 | - (void)awakeFromNib { 45 | [super awakeFromNib]; 46 | self.coverImgView.layer.cornerRadius = 6.0f; 47 | self.coverImgView.layer.masksToBounds = true; 48 | } 49 | 50 | - (void)setItem:(YCDownloadItem *)item { 51 | 52 | _item = item; 53 | VideoListInfoModel *mo = [VideoListInfoModel infoWithData:item.extraData]; 54 | self.titleLbl.text = mo.title; 55 | [self.coverImgView sd_setImageWithURL:[NSURL URLWithString:mo.cover_url]]; 56 | [self changeSizeLblDownloadedSize:item.downloadedSize totalSize:item.fileSize]; 57 | [self setDownloadStatus:item.downloadStatus]; 58 | self.speedLbl.hidden = !item.enableSpeed; 59 | } 60 | 61 | 62 | - (void)setDownloadStatus:(YCDownloadStatus)status { 63 | 64 | switch (status) { 65 | case YCDownloadStatusWaiting: 66 | self.statusLbl.text = @"正在等待"; 67 | break; 68 | case YCDownloadStatusDownloading: 69 | self.statusLbl.text = @"正在下载"; 70 | break; 71 | case YCDownloadStatusPaused: 72 | self.statusLbl.text = @"暂停下载"; 73 | break; 74 | case YCDownloadStatusFinished: 75 | self.statusLbl.text = @"下载成功"; 76 | self.progressView.progress = 1; 77 | break; 78 | case YCDownloadStatusFailed: 79 | self.statusLbl.text = @"下载失败"; 80 | break; 81 | 82 | default: 83 | break; 84 | } 85 | } 86 | 87 | 88 | 89 | - (void)changeSizeLblDownloadedSize:(int64_t)downloadedSize totalSize:(int64_t)totalSize { 90 | 91 | self.sizeLbl.text = [NSString stringWithFormat:@"%@ / %@",[YCDownloadUtils fileSizeStringFromBytes:downloadedSize], [YCDownloadUtils fileSizeStringFromBytes:totalSize]]; 92 | 93 | float progress = 0; 94 | if (totalSize != 0) { 95 | progress = (float)downloadedSize / totalSize; 96 | } 97 | self.progressView.progress = progress; 98 | } 99 | 100 | - (void)downloadItemStatusChanged:(YCDownloadItem *)item { 101 | [self setDownloadStatus:item.downloadStatus]; 102 | } 103 | 104 | - (void)downloadItem:(YCDownloadItem *)item downloadedSize:(int64_t)downloadedSize totalSize:(int64_t)totalSize { 105 | 106 | [self changeSizeLblDownloadedSize:downloadedSize totalSize:totalSize]; 107 | } 108 | 109 | - (void)downloadItem:(YCDownloadItem *)item speed:(NSUInteger)speed speedDesc:(NSString *)speedDesc { 110 | self.speedLbl.text = speedDesc; 111 | } 112 | 113 | 114 | @end 115 | -------------------------------------------------------------------------------- /YCDownloadSessionDemo/YCDownloadSession/Class/VideoCache/VideoCacheListCell.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 59 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /YCDownloadSessionDemo/YCDownloadSession/Class/VideoList/VideoListInfoCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // VideoListInfoCell.h 3 | // YCDownloadSession 4 | // 5 | // Created by wz on 17/3/23. 6 | // Copyright © 2017年 onezen.cc. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "VideoListInfoModel.h" 11 | #import "YCDownloadItem.h" 12 | @class VideoListInfoCell; 13 | 14 | @protocol VideoListInfoCellDelegate 15 | 16 | - (void)videoListCell:(VideoListInfoCell *)cell downloadVideo:(VideoListInfoModel *)model; 17 | 18 | @end 19 | 20 | @interface VideoListInfoCell : UITableViewCell 21 | @property (nonatomic, strong) VideoListInfoModel *videoModel; 22 | @property (nonatomic, weak) id delegate; 23 | + (instancetype)videoListInfoCellWithTableView:(UITableView *)tableView; 24 | + (CGFloat)rowHeight; 25 | - (void)setDownloadStatus:(YCDownloadStatus)status; 26 | @end 27 | -------------------------------------------------------------------------------- /YCDownloadSessionDemo/YCDownloadSession/Class/VideoList/VideoListInfoCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // VideoListInfoCell.m 3 | // YCDownloadSession 4 | // 5 | // Created by wz on 17/3/23. 6 | // Copyright © 2017年 onezen.cc. All rights reserved. 7 | // 8 | 9 | #import "VideoListInfoCell.h" 10 | #import "UIImageView+WebCache.h" 11 | #import 12 | 13 | @interface VideoListInfoCell() 14 | 15 | @property (weak, nonatomic) IBOutlet UIImageView *coverImgView; 16 | @property (weak, nonatomic) IBOutlet UILabel *titleLbl; 17 | @property (weak, nonatomic) IBOutlet UILabel *timeLbl; 18 | @property (weak, nonatomic) IBOutlet UIButton *downloadBtn; 19 | 20 | @property (weak, nonatomic) IBOutlet UILabel *videoSizeLbl; 21 | 22 | @end 23 | 24 | @implementation VideoListInfoCell 25 | 26 | + (instancetype)videoListInfoCellWithTableView:(UITableView *)tableView { 27 | static NSString *cellId = @"VideoListInfoCell"; 28 | VideoListInfoCell *cell = [tableView dequeueReusableCellWithIdentifier:cellId]; 29 | if (!cell) { 30 | cell = [[NSBundle mainBundle] loadNibNamed:@"VideoListInfoCell" owner:self options:nil].firstObject; 31 | 32 | } 33 | return cell; 34 | } 35 | 36 | + (CGFloat)rowHeight { 37 | return 84.0f; 38 | } 39 | 40 | - (void)awakeFromNib { 41 | [super awakeFromNib]; 42 | 43 | self.downloadBtn.layer.cornerRadius = 4.0f; 44 | self.downloadBtn.layer.masksToBounds = true; 45 | self.coverImgView.layer.cornerRadius = 6; 46 | self.coverImgView.layer.masksToBounds = true; 47 | 48 | } 49 | 50 | - (void)setVideoModel:(VideoListInfoModel *)videoModel { 51 | _videoModel = videoModel; 52 | self.titleLbl.text = videoModel.title; 53 | self.timeLbl.text = videoModel.video_desc; 54 | self.videoSizeLbl.text = [YCDownloadUtils fileSizeStringFromBytes:videoModel.file_size]; 55 | self.videoSizeLbl.hidden = videoModel.file_size<=0; 56 | [self.coverImgView sd_setImageWithURL:[NSURL URLWithString:videoModel.cover_url]]; 57 | } 58 | 59 | 60 | - (void)setDownloadStatus:(YCDownloadStatus)status { 61 | NSString *statusStr = @"下载"; 62 | switch (status) { 63 | case YCDownloadStatusWaiting: 64 | statusStr = @"正在等待"; 65 | break; 66 | case YCDownloadStatusDownloading: 67 | statusStr = @"正在下载"; 68 | break; 69 | case YCDownloadStatusPaused: 70 | statusStr = @"暂停下载"; 71 | break; 72 | case YCDownloadStatusFinished: 73 | statusStr = @"下载成功"; 74 | break; 75 | case YCDownloadStatusFailed: 76 | statusStr = @"下载失败"; 77 | break; 78 | 79 | default: 80 | break; 81 | } 82 | [self.downloadBtn setTitle:statusStr forState:UIControlStateNormal]; 83 | } 84 | 85 | 86 | - (IBAction)downloadBtnClick:(id)sender { 87 | if ([self.delegate respondsToSelector:@selector(videoListCell:downloadVideo:)]) { 88 | [self.delegate videoListCell:self downloadVideo:self.videoModel]; 89 | } 90 | } 91 | 92 | @end 93 | -------------------------------------------------------------------------------- /YCDownloadSessionDemo/YCDownloadSession/Class/VideoList/VideoListInfoCell.xib: -------------------------------------------------------------------------------- 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 | 37 | 43 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /YCDownloadSessionDemo/YCDownloadSession/Class/VideoList/VideoListInfoController.h: -------------------------------------------------------------------------------- 1 | // 2 | // VideoListInfoController.h 3 | // YCDownloadSession 4 | // 5 | // Created by wz on 17/3/23. 6 | // Copyright © 2017年 onezen.cc. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface VideoListInfoController : UITableViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /YCDownloadSessionDemo/YCDownloadSession/Class/VideoList/VideoListInfoController.m: -------------------------------------------------------------------------------- 1 | // 2 | // VideoListInfoController.m 3 | // YCDownloadSession 4 | // 5 | // Created by wz on 17/3/23. 6 | // Copyright © 2017年 onezen.cc. All rights reserved. 7 | // 8 | 9 | #import "VideoListInfoController.h" 10 | #import "VideoListInfoCell.h" 11 | #import "VideoListInfoModel.h" 12 | #import "VideoCacheController.h" 13 | #import "AFNetworking.h" 14 | #import "MJRefresh.h" 15 | #import 16 | 17 | static NSInteger const pageSize = 10; 18 | 19 | @interface VideoListInfoController () 20 | 21 | @property (nonatomic, strong) NSMutableArray *videoListArr; 22 | @property (nonatomic, assign) NSInteger page; 23 | 24 | @end 25 | 26 | @implementation VideoListInfoController 27 | 28 | - (void)viewDidLoad { 29 | [super viewDidLoad]; 30 | self.title = @"视频列表"; 31 | self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone; 32 | self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"缓存" style:UIBarButtonItemStylePlain target:self action:@selector(goCache)]; 33 | _videoListArr = [NSMutableArray array]; 34 | [self setupRefresh]; 35 | [self.tableView.mj_header beginRefreshing]; 36 | } 37 | 38 | - (void)setupRefresh{ 39 | self.tableView.mj_header = [MJRefreshNormalHeader headerWithRefreshingTarget:self refreshingAction:@selector(reloadData)]; 40 | self.tableView.mj_footer = [MJRefreshAutoNormalFooter footerWithRefreshingTarget:self refreshingAction:@selector(loadMoreData)]; 41 | } 42 | 43 | - (void)reloadData{ 44 | [self.tableView.mj_footer resetNoMoreData]; 45 | self.page = 0; 46 | [self getVideoList:false]; 47 | } 48 | 49 | - (void)loadMoreData { 50 | self.page++; 51 | [self getVideoList: true]; 52 | } 53 | 54 | - (void)getVideoList:(BOOL)isLoadMore { 55 | [self getLocalVideoList]; 56 | // [[AFHTTPSessionManager manager] GET:@"http://api.onezen.cc/v1/video/list" parameters:@{@"page": @(self.page), @"size": @(pageSize)} progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) { 57 | // NSArray *res = [responseObject valueForKey:@"data"]; 58 | // if ([res isKindOfClass:[NSNull class]] || res.count==0) { 59 | // [self.tableView.mj_footer endRefreshingWithNoMoreData]; 60 | // return ; 61 | // } 62 | // if (res.count>0) { 63 | // if (!isLoadMore){ 64 | // [self.tableView.mj_header endRefreshing]; 65 | // [self.videoListArr removeAllObjects]; 66 | // }else{ 67 | // (res.count < pageSize) ? [self.tableView.mj_footer endRefreshingWithNoMoreData] : [self.tableView.mj_footer endRefreshing]; 68 | // } 69 | // NSMutableArray *models = [VideoListInfoModel getVideoListInfo:res]; 70 | // [self.videoListArr addObjectsFromArray:models]; 71 | // [self.tableView reloadData]; 72 | // } 73 | // 74 | // } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) { 75 | // if (error) { 76 | // NSLog(@"%@",error); 77 | // } 78 | // 79 | // }]; 80 | } 81 | 82 | - (void)downloadAll { 83 | [_videoListArr enumerateObjectsUsingBlock:^(VideoListInfoModel* model, NSUInteger idx, BOOL * _Nonnull stop) { 84 | YCDownloadItem *item = nil; 85 | if (model.vid) { 86 | item = [YCDownloadManager itemWithFileId:model.vid]; 87 | }else if (model.video_url){ 88 | item = [YCDownloadManager itemsWithDownloadUrl:model.video_url].firstObject; 89 | } 90 | if (!item) { 91 | item = [YCDownloadItem itemWithUrl:model.video_url fileId:model.vid]; 92 | item.extraData = [VideoListInfoModel dateWithInfoModel:model]; 93 | [YCDownloadManager startDownloadWithItem:item]; 94 | } 95 | }]; 96 | } 97 | 98 | - (void)viewWillAppear:(BOOL)animated { 99 | 100 | [super viewWillAppear:animated]; 101 | [self.tableView reloadData]; 102 | } 103 | 104 | - (void)getLocalVideoList { 105 | [self.videoListArr removeAllObjects]; 106 | NSString *path = [[NSBundle mainBundle] pathForResource:@"video.json" ofType:nil]; 107 | NSData *data = [NSData dataWithContentsOfFile:path]; 108 | NSArray *arrM = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil]; 109 | NSMutableArray *models = [VideoListInfoModel getVideoListInfo:arrM]; 110 | [self.videoListArr addObjectsFromArray:models]; 111 | [self.tableView reloadData]; 112 | [self.tableView.mj_header endRefreshing]; 113 | [self.tableView.mj_footer endRefreshingWithNoMoreData]; 114 | } 115 | 116 | 117 | - (void)goCache { 118 | VideoCacheController *vc = [[VideoCacheController alloc] init]; 119 | vc.startAllBlk = ^{ 120 | [self downloadAll]; 121 | }; 122 | [self.navigationController pushViewController:vc animated:true]; 123 | } 124 | #pragma mark - videolistcell delegate 125 | 126 | 127 | /** 128 | 点击下载 129 | */ 130 | - (void)videoListCell:(VideoListInfoCell *)cell downloadVideo:(VideoListInfoModel *)model { 131 | YCDownloadItem *item = nil; 132 | if (model.vid) { 133 | item = [YCDownloadManager itemWithFileId:model.vid]; 134 | }else if (model.video_url){ 135 | item = [YCDownloadManager itemsWithDownloadUrl:model.video_url].firstObject; 136 | } 137 | if (!item) { 138 | item = [YCDownloadItem itemWithUrl:model.video_url fileId:model.vid]; 139 | item.extraData = [VideoListInfoModel dateWithInfoModel:model]; 140 | item.enableSpeed = true; 141 | [YCDownloadManager startDownloadWithItem:item]; 142 | } 143 | 144 | VideoCacheController *vc = [[VideoCacheController alloc] init]; 145 | [self.navigationController pushViewController:vc animated:true]; 146 | } 147 | 148 | #pragma mark - Table view data source & delegate 149 | 150 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { 151 | 152 | return self.videoListArr.count; 153 | } 154 | 155 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { 156 | VideoListInfoCell *cell = [VideoListInfoCell videoListInfoCellWithTableView:tableView]; 157 | VideoListInfoModel *model = self.videoListArr[indexPath.row]; 158 | [cell setVideoModel:model]; 159 | cell.delegate = self; 160 | YCDownloadItem *item = nil; 161 | if (model.vid) { 162 | item = [YCDownloadManager itemWithFileId:model.vid]; 163 | }else if (model.video_url){ 164 | item = [YCDownloadManager itemsWithDownloadUrl:model.video_url].firstObject; 165 | } 166 | [cell setDownloadStatus:item.downloadStatus]; 167 | return cell; 168 | } 169 | 170 | - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { 171 | return [VideoListInfoCell rowHeight]; 172 | } 173 | 174 | @end 175 | -------------------------------------------------------------------------------- /YCDownloadSessionDemo/YCDownloadSession/Class/VideoList/VideoListInfoModel.h: -------------------------------------------------------------------------------- 1 | // 2 | // VideoListInfoModel.h 3 | // YCDownloadSession 4 | // 5 | // Created by wz on 17/3/23. 6 | // Copyright © 2017年 onezen.cc. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface VideoListInfoModel : NSObject 12 | 13 | @property (nonatomic, copy) NSString *vid; 14 | @property (nonatomic, copy) NSString *m3u8_url; 15 | @property (nonatomic, copy) NSString *video_url; 16 | @property (nonatomic, copy) NSString *cover_url; 17 | @property (nonatomic, copy) NSString *title; 18 | @property (nonatomic, copy) NSString *category; 19 | @property (nonatomic, copy) NSString *video_desc; 20 | @property (nonatomic, copy) NSString *file_type; 21 | @property (nonatomic, assign) NSInteger file_size; 22 | 23 | 24 | + (NSMutableArray *)getVideoListInfo:(NSArray *)listInfos; 25 | 26 | + (NSData *)dateWithInfoModel:(VideoListInfoModel *)mo; 27 | + (VideoListInfoModel *)infoWithData:(NSData *)data; 28 | 29 | @end 30 | -------------------------------------------------------------------------------- /YCDownloadSessionDemo/YCDownloadSession/Class/VideoList/VideoListInfoModel.m: -------------------------------------------------------------------------------- 1 | // 2 | // VideoListInfoModel.m 3 | // YCDownloadSession 4 | // 5 | // Created by wz on 17/3/23. 6 | // Copyright © 2017年 onezen.cc. All rights reserved. 7 | // 8 | 9 | #import "VideoListInfoModel.h" 10 | #import 11 | 12 | @implementation VideoListInfoModel 13 | 14 | + (NSMutableArray *)getVideoListInfo:(NSArray *)listInfos { 15 | NSMutableArray *arrM = [NSMutableArray array]; 16 | for (NSDictionary *dict in listInfos) { 17 | VideoListInfoModel *model = [[VideoListInfoModel alloc] initWithDict:dict]; 18 | if ([model.vid isKindOfClass:[NSNumber class]]) { 19 | model.vid = [(NSNumber *)model.vid stringValue]; 20 | } 21 | [arrM addObject:model]; 22 | } 23 | return arrM; 24 | } 25 | 26 | - (void)setValue:(id)value forUndefinedKey:(NSString *)key {} 27 | 28 | - (void)setNilValueForKey:(NSString *)key{} 29 | 30 | - (instancetype)initWithDict:(NSDictionary *)dict { 31 | if (self = [super init]) { 32 | [self setValuesForKeysWithDictionary:dict]; 33 | } 34 | return self; 35 | } 36 | 37 | - (NSArray *)getAllKeys{ 38 | unsigned int count = 0; 39 | objc_property_t *properties = class_copyPropertyList(self.class, &count); 40 | NSMutableArray *keys = [NSMutableArray array]; 41 | for (int i=0; i 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | YCDownload 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIcons 12 | 13 | CFBundleIcons~ipad 14 | 15 | CFBundleIdentifier 16 | $(PRODUCT_BUNDLE_IDENTIFIER) 17 | CFBundleInfoDictionaryVersion 18 | 6.0 19 | CFBundleName 20 | $(PRODUCT_NAME) 21 | CFBundlePackageType 22 | APPL 23 | CFBundleShortVersionString 24 | 2.0.3 25 | CFBundleVersion 26 | 2 27 | LSRequiresIPhoneOS 28 | 29 | NSAppTransportSecurity 30 | 31 | NSAllowsArbitraryLoads 32 | 33 | 34 | UILaunchStoryboardName 35 | LaunchScreen 36 | UIRequiredDeviceCapabilities 37 | 38 | armv7 39 | 40 | UISupportedInterfaceOrientations 41 | 42 | UIInterfaceOrientationPortrait 43 | UIInterfaceOrientationLandscapeLeft 44 | UIInterfaceOrientationLandscapeRight 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /YCDownloadSessionDemo/YCDownloadSession/YCDownloadSwift.h: -------------------------------------------------------------------------------- 1 | // 2 | // YCDownloadSwift.h 3 | // YCDownloadSessionDemo 4 | // 5 | // Created by wz on 2018/10/10. 6 | // Copyright © 2018年 onezen.cc. All rights reserved. 7 | // 8 | 9 | #ifndef YCDownloadSwift_h 10 | #define YCDownloadSwift_h 11 | 12 | #import "YCDownloadSessionDemo-Swift.h" 13 | 14 | #endif /* YCDownloadSwift_h */ 15 | -------------------------------------------------------------------------------- /YCDownloadSessionDemo/YCDownloadSession/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // YCDownloadSession 4 | // 5 | // Created by wz on 17/3/14. 6 | // Copyright © 2017年 onezen.cc. 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 | -------------------------------------------------------------------------------- /YCDownloadSessionDemo/YCDownloadSession/video.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "vid": 10000, 4 | "video_url": "https://www.apple.com/105/media/us/iphone-x/2017/01df5b43-28e4-4848-bf20-490c34a926a7/films/feature/iphone-x-feature-tpl-cc-us-20170912_1280x720h.mp4", 5 | "cover_url": "http://boscdn.bpc.baidu.com/v1/mediaspot/fa6bb8d492a2480036514b7ab54e18e7.jpeg", 6 | "title": "iphone-x-feature-tpl-cc-us", 7 | "video_desc": "iphone-x-feature-tpl-cc-us-20170912_1280x720h", 8 | "m3u8_url": null, 9 | "file_size": 209413184, 10 | "file_type": "video/mp4", 11 | "createtime": "2018-12-05T08:23:29.000Z" 12 | }, 13 | { 14 | "vid": 10001, 15 | "video_url": "https://www.apple.com/105/media/cn/mac/family/2018/46c4b917_abfd_45a3_9b51_4e3054191797/films/bruce/mac-bruce-tpl-cn-2018_1280x720h.mp4", 16 | "cover_url": "http://boscdn.bpc.baidu.com/v1/mediaspot/fa6bb8d492a2480036514b7ab54e18e7.jpeg", 17 | "title": "mac-bruce-tpl-cn-2018", 18 | "video_desc": "mac-bruce-tpl-cn-2018_1280x720h", 19 | "m3u8_url": null, 20 | "file_size": 29536890, 21 | "file_type": "video/mp4", 22 | "createtime": "2018-12-05T08:24:58.000Z" 23 | }, 24 | { 25 | "vid": 10002, 26 | "video_url": "https://www.apple.com/105/media/us/mac/family/2018/46c4b917_abfd_45a3_9b51_4e3054191797/films/peter/mac-peter-tpl-cc-us-2018_1280x720h.mp4", 27 | "cover_url": "http://boscdn.bpc.baidu.com/v1/mediaspot/fa6bb8d492a2480036514b7ab54e18e7.jpeg", 28 | "title": "mac-peter-tpl-cc-us-2018_1280x720h", 29 | "video_desc": "mac-peter-tpl-cc-us-2018_1280x720h", 30 | "m3u8_url": null, 31 | "file_size": 29536890, 32 | "file_type": "video/mp4", 33 | "createtime": "2018-12-05T08:25:26.000Z" 34 | }, 35 | { 36 | "vid": 10003, 37 | "video_url": "https://www.apple.com/105/media/us/mac/family/2018/46c4b917_abfd_45a3_9b51_4e3054191797/films/grimes/mac-grimes-tpl-cc-us-2018_1280x720h.mp4", 38 | "cover_url": "http://boscdn.bpc.baidu.com/v1/mediaspot/fa6bb8d492a2480036514b7ab54e18e7.jpeg", 39 | "title": "mac-grimes-tpl-cc-us-2018_1280x720h", 40 | "video_desc": "mac-grimes-tpl-cc-us-2018_1280x720h", 41 | "m3u8_url": null, 42 | "file_size": 209413184, 43 | "file_type": "video/mp4", 44 | "createtime": "2018-12-05T08:23:29.000Z" 45 | }, 46 | { 47 | "vid": 10004, 48 | "video_url": "https://cdn.cnbj1.fds.api.mi-img.com/mi-mall/7194236f31b2e1e3da0fe06cfed4ba2b.mp4", 49 | "cover_url": "http://boscdn.bpc.baidu.com/v1/mediaspot/fa6bb8d492a2480036514b7ab54e18e7.jpeg", 50 | "title": "fa6bb8d492a2480036514b7ab54e18e7", 51 | "m3u8_url": null, 52 | "file_size": 29536890, 53 | "file_type": "video/mp4", 54 | "createtime": "2018-12-05T08:24:58.000Z" 55 | }, 56 | { 57 | "vid": 10005, 58 | "video_url": "http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4", 59 | "cover_url": "http://boscdn.bpc.baidu.com/v1/mediaspot/fa6bb8d492a2480036514b7ab54e18e7.jpeg", 60 | "title": "fa6bb8d492a2480036514b7ab54e18e7", 61 | "m3u8_url": null, 62 | "file_size": 29536890, 63 | "file_type": "video/mp4", 64 | "createtime": "2018-12-05T08:25:26.000Z" 65 | }, 66 | { 67 | "vid": 10006, 68 | "video_url": "http://vjs.zencdn.net/v/oceans.mp4", 69 | "cover_url": "http://boscdn.bpc.baidu.com/v1/mediaspot/fa6bb8d492a2480036514b7ab54e18e7.jpeg", 70 | "title": "fa6bb8d492a2480036514b7ab54e18e7", 71 | "m3u8_url": null, 72 | "file_size": 209413184, 73 | "file_type": "video/mp4", 74 | "createtime": "2018-12-05T08:23:29.000Z" 75 | }, 76 | { 77 | "vid": 10007, 78 | "video_url": "https://media.w3.org/2010/05/sintel/trailer.mp4", 79 | "cover_url": "http://boscdn.bpc.baidu.com/v1/mediaspot/fa6bb8d492a2480036514b7ab54e18e7.jpeg", 80 | "title": "fa6bb8d492a2480036514b7ab54e18e7", 81 | "m3u8_url": null, 82 | "file_size": 29536890, 83 | "file_type": "video/mp4", 84 | "createtime": "2018-12-05T08:24:58.000Z" 85 | }, 86 | { 87 | "vid": 10008, 88 | "video_url": "http://mirror.aarnet.edu.au/pub/TED-talks/911Mothers_2010W-480p.mp4", 89 | "cover_url": "http://boscdn.bpc.baidu.com/v1/mediaspot/fa6bb8d492a2480036514b7ab54e18e7.jpeg", 90 | "title": "fa6bb8d492a2480036514b7ab54e18e7", 91 | "m3u8_url": null, 92 | "file_size": 29536890, 93 | "file_type": "video/mp4", 94 | "createtime": "2018-12-05T08:25:26.000Z" 95 | }, 96 | { 97 | "vid": 100000, 98 | "video_url": "http://file-examples.com/wp-content/uploads/2017/04/file_example_MP4_1920_18MG.mp4", 99 | "cover_url": "http://boscdn.bpc.baidu.com/v1/mediaspot/fa6bb8d492a2480036514b7ab54e18e7.jpeg", 100 | "title": "file-examples", 101 | "video_desc": "测试视频", 102 | "category": "测试", 103 | "m3u8_url": null, 104 | "file_size": 17839845, 105 | "file_type": "video/mp4", 106 | "createtime": "2018-12-05T08:23:29.000Z" 107 | }, 108 | { 109 | "vid": 100001, 110 | "video_url": "http://oss.onezen.cc/video/test-download-xs-max.mp4", 111 | "cover_url": "http://boscdn.bpc.baidu.com/v1/mediaspot/fa6bb8d492a2480036514b7ab54e18e7.jpeg", 112 | "title": "301 file test", 113 | "video_desc": "测试视频301", 114 | "category": "测试", 115 | "m3u8_url": null, 116 | "file_size": 29536890, 117 | "file_type": "video/mp4", 118 | "createtime": "2018-12-05T08:24:58.000Z" 119 | }, 120 | { 121 | "vid": 100002, 122 | "video_url": "http://api.onezen.cc/video/301/1.mp4", 123 | "cover_url": "http://boscdn.bpc.baidu.com/v1/mediaspot/fa6bb8d492a2480036514b7ab54e18e7.jpeg", 124 | "title": "302 file test", 125 | "video_desc": "测试视频302", 126 | "category": "测试", 127 | "m3u8_url": null, 128 | "file_size": 29536890, 129 | "file_type": "video/mp4", 130 | "createtime": "2018-12-05T08:25:26.000Z" 131 | }, 132 | { 133 | "vid": 100004, 134 | "video_url": "http://api.onezen.cc/video/404/1.mp4", 135 | "cover_url": "http://boscdn.bpc.baidu.com/v1/mediaspot/fa6bb8d492a2480036514b7ab54e18e7.jpeg", 136 | "title": "404 file test", 137 | "video_desc": "测试视频404", 138 | "category": "测试", 139 | "m3u8_url": null, 140 | "file_size": null, 141 | "file_type": null, 142 | "createtime": "2018-12-05T08:25:52.000Z" 143 | }, 144 | { 145 | "vid": 100005, 146 | "video_url": "https://vd1.bdstatic.com/mda-hiqmm8s10vww26sx/mda-hiqmm8s10vww26sx.mp4?playlist=%5B%22hd%22%5D&auth_key=1506158514-0-0-6cde713ec6e6a15bd856fbb4f2564658&bcevod_channel=searchbox_feed", 147 | "cover_url": "http://boscdn.bpc.baidu.com/v1/mediaspot/fa6bb8d492a2480036514b7ab54e18e7.jpeg", 148 | "title": "1000度的钢丝网和彩色黏土", 149 | "video_desc": "测试视频", 150 | "category": "测试", 151 | "m3u8_url": null, 152 | "file_size": 19727666, 153 | "file_type": "video/mp4", 154 | "createtime": "2018-12-05T08:26:36.000Z" 155 | }, 156 | { 157 | "vid": 100006, 158 | "video_url": "https://vd1.bdstatic.com/mda-hez17qvhyauh9ybf/mda-hez17qvhyauh9ybf.mp4?auth_key=1506158741-0-0-d0a39ee4b472f0af492fed6d20396697&bcevod_channel=searchbox_feed", 159 | "cover_url": "http://boscdn.bpc.baidu.com/v1/mediaspot/fa6bb8d492a2480036514b7ab54e18e7.jpeg", 160 | "title": "亲子颜色学习", 161 | "video_desc": "测试视频", 162 | "category": "测试", 163 | "m3u8_url": null, 164 | "file_size": 68146903, 165 | "file_type": "video/mp4", 166 | "createtime": "2018-12-05T08:27:14.000Z" 167 | }, 168 | { 169 | "vid": 100007, 170 | "video_url": "https://vd1.bdstatic.com/mda-hiwgyhhpzdscwkyu/mda-hiwgyhhpzdscwkyu.mp4?playlist=%5B%22hd%22%5D&auth_key=1506159218-0-0-02860d7d3bad11758b123c96aa7271ec&bcevod_channel=searchbox_feed", 171 | "cover_url": "http://boscdn.bpc.baidu.com/v1/mediaspot/8b7ee815c80dc50c62aa702c1050a07b.png", 172 | "title": "赵丽颖", 173 | "video_desc": "测试视频", 174 | "category": "测试", 175 | "m3u8_url": null, 176 | "file_size": 3876551, 177 | "file_type": "video/mp4", 178 | "createtime": "2018-12-05T08:27:42.000Z" 179 | }, 180 | { 181 | "vid": 100008, 182 | "video_url": "http://vd1.bdstatic.com/mda-hifgbu4tm1a1qzeu/mda-hifgbu4tm1a1qzeu.mp4?playlist=%5B%22hd%22%2C%22sc%22%5D&auth_key=1506244843-0-0-ecdd91d0d43be82b39bfb7ec3419e6b3&bcevod_channel=pae_search", 183 | "cover_url": "http://cdn01.baidu-img.cn/timg?wisealaddin&size=f185_125&quality=80&sec=1506158531&di=7fa1950c703d972a57c1b8d55d86b6af&src=http%3A%2F%2Fbos.nj.bpc.baidu.com%2Fv1%2Fmediaspot%2Fa766f06fc409f12a0584a3e4848fed93.png", 184 | "title": "牛人易拉罐发动机", 185 | "video_desc": "测试视频", 186 | "category": "测试", 187 | "m3u8_url": null, 188 | "file_size": 2233312, 189 | "file_type": "video/mp4", 190 | "createtime": "2018-12-05T08:31:30.000Z" 191 | }, 192 | { 193 | "vid": 100009, 194 | "video_url": "http://vd1.bdstatic.com/mda-hhmf74humzsjh5vu/mda-hhmf74humzsjh5vu.mp4?playlist=%5B%22hd%22%5D&auth_key=1506244931-0-0-e44269ae5ad22c5727c790735a4493dc&bcevod_channel=pae_search", 195 | "cover_url": "http://cdn01.baidu-img.cn/timg?wisealaddin&size=f414_233&quality=80&sec=1506158531&di=86e9ef01bdc3d892bf7508002fa2da10&src=http%3A%2F%2Fboscdn.bpc.baidu.com%2Fv1%2Fmediaspot%2Fb3cc2d9b570b0d279f28d68c681661f3.png", 196 | "title": "黑科技易拉罐发动机", 197 | "video_desc": "测试视频", 198 | "category": "测试", 199 | "m3u8_url": null, 200 | "file_size": null, 201 | "file_type": null, 202 | "createtime": "2018-12-05T08:32:26.000Z" 203 | }, 204 | { 205 | "vid": 100010, 206 | "video_url": "http://vd1.bdstatic.com/mda-hippg2tb6m76yzn5/mda-hippg2tb6m76yzn5.mp4?playlist=%5B%22hd%22%2C%22sc%22%5D&auth_key=1506245036-0-0-ad4426cc88fef724b489fd33f2346aef&bcevod_channel=pae_search", 207 | "cover_url": "http://cdn01.baidu-img.cn/timg?wisealaddin&size=f185_125&quality=80&sec=1506158700&di=d1350b1e93b177385677c46ef59b3883&src=http%3A%2F%2Fpic.rmb.bdstatic.com%2Fvod_72b3a091d55f8adc30854f94c44898d5.jpg", 208 | "title": "摩托车发动机", 209 | "video_desc": "测试视频", 210 | "category": "测试", 211 | "m3u8_url": null, 212 | "file_size": 12526074, 213 | "file_type": "video/mp4", 214 | "createtime": "2018-12-05T08:33:32.000Z" 215 | }, 216 | { 217 | "vid": 100011, 218 | "video_url": "http://vd1.bdstatic.com/mda-hhbh2uf27n6a060c/mda-hhbh2uf27n6a060c.mp4?playlist=%5B%22hd%22%5D&auth_key=1506245100-0-0-6c6524317d829e486630aa35da9df353&bcevod_channel=pae_search", 219 | "cover_url": "http://cdn01.baidu-img.cn/timg?wisealaddin&size=f185_125&quality=80&sec=1506158700&di=d2769eefab28c3ea84327e167a713326&src=http%3A%2F%2Fpic.rmb.bdstatic.com%2F1365927fd049c6d4784c441b2b29c9b8.jpg", 220 | "title": "学生野外捡发动机", 221 | "video_desc": "测试视频", 222 | "category": "测试", 223 | "m3u8_url": null, 224 | "file_size": 4799439, 225 | "file_type": "video/mp4", 226 | "createtime": "2018-12-05T08:34:17.000Z" 227 | }, 228 | { 229 | "vid": 100012, 230 | "video_url": "http://vd1.bdstatic.com/mda-hirsd45wk2zkeder/mda-hirsd45wk2zkeder.mp4?playlist=%5B%22hd%22%5D&auth_key=1506245547-0-0-3de0aa3b920536e751b8c2e21b8f449f&bcevod_channel=pae_search", 231 | "cover_url": "http://cdn01.baidu-img.cn/timg?wisealaddin&size=f185_125&quality=80&sec=1506159044&di=b15c6720bb597a89bbe17b4c0dbfc6f1&src=http%3A%2F%2Fpic.rmb.bdstatic.com%2Fmda-hiqfs2cxi47zn6sz.jpg", 232 | "title": "12万", 233 | "video_desc": "测试视频", 234 | "category": "测试", 235 | "m3u8_url": null, 236 | "file_size": 6097771, 237 | "file_type": "video/mp4", 238 | "createtime": "2018-12-05T08:34:48.000Z" 239 | }, 240 | { 241 | "vid": 100013, 242 | "video_url": "http://vd1.bdstatic.com/mda-hihm59tq2dpzsai2/mda-hihm59tq2dpzsai2.mp4?playlist=%5B%22hd%22%5D&auth_key=1506245685-0-0-66217496c6306e00677b6b7f511581de&bcevod_channel=pae_search", 243 | "cover_url": "http://cdn01.baidu-img.cn/timg?wisealaddin&size=f185_125&quality=80&sec=1506159044&di=b15c6720bb597a89bbe17b4c0dbfc6f1&src=http%3A%2F%2Fpic.rmb.bdstatic.com%2Fmda-hiqfs2cxi47zn6sz.jpg", 244 | "title": "12万测试视频", 245 | "video_desc": "测试视频", 246 | "category": "测试", 247 | "m3u8_url": null, 248 | "file_size": 4570666, 249 | "file_type": "video/mp4", 250 | "createtime": "2018-12-05T08:36:19.000Z" 251 | }, 252 | { 253 | "vid": 100014, 254 | "video_url": "http://vd1.bdstatic.com/mda-hd0tzu0gfwgcx5rr/mda-hd0tzu0gfwgcx5rr.mp4?playlist=%5B%22hd%22%2C%22sc%22%5D&auth_key=1506245859-0-0-29f6ba3f0df614f61fe6f63093378ac5&bcevod_channel=pae_search", 255 | "cover_url": "https://ss0.bdstatic.com/9bA1vGfa2gU2pMbfm9GUKT-w/timg?wisealaddin&size=f160_120&quality=80&sec=1506159411&di=8b515e094171a9c83ccd6bf52444e7e6&src=http%3A%2F%2Fpuui.qpic.cn%2Fqqvideo_ori%2F0%2Fi0551wu0q3a_496_280%2F0", 256 | "title": "两个小家伙", 257 | "video_desc": "测试视频", 258 | "category": "测试", 259 | "m3u8_url": null, 260 | "file_size": 3253918, 261 | "file_type": "video/mp4", 262 | "createtime": "2018-12-05T08:36:31.000Z" 263 | }, 264 | { 265 | "vid": 100015, 266 | "video_url": "http://vd1.bdstatic.com/mda-hig01fitvbhmne29/mda-hig01fitvbhmne29.mp4?playlist=%5B%22hd%22%2C%22sc%22%5D&auth_key=1506245874-0-0-55657c86fa3761c70e20b387a9484086&bcevod_channel=pae_search", 267 | "cover_url": "http://cdn01.baidu-img.cn/timg?wisealaddin&size=f185_125&quality=80&sec=1506159581&di=3308dc12f9ff0c6c4115afabe6974546&src=http%3A%2F%2Fpic.rmb.bdstatic.com%2Fc64c6a2ea9e3fcf68ea05bb293a96caa.jpg", 268 | "title": "两个夜华搞笑", 269 | "video_desc": "测试视频", 270 | "category": "测试", 271 | "m3u8_url": null, 272 | "file_size": 4952171, 273 | "file_type": "video/mp4", 274 | "createtime": "2018-12-05T08:36:41.000Z" 275 | }, 276 | { 277 | "vid": 100016, 278 | "video_url": "http://vd1.bdstatic.com/mda-hiti1y2h7ujkruk7/mda-hiti1y2h7ujkruk7.mp4?playlist=%5B%22hd%22%5D&auth_key=1506245981-0-0-8df55a1752262f8ca985389c69687bc5&bcevod_channel=pae_search", 279 | "cover_url": "http://cdn01.baidu-img.cn/timg?wisealaddin&size=f185_125&quality=80&sec=1506159636&di=5227017ed04c227b0f22130f3f6c26d2&src=http%3A%2F%2Fboscdn.bpc.baidu.com%2Fv1%2Fmediaspot%2Fa753ad0ed04eafb0f5ecff61c42df64c.png", 280 | "title": "三叔,白浅上神", 281 | "video_desc": "测试视频", 282 | "category": "测试", 283 | "m3u8_url": null, 284 | "file_size": 2971534, 285 | "file_type": "video/mp4", 286 | "createtime": "2018-12-05T08:36:50.000Z" 287 | }, 288 | { 289 | "vid": 100017, 290 | "video_url": "http://vd1.bdstatic.com/mda-hc6mrcmk0s5ej4ed/mda-hc6mrcmk0s5ej4ed.mp4?playlist=%5B%22hd%22%2C%22sc%22%5D&auth_key=1506246036-0-0-2afa15ce89cfe65b0dcfe5cbcc91ab3f&bcevod_channel=pae_search", 291 | "cover_url": "http://cdn01.baidu-img.cn/timg?wisealaddin&size=f185_125&quality=80&sec=1506159636&di=0c811bc83cab8db42d6ca5391f776182&src=http%3A%2F%2Fpic.rmb.bdstatic.com%2F25f6c5d1e4aea512a68543a5fbe6b903.jpg", 292 | "title": "白浅说的这番话绝了", 293 | "video_desc": "测试视频", 294 | "category": "测试", 295 | "m3u8_url": null, 296 | "file_size": 1365334, 297 | "file_type": "video/mp4", 298 | "createtime": "2018-12-05T08:36:59.000Z" 299 | }, 300 | { 301 | "vid": 100018, 302 | "video_url": "http://vd1.bdstatic.com/mda-hhztvi8vq14feyvn/mda-hhztvi8vq14feyvn.mp4?playlist=%5B%22hd%22%5D&auth_key=1506246097-0-0-6505e29c0fe37fdebcf061e016d05020&bcevod_channel=pae_search", 303 | "cover_url": "http://cdn01.baidu-img.cn/timg?wisealaddin&size=f185_125&quality=80&sec=1506159761&di=f195f713cbf14bfd5f308277db64327b&src=http%3A%2F%2Fboscdn.bpc.baidu.com%2Fv1%2Fmediaspot%2Fec309e972415220fe3d51450dd3c7464.png", 304 | "title": "tfboys", 305 | "video_desc": "测试视频", 306 | "category": "测试", 307 | "m3u8_url": null, 308 | "file_size": 2080449, 309 | "file_type": "video/mp4", 310 | "createtime": "2018-12-05T08:37:06.000Z" 311 | }, 312 | { 313 | "vid": 100019, 314 | "video_url": "https://vd1.bdstatic.com/mda-hekxfmvyjyh6ju0m/mda-hekxfmvyjyh6ju0m.mp4?playlist=%5B%22hd%22%2C%22sc%22%5D&auth_key=1506158510-0-0-2843ff6bdc744d9f7f96ae0ca91bbee1&bcevod_channel=searchbox_feed", 315 | "cover_url": "http://vimg2.ws.126.net/image/snapshot/2017/6/L/H/VCLCVPLLH.jpg", 316 | "title": "1000度的钢丝网和彩色黏土", 317 | "video_desc": "测试视频", 318 | "category": "测试", 319 | "m3u8_url": null, 320 | "file_size": 18843623, 321 | "file_type": "video/mp4", 322 | "createtime": "2018-12-05T08:37:14.000Z" 323 | } 324 | ] 325 | -------------------------------------------------------------------------------- /YCDownloadSessionDemo/YCDownloadSessionTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 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 | -------------------------------------------------------------------------------- /YCDownloadSessionDemo/YCDownloadSessionTests/YCDownloadSessionTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // YCDownloadSessionTests.m 3 | // YCDownloadSessionTests 4 | // 5 | // Created by wz on 2017/12/12. 6 | // Copyright © 2017年 onezen.cc. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "YCDownloadManager.h" 11 | 12 | @interface YCDownloadSessionTests : XCTestCase 13 | 14 | @end 15 | 16 | @implementation YCDownloadSessionTests 17 | 18 | - (void)setUp { 19 | [super setUp]; 20 | 21 | 22 | 23 | } 24 | 25 | - (void)tearDown { 26 | // Put teardown code here. This method is called after the invocation of each test method in the class. 27 | [super tearDown]; 28 | } 29 | 30 | - (void)testExample { 31 | 32 | 33 | } 34 | 35 | - (void)testPerformanceExample { 36 | // This is an example of a performance test case. 37 | [self measureBlock:^{ 38 | // Put the code you want to measure the time of here. 39 | }]; 40 | } 41 | 42 | @end 43 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman --------------------------------------------------------------------------------