├── .gitignore ├── .swift-version ├── LICENSE ├── OfflineBreakPointDownload.xcodeproj ├── project.pbxproj └── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── OfflineBreakPointDownload ├── AppDelegate.h ├── AppDelegate.m ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── DwonloadDemo │ ├── LYDownloadManager.h │ ├── LYDownloadManager.m │ ├── LYProgressView.h │ ├── LYProgressView.m │ ├── ViewController.h │ └── ViewController.m ├── Info.plist └── main.m ├── README.md ├── SGDownload.podspec └── SGDwonload ├── NSString+SGHashString.h ├── NSString+SGHashString.m ├── NSURLSession+SGDownloadTask.h ├── NSURLSession+SGDownloadTask.m ├── SGCacheManager.h ├── SGCacheManager.m ├── SGDownloadManager.h ├── SGDownloadManager.m ├── SGDownloadOperation.h ├── SGDownloadOperation.m ├── SGDownloadQueue.h ├── SGDownloadQueue.m ├── SGDownloadSession.h └── SGDownloadSession.m /.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 | .DS_Store 30 | .DS_Store? 31 | *.swp 32 | ._* 33 | .Spotlight-V100 34 | .Trashes 35 | Icon? 36 | ehthumbs.db 37 | Thumbs.db 38 | 39 | # CocoaPods 40 | # 41 | # We recommend against adding the Pods directory to your .gitignore. However 42 | # you should judge for yourself, the pros and cons are mentioned at: 43 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 44 | # 45 | # Pods/ 46 | 47 | # Carthage 48 | # 49 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 50 | # Carthage/Checkouts 51 | 52 | Carthage/Build 53 | 54 | # fastlane 55 | # 56 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 57 | # screenshots whenever they are needed. 58 | # For more information about the recommended setup visit: 59 | # https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md 60 | 61 | fastlane/report.xml 62 | fastlane/screenshots 63 | 64 | #Code Injection 65 | # 66 | # After new code Injection tools there's a generated folder /iOSInjectionProject 67 | # https://github.com/johnno1962/injectionforxcode 68 | 69 | iOSInjectionProject/ 70 | -------------------------------------------------------------------------------- /.swift-version: -------------------------------------------------------------------------------- 1 | 2.3 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Shangen 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 | -------------------------------------------------------------------------------- /OfflineBreakPointDownload.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | A70A45121F62A12600AAF37A /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = A70A45111F62A12600AAF37A /* ViewController.m */; }; 11 | A70A45151F62A17900AAF37A /* LYProgressView.m in Sources */ = {isa = PBXBuildFile; fileRef = A70A45141F62A17900AAF37A /* LYProgressView.m */; }; 12 | A70A45181F62BFFD00AAF37A /* LYDownloadManager.m in Sources */ = {isa = PBXBuildFile; fileRef = A70A45171F62BFFD00AAF37A /* LYDownloadManager.m */; }; 13 | A75D1E691DE94FEE0008BAF6 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = A75D1E681DE94FEE0008BAF6 /* main.m */; }; 14 | A75D1E6C1DE94FEE0008BAF6 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = A75D1E6B1DE94FEE0008BAF6 /* AppDelegate.m */; }; 15 | A75D1E721DE94FEF0008BAF6 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = A75D1E701DE94FEF0008BAF6 /* Main.storyboard */; }; 16 | A75D1E741DE94FEF0008BAF6 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A75D1E731DE94FEF0008BAF6 /* Assets.xcassets */; }; 17 | A75D1E771DE94FEF0008BAF6 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = A75D1E751DE94FEF0008BAF6 /* LaunchScreen.storyboard */; }; 18 | A775BF4B1FE90B710031047D /* NSString+SGHashString.m in Sources */ = {isa = PBXBuildFile; fileRef = A775BF3E1FE90B710031047D /* NSString+SGHashString.m */; }; 19 | A775BF4C1FE90B710031047D /* NSURLSession+SGDownloadTask.m in Sources */ = {isa = PBXBuildFile; fileRef = A775BF401FE90B710031047D /* NSURLSession+SGDownloadTask.m */; }; 20 | A775BF4D1FE90B710031047D /* SGCacheManager.m in Sources */ = {isa = PBXBuildFile; fileRef = A775BF421FE90B710031047D /* SGCacheManager.m */; }; 21 | A775BF4E1FE90B710031047D /* SGDownloadManager.m in Sources */ = {isa = PBXBuildFile; fileRef = A775BF441FE90B710031047D /* SGDownloadManager.m */; }; 22 | A775BF4F1FE90B710031047D /* SGDownloadOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = A775BF461FE90B710031047D /* SGDownloadOperation.m */; }; 23 | A775BF501FE90B710031047D /* SGDownloadQueue.m in Sources */ = {isa = PBXBuildFile; fileRef = A775BF481FE90B710031047D /* SGDownloadQueue.m */; }; 24 | A775BF511FE90B710031047D /* SGDownloadSession.m in Sources */ = {isa = PBXBuildFile; fileRef = A775BF4A1FE90B710031047D /* SGDownloadSession.m */; }; 25 | /* End PBXBuildFile section */ 26 | 27 | /* Begin PBXFileReference section */ 28 | A70A45101F62A12600AAF37A /* ViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; 29 | A70A45111F62A12600AAF37A /* ViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; 30 | A70A45131F62A17900AAF37A /* LYProgressView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LYProgressView.h; sourceTree = ""; }; 31 | A70A45141F62A17900AAF37A /* LYProgressView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LYProgressView.m; sourceTree = ""; }; 32 | A70A45161F62BFFD00AAF37A /* LYDownloadManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LYDownloadManager.h; sourceTree = ""; }; 33 | A70A45171F62BFFD00AAF37A /* LYDownloadManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LYDownloadManager.m; sourceTree = ""; }; 34 | A75D1E641DE94FEE0008BAF6 /* OfflineBreakPointDownload.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = OfflineBreakPointDownload.app; sourceTree = BUILT_PRODUCTS_DIR; }; 35 | A75D1E681DE94FEE0008BAF6 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 36 | A75D1E6A1DE94FEE0008BAF6 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 37 | A75D1E6B1DE94FEE0008BAF6 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 38 | A75D1E711DE94FEF0008BAF6 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 39 | A75D1E731DE94FEF0008BAF6 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 40 | A75D1E761DE94FEF0008BAF6 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 41 | A75D1E781DE94FEF0008BAF6 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 42 | A775BF3D1FE90B710031047D /* NSString+SGHashString.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSString+SGHashString.h"; sourceTree = ""; }; 43 | A775BF3E1FE90B710031047D /* NSString+SGHashString.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSString+SGHashString.m"; sourceTree = ""; }; 44 | A775BF3F1FE90B710031047D /* NSURLSession+SGDownloadTask.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSURLSession+SGDownloadTask.h"; sourceTree = ""; }; 45 | A775BF401FE90B710031047D /* NSURLSession+SGDownloadTask.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSURLSession+SGDownloadTask.m"; sourceTree = ""; }; 46 | A775BF411FE90B710031047D /* SGCacheManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SGCacheManager.h; sourceTree = ""; }; 47 | A775BF421FE90B710031047D /* SGCacheManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SGCacheManager.m; sourceTree = ""; }; 48 | A775BF431FE90B710031047D /* SGDownloadManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SGDownloadManager.h; sourceTree = ""; }; 49 | A775BF441FE90B710031047D /* SGDownloadManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SGDownloadManager.m; sourceTree = ""; }; 50 | A775BF451FE90B710031047D /* SGDownloadOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SGDownloadOperation.h; sourceTree = ""; }; 51 | A775BF461FE90B710031047D /* SGDownloadOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SGDownloadOperation.m; sourceTree = ""; }; 52 | A775BF471FE90B710031047D /* SGDownloadQueue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SGDownloadQueue.h; sourceTree = ""; }; 53 | A775BF481FE90B710031047D /* SGDownloadQueue.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SGDownloadQueue.m; sourceTree = ""; }; 54 | A775BF491FE90B710031047D /* SGDownloadSession.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SGDownloadSession.h; sourceTree = ""; }; 55 | A775BF4A1FE90B710031047D /* SGDownloadSession.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SGDownloadSession.m; sourceTree = ""; }; 56 | /* End PBXFileReference section */ 57 | 58 | /* Begin PBXFrameworksBuildPhase section */ 59 | A75D1E611DE94FEE0008BAF6 /* Frameworks */ = { 60 | isa = PBXFrameworksBuildPhase; 61 | buildActionMask = 2147483647; 62 | files = ( 63 | ); 64 | runOnlyForDeploymentPostprocessing = 0; 65 | }; 66 | /* End PBXFrameworksBuildPhase section */ 67 | 68 | /* Begin PBXGroup section */ 69 | A70A450F1F62A11000AAF37A /* DwonloadDemo */ = { 70 | isa = PBXGroup; 71 | children = ( 72 | A70A45101F62A12600AAF37A /* ViewController.h */, 73 | A70A45111F62A12600AAF37A /* ViewController.m */, 74 | A75D1E701DE94FEF0008BAF6 /* Main.storyboard */, 75 | A70A45131F62A17900AAF37A /* LYProgressView.h */, 76 | A70A45141F62A17900AAF37A /* LYProgressView.m */, 77 | A70A45161F62BFFD00AAF37A /* LYDownloadManager.h */, 78 | A70A45171F62BFFD00AAF37A /* LYDownloadManager.m */, 79 | ); 80 | path = DwonloadDemo; 81 | sourceTree = ""; 82 | }; 83 | A75D1E5B1DE94FEE0008BAF6 = { 84 | isa = PBXGroup; 85 | children = ( 86 | A75D1E661DE94FEE0008BAF6 /* OfflineBreakPointDownload */, 87 | A75D1E651DE94FEE0008BAF6 /* Products */, 88 | ); 89 | sourceTree = ""; 90 | }; 91 | A75D1E651DE94FEE0008BAF6 /* Products */ = { 92 | isa = PBXGroup; 93 | children = ( 94 | A75D1E641DE94FEE0008BAF6 /* OfflineBreakPointDownload.app */, 95 | ); 96 | name = Products; 97 | sourceTree = ""; 98 | }; 99 | A75D1E661DE94FEE0008BAF6 /* OfflineBreakPointDownload */ = { 100 | isa = PBXGroup; 101 | children = ( 102 | A775BF3C1FE90B710031047D /* SGDwonload */, 103 | A70A450F1F62A11000AAF37A /* DwonloadDemo */, 104 | A75D1E671DE94FEE0008BAF6 /* Supporting Files */, 105 | ); 106 | path = OfflineBreakPointDownload; 107 | sourceTree = ""; 108 | }; 109 | A75D1E671DE94FEE0008BAF6 /* Supporting Files */ = { 110 | isa = PBXGroup; 111 | children = ( 112 | A75D1E6A1DE94FEE0008BAF6 /* AppDelegate.h */, 113 | A75D1E6B1DE94FEE0008BAF6 /* AppDelegate.m */, 114 | A75D1E731DE94FEF0008BAF6 /* Assets.xcassets */, 115 | A75D1E751DE94FEF0008BAF6 /* LaunchScreen.storyboard */, 116 | A75D1E781DE94FEF0008BAF6 /* Info.plist */, 117 | A75D1E681DE94FEE0008BAF6 /* main.m */, 118 | ); 119 | name = "Supporting Files"; 120 | sourceTree = ""; 121 | }; 122 | A775BF3C1FE90B710031047D /* SGDwonload */ = { 123 | isa = PBXGroup; 124 | children = ( 125 | A775BF3D1FE90B710031047D /* NSString+SGHashString.h */, 126 | A775BF3E1FE90B710031047D /* NSString+SGHashString.m */, 127 | A775BF3F1FE90B710031047D /* NSURLSession+SGDownloadTask.h */, 128 | A775BF401FE90B710031047D /* NSURLSession+SGDownloadTask.m */, 129 | A775BF411FE90B710031047D /* SGCacheManager.h */, 130 | A775BF421FE90B710031047D /* SGCacheManager.m */, 131 | A775BF431FE90B710031047D /* SGDownloadManager.h */, 132 | A775BF441FE90B710031047D /* SGDownloadManager.m */, 133 | A775BF451FE90B710031047D /* SGDownloadOperation.h */, 134 | A775BF461FE90B710031047D /* SGDownloadOperation.m */, 135 | A775BF471FE90B710031047D /* SGDownloadQueue.h */, 136 | A775BF481FE90B710031047D /* SGDownloadQueue.m */, 137 | A775BF491FE90B710031047D /* SGDownloadSession.h */, 138 | A775BF4A1FE90B710031047D /* SGDownloadSession.m */, 139 | ); 140 | path = SGDwonload; 141 | sourceTree = SOURCE_ROOT; 142 | }; 143 | /* End PBXGroup section */ 144 | 145 | /* Begin PBXNativeTarget section */ 146 | A75D1E631DE94FEE0008BAF6 /* OfflineBreakPointDownload */ = { 147 | isa = PBXNativeTarget; 148 | buildConfigurationList = A75D1E7B1DE94FEF0008BAF6 /* Build configuration list for PBXNativeTarget "OfflineBreakPointDownload" */; 149 | buildPhases = ( 150 | A75D1E601DE94FEE0008BAF6 /* Sources */, 151 | A75D1E611DE94FEE0008BAF6 /* Frameworks */, 152 | A75D1E621DE94FEE0008BAF6 /* Resources */, 153 | ); 154 | buildRules = ( 155 | ); 156 | dependencies = ( 157 | ); 158 | name = OfflineBreakPointDownload; 159 | productName = OfflineBreakPointDownload; 160 | productReference = A75D1E641DE94FEE0008BAF6 /* OfflineBreakPointDownload.app */; 161 | productType = "com.apple.product-type.application"; 162 | }; 163 | /* End PBXNativeTarget section */ 164 | 165 | /* Begin PBXProject section */ 166 | A75D1E5C1DE94FEE0008BAF6 /* Project object */ = { 167 | isa = PBXProject; 168 | attributes = { 169 | LastUpgradeCheck = 1000; 170 | ORGANIZATIONNAME = "Shangen Zhang"; 171 | TargetAttributes = { 172 | A75D1E631DE94FEE0008BAF6 = { 173 | CreatedOnToolsVersion = 8.1; 174 | DevelopmentTeam = WZ22KTPCG8; 175 | ProvisioningStyle = Automatic; 176 | }; 177 | }; 178 | }; 179 | buildConfigurationList = A75D1E5F1DE94FEE0008BAF6 /* Build configuration list for PBXProject "OfflineBreakPointDownload" */; 180 | compatibilityVersion = "Xcode 3.2"; 181 | developmentRegion = English; 182 | hasScannedForEncodings = 0; 183 | knownRegions = ( 184 | en, 185 | Base, 186 | ); 187 | mainGroup = A75D1E5B1DE94FEE0008BAF6; 188 | productRefGroup = A75D1E651DE94FEE0008BAF6 /* Products */; 189 | projectDirPath = ""; 190 | projectRoot = ""; 191 | targets = ( 192 | A75D1E631DE94FEE0008BAF6 /* OfflineBreakPointDownload */, 193 | ); 194 | }; 195 | /* End PBXProject section */ 196 | 197 | /* Begin PBXResourcesBuildPhase section */ 198 | A75D1E621DE94FEE0008BAF6 /* Resources */ = { 199 | isa = PBXResourcesBuildPhase; 200 | buildActionMask = 2147483647; 201 | files = ( 202 | A75D1E771DE94FEF0008BAF6 /* LaunchScreen.storyboard in Resources */, 203 | A75D1E741DE94FEF0008BAF6 /* Assets.xcassets in Resources */, 204 | A75D1E721DE94FEF0008BAF6 /* Main.storyboard in Resources */, 205 | ); 206 | runOnlyForDeploymentPostprocessing = 0; 207 | }; 208 | /* End PBXResourcesBuildPhase section */ 209 | 210 | /* Begin PBXSourcesBuildPhase section */ 211 | A75D1E601DE94FEE0008BAF6 /* Sources */ = { 212 | isa = PBXSourcesBuildPhase; 213 | buildActionMask = 2147483647; 214 | files = ( 215 | A775BF501FE90B710031047D /* SGDownloadQueue.m in Sources */, 216 | A775BF4B1FE90B710031047D /* NSString+SGHashString.m in Sources */, 217 | A70A45121F62A12600AAF37A /* ViewController.m in Sources */, 218 | A75D1E6C1DE94FEE0008BAF6 /* AppDelegate.m in Sources */, 219 | A70A45151F62A17900AAF37A /* LYProgressView.m in Sources */, 220 | A775BF4D1FE90B710031047D /* SGCacheManager.m in Sources */, 221 | A70A45181F62BFFD00AAF37A /* LYDownloadManager.m in Sources */, 222 | A75D1E691DE94FEE0008BAF6 /* main.m in Sources */, 223 | A775BF511FE90B710031047D /* SGDownloadSession.m in Sources */, 224 | A775BF4E1FE90B710031047D /* SGDownloadManager.m in Sources */, 225 | A775BF4C1FE90B710031047D /* NSURLSession+SGDownloadTask.m in Sources */, 226 | A775BF4F1FE90B710031047D /* SGDownloadOperation.m in Sources */, 227 | ); 228 | runOnlyForDeploymentPostprocessing = 0; 229 | }; 230 | /* End PBXSourcesBuildPhase section */ 231 | 232 | /* Begin PBXVariantGroup section */ 233 | A75D1E701DE94FEF0008BAF6 /* Main.storyboard */ = { 234 | isa = PBXVariantGroup; 235 | children = ( 236 | A75D1E711DE94FEF0008BAF6 /* Base */, 237 | ); 238 | name = Main.storyboard; 239 | path = ..; 240 | sourceTree = ""; 241 | }; 242 | A75D1E751DE94FEF0008BAF6 /* LaunchScreen.storyboard */ = { 243 | isa = PBXVariantGroup; 244 | children = ( 245 | A75D1E761DE94FEF0008BAF6 /* Base */, 246 | ); 247 | name = LaunchScreen.storyboard; 248 | sourceTree = ""; 249 | }; 250 | /* End PBXVariantGroup section */ 251 | 252 | /* Begin XCBuildConfiguration section */ 253 | A75D1E791DE94FEF0008BAF6 /* Debug */ = { 254 | isa = XCBuildConfiguration; 255 | buildSettings = { 256 | ALWAYS_SEARCH_USER_PATHS = NO; 257 | CLANG_ANALYZER_NONNULL = YES; 258 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 259 | CLANG_CXX_LIBRARY = "libc++"; 260 | CLANG_ENABLE_MODULES = YES; 261 | CLANG_ENABLE_OBJC_ARC = YES; 262 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 263 | CLANG_WARN_BOOL_CONVERSION = YES; 264 | CLANG_WARN_COMMA = YES; 265 | CLANG_WARN_CONSTANT_CONVERSION = YES; 266 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 267 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 268 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 269 | CLANG_WARN_EMPTY_BODY = YES; 270 | CLANG_WARN_ENUM_CONVERSION = YES; 271 | CLANG_WARN_INFINITE_RECURSION = YES; 272 | CLANG_WARN_INT_CONVERSION = YES; 273 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 274 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = 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_SUSPICIOUS_MOVES = YES; 281 | CLANG_WARN_UNREACHABLE_CODE = YES; 282 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 283 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 284 | COPY_PHASE_STRIP = NO; 285 | DEBUG_INFORMATION_FORMAT = dwarf; 286 | ENABLE_STRICT_OBJC_MSGSEND = YES; 287 | ENABLE_TESTABILITY = YES; 288 | GCC_C_LANGUAGE_STANDARD = gnu99; 289 | GCC_DYNAMIC_NO_PIC = NO; 290 | GCC_NO_COMMON_BLOCKS = YES; 291 | GCC_OPTIMIZATION_LEVEL = 0; 292 | GCC_PREPROCESSOR_DEFINITIONS = ( 293 | "DEBUG=1", 294 | "$(inherited)", 295 | ); 296 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 297 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 298 | GCC_WARN_UNDECLARED_SELECTOR = YES; 299 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 300 | GCC_WARN_UNUSED_FUNCTION = YES; 301 | GCC_WARN_UNUSED_VARIABLE = YES; 302 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 303 | MTL_ENABLE_DEBUG_INFO = YES; 304 | ONLY_ACTIVE_ARCH = YES; 305 | SDKROOT = iphoneos; 306 | TARGETED_DEVICE_FAMILY = "1,2"; 307 | }; 308 | name = Debug; 309 | }; 310 | A75D1E7A1DE94FEF0008BAF6 /* Release */ = { 311 | isa = XCBuildConfiguration; 312 | buildSettings = { 313 | ALWAYS_SEARCH_USER_PATHS = NO; 314 | CLANG_ANALYZER_NONNULL = YES; 315 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 316 | CLANG_CXX_LIBRARY = "libc++"; 317 | CLANG_ENABLE_MODULES = YES; 318 | CLANG_ENABLE_OBJC_ARC = YES; 319 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 320 | CLANG_WARN_BOOL_CONVERSION = YES; 321 | CLANG_WARN_COMMA = YES; 322 | CLANG_WARN_CONSTANT_CONVERSION = YES; 323 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 324 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 325 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 326 | CLANG_WARN_EMPTY_BODY = YES; 327 | CLANG_WARN_ENUM_CONVERSION = YES; 328 | CLANG_WARN_INFINITE_RECURSION = YES; 329 | CLANG_WARN_INT_CONVERSION = YES; 330 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 331 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 332 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 333 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 334 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 335 | CLANG_WARN_STRICT_PROTOTYPES = YES; 336 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 337 | CLANG_WARN_SUSPICIOUS_MOVES = YES; 338 | CLANG_WARN_UNREACHABLE_CODE = YES; 339 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 340 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 341 | COPY_PHASE_STRIP = NO; 342 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 343 | ENABLE_NS_ASSERTIONS = NO; 344 | ENABLE_STRICT_OBJC_MSGSEND = YES; 345 | GCC_C_LANGUAGE_STANDARD = gnu99; 346 | GCC_NO_COMMON_BLOCKS = YES; 347 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 348 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 349 | GCC_WARN_UNDECLARED_SELECTOR = YES; 350 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 351 | GCC_WARN_UNUSED_FUNCTION = YES; 352 | GCC_WARN_UNUSED_VARIABLE = YES; 353 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 354 | MTL_ENABLE_DEBUG_INFO = NO; 355 | SDKROOT = iphoneos; 356 | TARGETED_DEVICE_FAMILY = "1,2"; 357 | VALIDATE_PRODUCT = YES; 358 | }; 359 | name = Release; 360 | }; 361 | A75D1E7C1DE94FEF0008BAF6 /* Debug */ = { 362 | isa = XCBuildConfiguration; 363 | buildSettings = { 364 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 365 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 366 | DEVELOPMENT_TEAM = WZ22KTPCG8; 367 | INFOPLIST_FILE = OfflineBreakPointDownload/Info.plist; 368 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 369 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 370 | PRODUCT_BUNDLE_IDENTIFIER = com.likebutton.zsg; 371 | PRODUCT_NAME = "$(TARGET_NAME)"; 372 | PROVISIONING_PROFILE_SPECIFIER = ""; 373 | }; 374 | name = Debug; 375 | }; 376 | A75D1E7D1DE94FEF0008BAF6 /* Release */ = { 377 | isa = XCBuildConfiguration; 378 | buildSettings = { 379 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 380 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 381 | DEVELOPMENT_TEAM = WZ22KTPCG8; 382 | INFOPLIST_FILE = OfflineBreakPointDownload/Info.plist; 383 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 384 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 385 | PRODUCT_BUNDLE_IDENTIFIER = com.likebutton.zsg; 386 | PRODUCT_NAME = "$(TARGET_NAME)"; 387 | PROVISIONING_PROFILE_SPECIFIER = ""; 388 | }; 389 | name = Release; 390 | }; 391 | /* End XCBuildConfiguration section */ 392 | 393 | /* Begin XCConfigurationList section */ 394 | A75D1E5F1DE94FEE0008BAF6 /* Build configuration list for PBXProject "OfflineBreakPointDownload" */ = { 395 | isa = XCConfigurationList; 396 | buildConfigurations = ( 397 | A75D1E791DE94FEF0008BAF6 /* Debug */, 398 | A75D1E7A1DE94FEF0008BAF6 /* Release */, 399 | ); 400 | defaultConfigurationIsVisible = 0; 401 | defaultConfigurationName = Release; 402 | }; 403 | A75D1E7B1DE94FEF0008BAF6 /* Build configuration list for PBXNativeTarget "OfflineBreakPointDownload" */ = { 404 | isa = XCConfigurationList; 405 | buildConfigurations = ( 406 | A75D1E7C1DE94FEF0008BAF6 /* Debug */, 407 | A75D1E7D1DE94FEF0008BAF6 /* Release */, 408 | ); 409 | defaultConfigurationIsVisible = 0; 410 | defaultConfigurationName = Release; 411 | }; 412 | /* End XCConfigurationList section */ 413 | }; 414 | rootObject = A75D1E5C1DE94FEE0008BAF6 /* Project object */; 415 | } 416 | -------------------------------------------------------------------------------- /OfflineBreakPointDownload.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /OfflineBreakPointDownload.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /OfflineBreakPointDownload/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // OfflineBreakPointDownload 4 | // 5 | // Created by Shangen Zhang on 16/11/26. 6 | // Copyright © 2016年 Shangen Zhang. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | 16 | @end 17 | 18 | -------------------------------------------------------------------------------- /OfflineBreakPointDownload/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // OfflineBreakPointDownload 4 | // 5 | // Created by Shangen Zhang on 16/11/26. 6 | // Copyright © 2016年 Shangen Zhang. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | #import "LYDownloadManager.h" 11 | 12 | @interface AppDelegate () 13 | 14 | @end 15 | 16 | @implementation AppDelegate 17 | 18 | 19 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 20 | // Override point for customization after application launch. 21 | return YES; 22 | } 23 | 24 | 25 | @end 26 | -------------------------------------------------------------------------------- /OfflineBreakPointDownload/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 | "info" : { 90 | "version" : 1, 91 | "author" : "xcode" 92 | } 93 | } -------------------------------------------------------------------------------- /OfflineBreakPointDownload/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 | -------------------------------------------------------------------------------- /OfflineBreakPointDownload/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 | 38 | 52 | 66 | 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 | 102 | 103 | 104 | 105 | 106 | 107 | -------------------------------------------------------------------------------- /OfflineBreakPointDownload/DwonloadDemo/LYDownloadManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // LYDownloadManager.h 3 | // OfflineBreakPointDownload 4 | // 5 | // Created by Shangen Zhang on 2017/9/8. 6 | // Copyright © 2017年 Shangen Zhang. All rights reserved. 7 | // 8 | 9 | #import "SGDownloadManager.h" 10 | 11 | @interface LYDownloadManager : SGDownloadManager 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /OfflineBreakPointDownload/DwonloadDemo/LYDownloadManager.m: -------------------------------------------------------------------------------- 1 | // 2 | // LYDownloadManager.m 3 | // OfflineBreakPointDownload 4 | // 5 | // Created by Shangen Zhang on 2017/9/8. 6 | // Copyright © 2017年 Shangen Zhang. All rights reserved. 7 | // 8 | 9 | #import "LYDownloadManager.h" 10 | 11 | @implementation LYDownloadManager 12 | + (instancetype)shareManager { 13 | static LYDownloadManager *_instance = nil; 14 | static dispatch_once_t onceToken; 15 | dispatch_once(&onceToken, ^{ 16 | _instance = [[super alloc] init]; 17 | //_instance = [[self alloc] initWithBackgroundSessionConfigurationWithIdentifier:@"com.zhangsg.OfflineBreakPointDownload"]; 18 | }); 19 | return _instance; 20 | } 21 | @end 22 | -------------------------------------------------------------------------------- /OfflineBreakPointDownload/DwonloadDemo/LYProgressView.h: -------------------------------------------------------------------------------- 1 | // 2 | // LYProgressView.h 3 | // LYPhotoBrowserDemo 4 | // 5 | // Created by Shangen Zhang on 17/3/19. 6 | // Copyright © 2017年 HHLY. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface LYProgressView : UIView 12 | /** 快速创建一个50 X 50的进度条View */ 13 | + (instancetype)progressView; 14 | 15 | /** 快速创建一个width X width的进度条View */ 16 | + (instancetype)progressViewWithWidth:(CGFloat)width; 17 | 18 | /** 进度值 */ 19 | @property (nonatomic,assign) CGFloat progress; 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /OfflineBreakPointDownload/DwonloadDemo/LYProgressView.m: -------------------------------------------------------------------------------- 1 | // 2 | // LYProgressView.m 3 | // LYPhotoBrowserDemo 4 | // 5 | // Created by Shangen Zhang on 17/3/19. 6 | // Copyright © 2017年 HHLY. All rights reserved. 7 | // 8 | 9 | #import "LYProgressView.h" 10 | 11 | @interface LYProgressView () 12 | 13 | /** center point */ 14 | @property (nonatomic,assign) CGPoint centerP; 15 | 16 | /** 外部大圆圈 */ 17 | @property (nonatomic,strong) UIBezierPath *outCirclePath; 18 | 19 | /** 里面的进度圈 */ 20 | @property (nonatomic,strong) UIBezierPath *inCirclePath; 21 | 22 | /** 背景底色 */ 23 | @property (nonatomic,strong) UIBezierPath *backgoundPath; 24 | 25 | /** 开始角度 */ 26 | @property (nonatomic,assign) CGFloat startAngle; 27 | 28 | /** 结束角度 */ 29 | @property (nonatomic,assign) CGFloat endAngle; 30 | 31 | @end 32 | 33 | @implementation LYProgressView 34 | #pragma mark - class 35 | + (instancetype)progressViewWithWidth:(CGFloat)width { 36 | return [[self alloc] initWithFrame:CGRectMake(0, 0, width, width)]; 37 | } 38 | + (instancetype)progressView { 39 | return [self progressViewWithWidth:50.0]; 40 | } 41 | #pragma mark - init 42 | - (instancetype)initWithFrame:(CGRect)frame { 43 | if (self = [super initWithFrame:frame]) { 44 | self.backgroundColor = [UIColor clearColor]; 45 | } 46 | return self; 47 | } 48 | - (void)awakeFromNib { 49 | [super awakeFromNib]; 50 | self.backgroundColor = [UIColor clearColor]; 51 | } 52 | #pragma mark - setter 53 | - (void)setProgress:(CGFloat)progress { 54 | 55 | _progress = progress; 56 | 57 | [self setNeedsDisplay]; 58 | } 59 | #pragma mark - drawing 60 | - (void)drawRect:(CGRect)rect { 61 | 62 | // 背景颜色 63 | [[UIColor colorWithWhite:0.0 alpha:0.4] set]; 64 | 65 | [self.backgoundPath fill]; 66 | 67 | // 大圈 68 | [[UIColor whiteColor] set]; 69 | [self.outCirclePath stroke]; 70 | 71 | // 进度圈 72 | self.endAngle = _progress * M_PI * 2 + self.startAngle; 73 | 74 | self.inCirclePath = [UIBezierPath bezierPathWithArcCenter:self.centerP radius:self.width * 0.5 - 4 startAngle:self.startAngle endAngle:self.endAngle clockwise:YES]; 75 | 76 | [self.inCirclePath addLineToPoint:self.centerP]; 77 | 78 | [[UIColor colorWithWhite:1 alpha:0.9] set]; 79 | 80 | [self.inCirclePath fill]; 81 | } 82 | #pragma mark - getter 83 | - (CGPoint)centerP { 84 | 85 | if (!_centerP.x) { 86 | _centerP = CGPointMake(self.width * 0.5, self.height * 0.5); 87 | } 88 | return _centerP; 89 | } 90 | 91 | - (UIBezierPath *)backgoundPath { 92 | 93 | if (!_backgoundPath) { 94 | 95 | _backgoundPath = [UIBezierPath bezierPathWithArcCenter:self.centerP radius:self.width * 0.5 - 5 startAngle:0 endAngle:M_PI * 2 clockwise:YES]; 96 | } 97 | 98 | return _backgoundPath; 99 | } 100 | - (UIBezierPath *)outCirclePath { 101 | 102 | if (!_outCirclePath) { 103 | 104 | _outCirclePath = [UIBezierPath bezierPathWithArcCenter:self.centerP radius:self.width * 0.5 -2 startAngle:0 endAngle:M_PI * 2 clockwise:YES]; 105 | _outCirclePath.lineWidth = 2.0; 106 | } 107 | 108 | return _outCirclePath; 109 | } 110 | 111 | - (CGFloat)startAngle { 112 | return - M_PI_2; 113 | } 114 | - (CGFloat)width { 115 | return self.bounds.size.width; 116 | } 117 | - (CGFloat)height { 118 | return self.bounds.size.height; 119 | } 120 | @end 121 | -------------------------------------------------------------------------------- /OfflineBreakPointDownload/DwonloadDemo/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // OfflineBreakPointDownload 4 | // 5 | // Created by Shangen Zhang on 16/11/26. 6 | // Copyright © 2016年 Shangen Zhang. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ViewController : UIViewController 12 | 13 | 14 | @end 15 | 16 | -------------------------------------------------------------------------------- /OfflineBreakPointDownload/DwonloadDemo/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // OfflineBreakPointDownload 4 | // 5 | // Created by Shangen Zhang on 16/11/26. 6 | // Copyright © 2016年 Shangen Zhang. All rights reserved. 7 | // 8 | 9 | #import "ViewController.h" 10 | #import "LYDownloadManager.h" 11 | #import "SGCacheManager.h" 12 | #import "LYProgressView.h" 13 | 14 | 15 | @interface ViewController () 16 | 17 | @property(nonatomic,strong) NSArray *dataList; 18 | @property (weak, nonatomic) IBOutlet LYProgressView *progressView1; 19 | 20 | @property (weak, nonatomic) IBOutlet LYProgressView *progressView2; 21 | @property (weak, nonatomic) IBOutlet LYProgressView *progressView3; 22 | 23 | 24 | @end 25 | 26 | @implementation ViewController 27 | 28 | - (void)viewDidLoad { 29 | [super viewDidLoad]; 30 | self.view.backgroundColor = [UIColor purpleColor]; 31 | 32 | } 33 | 34 | 35 | - (IBAction)clickDownload:(UIButton *)sender { 36 | 37 | sender.selected = !sender.selected; 38 | 39 | NSInteger index = sender.tag - 11; 40 | SGDownloadManager *manager = [LYDownloadManager shareManager]; 41 | 42 | if (index >= self.dataList.count || index < 0) { // 越界 校验 43 | 44 | if(sender.tag == 0) { 45 | [manager stopAllDownloads]; // 取消所有下载 46 | } 47 | return; 48 | } 49 | 50 | NSURL *url = [NSURL URLWithString:self.dataList[index]]; // 获取网络请求 51 | 52 | if (sender.selected) { 53 | [self downlaodWithUrl:url withBtn:sender]; // 开启下载 54 | 55 | }else { 56 | [manager supendDownloadWithUrl:url.absoluteString]; // 暂停下载 57 | } 58 | } 59 | 60 | - (void)downlaodWithUrl:(NSURL *)url withBtn:(UIButton *)sender{ 61 | 62 | LYProgressView *proressView = nil; 63 | switch (sender.tag - 10) { 64 | case 1: 65 | proressView = self.progressView1; 66 | break; 67 | case 2: 68 | proressView = self.progressView2; 69 | break; 70 | case 3: 71 | proressView = self.progressView3; 72 | break; 73 | 74 | default: 75 | break; 76 | } 77 | 78 | [[LYDownloadManager shareManager] downloadWithURL:url progress:^(NSInteger completeSize, NSInteger expectSize) { // 进度监听 79 | proressView.progress = 1.0 * completeSize / expectSize; 80 | NSLog(@"任务:%d -- %.2f%%",(int)index,100.0 * completeSize / expectSize); 81 | 82 | }complete:^(NSDictionary *respose, NSError *error) { // 下载完成 83 | 84 | [sender setTitle:@"完成" forState:UIControlStateDisabled]; 85 | sender.selected = NO; 86 | 87 | if(error) { 88 | NSLog(@"任务:%d 下载错误%@",(int)index,error); 89 | 90 | return ; 91 | } 92 | proressView.progress = 1.0; 93 | NSLog(@"任务:%d 下载完成%@",(int)index,respose); 94 | 95 | sender.enabled = NO; 96 | }]; 97 | } 98 | 99 | 100 | - (NSArray *)dataList { 101 | if (!_dataList) { 102 | _dataList = @[ 103 | @"https://bityou-io.oss-cn-beijing.aliyuncs.com/4106469bc2c5a0a8_4a0280988003426f.mp3", 104 | @"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1537016004758&di=994420f271c6595be0272a5e3d0e956e&imgtype=0&src=http%3A%2F%2Fs16.sinaimg.cn%2Fmiddle%2F70858d01gc87794e6164f%26690", 105 | @"http://120.25.226.186:32812/resources/videos/minion_08.mp4", 106 | ]; 107 | } 108 | return _dataList; 109 | } 110 | @end 111 | -------------------------------------------------------------------------------- /OfflineBreakPointDownload/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleExecutable 6 | $(EXECUTABLE_NAME) 7 | CFBundleIdentifier 8 | $(PRODUCT_BUNDLE_IDENTIFIER) 9 | CFBundleInfoDictionaryVersion 10 | 6.0 11 | CFBundleName 12 | $(PRODUCT_NAME) 13 | CFBundlePackageType 14 | APPL 15 | CFBundleShortVersionString 16 | 1.0 17 | CFBundleVersion 18 | 1 19 | LSRequiresIPhoneOS 20 | 21 | NSAppTransportSecurity 22 | 23 | NSAllowsArbitraryLoads 24 | 25 | 26 | NSPhotoLibraryUsageDescription 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 | -------------------------------------------------------------------------------- /OfflineBreakPointDownload/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // OfflineBreakPointDownload 4 | // 5 | // Created by Shangen Zhang on 16/11/26. 6 | // Copyright © 2016年 Shangen Zhang. 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OfflineBreakpointDownload 2 | 离线断点下载器,基于文件句柄与NSURLSession实现的多文件离线断点下载器,下载较大文件时如音频、视频可以实时将下载的数据写入磁盘避免手机内存飙升导致崩溃,同时通过他添加下载任务可以实时监听下载进度。 3 | 4 | # how to import for your project 5 | 6 | 1、直接集成 (directly integrating) 7 | 8 | 下载zip包解压后, 直接将 “SGDwonload” 文件夹下所有文件拖到你的项目 (Download the zip package decompression, directly under the "SGDwonload" folder to drag all your files to your project) 9 | 10 | 11 | 2、使用CocoaPods (use CocoaPods) 12 | 13 | To integrate SGDwonload into your Xcode project using CocoaPods, specify it in your Podfile: 14 | 15 | source 'https://github.com/CocoaPods/Specs.git' 16 | platform :ios, '8.0' 17 | 18 | target 'TargetName' do 19 | pod 'SGDwonload', '~> 0.0.3' 20 | end 21 | Then, run the following command: 22 | 23 | $ pod install 24 | 25 | 26 | # how to use 27 | 28 | #### 1、下载任务: 29 | 30 | 导入头文件(import head file) : SGDownloadManager.h 31 | 32 | 添加下载任务: 33 | add download task: 34 | 35 | SGDownloadManager *manager = [SGDownloadManager shareManager]; 36 | [manager downloadWithURL:url 37 | 38 | progress:^(NSInteger completeSize, NSInteger expectSize) { //下载进度 39 | 40 | NSLog(@"任务:%zd -- %.2f%%",index,100.0 * completeSize / expectSize); 41 | 42 | } 43 | complete:^(NSDictionary *respose, NSError *error) { // 下载完成回调 44 | 45 | if(error) { // 失败回调 46 | NSLog(@"任务:%zd 下载错误%@",index,error); 47 | return ; 48 | } 49 | 50 | NSLog(@"任务:%zd 下载完成%@",index,respose); 51 | 52 | }]; 53 | 54 | 暂停任务: 55 | suspend task : 56 | 57 | [manager supendDownloadWithUrl:url.absoluteString]; // 暂停下载 (suspend task) 58 | 59 | 取消所有下载: 60 | cancel add tasks : 61 | [manager stopAllDownloads]; // 取消所有下载 (cancel add tasks) 62 | 63 | // 成功回调respose的数据示例: 64 | 65 | 任务:2 下载完成{ 66 | // 保存文件的文件名 67 | fileName = "772ace4b7a404a3ce025d08f5e786ce2.mp4"; 68 | 69 | // 保存文件的本地路径 70 | filePath = "/Users/xxx/Library/Developer/CoreSimulator/Devices/7DCECAF6-0038-430B-8103-F021EEF75428/data/Containers/Data/Application/CB2103AC-7894-418D-9AD6-2DF6636A1C4B/Library/Caches/downloads/772ace4b7a404a3ce025d08f5e786ce2.mp4"; 71 | 72 | fileSize = 4149462; // 已下载文件大小 73 | 74 | fileUrl = "http://120.25.226.186:32812/resources/videos/minion_08.mp4"; // 下载任务目标网络url 75 | 76 | isFinished = 1; // 0代表未完成 77 | 78 | totalSize = 4149462; // 文件总大小 79 | 80 | #### 2、本地磁盘查询内存清理(Query local disk and memory cleaner): 81 | 82 | 导入头文件(import head file) : SGCacheManager.h 83 | 84 | a、查询本地信息(Query file information form local disk): 85 | 86 | 查询文件下载信息 返回结果参考 上述 “成功回调respose的数据示例” 87 | 调用类方法 88 | [SGCacheManager queryFileInfoWithUrl:url.absoluteString] 89 | 90 | b、清理内存 (clear Memory/disk): 91 | 92 | /** 删除某个文件 */ 93 | + (BOOL)deleteFileWithUrl:(NSString *)url; 94 | 95 | /** 清理所有下载文件及下载信息 */ 96 | + (BOOL)clearDisks; 97 | 98 | /** 取消所有当前下载的文件 清理内存缓存的数据 */ 99 | + (BOOL)clearMemory; 100 | 101 | /** 取消所有当前下载的文件 删除磁盘所有的下载 清理内存缓存的数据 */ 102 | + (BOOL)clearMemoryAndDisk; 103 | 104 | 105 | # SOME TIPS 106 | 107 | 使用该工具可能会在Xcode 控制台输出这样的log: 108 | 109 | nw_socket_set_common_sockopts setsockopt SO_NOAPNFALLBK failed: [42] Protocol not available, dumping backtrace: 110 | [x86_64] libnetcore-856.1.8 111 | 0 libsystem_network.dylib 0x000000010b23380e __nw_create_backtrace_string + 123 112 | 1 libnetwork.dylib 0x000000010c032194 nw_socket_add_input_handler + 3002 113 | 2 libnetwork.dylib 0x000000010c00fdb8 nw_endpoint_flow_attach_protocols + 3768 114 | 3 libnetwork.dylib 0x000000010c00edd5 nw_endpoint_flow_setup_socket + 563 115 | 4 libnetwork.dylib 0x000000010c00db34 -[NWConcrete_nw_endpoint_flow startWithHandler:] + 2612 116 | 5 libnetwork.dylib 0x000000010c028d11 nw_endpoint_handler_path_change + 1261 117 | 6 libnetwork.dylib 0x000000010c028740 nw_endpoint_handler_start + 570 118 | 7 libnetwork.dylib 0x000000010c040003 nw_endpoint_resolver_start_next_child + 2240 119 | 8 libdispatch.dylib 0x000000 120 | 121 | 8 libdispatch.dylib 0x000000的 122 | 8 libdispatch.dylib 0x000000 123 | 8 libdispatch.dylib 0x000000 124 | 125 | 解决方案: 126 | 1. 到 Edit Scheme (快捷键 command + shift + <) 127 | 128 | 2.选择 Arguments 选项 129 | 130 | 3.选择 environment variables 添加变量 OS_ACTIVITY_MODE = disable 131 | 132 | 133 | 134 | -------------------------------------------------------------------------------- /SGDownload.podspec: -------------------------------------------------------------------------------- 1 | 2 | Pod::Spec.new do |s| 3 | 4 | s.name = "SGDownload" 5 | s.version = "0.0.4" 6 | 7 | s.summary = "An iOS big file download tool" 8 | s.description = "SGDownload is used to download Through which you can download it for big files At the same time support offline breakpoint downloading." 9 | 10 | 11 | s.homepage = "https://github.com/install-b/OfflineBreakpointDownload" 12 | 13 | s.license = "MIT" 14 | s.author = { "ShangenZhang" => "gkzhangshangen@163.com" } 15 | s.platform = :ios, "8.0" 16 | 17 | 18 | s.source = { :git => "https://github.com/install-b/OfflineBreakpointDownload.git", :tag => s.version } 19 | 20 | 21 | 22 | s.source_files = "SGDwonload/*.{h,m}" 23 | s.public_header_files = "SGDwonload/SGCacheManager.h", "SGDwonload/SGDownloadManager.h" 24 | 25 | 26 | s.framework = "UIKit" 27 | 28 | s.requires_arc = true 29 | 30 | end 31 | -------------------------------------------------------------------------------- /SGDwonload/NSString+SGHashString.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSString+SGHashString.h 3 | // OfflineBreakPointDownload 4 | // 5 | // Created by Shangen Zhang on 16/11/27. 6 | // Copyright © 2016年 Shangen Zhang. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #define KFullPath [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject] 12 | 13 | #define KFullDirector [NSString stringWithFormat:@"%@/downloads/",KFullPath] 14 | 15 | 16 | @interface NSString (SGHashString) 17 | 18 | /** 获取MD5加密哈希散列值字符串 */ 19 | - (NSString *)sg_md5HashString; 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /SGDwonload/NSString+SGHashString.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSString+SGHashString.m 3 | // OfflineBreakPointDownload 4 | // 5 | // Created by Shangen Zhang on 16/11/27. 6 | // Copyright © 2016年 Shangen Zhang. All rights reserved. 7 | // 8 | 9 | #import "NSString+SGHashString.h" 10 | #import 11 | 12 | @implementation NSString (SGHashString) 13 | #pragma mark - MD5加密 14 | - (NSString *)sg_md5HashString { 15 | const char *str = self.UTF8String; 16 | 17 | uint8_t buffer[CC_MD5_DIGEST_LENGTH]; 18 | 19 | CC_MD5(str, (CC_LONG)strlen(str), buffer); 20 | 21 | return [self sg_stringFromBytes:buffer length:CC_MD5_DIGEST_LENGTH]; 22 | 23 | } 24 | 25 | /** 26 | * 返回二进制 Bytes 流的字符串表示形式 27 | * 28 | * @param bytes 二进制 Bytes 数组 29 | * @param length 数组长度 30 | * 31 | * @return 字符串表示形式 32 | */ 33 | - (NSString *)sg_stringFromBytes:(uint8_t *)bytes length:(int)length { 34 | NSMutableString *strM = [NSMutableString string]; 35 | 36 | for (int i = 0; i < length; i++) { 37 | [strM appendFormat:@"%02x", bytes[i]]; 38 | } 39 | 40 | return [strM copy]; 41 | } 42 | 43 | @end 44 | -------------------------------------------------------------------------------- /SGDwonload/NSURLSession+SGDownloadTask.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSURLSession+SGDownloadTask.h 3 | // OfflineBreakPointDownload 4 | // 5 | // Created by Shangen Zhang on 2017/12/18. 6 | // Copyright © 2017年 Shangen Zhang. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface NSURLSession (SGDownloadTask) 12 | 13 | /** 14 | 构造一个从特定位置开始下载的任务 15 | 16 | @param urlString 资源路径的URLstring 17 | @param startSize 开始的位置 18 | @return 下载任务 19 | */ 20 | - (NSURLSessionDataTask *)sg_downloadDataTaskWithURLString:(NSString *)urlString 21 | startSize:(int64_t)startSize; 22 | @end 23 | -------------------------------------------------------------------------------- /SGDwonload/NSURLSession+SGDownloadTask.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSURLSession+SGDownloadTask.m 3 | // OfflineBreakPointDownload 4 | // 5 | // Created by Shangen Zhang on 2017/12/18. 6 | // Copyright © 2017年 Shangen Zhang. All rights reserved. 7 | // 8 | 9 | #import "NSURLSession+SGDownloadTask.h" 10 | 11 | @implementation NSURLSession (SGDownloadTask) 12 | - (NSURLSessionDataTask *)sg_downloadDataTaskWithURLString:(NSString *)urlString 13 | startSize:(int64_t)startSize { 14 | // 校验URL 15 | if (urlString.length == 0) { 16 | return nil; 17 | } 18 | NSURL *url = [NSURL URLWithString:urlString]; 19 | if (url == nil) { 20 | return nil; 21 | } 22 | 23 | // 创建请求 设置请求下载的位置 24 | NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; 25 | 26 | /* 27 | bytes=0-100 请求0-100 28 | bytes=200-1000 29 | bytes=200- 从200开始直到结尾 30 | bytes=-100 31 | */ 32 | NSString *rangeStr = [NSString stringWithFormat:@"bytes=%lld-",startSize]; 33 | 34 | [request setValue:rangeStr forHTTPHeaderField:@"Range"]; 35 | 36 | // 创建task 37 | return [self dataTaskWithRequest:request]; 38 | } 39 | @end 40 | -------------------------------------------------------------------------------- /SGDwonload/SGCacheManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // SGCacheManager.h 3 | // OfflineBreakPointDownload 4 | // 5 | // Created by Shangen Zhang on 16/11/26. 6 | // Copyright © 2016年 Shangen Zhang. All rights reserved. 7 | // 8 | 9 | 10 | #define SG_EXTERN extern 11 | #import 12 | 13 | 14 | SG_EXTERN NSString const * filePath; 15 | SG_EXTERN NSString const * fileSize; 16 | SG_EXTERN NSString const * totalSize; 17 | SG_EXTERN NSString const * fileName; 18 | SG_EXTERN NSString const * fileUrl; 19 | SG_EXTERN NSString const * isFinished; 20 | 21 | 22 | @interface SGCacheManager : NSObject 23 | 24 | /** 查询文件信息 */ 25 | + (NSDictionary *)queryFileInfoWithUrl:(NSString *)url; 26 | 27 | /** 查询要下载的文件大小 */ 28 | + (NSInteger)totalSizeWith:(NSString *)url; 29 | 30 | /** 记录要下载的文件大小 */ 31 | + (BOOL)saveTotalSizeWithSize:(NSInteger)size forURL:(NSString *)url; 32 | 33 | /** 增加配置信息 */ 34 | + (BOOL)saveFileInfoWithDict:(NSDictionary *)dict; 35 | 36 | 37 | /** 删除某个文件 */ 38 | + (BOOL)deleteFileWithUrl:(NSString *)url; 39 | 40 | /** 清理所有下载文件及下载信息 */ 41 | + (BOOL)clearDisks; 42 | 43 | /** 取消所有当前下载的文件 清理内存缓存的数据 */ 44 | + (BOOL)clearMemory; 45 | 46 | /** 取消所有当前下载的文件 删除磁盘所有的下载 清理内存缓存的数据 */ 47 | + (BOOL)clearMemoryAndDisk; 48 | @end 49 | -------------------------------------------------------------------------------- /SGDwonload/SGCacheManager.m: -------------------------------------------------------------------------------- 1 | // 2 | // SGCacheManager.m 3 | // OfflineBreakPointDownload 4 | // 5 | // Created by Shangen Zhang on 16/11/26. 6 | // Copyright © 2016年 Shangen Zhang. All rights reserved. 7 | // 8 | 9 | #import "SGCacheManager.h" 10 | #import "NSString+SGHashString.h" 11 | 12 | static NSMutableDictionary *_downloadList; 13 | 14 | static dispatch_semaphore_t _semaphore; 15 | 16 | 17 | NSString const * filePath = @"filePath"; 18 | NSString const * fileSize = @"fileSize"; 19 | NSString const * fileName = @"fileName"; 20 | NSString const * fileUrl = @"fileUrl"; 21 | NSString const * isFinished = @"isFinished"; 22 | NSString const * totalSize = @"totalSize"; 23 | 24 | 25 | 26 | #define SGDownloadInfoPath [KFullDirector stringByAppendingString:@"downloadInfo.plist"] 27 | 28 | #define SGDownloadList [self getDownloadList] 29 | 30 | @interface SGCacheManager () 31 | + (NSMutableDictionary *)getDownloadList; 32 | 33 | @end 34 | 35 | 36 | 37 | @implementation SGCacheManager 38 | 39 | + (void)initialize { 40 | _semaphore = dispatch_semaphore_create(1); 41 | 42 | } 43 | 44 | 45 | + (NSMutableDictionary *)getDownloadList { 46 | 47 | if (!_downloadList) { // 内存没有 48 | _downloadList = [[NSDictionary dictionaryWithContentsOfFile:SGDownloadInfoPath] mutableCopy]; // 本地加载 49 | if (!_downloadList) { // 本地没有,分配内存 50 | _downloadList = [NSMutableDictionary dictionary]; 51 | } 52 | } 53 | return _downloadList; 54 | } 55 | 56 | #pragma mark - save 57 | //+ (void)didReciveDownloadCompleteNoti:(NSNotification *)noti { 58 | // 59 | // // 缓存记录 60 | // NSMutableDictionary *dictM = [noti.userInfo mutableCopy]; 61 | // 62 | // // 从磁盘获取到下载了 63 | // if ([noti.object integerValue] == 2 && ([self queryFileInfoWithUrl:dictM[fileUrl]])) { 64 | // return; 65 | // } 66 | // 67 | // // 缓存记录 68 | // [dictM setObject:@(YES) forKey:isFinished]; 69 | // NSString *key = [dictM[fileUrl] sg_md5HashString]; 70 | // 71 | // [SGDownloadList setObject:dictM forKey:key]; 72 | // 73 | // [SGDownloadList writeToFile:SGDownloadInfoPath atomically:YES]; 74 | // 75 | //} 76 | 77 | #pragma mark - query 78 | + (NSDictionary *)queryFileInfoWithUrl:(NSString *)url { 79 | // 本地查找 80 | NSString *key = [url sg_md5HashString]; 81 | //NSMutableDictionary *dictM = [[SGUserDefaults objectForKey:key] mutableCopy]; 82 | 83 | NSMutableDictionary *dictM = [[SGDownloadList objectForKey:key] mutableCopy]; 84 | 85 | if (dictM) { 86 | NSString *path = [KFullDirector stringByAppendingString:dictM[fileName]]; 87 | [dictM setObject:path forKey:filePath]; 88 | } 89 | 90 | return dictM; 91 | 92 | } 93 | 94 | + (NSInteger)totalSizeWith:(NSString *)url { 95 | //NSNumber *size = [self queryFileInfoWithUrl:url][totalSize]; 96 | 97 | return [[self queryFileInfoWithUrl:url][totalSize] integerValue]; 98 | } 99 | 100 | /** 记录要下载的文件大小 */ 101 | + (BOOL)saveTotalSizeWithSize:(NSInteger)size forURL:(NSString *)url { 102 | 103 | return YES; 104 | } 105 | 106 | /** 增加配置信息 */ 107 | + (BOOL)saveFileInfoWithDict:(NSDictionary *)dict { 108 | 109 | // 线程等待 (信号量 + 1) 110 | dispatch_semaphore_wait(_semaphore, DISPATCH_TIME_FOREVER); 111 | 112 | NSString *key = [dict[fileUrl] sg_md5HashString]; 113 | NSMutableDictionary *dictM = SGDownloadList; 114 | [dictM setObject:dict forKey:key]; 115 | BOOL flag = [dictM writeToFile:SGDownloadInfoPath atomically:YES]; 116 | 117 | // 线程结束 (信号量 - 1) 118 | dispatch_semaphore_signal(_semaphore); 119 | 120 | return flag; 121 | 122 | } 123 | 124 | /** 删除配置信息 */ 125 | + (BOOL)deleteFileWithUrl:(NSString *)url { 126 | // 线程等待 分配信号量 (信号量 + 1) 127 | dispatch_semaphore_wait(_semaphore, DISPATCH_TIME_FOREVER); 128 | 129 | NSString *key = [url sg_md5HashString]; 130 | NSDictionary *dict = SGDownloadList[key]; 131 | BOOL flag = [[NSFileManager defaultManager] removeItemAtPath:dict[filePath] error:nil]; 132 | [SGDownloadList removeObjectForKey:key]; 133 | BOOL writeFlag = [SGDownloadList writeToFile:SGDownloadInfoPath atomically:YES]; 134 | 135 | // 线程结束 释放信号量(信号量 - 1) 136 | dispatch_semaphore_signal(_semaphore); 137 | return (flag && writeFlag); 138 | } 139 | 140 | 141 | 142 | #pragma mark - 143 | 144 | + (BOOL)clearDisks { 145 | // 1.删除所有的文件下载信息关联表 146 | // 2.删除cache 下的download文件夹 147 | return [[NSFileManager defaultManager] removeItemAtPath:KFullDirector error:nil]; 148 | 149 | } 150 | 151 | /** 取消所有当前下载的文件 清理内存缓存的数据 */ 152 | + (BOOL)clearMemory { 153 | // 删除信息关联 154 | _downloadList = nil; 155 | 156 | return YES; 157 | } 158 | 159 | /** 取消所有当前下载的文件 删除磁盘所有的下载 清理内存缓存的数据 */ 160 | + (BOOL)clearMemoryAndDisk { 161 | return ([self clearMemory] && [self clearDisks]); 162 | } 163 | 164 | 165 | 166 | 167 | @end 168 | -------------------------------------------------------------------------------- /SGDwonload/SGDownloadManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // SGDownloadManager.h 3 | // OfflineBreakPointDownload 4 | // 5 | // Created by Shangen Zhang on 16/11/26. 6 | // Copyright © 2016年 Shangen Zhang. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | 12 | 13 | /** 14 | block 回调 15 | */ 16 | typedef void(^SGDownloadBeginBlock)(NSString * filePath); 17 | typedef void(^SGDownloadProgressBlock)(NSInteger completeSize,NSInteger expectSize); 18 | typedef void(^SGDownloadCompleteBlock)(NSDictionary *respose,NSError *error); 19 | 20 | 21 | /** 22 | 下载管理 23 | */ 24 | @interface SGDownloadManager : NSObject 25 | 26 | /** 实例化对象(单例) */ 27 | + (instancetype)shareManager; 28 | 29 | #pragma mark - 添加下载任务同时开启任务下载 30 | /** 开启下载任务 监听完成下载 */ 31 | - (void)downloadWithURL:(NSURL *)url 32 | complete:(SGDownloadCompleteBlock)complete; 33 | 34 | /** 开启下载任务 监听下载进度、完成下载 */ 35 | - (void)downloadWithURL:(NSURL *)url 36 | progress:(SGDownloadProgressBlock)progress 37 | complete:(SGDownloadCompleteBlock)complete; 38 | 39 | /** 开启下载任务 监听开始下载信息、下载进度、完成下载 */ 40 | - (void)downloadWithURL:(NSURL *)url 41 | begin:(SGDownloadBeginBlock)begin 42 | progress:(SGDownloadProgressBlock)progress 43 | complete:(SGDownloadCompleteBlock)complete; 44 | 45 | 46 | 47 | #pragma mark - 队列中的任务进行操作 48 | /** 开始任务(不会自动添加任务,列队中没有就直接返回) */ 49 | - (void)startDownLoadWithUrl:(NSString *)url; 50 | 51 | /** 暂停任务(暂停下载url内容的任务) */ 52 | - (void)supendDownloadWithUrl:(NSString *)url; 53 | 54 | /** 取消任务(取消下载url内容的任务) */ 55 | - (void)cancelDownloadWithUrl:(NSString *)url; 56 | 57 | 58 | /** 暂停当前所有的下载任务 下载任务不会从列队中删除 */ 59 | - (void)suspendAllDownloadTask; 60 | 61 | /** 开启当前列队中所有被暂停的下载任务 */ 62 | - (void)startAllDownloadTask; 63 | 64 | /** 停止当前所有的下载任务 调用此方法会清空所有列队下载任务 */ 65 | - (void)stopAllDownloads; 66 | 67 | @end 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /SGDwonload/SGDownloadManager.m: -------------------------------------------------------------------------------- 1 | // 2 | // SGDownloadManager.m 3 | // OfflineBreakPointDownload 4 | // 5 | // Created by Shangen Zhang on 16/11/26. 6 | // Copyright © 2016年 Shangen Zhang. All rights reserved. 7 | // 8 | 9 | #import "SGDownloadManager.h" 10 | #import "SGDownloadSession.h" 11 | #import "SGCacheManager.h" 12 | 13 | 14 | @interface SGDownloadManager () 15 | 16 | @property(nonatomic,strong) SGDownloadSession *downloadSession; 17 | 18 | @end 19 | 20 | 21 | @implementation SGDownloadManager 22 | 23 | + (instancetype)shareManager { 24 | static SGDownloadManager *_instance; 25 | static dispatch_once_t onceToken; 26 | dispatch_once(&onceToken, ^{ 27 | _instance = [[self alloc] init]; 28 | }); 29 | return _instance; 30 | } 31 | 32 | 33 | #pragma mark - 外界交互 34 | - (void)downloadWithURL:(NSURL *)url complete:(void(^)(NSDictionary *,NSError *))complete{ 35 | [self downloadWithURL:url begin:nil progress:nil complete:complete]; 36 | } 37 | 38 | - (void)downloadWithURL:(NSURL *)url progress:(void(^)(NSInteger,NSInteger ))progress complete:(void(^)(NSDictionary *,NSError *))complete { 39 | [self downloadWithURL:url begin:nil progress:progress complete:complete]; 40 | } 41 | 42 | - (void)downloadWithURL:(NSURL *)url begin:(void(^)(NSString *))begin progress:(void(^)(NSInteger,NSInteger))progress complete:(void(^)(NSDictionary *,NSError *))complete { 43 | 44 | if (![url isKindOfClass:NSURL.class]) { 45 | if ([url isKindOfClass:NSString.class]) { 46 | url = [NSURL URLWithString:(NSString *)url]; 47 | }else { 48 | // 失败回调 49 | 50 | return; 51 | } 52 | } 53 | // 开启异步 操作 54 | dispatch_async(dispatch_get_global_queue(0, 0), ^{ 55 | // 本地查找 56 | NSDictionary *fileInfo = [SGCacheManager queryFileInfoWithUrl:url.absoluteString]; 57 | 58 | // 本地存在直接返回 59 | if ([fileInfo[isFinished] integerValue]) { 60 | 61 | dispatch_async(dispatch_get_main_queue(), ^{ 62 | !complete ? : complete(fileInfo,nil); 63 | }); 64 | return; 65 | } 66 | 67 | // 交给downloader下载 68 | [self.downloadSession downloadWithURL:url begin:begin progress:progress complete:complete]; 69 | }); 70 | 71 | } 72 | 73 | #pragma mark - 74 | - (void)startDownLoadWithUrl:(NSString *)url { 75 | // 本地查找 76 | NSDictionary *fileInfo = [SGCacheManager queryFileInfoWithUrl:url]; 77 | 78 | if (fileInfo) { 79 | return; 80 | } 81 | // 82 | [self.downloadSession startDownLoadWithUrl:url]; 83 | } 84 | 85 | - (void)supendDownloadWithUrl:(NSString *)url { 86 | // 暂停下载 87 | [_downloadSession supendDownloadWithUrl:url]; 88 | } 89 | 90 | - (void)cancelDownloadWithUrl:(NSString *)url { 91 | // 取消下载 92 | [_downloadSession cancelDownloadWithUrl:url]; 93 | } 94 | 95 | 96 | /** 暂停当前所有的下载任务 下载任务不会从列队中删除 */ 97 | - (void)suspendAllDownloadTask { 98 | [_downloadSession suspendAllDownloads]; 99 | } 100 | 101 | /** 开启当前列队中所有被暂停的下载任务 */ 102 | - (void)startAllDownloadTask { 103 | [_downloadSession startAllDownloads]; 104 | } 105 | 106 | /** 停止当前所有的下载任务 调用此方法会清空所有列队下载任务 */ 107 | - (void)stopAllDownloads { 108 | [_downloadSession cancelAllDownloads]; 109 | _downloadSession = nil; 110 | } 111 | 112 | #pragma mark - lazy load 113 | - (SGDownloadSession *)downloadSession { 114 | if (!_downloadSession) { 115 | _downloadSession = [[SGDownloadSession alloc] init]; 116 | } 117 | return _downloadSession; 118 | } 119 | 120 | @end 121 | -------------------------------------------------------------------------------- /SGDwonload/SGDownloadOperation.h: -------------------------------------------------------------------------------- 1 | // 2 | // SGDownloadOperation.h 3 | // OfflineBreakPointDownload 4 | // 5 | // Created by Shangen Zhang on 16/11/26. 6 | // Copyright © 2016年 Shangen Zhang. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "SGCacheManager.h" 11 | 12 | 13 | SG_EXTERN NSString * const SGDownloadCompleteNoti; 14 | 15 | typedef void(^SGReceiveResponseOperation)(NSString *filePath); 16 | typedef void(^SGReceivDataOperation)(NSInteger completeSize,NSInteger expectSize); 17 | typedef void(^SGCompleteOperation)(NSDictionary *respose,NSError *error); 18 | 19 | 20 | @protocol SGDownloadOperationProtocol 21 | 22 | // 供queue管理方法 23 | 24 | // 处理响应值 25 | - (void)operateWithResponse:(NSURLResponse *)response; 26 | // 处理接收到的碎片 27 | - (void)operateWithReceivingData:(NSData *)data; 28 | // 处理完成回调 29 | - (void)operateWithComplete:(NSError *)error; 30 | 31 | 32 | /** 33 | 设置block回调 34 | 35 | @param didReceiveResponse 开始下载的回调 36 | @param didReceivData 接收到下载的回调 37 | @param didComplete 下载完成的回调 38 | */ 39 | - (void)configCallBacksWithDidReceiveResponse:(SGReceiveResponseOperation)didReceiveResponse 40 | didReceivData:(SGReceivDataOperation)didReceivData 41 | didComplete:(SGCompleteOperation)didComplete; 42 | 43 | @end 44 | 45 | @interface SGDownloadOperation : NSObject 46 | 47 | // 创建下载操作任务 48 | - (instancetype)initWith:(NSString *)url session:(NSURLSession *)session; 49 | 50 | // 绑定的标示及task的创建 51 | @property (readonly,nonatomic, copy)NSString *url; 52 | 53 | // 下载任务 54 | @property (readonly,nonatomic,strong)NSURLSessionDataTask *dataTask; 55 | 56 | @end 57 | 58 | -------------------------------------------------------------------------------- /SGDwonload/SGDownloadOperation.m: -------------------------------------------------------------------------------- 1 | // 2 | // SGDownloadOperation.m 3 | // OfflineBreakPointDownload 4 | // 5 | // Created by Shangen Zhang on 16/11/26. 6 | // Copyright © 2016年 Shangen Zhang. All rights reserved. 7 | // 8 | 9 | #import "SGDownloadOperation.h" 10 | #import "NSString+SGHashString.h" 11 | #import "NSURLSession+SGDownloadTask.h" 12 | 13 | NSString * const SGDownloadCompleteNoti = @"SGDownloadCompleteNoti"; 14 | 15 | @interface SGDownloadOperation () 16 | 17 | /* <#des#> */ 18 | @property (nonatomic,copy) SGReceiveResponseOperation didReceiveResponseCallBack; 19 | /* <#des#> */ 20 | @property (nonatomic,copy) SGReceivDataOperation didReceivDataCallBack; 21 | /* <#des#> */ 22 | @property (nonatomic,copy) SGCompleteOperation didCompleteCallBack; 23 | 24 | /** 文件句柄 可以记录文件的下载的位置 */ 25 | @property (nonatomic,strong) NSFileHandle *handle; 26 | 27 | /** 下载的文件总大小 */ 28 | @property (nonatomic,assign) int64_t totalSize; 29 | 30 | /** 当前下载了多少 */ 31 | @property (nonatomic,assign) int64_t currentSize; 32 | 33 | /** 当前下载文件名称 */ 34 | @property (nonatomic,copy) NSString *fileName; 35 | 36 | /** 当前下载文件沙盒全路径 */ 37 | @property (nonatomic,copy) NSString *fullPath; 38 | 39 | @end 40 | 41 | @implementation SGDownloadOperation 42 | 43 | - (instancetype)initWith:(NSString *)url session:(NSURLSession *)session { 44 | 45 | if (self = [super init]) { 46 | _url = url; 47 | // 初始化下载信息 48 | _currentSize = [self getFileSizeWithURL:url]; 49 | 50 | // 偏好设置里面存储总数据 51 | _totalSize = [SGCacheManager totalSizeWith:url]; 52 | 53 | // 校验 54 | if (self.currentSize == self.totalSize && self.totalSize != 0) { 55 | return nil; 56 | } 57 | 58 | _dataTask = [session sg_downloadDataTaskWithURLString:url startSize:_currentSize]; 59 | } 60 | return _dataTask ? self : nil; 61 | } 62 | 63 | #pragma mark - setups 64 | - (int64_t)getFileSizeWithURL:(NSString *)url { 65 | // md5文件名加密 66 | NSString *md5FielName = [url sg_md5HashString]; 67 | // 获取后缀名 68 | NSArray *subString = [[url lastPathComponent] componentsSeparatedByString:@"."]; 69 | // 拼接后缀名 70 | self.fileName = [NSString stringWithFormat:@"%@.%@",md5FielName,subString.lastObject]; 71 | 72 | // 创建文件储存路径 73 | if (![[NSFileManager defaultManager] fileExistsAtPath:KFullDirector]) { 74 | [[NSFileManager defaultManager] createDirectoryAtPath:KFullDirector withIntermediateDirectories:YES attributes:nil error:nil]; 75 | } 76 | 77 | // 设置下载路径 78 | self.fullPath = [KFullDirector stringByAppendingString:self.fileName]; 79 | 80 | // 获取下载进度 81 | NSDictionary *fileInfo = [[NSFileManager defaultManager] attributesOfItemAtPath:self.fullPath error:nil]; 82 | // 获取已下载的长度 83 | return [fileInfo[NSFileSize] longLongValue]; 84 | } 85 | 86 | #pragma mark - SGDownloadOperationProtocol 87 | // 接收到相应时 88 | - (void)operateWithResponse:(NSURLResponse *)response { 89 | // 总的size 90 | self.totalSize = self.currentSize + response.expectedContentLength; 91 | 92 | // 创建空的文件夹 93 | if (self.currentSize == 0) { 94 | // 创建空的文件 95 | [[NSFileManager defaultManager] createFileAtPath:self.fullPath contents:nil attributes:nil]; 96 | } 97 | 98 | // 创建文件句柄 99 | self.handle = [NSFileHandle fileHandleForWritingAtPath:self.fullPath]; 100 | 101 | // 文件句柄移动到文件末尾 位置 // 返回值是 unsign long long 102 | [self.handle seekToEndOfFile]; 103 | 104 | // 开始下载记录文件下载信息 105 | [SGCacheManager saveFileInfoWithDict:[self downLoadInfoWithFinished:NO]]; 106 | 107 | // 回调给外界 108 | if (_didReceiveResponseCallBack) { 109 | dispatch_async(dispatch_get_main_queue(), ^{ 110 | self.didReceiveResponseCallBack(self.fullPath); 111 | }); 112 | 113 | } 114 | } 115 | 116 | - (void)operateWithReceivingData:(NSData *)data { 117 | // 获得已经下载的文件大小 118 | self.currentSize += data.length; 119 | 120 | // 写入文件 121 | [self.handle writeData:data]; 122 | 123 | // 下载状态 通知代理 124 | if (_didReceivDataCallBack) { 125 | dispatch_async(dispatch_get_main_queue(), ^{ 126 | self.didReceivDataCallBack((NSUInteger)self.currentSize,(NSUInteger)self.totalSize); 127 | }); 128 | } 129 | } 130 | 131 | - (void)operateWithComplete:(NSError *)error { 132 | // 关闭文件句柄 133 | [self.handle closeFile]; 134 | // 释放文件句柄 135 | self.handle = nil; 136 | 137 | // 完成下载 通知 block 138 | if (error) { 139 | [self completFailueWithError:error]; 140 | } else { 141 | [self completCusesseWithCode:1]; 142 | } 143 | } 144 | 145 | - (void)configCallBacksWithDidReceiveResponse:(SGReceiveResponseOperation)didReceiveResponse 146 | didReceivData:(SGReceivDataOperation)didReceivData 147 | didComplete:(SGCompleteOperation)didComplete { 148 | _didReceiveResponseCallBack = didReceiveResponse; 149 | _didReceivDataCallBack = didReceivData; 150 | _didCompleteCallBack = didComplete; 151 | 152 | } 153 | 154 | #pragma mark - operations 155 | /** 成功回调 1代表下载后成功回调 2代表直接从磁盘中获取了 */ 156 | - (void)completCusesseWithCode:(NSInteger)code { 157 | // 获取下载信息 158 | NSDictionary *dict = [self downLoadInfoWithFinished:YES]; 159 | 160 | 161 | // 通知 162 | [[NSNotificationCenter defaultCenter] postNotificationName:SGDownloadCompleteNoti object:self userInfo:dict]; 163 | 164 | if (code == 1) { 165 | // 存储 文件下载信息 166 | [SGCacheManager saveFileInfoWithDict:dict]; 167 | } 168 | 169 | // 回到主线程 回调 170 | if (_didCompleteCallBack) { 171 | dispatch_async(dispatch_get_main_queue(), ^{ 172 | self.didCompleteCallBack(dict,nil); 173 | }); 174 | } 175 | } 176 | 177 | /** 失败回调 */ 178 | - (void)completFailueWithError:(NSError *)error { 179 | 180 | // 发通知 181 | [[NSNotificationCenter defaultCenter] postNotificationName:SGDownloadCompleteNoti object:self userInfo:@{@"error":error}]; 182 | // 存储 183 | [SGCacheManager saveFileInfoWithDict:[self downLoadInfoWithFinished:NO]]; 184 | 185 | // 回调 186 | if (_didCompleteCallBack) { 187 | dispatch_async(dispatch_get_main_queue(), ^{ 188 | self.didCompleteCallBack(nil,error); 189 | }); 190 | } 191 | } 192 | 193 | #pragma mark - get download info 194 | // 构造回调信息 195 | - (NSDictionary *)downLoadInfoWithFinished:(BOOL)finished { 196 | return @{ 197 | fileUrl : self.url, 198 | fileName : self.fileName, 199 | filePath : self.fullPath, 200 | fileSize : @(self.currentSize), 201 | totalSize : @(self.totalSize), 202 | isFinished : @(finished) 203 | }; 204 | } 205 | @end 206 | -------------------------------------------------------------------------------- /SGDwonload/SGDownloadQueue.h: -------------------------------------------------------------------------------- 1 | // 2 | // SGDownloadQueue.h 3 | // OfflineBreakPointDownload 4 | // 5 | // Created by Shangen Zhang on 16/11/26. 6 | // Copyright © 2016年 Shangen Zhang. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | /** 下载处理 */ 12 | typedef enum : NSUInteger { 13 | DownloadHandleTypeStart, // 开始下载 14 | DownloadHandleTypeSuspend, // 暂停下载 15 | DownloadHandleTypeCancel, // 取消下载 16 | } DownloadHandleType; 17 | 18 | 19 | @interface SGDownloadQueue : NSObject 20 | 21 | // 添加下载任务 22 | - (void)addDownloadWithSession:(NSURLSession *)session 23 | URL:(NSURL *)url 24 | begin:(void(^)(NSString * filePath))begin 25 | progress:(void(^)(NSInteger completeSize,NSInteger expectSize))progress 26 | complete:(void(^)(NSDictionary *respose,NSError *error))complet; 27 | 28 | // 对当前任务进行操作 29 | - (void)operateDownloadWithUrl:(NSString *)url handle:(DownloadHandleType)handle; 30 | 31 | // 取消所有任务 32 | - (void)cancelAllTasks; 33 | - (void)suspendAllTasks; 34 | - (void)startAllTasks; 35 | 36 | // 供downloader 处理下载调用 37 | - (void)dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response; 38 | 39 | - (void)dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data; 40 | 41 | - (void)task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error; 42 | @end 43 | -------------------------------------------------------------------------------- /SGDwonload/SGDownloadQueue.m: -------------------------------------------------------------------------------- 1 | // 2 | // SGDownloadQueue.m 3 | // OfflineBreakPointDownload 4 | // 5 | // Created by Shangen Zhang on 16/11/26. 6 | // Copyright © 2016年 Shangen Zhang. All rights reserved. 7 | // 8 | 9 | #import "SGDownloadQueue.h" 10 | #import "SGDownloadOperation.h" 11 | 12 | 13 | @interface SGDownloadQueue () 14 | // 列队管理集合 15 | @property (nonatomic,strong) NSMutableSet *operations; 16 | 17 | @end 18 | 19 | @implementation SGDownloadQueue 20 | 21 | - (instancetype)init { 22 | 23 | if (self = [super init]) { 24 | // 监听完成通知 25 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didResiveDownloadFileCompete:) name:SGDownloadCompleteNoti object:nil]; 26 | } 27 | return self; 28 | } 29 | 30 | - (void)dealloc { 31 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 32 | } 33 | 34 | - (void)didResiveDownloadFileCompete:(NSNotification *)noti { 35 | SGDownloadOperation *operation = noti.object; 36 | if (operation) { 37 | [self.operations removeObject:operation]; 38 | } 39 | } 40 | 41 | #pragma mark - handle Out operations 42 | - (void)addDownloadWithSession:(NSURLSession *)session URL:(NSURL *)url begin:(void(^)(NSString *))begin progress:(void(^)(NSInteger,NSInteger))progress complete:(void(^)(NSDictionary *,NSError *))complet { 43 | // 获取operation对象 44 | SGDownloadOperation *operation = [self operationWithUrl:url.absoluteString]; 45 | 46 | if (operation == nil) { 47 | 48 | operation = [[SGDownloadOperation alloc] initWith:url.absoluteString session:session]; 49 | 50 | if (operation == nil) { 51 | // 没有下载任务代表已下载完成 52 | NSDictionary *fileInfo = [SGCacheManager queryFileInfoWithUrl:url.absoluteString]; 53 | if (fileInfo && complet) { 54 | complet(fileInfo,nil); 55 | }else { 56 | complet(nil,[NSError errorWithDomain:@"构建下载任务失败" code:-1 userInfo:nil]); 57 | } 58 | return; 59 | } 60 | 61 | [self.operations addObject:operation]; 62 | } 63 | 64 | // 回调赋值operation 65 | [operation configCallBacksWithDidReceiveResponse:begin didReceivData:progress didComplete:complet]; 66 | 67 | [operation.dataTask resume]; 68 | 69 | } 70 | 71 | - (void)operateDownloadWithUrl:(NSString *)url handle:(DownloadHandleType)handle{ 72 | 73 | SGDownloadOperation *operation = [self operationWithUrl:url]; 74 | 75 | if (!operation) { 76 | return; 77 | } else if (!operation.dataTask) { 78 | // if (!operation.didComplete || !(handle == DownloadHandleTypeStart)) { 79 | // [self.operations removeObject:operation]; 80 | // return; 81 | // } 82 | // 83 | // NSDictionary *fileInfo = [SGCacheManager queryFileInfoWithUrl:url]; 84 | // 85 | // if (fileInfo) { 86 | // operation.didComplete(fileInfo,nil); 87 | // } 88 | 89 | [self.operations removeObject:operation]; 90 | return; 91 | } 92 | 93 | 94 | switch (handle) { 95 | case DownloadHandleTypeStart: 96 | [operation.dataTask resume]; // 开始 97 | break; 98 | case DownloadHandleTypeSuspend: 99 | [operation.dataTask suspend]; // 暂停 100 | break; 101 | case DownloadHandleTypeCancel: 102 | [operation.dataTask cancel]; // 取消 103 | [self.operations removeObject:operation]; // 删除任务 104 | break; 105 | } 106 | } 107 | 108 | - (void)cancelAllTasks { 109 | // 取消所有的任务 110 | [_operations enumerateObjectsUsingBlock:^(SGDownloadOperation * _Nonnull obj, BOOL * _Nonnull stop) { 111 | [obj.dataTask cancel]; 112 | }]; 113 | // 清理内存 114 | _operations = nil; 115 | } 116 | - (void)suspendAllTasks { 117 | // 取消所有的任务 118 | [_operations enumerateObjectsUsingBlock:^(SGDownloadOperation * _Nonnull obj, BOOL * _Nonnull stop) { 119 | [obj.dataTask suspend]; 120 | }]; 121 | } 122 | - (void)startAllTasks { 123 | // 取消所有的任务 124 | [_operations enumerateObjectsUsingBlock:^(SGDownloadOperation * _Nonnull obj, BOOL * _Nonnull stop) { 125 | [obj.dataTask resume]; 126 | }]; 127 | } 128 | 129 | 130 | #pragma mark - handle download 131 | - (void)dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response { 132 | 133 | [[self oprationWithDataTask:dataTask] operateWithResponse:response]; 134 | } 135 | 136 | - (void)dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data { 137 | [[self oprationWithDataTask:dataTask] operateWithReceivingData:data]; 138 | } 139 | 140 | 141 | - (void)task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error { 142 | [[self oprationWithDataTask:task] operateWithComplete:error]; 143 | } 144 | 145 | 146 | #pragma mark - query operation 147 | - (SGDownloadOperation *)operationWithUrl:(NSString *)url{ 148 | __block SGDownloadOperation *operation = nil; 149 | 150 | [self.operations enumerateObjectsUsingBlock:^(SGDownloadOperation * _Nonnull obj, BOOL * _Nonnull stop) { 151 | if ([obj.url isEqualToString:url]) { 152 | operation = obj; 153 | *stop = YES; 154 | } 155 | }]; 156 | 157 | return operation; 158 | } 159 | 160 | // 寻找operation 161 | - (SGDownloadOperation *)oprationWithDataTask:(NSURLSessionTask *)dataTask { 162 | 163 | __block SGDownloadOperation *operation = nil; 164 | 165 | [self.operations enumerateObjectsUsingBlock:^(SGDownloadOperation * _Nonnull obj, BOOL * _Nonnull stop) { 166 | if (obj.dataTask == dataTask) { 167 | operation = obj; 168 | *stop = YES; 169 | } 170 | }]; 171 | 172 | return operation; 173 | } 174 | #pragma mark - lazy load 175 | - (NSMutableSet *)operations { 176 | 177 | if (!_operations) { 178 | _operations = [NSMutableSet set]; 179 | } 180 | return _operations; 181 | } 182 | @end 183 | -------------------------------------------------------------------------------- /SGDwonload/SGDownloadSession.h: -------------------------------------------------------------------------------- 1 | // 2 | // SGDownloader.h 3 | // OfflineBreakPointDownload 4 | // 5 | // Created by Shangen Zhang on 16/11/26. 6 | // Copyright © 2016年 Shangen Zhang. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "SGDownloadManager.h" 11 | 12 | @interface SGDownloadSession : NSObject 13 | 14 | // 接口回调 15 | - (void)downloadWithURL:(NSURL *)url 16 | begin:(void(^)(NSString *))begin 17 | progress:(void(^)(NSInteger,NSInteger))progress 18 | complete:(void(^)(NSDictionary *,NSError *))complet; 19 | 20 | 21 | - (void)startDownLoadWithUrl:(NSString *)url; 22 | 23 | - (void)supendDownloadWithUrl:(NSString *)url; 24 | 25 | - (void)cancelDownloadWithUrl:(NSString *)url; 26 | 27 | - (void)cancelAllDownloads; 28 | - (void)startAllDownloads; 29 | - (void)suspendAllDownloads; 30 | @end 31 | -------------------------------------------------------------------------------- /SGDwonload/SGDownloadSession.m: -------------------------------------------------------------------------------- 1 | // 2 | // SGDownloader.m 3 | // OfflineBreakPointDownload 4 | // 5 | // Created by Shangen Zhang on 16/11/26. 6 | // Copyright © 2016年 Shangen Zhang. All rights reserved. 7 | // 8 | 9 | #import "SGDownloadSession.h" 10 | #import "SGDownloadQueue.h" 11 | 12 | @interface SGDownloadSession () 13 | 14 | /** session 可以支持多个任务下载,创建一次就可以 */ 15 | @property (nonatomic,strong) NSURLSession *session; 16 | 17 | /** 下载列队管理 专门负责接收到数据时分配给不同operation */ 18 | @property (nonatomic,strong) SGDownloadQueue *queue; 19 | 20 | @end 21 | 22 | 23 | @implementation SGDownloadSession 24 | // 添加任务 25 | - (void)downloadWithURL:(NSURL *)url begin:(void(^)(NSString *))begin progress:(void(^)(NSInteger,NSInteger))progress complete:(void(^)(NSDictionary *,NSError *))complet { 26 | // 交给列队管理 27 | [self.queue addDownloadWithSession:self.session URL:url begin:begin progress:progress complete:complet]; 28 | } 29 | 30 | #pragma mark - 操作任务接口 31 | - (void)startDownLoadWithUrl:(NSString *)url { 32 | [self.queue operateDownloadWithUrl:url handle:DownloadHandleTypeStart]; 33 | } 34 | 35 | - (void)supendDownloadWithUrl:(NSString *)url { 36 | [self.queue operateDownloadWithUrl:url handle:DownloadHandleTypeSuspend]; 37 | } 38 | 39 | - (void)cancelDownloadWithUrl:(NSString *)url { 40 | [self.queue operateDownloadWithUrl:url handle:DownloadHandleTypeCancel]; 41 | } 42 | 43 | - (void)cancelAllDownloads { 44 | 45 | dispatch_async(dispatch_get_global_queue(0, 0), ^{ 46 | // 取消所有session的任务 47 | // 耗时操作 48 | // 会调用 URLSession:task:didCompleteWithError: 方法抛出error取消 49 | [self.session invalidateAndCancel]; 50 | }); 51 | } 52 | - (void)startAllDownloads { 53 | [_queue startAllTasks]; 54 | } 55 | - (void)suspendAllDownloads { 56 | [_queue suspendAllTasks]; 57 | } 58 | 59 | #pragma mark - 60 | 61 | // ssl 服务 证书信任 62 | - (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * _Nullable credential))completionHandler{ 63 | if(![challenge.protectionSpace.authenticationMethod isEqualToString:@"NSURLAuthenticationMethodServerTrust"]) { 64 | return; 65 | } 66 | 67 | // 信任该插件 68 | NSURLCredential *credential = [[NSURLCredential alloc] initWithTrust:challenge.protectionSpace.serverTrust]; 69 | // 第一个参数 告诉系统如何处置 70 | completionHandler(NSURLSessionAuthChallengeUseCredential,credential); 71 | } 72 | 73 | //当请求协议是https的时候回调用该方法 74 | //Challenge 挑战 质询(受保护空间) 75 | //NSURLAuthenticationMethodServerTrust 服务器信任证书 76 | - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * __nullable credential))completionHandler { 77 | 78 | if(![challenge.protectionSpace.authenticationMethod isEqualToString:@"NSURLAuthenticationMethodServerTrust"]) { 79 | return; 80 | } 81 | 82 | // 信任该插件 83 | NSURLCredential *credential = [[NSURLCredential alloc] initWithTrust:challenge.protectionSpace.serverTrust]; 84 | // 第一个参数 告诉系统如何处置 85 | completionHandler(NSURLSessionAuthChallengeUseCredential,credential); 86 | 87 | } 88 | 89 | 90 | // 接受到响应调用 91 | - (void)URLSession:(NSURLSession *)session 92 | dataTask:(NSURLSessionDataTask *)dataTask 93 | didReceiveResponse:(NSURLResponse *)response 94 | completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler { 95 | 96 | // 将响应交给列队处理 97 | [self.queue dataTask:dataTask didReceiveResponse:response]; 98 | 99 | // 允许下载 100 | completionHandler(NSURLSessionResponseAllow); 101 | } 102 | 103 | // 接受到数据碎片 的时候调用,调用多次 104 | - (void)URLSession:(NSURLSession *)session 105 | dataTask:(NSURLSessionDataTask *)dataTask 106 | didReceiveData:(NSData *)data { 107 | // 接收到session 下载碎片交个列队管理 108 | [self.queue dataTask:dataTask didReceiveData:data]; 109 | } 110 | 111 | // 完成下载 112 | - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(nullable NSError *)error { 113 | [self.queue task:task didCompleteWithError:error]; 114 | } 115 | 116 | 117 | #pragma mark - lazy load 118 | - (NSURLSession *)session { 119 | if (!_session) { 120 | NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration]; 121 | // 设置请求超时 122 | config.timeoutIntervalForRequest = -1; 123 | // config.networkServiceType = NSURLNetworkServiceTypeVideo; 124 | // config.timeoutIntervalForResource = -1; 125 | // config.TLSMaximumSupportedProtocol = kSSLProtocolAll; 126 | 127 | _session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:[NSOperationQueue currentQueue]]; 128 | } 129 | 130 | return _session; 131 | } 132 | 133 | - (SGDownloadQueue *)queue { 134 | if (!_queue) { 135 | _queue = [[SGDownloadQueue alloc] init]; 136 | } 137 | return _queue; 138 | } 139 | @end 140 | --------------------------------------------------------------------------------