                     if let diskURL = cache?.diskURLForCachedURL(url), data = NSData(contentsOfURL: diskURL) {
                         dispatch_sync(queue) {
                             if combinedTask.cancelled {
-                                notifyProgressInvalid(progress)
-                                completion(.Cancelled(url: url))
+                                tracker.notifyProgress(PTInvalidProgressInfo)
+                                tracker.notifyCompletion(.Cancelled(url: url))
                             } else {
                                 let length = Int64(data.length)
-                                progress?((length, length ,length))
-                                completion(.Success(url: url, data: data))
+                                tracker.notifyProgress((length, length ,length))
+                                tracker.notifyCompletion(.Success(url: url, data: data))
                             }
+                            self?.removeTracker(task)
                         }
                     } else {
                         dispatch_sync(queue) {
                             if !combinedTask.cancelled {
-                                combinedTask.sessionTask = taskGenerator(url: url, progress: progress,
+                                combinedTask.sessionTask = taskGenerator(url: url,
+                                    progress: { c, tr, te in
+                                        tracker.notifyProgress((c, tr, te))
+                                    },
                                     completion: { result in
                                         let _ = combinedTask // just keep task's live
                                         if case .Success(let url, let data) = result {
                                             cache?.cache(url, data: data)
                                         } else {
-                                            notifyProgressInvalid(progress)
+                                            tracker.notifyProgress(PTInvalidProgressInfo)
                                         }
-                                        completion(result)
+                                        tracker.notifyCompletion(result)
+                                        self?.removeTracker(task)
                                 })
                             } else {
-                                notifyProgressInvalid(progress)
-                                completion(.Cancelled(url: url))
+                                tracker.notifyProgress(PTInvalidProgressInfo)
+                                tracker.notifyCompletion(.Cancelled(url: url))
+                                self?.removeTracker(task)
                             }
                         }
                     }

                task = combinedTask
            }
        }
        return task
    }
    
    public func taskForURL(url: NSURL, progress: DownloadProgressHandler?, completion: DownloadCompletionHandler) -> Task {
        let sessionTask = session.downloadTaskWithURL(url)
        sessionTask.taskDelegate = TaskDelegate(url: url, progress: progress, completion: completion)
        sessionTask.resume()

    case Cancelled(url: NSURL)
}

// MARK: - Task Tracking
final public class TrackingToken: Hashable {
    public var hashValue: Int {
        return "\(unsafeAddressOf(self))".hashValue
    }
}

public func ==(lhs: TrackingToken, rhs: TrackingToken) -> Bool {
    return lhs === rhs
}

public protocol TaskTracker: class {
    var progressInfo: ProgressInfo? { get }
    
    func addTracking(progress progress: DownloadProgressHandler?, completion: DownloadCompletionHandler?) -> TrackingToken?
    func removeTracking(token: TrackingToken?)
}


// Task
public protocol Task: class {
    var cancelled: Bool { get }
    func cancel()
}


// MARK: downloader
public protocol Downloader: class {
    func download(url: NSURL, progress: DownloadProgressHandler?, completion: DownloadCompletionHandler) -> Task
    func download(url: NSURL, cache: Cache?, progress: DownloadProgressHandler?, completion: DownloadCompletionHandler) -> Task
    
    func download(url: NSURL, taskGenerator: TaskGenerator?, cache: Cache?, progress: DownloadProgressHandler?, completion: DownloadCompletionHandler) -> Task
    
    func trackerForTask(task: Task) -> TaskTracker?
    
}

public extension Downloader {
