var doSth: ((String?) -> ())?
}
class B: NSObject {
init() {
let a = A()
a.doSth = doSth
}
var doSth: ((String?) -> ())?
}
这种情况下,A执行 doSth 是可以调用到 B 中的方法,但是会有一个循环引用的问题, A->(strong)B, B->(strong)A。
当设置 B = nil 的时候不能正确释放掉资源,应该修改 B 中 a 的赋值方法,设为弱引用:
let a = A()
a.doSth = { [weak self] value in
self?.doSth(value)
}
]]>Content-Length的类型是 NSTaggedPointerString,详细解释可以看这里http://www.cocoachina.com/ios/20150918/13449.html。
虽然它也是一个字符串,但是用 dict["Content-Type"] as NSNumber是会导致程序崩溃的:Could not cast value of type 'NSTaggedPointerString' to 'NSNumber',需要先将该字段转为 string,然后再将string转成number才行。
let numberStr = dict["Content-Length"] as! String
if let number = NSNumberFormatter().numberFromString(numberStr){
print(number.longLongValue)
}
]]>
class A {
var b = B()
function c(obj: AnyObject?){ print("cccc") }
}
class B{
init(){}
function toC(){ print("to c") }
}
这种情况下怎么在 class B 的 toC 方法里面执行 class A 的 c 方法?
除了委托,还有另外一种方式:把 function c 当作一个参数传给 class B 就可以了。
class A {
var b = B()
b.c = c
function c(obj: AnyObject?){ print("cccc") }
}
class B{
var c: ((AnyObject?) -> ())?
init(){ toC() }
function toC(){ c?("hello c") }
}
]]>iOS中[UILabel]将部分文字改成[上标]的问题。
需求是将中括号 [] 里面的内容改成上标,变成:
iOS中UILabel将部分文字改成上标的问题。
NSMutableAttributedString可以添加一个属性 NSBaselineOffsetAttributeName,将部分文字做偏移处理,这样也能实现效果,但是如果同时加上了NSFontAttributeName,那么这段文字在UILabel中只会显示一行,换行的部分会消失不见,没有找到具体的原因。
后来用了其它的属性kCTSuperscriptAttributeName替换,实现起来也更简单。
var superScripts = [String]()
if tempRemark.containsString("[") && tempRemark.containsString("]") {
let array = tempRemark.componentsSeparatedByString("[")
for temp in array {
if temp.containsString("]") {
let t = temp.componentsSeparatedByString("]")
if t.count > 0 { superScripts.append("[\(t[0])]") }
}
}
}
let attrStr = NSMutableAttributedString(string: tempRemark, attributes: [NSFontAttributeName: UIFont(name: "ITCAvantGardeStd-Bk", size: 14)])
for s in superScripts {
let totalLength = tempRemark.componentsSeparatedByString(s)[0].characters.count
attrStr.addAttribute(NSFontAttributeName, value: UIFont(name: "ITCAvantGardeStd-Bk", size: 10), range: NSMakeRange(totalLength!, s.characters.count))
attrStr.addAttribute(String(kCTSuperscriptAttributeName), value: 1, range: NSMakeRange(totalLength!, s.characters.count))
let tmpStr = s.stringByReplacingOccurrencesOfString("[", withString: "").stringByReplacingOccurrencesOfString("]", withString: "")
attrStr.replaceCharactersInRange(NSMakeRange(totalLength!, s.characters.count), withString: tmpStr)
}
let remarkLabel = UILabel()
remarkLabel.lineBreakMode = .ByWordWrapping
remarkLabel.numberOfLines = 0
remarkLabel.attributedText = attrStr
self.addSubview(remarkLabel)
ATS 简介
如果你在 iOS 9 以上平台编译项目后,你会发现无论你使用 NSURLSession 请求接口数据,或者使用 WebView 打开网页,只要不是 HTTPS 协议都会失败。造成这个现象的原因就是 iOS 9 引入的 ATS 机制。
当然,苹果在引入这个特性的时候,也会考虑到开发者的过渡问题,所以还为我们留了一个开关,只需要在 Info.plist 中加上这个节点即可关闭 ATS 的默认行为:
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
其中 NSAppTransportSecurity 是 ATS 设置项的根节点, NSAllowsArbitraryLoads 代表是否允许访问任意资源。 我们只需要将这个属性设置为 true, 就可以关闭 ATS 的默认拦截行为,继续使用 HTTP 了。
iOS 10 中的变化
iOS 10 依然延续了 ATS 机制。并且把它加强了。 从苹果官方目前发布出来的消息上看, 在 2017 年一月份开始,就不再允许开发者打开 NSAllowsArbitraryLoads 设置了。
也就是说,这种将 ATS 全局关闭的能力不给我们了。 可见苹果这次的决心。
那么,我们不能全局关闭 ATS 是否就意味着我们必须强制使用 HTTPS 呢? 下面咱们继续讨论。
ATS 除了提供一个 NSAllowsArbitraryLoads 属性,其实还有另外的属性。 可以设置 NSExceptionDomains 属性来将需要排除强制验证的域名写进来。 也就是说我如果我们的 app 只访问我们自己的服务器, 我们可以将我们服务器的域名添加进来,依然可以继续使用 HTTP。但我们不能像之前那样简单粗暴的直接把所有的请求都通过。这样就更加精确,其实对真正的用户来说,这其实是一个好事。尽管他们或许感知不到,但这样确实可以极大的减少用户的安全风险。
NSExceptionDomains 的设置方法如下, 比如我们要将 sapze.com 这个域名排除在 ATS 验证之外,就可以这样:
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
<key>NSExceptionDomains</key>
<dict>
<key>sapze.com</key>
<dict>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
</dict>
</dict>
</dict>
上面说的是在我们已经确定 App 所访问的数据范围的情况下的处理方法。 但还有一种情况, 如果我们开发的是类似uedbet手机app器的 App。这样用户需要输入任意的地址,那怎么办呢? 我们不可能把所有的域名都写到 NSExceptionDomains 里面。
这就可以使用 iOS 10 对 ATS 提供的一个新属性 NSAllowsArbitraryLoadsInWebContent。 顾名思义了,就是只允许加载任意类型的 web 内容。这样我们的 WKWebView 就可以加载任何类型的页面了。
使用 NSAllowsArbitraryLoadsInWebContent 的例子:
NSAppTransportSecurity
NSAllowsArbitraryLoadsInWebContent
结尾
总之 iOS 10 之后,对于 ATS 的安全保护会更加加强。也能看出苹果对于尽可能的去掉 HTTP 不安全性的决心。所以大家如果在提交新 App 的时候,需要注意一下这个特性,并及时作出相应的调整
]]>有一个UITableView,TableViewCell里面包含一个下拉框控件,控件用的是我之前写的一个类似Web端dropdownlist的功能。但是实际使用起来的时候遇到一个问题,就是控件的点击区域不包含在当前UIView里面。
比如图中这样,当点击下拉选项的时候触发的是UITableView的事件,不会触发到下拉框的选择事件,原因很简单,事件的先后顺序而已。
这里使用到的解决方法也蛮简单的。
hitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView?
iOS系统检测到手指触摸(Touch)操作时会将其放入当前活动Application的事件队列,UIApplication会从事件队列中取出触摸事件并传递给key window(当前接收用户事件的窗口)处理,window对象首先会使用hitTest:withEvent:方法寻找此次Touch操作初始点所在的视图(View),即需要将触摸事件传递给其处理的视图,称之为hit-test view。
window对象会在首先在view hierarchy的顶级view上调用hitTest:withEvent:,此方法会在视图层级结构中的每个视图上调用pointInside:withEvent:,如果pointInside:withEvent:返回YES,则继续逐级调用,直到找到touch操作发生的位置,这个视图也就是hit-test view。
在我的项目中,我是把hitTest部分写在UITableViewCell里面如下:
override func hitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView? {
if tableView?.numberOfRowsInSection(0) > 0 {
for i in 0..<tableView!.numberOfRowsInSection(0) {
if let cell = tableView?.cellForRowAtIndexPath(NSIndexPath(forRow: i, inSection: 0)) as? Cell {
if cell.tag != -99 {
if let agTableView = cell.textFieldName.agTableView {
if agTableView.alpha == 1 {
let pFrame = agTableView.converRect(agTableView.bounds, toView: tableView!)
let agFrame = CGRectMake(pFrame.orign.x, pFrame.orign.y - self.frame.orign.y, pFrame.width, pFrame.height)
if CGRectContainsPoint(agFrame, point) {
return agTableView
}
}
}
}
}
}
}
return super.hitTest(point, withEvent: event)
}
代码部分很简单,就不解释了。
总结:当控件超出了UIView的范围,或者被其它控件挡住了的时候,都可以用类似的方法解决,只要知道事件触发的先后顺序就行了。
]]>
let color = UIColor.redColor().CGColor
let numComponents = CGColorGetNumberOfComponents(color)
if numComponents == 4 {
let components = CGColorGetComponents(color)
let red = components[0]
let green = components[1]
let blue = components[2]
let alpha = components[3]
}
https://github.com/peheje/JsonSerializerSwift/blob/master/JsonSerializerSwift/JsonSerializer.swift
但是此方法并不能很好的满足实际需求,会有些问题。
问题1:如果model是一个数组,该方法的json字符串会以 {"[0]" 开头,并不是我们常用的 [{}]格式
问题2:如果model包含另一个实体类,该方法会直接将实体类的类名输出为字符串,也就是没有递归解析
问题3:代码太多
自己动手改了一点,整体的逻辑和方法还是用的原来的代码,基本可以满足项目的需求了。
代码如下:
class func toJson(object: Any, prettify: Bool = false) -> String {
var json = "{"
let mirror = Mirror(reflecting: object)
var children = [(label: String?, value: Any)]()
let mirrorChildrenCollection = AnyRandomAccessCollection(mirror.children)!
children += mirrorChildrenCollection
var currentMirror = mirror
while let superclassChildren = currentMirror.superclassMirror()?.children {
let randomCollection = AnyRandomAccessCollection(superclassChildren)!
children += randomCollection
currentMirror = currentMirror.superclassMirror()!
}
var filterredChildren = [(label: String?, value: Any)]()
for (optionalPropertyName, values) in children {
if !optionalPropertyName!.containsString("notMapped_") {
filterredChildren += [(optionalPropertyName, values)]
}
}
var skip = false
let size = filterredChildren.count
var index = 0
if let objArray = object as? NSArray {
var jsonArray = "["
for (index, value) in objArray.enumerate() {
jsonArray += toJson(value, prettify: prettify)
jsonArray += (index < objArray.count - 1 ? ", " : "")
}
jsonArray += "]"
return jsonArray
}
for (optionalPropertyName, value) in filterredChildren {
skip = false
let propertyName = optionalPropertyName!
let property = Mirror(reflecting: value)
var handleValue = String()
if property.displayStyle == Mirror.DisplayStyle.Struct || property.displayStyle == Mirror.DisplayStyle.Class {
handleValue = toJson(value)
skip = true
} else if (value is Int || value is Double || value is Float || value is Bool) && property.displayStyle != Mirror.DisplayStyle.Optional {
handleValue = String(value ?? "null")
} else if property.displayStyle == Mirror.DisplayStyle.Optional {
let str = String(value)
if str != "nil" {
let type = String(property.subjectType)
if type.containsString("String")
|| type.containsString("Int")
|| type.containsString("Double")
|| type.containsString("Bool")
|| type.containsString("NSDate")
|| type.containsString("Float") {
handleValue = String(str).substringWithRange(str.startIndex.advancedBy(9)..<str.endIndex.advancedBy(-1))
} else {
handleValue = toJson(value)
}
} else {
handleValue = "null"
}
} else {
handleValue = String(value) != "nil" ? "\"\(value)\"" : "null"
}
if !skip {
json += "\"\(propertyName)\": \(handleValue)" + (index < size - 1 ? ", " : "")
} else {
json = "\(handleValue)" + (index < size - 1 ? ", " : "")
}
index += 1
}
if !skip {
json += "}"
}
if prettify {
do {
let jsonData = json.dataUsingEncoding(NSUTF8StringEncoding)!
let jsonObject: AnyObject = try NSJSONSerialization.JSONObjectWithData(jsonData, options: [])
let prettyJsonData = try NSJSONSerialization.dataWithJSONObject(jsonObject, options: .PrettyPrinted)
json = NSString(data: prettyJsonData, encoding: NSUTF8StringEncoding)! as String
} catch _ {
// print(error)
}
}
return json
}
多次测试暂时没发现问题,希望测试到问题的同学能给我反馈,谢谢。
]]>+ (NSMutableDictionary *)getKeychainQuery:(NSString *)service {
return [NSMutableDictionary dictionaryWithObjectsAndKeys:
(id)kSecClassGenericPassword, (id)kSecClass,
service, (id)kSecAttrServer,
service, (id)kSecAttrAccount,
(id)kSecAttrAccessibleAfterFirstUnlock, (id)kSecAttrAccessible,
nil];
}
相信很多同学在uedbet下载获取唯一UUID的时候都会找到这段代码,看上去也没有任何问题,但是我在实际使用中SecItemAdd一直返回-50,也就是参数错误,百度谷歌都没有找到问题答案,于是我一怒之下从删参数开始测,当我注释掉service, (id)kSecAttrServer,这一行的时候奇迹出现了。SecItemAdd居然没报错,而且真机模拟器上面都跑过了,UUID是唯一的,删掉之后再安装也还是不变。这是为什么呢?
测试环境是:xcode 7.3 iOS9.3 iOS7.0
Apple给的说明https://developer.apple.com/reference/security/ksecattrserver?language=objc
The corresponding value is of type CFStringRef
and contains the server's domain name or IP address. Items of class kSecClassInternetPassword
have this attribute.
可是我仍然不知道是什么原因造成的。希望广大网友们有碰到这个问题的时候可以帮我解释一下,谢谢。
]]>//
// UpdatingView.swift
// CanonPixma
//
// Created by huangyawei on 16/4/20.
// Copyright © 2016年 forecepts. All rights reserved.
//
import UIKit
import Zip
import SwiftyJSON
class UpdatingView: UIView {
private var downloadTask: NSURLSessionDownloadTask?
private var urlString: String?
init(urlString: String) {
super.init(frame: CGRectZero)
self.urlString = urlString
loadUI()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func startDownload() {
downloadFile()
}
// MARK: load ui
var titleLabel: UILabel?
var totalSizeLengthView: UIView?
var currentSizeLengthLabel: UILabel?
var sizeDescriptionLabel: UILabel?
func loadUI() {
let coverView = UIView()
coverView.backgroundColor = UIColor.blackColor()
coverView.alpha = 0.75
self.addSubview(coverView)
coverView.snp_makeConstraints{ (make) -> Void in
make.edges.equalTo(self)
}
let containerView = UIView()
containerView.layer.cornerRadius = 4.0
containerView.clipsToBounds = true
containerView.backgroundColor = UIColor.lightGrayColor()
coverView.addSubview(containerView)
containerView.snp_makeConstraints{ (make) -> Void in
make.centerX.equalTo(coverView.snp_centerX)
make.centerY.equalTo(coverView.snp_centerY)
make.width.equalTo(coverView).multipliedBy(0.6)
make.height.equalTo(containerView.snp_width)
}
titleLabel = UILabel()
titleLabel?.text = "Downloading"
containerView.addSubview(titleLabel!)
titleLabel?.snp_makeConstraints{ (make) -> Void in
make.centerX.equalTo(containerView.snp_centerX)
make.height.equalTo(20)
make.top.equalTo(containerView).offset(20)
}
totalSizeLengthView = UIView()
totalSizeLengthView?.backgroundColor = UIColor.whiteColor()
totalSizeLengthView?.layer.cornerRadius = 2
totalSizeLengthView?.clipsToBounds = true
containerView.addSubview(totalSizeLengthView!)
totalSizeLengthView?.snp_makeConstraints{ (make) -> Void in
make.left.equalTo(containerView).offset(10)
make.right.equalTo(containerView).offset(-10)
make.centerY.equalTo(containerView.snp_centerY)
make.height.equalTo(4)
}
currentSizeLengthLabel = UILabel()
currentSizeLengthLabel?.text = ""
currentSizeLengthLabel?.backgroundColor = UIColor.blueColor()
currentSizeLengthLabel?.layer.cornerRadius = 2
totalSizeLengthView?.addSubview(currentSizeLengthLabel!)
currentSizeLengthLabel?.snp_makeConstraints{ (make) -> Void in
make.left.equalTo(totalSizeLengthView!).offset(0)
make.centerY.equalTo(totalSizeLengthView!.snp_centerY)
make.height.equalTo(self.totalSizeLengthView!).multipliedBy(1)
make.width.equalTo(self.totalSizeLengthView!).multipliedBy(0).priorityLow()
}
sizeDescriptionLabel = UILabel()
sizeDescriptionLabel?.text = "0/0 M"
containerView.addSubview(sizeDescriptionLabel!)
sizeDescriptionLabel?.snp_makeConstraints{ (make) -> Void in
make.centerX.equalTo(containerView.snp_centerX)
make.top.equalTo(totalSizeLengthView!.snp_bottom).offset(20)
}
}
// MARK: download zip file
func downloadFile() {
let url = NSURL(string: urlString!)
var totalSize: Float = 0.00
guard let fileName = urlString!.componentsSeparatedByString("/").last else {
return
}
downloadTask = SapDownloader.download(fileName, downloadSource: url!, progressBlockCompletion: { (bytesWritten, bytesExpectedToWrite) -> () in
// print("progress: \(bytesWritten) / \(bytesExpectedToWrite)")
let downloadedSize = Float(bytesWritten) / Float(1024 * 1024)
if totalSize == 0.00 {
totalSize = Float(bytesExpectedToWrite) / Float(1024 * 1024)
}
let percent = downloadedSize / totalSize
let csize = NSString(format: "%.2f", downloadedSize) as String
let tsize = NSString(format: "%.2f", totalSize) as String
dispatch_async(dispatch_get_main_queue(), {
self.sizeDescriptionLabel?.text = csize + "/" + tsize + " MB"
let totalFrameWidth = self.totalSizeLengthView!.frame.width
self.currentSizeLengthLabel?.frame.size.width = totalFrameWidth * CGFloat(percent)
})
}){ (error, fileDestination) -> () in
if error == nil {
dispatch_async(dispatch_get_main_queue(), {
self.unZipFile(fileDestination)
})
}
}
}
// MARK: unzip file
func unZipFile(filePathUrl: NSURL) {
do {
self.titleLabel?.text = "UnZiping..."
let unzipDirectory = try Zip.quickUnzipFile(filePathUrl, progress: { progress in
dispatch_async(dispatch_get_main_queue(), {
let totalFrameWidth = self.totalSizeLengthView!.frame.width
self.currentSizeLengthLabel?.frame.size.width = totalFrameWidth * CGFloat(progress)
})
})
reSetModelAndImages(unzipDirectory)
} catch {
UIAlertView(title: "Error", message: "Something went wrong.", delegate: nil, cancelButtonTitle: "OK").show()
}
}
// MARK: set file to Model and restore images
func reSetModelAndImages(unzipDirectory: NSURL) {
self.titleLabel?.text = "Restoring..."
let fileManager = NSFileManager.defaultManager()
let updatingFilesPath = unzipDirectory.absoluteString.stringByReplacingOccurrencesOfString("file://", withString: "")
let tempImagesPath = updatingFilesPath + "images"
var isDir: ObjCBool = false
let fileExists = fileManager.fileExistsAtPath(tempImagesPath, isDirectory: &isDir)
if fileExists && isDir {
let path = fileManager.URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask).first
let imagePath = path!.URLByAppendingPathComponent("images")
do {
try fileManager.createDirectoryAtURL(imagePath, withIntermediateDirectories: true, attributes: nil)
} catch let error as NSError {
print(error)
}
//remove images to image filepath
do {
let imageArray = try fileManager.contentsOfDirectoryAtPath(tempImagesPath)
for image in imageArray {
let sourceFile = tempImagesPath.stringByReplacingOccurrencesOfString("file://", withString: "") + "/" + image
let destFile = imagePath.absoluteString.stringByReplacingOccurrencesOfString("file://", withString: "") + "/" + image
try fileManager.moveItemAtPath(sourceFile, toPath: destFile)
}
} catch {
}
//make json data to model
makeModels(updatingFilesPath + "AppSettings.txt") { json in
let appSettingModel = AppSettingModel(json: json)
var appSettingArray = [AppSettingModel]()
appSettingArray.append(appSettingModel)
CacheArchive.sharedInstance().save(appSettingArray, dataType: CacheArchive.ArchiveDataType.AppSetting)
}
makeModels(updatingFilesPath + "ProductCategories.txt") { json in
guard let categories = json.array else {
return
}
var productCategoryArray = [ProductCategoryModel]()
let parentCategories = categories.filter{ $0["ParentId"] == 0 }
for parentCategory in parentCategories {
let productCategoryModel = ProductCategoryModel(json: parentCategory)
//sub category
var subProductCategoryArray = [ProductCategoryModel]()
let subCategories = categories.filter{ $0["ParentId"] == parentCategory["Id"] }
for subCategory in subCategories {
let subProductCategoryModel = ProductCategoryModel(json: subCategory)
subProductCategoryArray.append(subProductCategoryModel)
}
subProductCategoryArray = subProductCategoryArray.sort{ $0.sortNo < $1.sortNo }
productCategoryModel.productCategoryModel = subProductCategoryArray
productCategoryArray.append(productCategoryModel)
}
CacheArchive.sharedInstance().save(productCategoryArray, dataType: CacheArchive.ArchiveDataType.ProductCategory)
}
makeModels(updatingFilesPath + "Products.txt") { json in
guard let products = json.array else {
return
}
var productArray = [ProductModel]()
for product in products {
let productModel = ProductModel(json: product)
productArray.append(productModel)
}
CacheArchive.sharedInstance().save(productArray, dataType: CacheArchive.ArchiveDataType.Product)
}
makeModels(updatingFilesPath + "VersionLogs.txt") { json in
let versionModel = VersionLogModel(json: json)
var versionArray = [VersionLogModel]()
versionArray.append(versionModel)
CacheArchive.sharedInstance().save(versionArray, dataType: CacheArchive.ArchiveDataType.VersionLog)
}
makeModels(updatingFilesPath + "Articles.txt") { json in
guard let articles = json.array else {
return
}
var articlesArray = [ArticleModel]()
for article in articles {
let articleModel = ArticleModel(json: article)
articlesArray.append(articleModel)
}
CacheArchive.sharedInstance().save(articlesArray, dataType: CacheArchive.ArchiveDataType.Article)
}
//remove temp files
Common.removeFiles(filePath: updatingFilesPath)
Common.removeFiles(download: "downloaded")
//set root window
let landingController = LandingController()
self.window?.rootViewController = landingController
self.window?.makeKeyAndVisible()
}
}
// MARK: make json do model
private func makeModels(filePath: String, model: (JSON) -> Void) {
if Common.fileManager.fileExistsAtPath(filePath) {
guard let data = NSData(contentsOfFile: filePath) else {
return
}
let jsonModels = JSON(data: data)
if jsonModels == nil {
return
}
model(jsonModels)
}
}
}
class SapDownloader: NSObject, NSURLSessionDelegate, NSURLSessionDownloadDelegate {
var downloads = Array<InfoDownload>()
var session: NSURLSession!
let identifierDownload = "com.SapDownloader"
let pathDirectory: NSURL? = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask).first
class InfoDownload: NSObject {
var fileTitle: String!
var downloadSource: NSURL!
var downloadTask: NSURLSessionDownloadTask!
var taskResumeData: NSData!
var isDownloading: Bool!
var downloadComplete: Bool!
var pathDestination: NSURL!
var progressBlockCompletion: ((bytesWritten: Int64, bytesExpectedToWrite: Int64) -> ())!
var responseBlockCompletion: ((error: NSError!, fileDestination: NSURL!) -> ())!
init(downloadTitle fileTitle: String, downloadSource source: NSURL) {
super.init()
self.fileTitle = fileTitle
self.downloadSource = source
self.pathDestination = nil
self.isDownloading = false
self.downloadComplete = false
}
}
class Singleton {
class var sharedInstance: SapDownloader {
struct Static {
static var instance: SapDownloader?
static var token: dispatch_once_t = 0
}
dispatch_once(&Static.token, { () -> Void in
Static.instance = SapDownloader()
Static.instance?.initSessionDownload()
})
return Static.instance!
}
}
private func initSessionDownload() {
let sessionConfiguration = NSURLSessionConfiguration.backgroundSessionConfigurationWithIdentifier(self.identifierDownload)
sessionConfiguration.allowsCellularAccess = true
sessionConfiguration.HTTPMaximumConnectionsPerHost = 3
self.session = NSURLSession(configuration: sessionConfiguration, delegate: self, delegateQueue: nil)
let defaults = NSUserDefaults.standardUserDefaults()
if let sessionDownloads = defaults.valueForKey("downloadSession") {
self.downloads = sessionDownloads as! Array<InfoDownload>
}
}
private func saveDataTaskDownload(currentDownload: InfoDownload, location: NSURL) -> NSError? {
let fileManager = NSFileManager.defaultManager()
let pathData = currentDownload.pathDestination
var theError: NSError?
if fileManager.fileExistsAtPath(pathData!.path!) == true {
do {
try fileManager.replaceItemAtURL(pathData!, withItemAtURL: location, backupItemName: nil, options: .UsingNewMetadataOnly, resultingItemURL: nil)
} catch let error as NSError {
theError = error
}
} else {
do {
try fileManager.moveItemAtURL(location, toURL: pathData!)
} catch let error as NSError {
theError = error
}
}
return theError
}
class func setDestinationDownload(currentDownload: InfoDownload, urlDestination: NSURL?) -> NSError? {
let fileManager = NSFileManager.defaultManager()
var theError: NSError?
if urlDestination == nil {
currentDownload.pathDestination = fileManager.URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask).first
let path = currentDownload.pathDestination.URLByAppendingPathComponent("downloaded")
do {
try fileManager.createDirectoryAtURL(path, withIntermediateDirectories: true, attributes: nil)
currentDownload.pathDestination = path.URLByAppendingPathComponent("\(currentDownload.fileTitle)")
} catch let error as NSError {
theError = error
}
}else {
var path = fileManager.URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask).first
path = path?.URLByAppendingPathComponent(urlDestination!.path!)
do {
try fileManager.createDirectoryAtURL(path!, withIntermediateDirectories: true, attributes: nil)
currentDownload.pathDestination = path?.URLByAppendingPathComponent(currentDownload.fileTitle)
} catch let error as NSError {
theError = error
}
}
return theError
}
func URLSessionDidFinishEventsForBackgroundURLSession(session: NSURLSession) {
session.getTasksWithCompletionHandler{ (dataTask: [NSURLSessionDataTask]!, uploadTask: [NSURLSessionUploadTask]!, downloadTask: [NSURLSessionDownloadTask]!) in
}
}
func URLSession(session: NSURLSession, downloadTask: NSURLSessionDownloadTask, didFinishDownloadingToURL location: NSURL) {
if let selectedDownloadTask = SapDownloader.getTaskByIdentifier(downloadTask.taskIdentifier) {
selectedDownloadTask.downloadTask.cancel()
self.saveDataTaskDownload(selectedDownloadTask, location: location)
selectedDownloadTask.responseBlockCompletion(error: nil, fileDestination: selectedDownloadTask.pathDestination)
if let index = Singleton.sharedInstance.downloads.indexOf(selectedDownloadTask) {
Singleton.sharedInstance.downloads.removeAtIndex(index)
}
}
}
func URLSession(session: NSURLSession, downloadTask: NSURLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
if let selectedDownloadTask = SapDownloader.getTaskByIdentifier(downloadTask.taskIdentifier) {
selectedDownloadTask.progressBlockCompletion?(bytesWritten: totalBytesWritten, bytesExpectedToWrite: totalBytesExpectedToWrite)
}
}
class func getTaskByIdentifier(identifier: Int) -> InfoDownload! {
var selectedDownload: InfoDownload! = nil
for currentDownload in Singleton.sharedInstance.downloads {
if currentDownload.downloadTask.taskIdentifier == identifier {
selectedDownload = currentDownload
return selectedDownload
}
}
return nil
}
private class func downloadFile(fileName: String, downloadSource sourceUrl: NSURL, destination: NSURL?, progressBlockCompletion progressBlock: ((bytesWritten: Int64, bytesExpectedToWrite: Int64)->())?, responseBlockCompletion responseBlock: ((error: NSError!, fileDestination: NSURL!) -> ())) -> NSURLSessionDownloadTask {
for infoDownload in Singleton.sharedInstance.downloads {
if infoDownload.downloadSource == sourceUrl {
infoDownload.isDownloading = true
infoDownload.downloadTask = Singleton.sharedInstance.session.downloadTaskWithResumeData(infoDownload.taskResumeData)
infoDownload.downloadTask.resume()
return infoDownload.downloadTask
}
}
let newDownload = InfoDownload(downloadTitle: fileName, downloadSource: sourceUrl)
newDownload.progressBlockCompletion = progressBlock
newDownload.responseBlockCompletion = responseBlock
if let errorDestination = self.setDestinationDownload(newDownload, urlDestination: destination) {
responseBlock(error: errorDestination, fileDestination: nil)
return newDownload.downloadTask
}
newDownload.downloadTask = Singleton.sharedInstance.session.downloadTaskWithURL(newDownload.downloadSource)
newDownload.downloadTask.resume()
newDownload.isDownloading = true
Singleton.sharedInstance.downloads.append(newDownload)
return newDownload.downloadTask
}
class func download(fileName: String, downloadSource sourceUrl: NSURL, progressBlockCompletion progressBlock: ((bytesWritten: Int64, bytesExpectedToWrite: Int64)->())?, responseBlockCompletion responseBlock: ((error: NSError!, fileDestination: NSURL!) -> ())) -> NSURLSessionDownloadTask {
return self.downloadFile(fileName, downloadSource: sourceUrl, destination: nil, progressBlockCompletion: progressBlock, responseBlockCompletion: responseBlock)
}
class func download(fileName: String, downloadSource sourceUrl: NSURL, pathDestination destination: NSURL,progressBlockCompletion progressBlock: ((bytesWritten: Int64, bytesExpectedToWrite: Int64)->())?, responseBlockCompletion responseBlock: ((error: NSError!, fileDestination: NSURL!) -> ())) -> NSURLSessionDownloadTask {
return self.downloadFile(fileName, downloadSource: sourceUrl, destination: destination, progressBlockCompletion: progressBlock, responseBlockCompletion: responseBlock)
}
class func pauseDownload(downloadTask task: NSURLSessionDownloadTask) {
if let selectedDownload = self.getTaskByIdentifier(task.taskIdentifier) {
selectedDownload.isDownloading = false
task.cancelByProducingResumeData{ (data: NSData?) -> Void in
selectedDownload.taskResumeData = data
selectedDownload.isDownloading = false
}
}
}
class func resumeDownload(downloadTask task: NSURLSessionDownloadTask) {
if let selectedDownload = self.getTaskByIdentifier(task.taskIdentifier) {
if selectedDownload.isDownloading == false {
selectedDownload.downloadTask = Singleton.sharedInstance.session.downloadTaskWithResumeData(selectedDownload.taskResumeData)
selectedDownload.isDownloading = true
selectedDownload.downloadTask.resume()
}
}
}
class func cancelDownload(downloadTask task: NSURLSessionDownloadTask) {
if let selectedDownload = self.getTaskByIdentifier(task.taskIdentifier) {
selectedDownload.downloadTask.cancel()
}
}
}
]]>