├── AsyncOperations.playground ├── Contents.swift ├── Sources │ ├── SlowSum.swift │ └── imageLoad.swift ├── contents.xcplayground ├── playground.xcworkspace │ ├── contents.xcworkspacedata │ └── xcuserdata │ │ └── tatianakornilova.xcuserdatad │ │ └── UserInterfaceState.xcuserstate └── timeline.xctimeline ├── Cancellation.playground ├── Contents.swift ├── Sources │ ├── DelayUtil.swift │ ├── SlowSum.swift │ └── StopClock.swift ├── contents.xcplayground ├── playground.xcworkspace │ ├── contents.xcworkspacedata │ └── xcuserdata │ │ └── tatianakornilova.xcuserdatad │ │ └── UserInterfaceState.xcuserstate └── timeline.xctimeline ├── CancellationFourImages.playground ├── Contents.swift ├── Sources │ ├── FourImages.swift │ ├── GradientGenerator.swift │ ├── ImageProvider.swift │ ├── Operations │ │ ├── Abstract │ │ │ ├── AsyncOperation.swift │ │ │ └── ImageTakeOperation.swift │ │ ├── Filter.swift │ │ ├── ImageLoadOperation.swift │ │ ├── ImageOutputOperation.swift │ │ └── PostProcessingImage.swift │ ├── PostProcess.swift │ ├── StopClock.swift │ └── UIImageExtensions.swift ├── contents.xcplayground ├── playground.xcworkspace │ ├── contents.xcworkspacedata │ └── xcuserdata │ │ └── tatianakornilova.xcuserdatad │ │ └── UserInterfaceState.xcuserstate └── timeline.xctimeline ├── CancellationGroup.playground ├── Contents.swift ├── Sources │ ├── DelayUtil.swift │ ├── SlowSum.swift │ └── StopClock.swift ├── contents.xcplayground ├── playground.xcworkspace │ ├── contents.xcworkspacedata │ └── xcuserdata │ │ └── tatianakornilova.xcuserdatad │ │ └── UserInterfaceState.xcuserstate └── timeline.xctimeline ├── LoadAndFilter.playground ├── Contents.swift ├── Resources │ ├── dark_road_small.jpg │ ├── train_day.jpg │ ├── train_dusk.jpg │ └── train_night.jpg ├── Sources │ ├── AsyncOperation.swift │ ├── Duration.swift │ ├── Filter.swift │ ├── GradientGenerator.swift │ ├── ImageLoad.swift │ └── UIImage+Blur.swift ├── contents.xcplayground ├── playground.xcworkspace │ ├── contents.xcworkspacedata │ └── xcuserdata │ │ └── tatianakornilova.xcuserdatad │ │ └── UserInterfaceState.xcuserstate └── timeline.xctimeline ├── Operation.playground ├── Contents.swift ├── Resources │ └── dark_road_small.jpg ├── Sources │ ├── Duration.swift │ ├── Filter.swift │ ├── GradientGenerator.swift │ └── UIImageExtensions.swift ├── contents.xcplayground ├── playground.xcworkspace │ ├── contents.xcworkspacedata │ └── xcuserdata │ │ └── tatianakornilova.xcuserdatad │ │ └── UserInterfaceState.xcuserstate └── timeline.xctimeline ├── OperationQueue.playground ├── Contents.swift ├── Resources │ ├── city.jpg │ ├── dark_road.jpg │ ├── train_day.jpg │ ├── train_dusk.jpg │ └── train_night.jpg ├── Sources │ ├── Duration.swift │ ├── Filter.swift │ ├── GradientGenerator.swift │ ├── StopClock.swift │ └── UIImageExtensions.swift ├── contents.xcplayground ├── playground.xcworkspace │ ├── contents.xcworkspacedata │ └── xcuserdata │ │ └── tatianakornilova.xcuserdatad │ │ └── UserInterfaceState.xcuserstate └── timeline.xctimeline └── OperationTableViewController ├── GCDTableViewController.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcuserdata │ │ └── tatianakornilova.xcuserdatad │ │ └── UserInterfaceState.xcuserstate └── xcuserdata │ └── tatianakornilova.xcuserdatad │ ├── xcdebugger │ └── Breakpoints_v2.xcbkptlist │ └── xcschemes │ ├── GCDTableViewController.xcscheme │ └── xcschememanagement.plist └── GCDTableViewController ├── AppDelegate.swift ├── Assets.xcassets └── AppIcon.appiconset │ └── Contents.json ├── AsyncOperation.swift ├── Base.lproj ├── LaunchScreen.storyboard └── Main.storyboard ├── Filter.swift ├── GradientGenerator.swift ├── ImageLoadOperation.swift ├── ImageOutputOperation.swift ├── ImageProvider.swift ├── ImageTableViewCell.swift ├── ImageTableViewController.swift ├── ImageTakeOperation.swift ├── Info.plist ├── PostProcess.swift ├── PostProcessingImage.swift └── UIImageExtensions.swift /AsyncOperations.playground/Contents.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import PlaygroundSupport 3 | PlaygroundPage.current.needsIndefiniteExecution = true 4 | //: ## AsyncOperation: Превращение Asynchronous Function в Operation 5 | /*: 6 | Для СИНХРОННЫХ задач мы можем создать subclass `Operation` путем переопределения метода `main()`. 7 | 8 | `AsyncOperation` - это пользовательский subclass `Operation`, который управляет изменением состояния операции автоматически. Для того, чтобы вы смогли воспользоваться этим subclass `AsyncOperation` и превратить свою АСИНХРОННУЮ функцию в `Operation`, вам нужно: 9 | 10 | 1. Создать subclass класса `AsyncOperation`. 11 | 2. Переопределить (override) `main()` и вызвать свою АСИНХРОННУЮ функцию. 12 | 3. Изменить значение свойства `state` вашего subclass `AsyncOperation` на `.finished` в асинхронном callback. 13 | 14 | - ВАЖНО: 15 | Шаг 3 этой инструкции является *чрезвычайно* важным - именно так вы сообщаете очереди операций `OperationQueue`, что запущенная операция закончилась. В противном случае она никогда не закончится. 16 | */ 17 | class AsyncOperation: Operation { 18 | 19 | // Определяем перечисление enum State со свойством keyPath 20 | enum State: String { 21 | case ready, executing, finished 22 | 23 | fileprivate var keyPath: String { 24 | return "is" + rawValue.capitalized 25 | } 26 | } 27 | 28 | // Помещаем в subclass свойство state типа State 29 | var state = State.ready { 30 | willSet { 31 | willChangeValue(forKey: newValue.keyPath) 32 | willChangeValue(forKey: state.keyPath) 33 | } 34 | didSet { 35 | didChangeValue(forKey: oldValue.keyPath) 36 | didChangeValue(forKey: state.keyPath) 37 | } 38 | } 39 | } 40 | /*: 41 | Каждое свойство состояния, наследуемое из класса `Operation` переопределяем таким образом, чтобы "подменить" их новым свойством `state`. 42 | 43 | Свойство `asynchronous` нужно установить в `true`, чтобы сообщить системе, что мы будем управлять состоянием операции вручную. 44 | 45 | Мы также вынуждены переопределить методы `start()` и `cancel()` для того, чтобы "имплантировать" новое свойство `state`. 46 | */ 47 | extension AsyncOperation { 48 | // Переопределения для Operation 49 | override var isReady: Bool { 50 | return super.isReady && state == .ready 51 | } 52 | 53 | override var isExecuting: Bool { 54 | return state == .executing 55 | } 56 | 57 | override var isFinished: Bool { 58 | return state == .finished 59 | } 60 | 61 | override var isAsynchronous: Bool { 62 | return true 63 | } 64 | 65 | override func start() { 66 | if isCancelled { 67 | state = .finished 68 | return 69 | } 70 | main() 71 | state = .executing 72 | } 73 | 74 | override func cancel() { 75 | state = .finished 76 | } 77 | 78 | } 79 | /*: 80 | Теперь "обернуть" АСИНХРОННУЮ функцию в операцию `AsyncOperation` проще простого - переопределяем метод `main()`, не забывая установить свойству `state` в замыкании completion значение `.finished`: 81 | */ 82 | class SumOperation: AsyncOperation { 83 | // Определяем: Properties, init, main 84 | let lhs: Int 85 | let rhs: Int 86 | var result: Int? 87 | 88 | init(lhs: Int, rhs: Int) { 89 | self.lhs = lhs 90 | self.rhs = rhs 91 | super.init() 92 | } 93 | 94 | override func main() { 95 | asyncAdd(lhs: lhs, rhs: rhs) { result in 96 | self.result = result 97 | self.state = .finished 98 | } 99 | } 100 | } 101 | //: Используем `OperationQueue` и массив пар чисел: 102 | let additionQueue = OperationQueue() 103 | 104 | let input = [(1,5), (5,8), (6,1), (3,9), (6,12), (1,0)] 105 | 106 | for (lhs, rhs) in input { 107 | // Создаем объект SumOperation 108 | let operation = SumOperation(lhs: lhs, rhs: rhs) 109 | operation.completionBlock = { 110 | guard let result = operation.result else { return } 111 | print("\(lhs) + \(rhs) = \(result)") 112 | } 113 | 114 | // Добавляем SumOperation на очередь additionQueue 115 | additionQueue.addOperation(operation) 116 | } 117 | 118 | 119 | class ImageLoadOperation: AsyncOperation { 120 | var url: URL? 121 | var outputImage: UIImage? 122 | 123 | init(url: URL?) { 124 | self.url = url 125 | super.init() 126 | } 127 | 128 | override func main() { 129 | if let imageURL = url { 130 | asyncImageLoad(imageURL: imageURL) { [unowned self] image in 131 | self.outputImage = image 132 | self.state = .finished 133 | } 134 | } 135 | } 136 | } 137 | 138 | //------------------------------------------------------------------------------ 139 | var view = UIView(frame: CGRect(x: 0, y: 0, width: 200, height: 200)) 140 | var eiffelImage = UIImageView(frame: CGRect(x: 0, y: 0, width: 200, height: 200)) 141 | eiffelImage.backgroundColor = UIColor.yellow 142 | eiffelImage.contentMode = .scaleAspectFit 143 | view.addSubview(eiffelImage) 144 | //------------------------------------------------------------------------------ 145 | 146 | PlaygroundPage.current.liveView = view 147 | 148 | let imageURL = URL(string:"http://www.planetware.com/photos-large/F/france-paris-eiffel-tower.jpg") 149 | 150 | // Создаем операцию загрузки изображения operationLoad 151 | 152 | let operationLoad = ImageLoadOperation(url: imageURL) 153 | 154 | operationLoad.completionBlock = { 155 | OperationQueue.main.addOperation { 156 | eiffelImage.image = operationLoad.outputImage 157 | } 158 | } 159 | 160 | // Добавляем operationLoad на очередь loadQueue 161 | 162 | let loadQueue = OperationQueue() 163 | loadQueue.addOperation(operationLoad) 164 | loadQueue.waitUntilAllOperationsAreFinished() 165 | sleep(5) 166 | operationLoad.outputImage 167 | //PlaygroundPage.current.finishExecution() 168 | -------------------------------------------------------------------------------- /AsyncOperations.playground/Sources/SlowSum.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016 Razeware LLC 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | import Foundation 24 | 25 | public func slowAdd(_ input: (Int, Int)) -> Int { 26 | sleep(1) 27 | return input.0 + input.1 28 | } 29 | 30 | public func slowAddArray(_ input: [(Int, Int)], progress: ((Double) -> (Bool))? = nil) -> [Int] { 31 | var results = [Int]() 32 | for pair in input { 33 | results.append(slowAdd(pair)) 34 | if let progress = progress { 35 | if !progress(Double(results.count) / Double(input.count)) { return results } 36 | } 37 | } 38 | return results 39 | } 40 | 41 | private let workerQueue = DispatchQueue(label: "com.raywenderlich.slowsum", attributes: DispatchQueue.Attributes.concurrent) 42 | 43 | public func asyncAdd_GCD(_ input: (Int, Int), completionQueue: DispatchQueue, completion:@escaping (Int) -> ()) { 44 | workerQueue.async { 45 | let result = slowAdd(input) 46 | completionQueue.async { 47 | completion(result) 48 | } 49 | } 50 | } 51 | 52 | 53 | public func asyncAdd(lhs: Int, rhs: Int, callback: @escaping (Int) -> ()) { 54 | let additionQueue = OperationQueue() 55 | additionQueue.addOperation { 56 | sleep(1) 57 | callback(lhs + rhs) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /AsyncOperations.playground/Sources/imageLoad.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | public func asyncImageLoad(imageURL: URL, completion:@escaping (UIImage?) -> () ) { 4 | let task = URLSession.shared.dataTask(with: imageURL){ 5 | (data, response, error) in 6 | if let data = data { 7 | completion(UIImage(data: data)) 8 | } 9 | } 10 | task.resume() 11 | } 12 | -------------------------------------------------------------------------------- /AsyncOperations.playground/contents.xcplayground: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /AsyncOperations.playground/playground.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /AsyncOperations.playground/playground.xcworkspace/xcuserdata/tatianakornilova.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BestKora/Operation_OperatioQueue/fa74adad6dc9a725203002d1cb56bb888a4890a4/AsyncOperations.playground/playground.xcworkspace/xcuserdata/tatianakornilova.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /AsyncOperations.playground/timeline.xctimeline: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 9 | 10 | 14 | 15 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /Cancellation.playground/Contents.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import UIKit 3 | import PlaygroundSupport 4 | PlaygroundPage.current.needsIndefiniteExecution = true 5 | //: ## Operation Cancellation 6 | //: Операция `ArraySumOperation` берет массив кортежей `(Int, Int)`, использует функцию медленного сложения `slowAdd()`, представленную в __Sources__ и создает массив сумм чисел, составляющих кортеж. 7 | class ArraySumOperation: Operation { 8 | let inputArray: [(Int, Int)] 9 | var outputArray = [Int]() 10 | 11 | init(input: [(Int, Int)]) { 12 | inputArray = input 13 | super.init() 14 | } 15 | 16 | override func main() { 17 | for pair in inputArray { 18 | if isCancelled { return } 19 | outputArray.append (slowAdd(pair)) 20 | } 21 | } 22 | } 23 | //: Другая операция `AnotherArraySumOperation` использует уже готовую функцию формирования из массива кортежей массива сумм чисел, составляющих кортеж 24 | class AnotherArraySumOperation: Operation { 25 | let inputArray: [(Int, Int)] 26 | var outputArray: [Int]? 27 | 28 | init(input: [(Int, Int)]) { 29 | inputArray = input 30 | super.init() 31 | } 32 | 33 | override func main() { 34 | // DONE: Fill this in 35 | outputArray = slowAddArray(inputArray) { 36 | progress in 37 | print("\(progress*100)% of the array processed") 38 | return !self.isCancelled 39 | } 40 | } 41 | } 42 | //: Входной массив 43 | let numberArray = [(1,2), (3,4), (5,6), (7,8), (9,10)] 44 | //: Операция `sumOperation` и очередь операций `queue` 45 | let sumOperation = AnotherArraySumOperation(input: numberArray) 46 | let queue = OperationQueue() 47 | //: Добавляем операцию в очередь операций и запускаем таймер. Таймер в дальнейшем поможет нам отрегулировать время "ожидания", спустя которое мы сможем проверить реакцию `sumOperation` на вызов метода `cancel()`. 48 | startClock() 49 | queue.addOperation(sumOperation) 50 | //: Немного "ожидаем" с помощью sleep(...), а затем удаляем операцию 51 | sleep(4) 52 | sumOperation.cancel() 53 | 54 | sumOperation.completionBlock = { 55 | stopClock() 56 | sumOperation.outputArray 57 | PlaygroundPage.current.finishExecution() 58 | } 59 | -------------------------------------------------------------------------------- /Cancellation.playground/Sources/DelayUtil.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016 Razeware LLC 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | import Foundation 24 | 25 | public func delay(_ delay: Double, closure: @escaping () -> ()) { 26 | DispatchQueue.main.asyncAfter( 27 | deadline: DispatchTime.now() + Double(Int64(delay * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC), 28 | execute: closure) 29 | } 30 | 31 | -------------------------------------------------------------------------------- /Cancellation.playground/Sources/SlowSum.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016 Razeware LLC 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | import Foundation 24 | 25 | public func slowAdd(_ input: (Int, Int)) -> Int { 26 | sleep(1) 27 | return input.0 + input.1 28 | } 29 | 30 | public func slowAddArray(_ input: [(Int, Int)], 31 | progress: ((Double) -> (Bool))? = nil) -> [Int] { 32 | var results = [Int]() 33 | for pair in input { 34 | results.append(slowAdd(pair)) 35 | if let progress = progress { 36 | if !progress(Double(results.count) / Double(input.count)) { return results } 37 | } 38 | } 39 | return results 40 | } 41 | 42 | private let workerQueue = DispatchQueue(label: "com.raywenderlich.slowsum", attributes: DispatchQueue.Attributes.concurrent) 43 | 44 | public func asyncAdd_GCD(_ input: (Int, Int), completionQueue: DispatchQueue, completion:@escaping (Int) -> ()) { 45 | workerQueue.async { 46 | let result = slowAdd(input) 47 | completionQueue.async { 48 | completion(result) 49 | } 50 | } 51 | } 52 | 53 | private let additionQueue = OperationQueue() 54 | public func asyncAdd_OpQ(lhs: Int, rhs: Int, callback: @escaping (Int) -> ()) { 55 | additionQueue.addOperation { 56 | sleep(1) 57 | callback(lhs + rhs) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Cancellation.playground/Sources/StopClock.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016 Razeware LLC 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | import Foundation 24 | 25 | fileprivate var lastStartTime = Date() 26 | 27 | public func startClock(){ 28 | lastStartTime = Date() 29 | } 30 | 31 | public func stopClock() -> TimeInterval { 32 | return Date().timeIntervalSince(lastStartTime) 33 | } 34 | -------------------------------------------------------------------------------- /Cancellation.playground/contents.xcplayground: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /Cancellation.playground/playground.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Cancellation.playground/playground.xcworkspace/xcuserdata/tatianakornilova.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BestKora/Operation_OperatioQueue/fa74adad6dc9a725203002d1cb56bb888a4890a4/Cancellation.playground/playground.xcworkspace/xcuserdata/tatianakornilova.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /Cancellation.playground/timeline.xctimeline: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 9 | 10 | 14 | 15 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /CancellationFourImages.playground/Contents.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import PlaygroundSupport 3 | PlaygroundPage.current.needsIndefiniteExecution = true 4 | 5 | //: 'ImageProvider' - это обычный класс, который управляет очередью операций и набор операций, работающих на единую цель, представляющую собой граф зависимых операций, исполняемых одна за другой. 6 | class ImageProvider { 7 | 8 | private var operationQueue = OperationQueue () 9 | 10 | init(imageURLString: String,completion: @escaping (UIImage?) -> ()) { 11 | operationQueue.isSuspended = true 12 | operationQueue.maxConcurrentOperationCount = 2 13 | 14 | guard let imageURL = URL(string: imageURLString) else {return} 15 | 16 | // Создаем операции 17 | let dataLoad = ImageLoadOperation(url: imageURL) 18 | let filter = Filter(image: nil) 19 | let postProcess = PostProcessImageOperation(image: nil) 20 | let output = ImageOutputOperation(){image in 21 | OperationQueue.main.addOperation {completion (image) }} 22 | 23 | let operations = [dataLoad, filter, postProcess, output] 24 | 25 | // Добавляем dependencies 26 | filter.addDependency(dataLoad) 27 | postProcess.addDependency(filter) 28 | output.addDependency(postProcess) 29 | operationQueue.addOperations(operations, waitUntilFinished: false) 30 | } 31 | 32 | func start() { 33 | operationQueue.isSuspended = false 34 | } 35 | 36 | func cancel() { 37 | operationQueue.cancelAllOperations() 38 | } 39 | 40 | func wait() { 41 | operationQueue.waitUntilAllOperationsAreFinished() 42 | } 43 | } 44 | 45 | 46 | var view = FourImages(frame: CGRect(x: 0, y: 0, width: 400, height: 400)) 47 | view.backgroundColor = UIColor.red 48 | 49 | let imageURLs = ["http://www.planetware.com/photos-large/F/france-paris-eiffel-tower.jpg", 50 | "http://adriatic-lines.com/wp-content/uploads/2015/04/canal-of-Venice.jpg", 51 | "http://bestkora.com/IosDeveloper/wp-content/uploads/2016/12/Screen-Shot-2017-01-17-at-9.33.52-PM.png", 52 | "http://www.picture-newsletter.com/arctic/arctic-12.jpg" ] 53 | var images = [UIImage] () 54 | var ip: [ImageProvider] = [] 55 | PlaygroundPage.current.liveView = view 56 | 57 | //: ### Формируем массив 'ip : [ImageProvider]' провайдеров для загрузки и фильтрации 4-х изображений 58 | 59 | for i in 0...3 { 60 | ip.append(ImageProvider(imageURLString: imageURLs[i]) { (image) in 61 | OperationQueue.main.addOperation {view.ivs[i].image = image; print (i)} 62 | }) 63 | 64 | } 65 | //: ### Стартуем загрузку изображений 66 | startClock() 67 | for ipx in ip { 68 | ipx.start() 69 | } 70 | 71 | //: ### Удаляем операции загрузки изображений спустя некоторое время 72 | sleep (6) 73 | for ipx in ip { 74 | ipx.cancel() 75 | } 76 | stopClock() 77 | 78 | //: ### Ждем окончания загрузки 79 | /*for ipx in ip { 80 | ipx.wait() 81 | } 82 | stopClock() 83 | */ 84 | //PlaygroundPage.current.finishExecution() 85 | // Остановите Playground вручную, но изображение исчезнет 86 | -------------------------------------------------------------------------------- /CancellationFourImages.playground/Sources/FourImages.swift: -------------------------------------------------------------------------------- 1 | 2 | // FourImages.swift 3 | // 4 | // 5 | 6 | import UIKit 7 | public class FourImages: UIView{ 8 | public var ivs = [UIImageView] () 9 | 10 | public override init (frame: CGRect) { 11 | super.init (frame: frame) 12 | ivs.append(UIImageView(frame: CGRect(x: 0, y: 0, width: 200, height: 200))) 13 | ivs.append(UIImageView(frame: CGRect(x: 200, y: 0, width: 200, height: 200))) 14 | ivs.append(UIImageView(frame: CGRect(x: 0, y: 200, width: 200, height: 200))) 15 | ivs.append(UIImageView(frame: CGRect(x: 200, y: 200, width: 200, height: 200))) 16 | for i in 0...3 { 17 | // ivs[i].contentMode = .scaleAspectFit 18 | self.addSubview(ivs[i]) 19 | } 20 | 21 | } 22 | 23 | public required init?(coder aDecoder: NSCoder) { 24 | super.init(coder: aDecoder) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /CancellationFourImages.playground/Sources/GradientGenerator.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | public func topAndBottomGradient(_ size: CGSize, clearLocations: [CGFloat] = [0.4, 0.6], innerIntensity: CGFloat = 0.5) -> UIImage { 4 | 5 | let context = CGContext(data: nil, width: Int(size.width), height: Int(size.height), bitsPerComponent: 8, bytesPerRow: 0, space: CGColorSpaceCreateDeviceGray(), bitmapInfo: CGImageAlphaInfo.none.rawValue) 6 | 7 | let colors = [ 8 | UIColor.white, 9 | UIColor(white: innerIntensity, alpha: 1.0), 10 | UIColor.black, 11 | UIColor(white: innerIntensity, alpha: 1.0), 12 | UIColor.white 13 | ].map { $0.cgColor } 14 | let colorLocations : [CGFloat] = [0, clearLocations[0], (clearLocations[0] + clearLocations[1]) / 2.0, clearLocations[1], 1] 15 | 16 | let gradient = CGGradient(colorsSpace: CGColorSpaceCreateDeviceGray(), colors: colors as CFArray, locations: colorLocations) 17 | 18 | let startPoint = CGPoint(x: 0, y: 0) 19 | let endPoint = CGPoint(x: 0, y: size.height) 20 | context?.drawLinearGradient(gradient!, start: startPoint, end: endPoint, options: CGGradientDrawingOptions()) 21 | 22 | let cgImage = context?.makeImage() 23 | 24 | return UIImage(cgImage: cgImage!) 25 | } 26 | 27 | 28 | -------------------------------------------------------------------------------- /CancellationFourImages.playground/Sources/ImageProvider.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImageProvider.swift 3 | // ConcurrencyDemo 4 | // 5 | // Created by Tatiana Kornilova on 2/8/17. 6 | // Copyright © 2017 Hossam Ghareeb. All rights reserved. 7 | // 8 | /*import UIKit 9 | 10 | public class ImageProvider { 11 | private var operationQueue = OperationQueue () 12 | 13 | public init(imageURLString: String,completion: @escaping (UIImage?) -> ()) { 14 | operationQueue.isSuspended = true 15 | operationQueue.maxConcurrentOperationCount = 2 16 | 17 | if let imageURL = URL(string: imageURLString){ 18 | 19 | // Создаем operations 20 | let dataLoad = ImageLoadOperation(url: imageURL) 21 | let filter = Filter(image: nil) 22 | let output = ImageOutputOperation(){image in 23 | OperationQueue.main.addOperation {completion (image) }} 24 | 25 | let operations = [dataLoad, filter, output] 26 | 27 | // Добавляем dependencies 28 | filter.addDependency(dataLoad) 29 | output.addDependency(filter) 30 | 31 | operationQueue.addOperations(operations, waitUntilFinished: false) 32 | } 33 | } 34 | public func start() { 35 | operationQueue.isSuspended = false 36 | } 37 | 38 | public func cancel() { 39 | operationQueue.cancelAllOperations() 40 | } 41 | public func wait() { 42 | operationQueue.waitUntilAllOperationsAreFinished() 43 | } 44 | } 45 | */ 46 | 47 | -------------------------------------------------------------------------------- /CancellationFourImages.playground/Sources/Operations/Abstract/AsyncOperation.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | open class AsyncOperation: Operation { 4 | public enum State: String { 5 | case ready, executing, finished 6 | 7 | fileprivate var keyPath: String { 8 | return "is" + rawValue.capitalized 9 | } 10 | } 11 | 12 | public var state = State.ready { 13 | willSet { 14 | willChangeValue(forKey: newValue.keyPath) 15 | willChangeValue(forKey: state.keyPath) 16 | } 17 | didSet { 18 | didChangeValue(forKey: oldValue.keyPath) 19 | didChangeValue(forKey: state.keyPath) 20 | } 21 | } 22 | } 23 | 24 | 25 | extension AsyncOperation { 26 | // NSOperation Overrides 27 | override open var isReady: Bool { 28 | return super.isReady && state == .ready 29 | } 30 | 31 | override open var isExecuting: Bool { 32 | return state == .executing 33 | } 34 | 35 | override open var isFinished: Bool { 36 | return state == .finished 37 | } 38 | 39 | override open var isAsynchronous: Bool { 40 | return true 41 | } 42 | 43 | override open func start() { 44 | if isCancelled { 45 | state = .finished 46 | return 47 | } 48 | 49 | main() 50 | state = .executing 51 | } 52 | 53 | open override func cancel() { 54 | state = .finished 55 | } 56 | } 57 | 58 | -------------------------------------------------------------------------------- /CancellationFourImages.playground/Sources/Operations/Abstract/ImageTakeOperation.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | protocol ImagePass { 4 | var image: UIImage? { get } 5 | } 6 | 7 | open class ImageTakeOperation: Operation { 8 | var outputImage: UIImage? 9 | private let _inputImage: UIImage? 10 | 11 | public init(image: UIImage?) { 12 | _inputImage = image 13 | super.init() 14 | } 15 | 16 | public var inputImage: UIImage? { 17 | var image: UIImage? 18 | if let inputImage = _inputImage { 19 | image = inputImage 20 | } else if let dataProvider = dependencies 21 | .filter({ $0 is ImagePass }) 22 | .first as? ImagePass { 23 | image = dataProvider.image 24 | } 25 | return image 26 | } 27 | } 28 | 29 | extension ImageTakeOperation: ImagePass { 30 | var image: UIImage? { 31 | return outputImage 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /CancellationFourImages.playground/Sources/Operations/Filter.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | public class Filter : ImageTakeOperation { 4 | 5 | override public func main() { 6 | if isCancelled { return } 7 | guard let inputImage = inputImage else { return } 8 | 9 | if isCancelled { return } 10 | let mask = topAndBottomGradient(inputImage.size) 11 | 12 | if isCancelled { return } 13 | outputImage = inputImage.applyBlurWithRadius(6, maskImage: mask) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /CancellationFourImages.playground/Sources/Operations/ImageLoadOperation.swift: -------------------------------------------------------------------------------- 1 | 2 | import UIKit 3 | 4 | public class ImageLoadOperation: AsyncOperation { 5 | private var url: URL? 6 | fileprivate var outputImage: UIImage? 7 | 8 | public init(url: URL?) { 9 | self.url = url 10 | super.init() 11 | } 12 | 13 | override public func main() { 14 | if self.isCancelled { return} 15 | guard let imageURL = url else {return} 16 | let task = URLSession.shared.dataTask(with: imageURL){(data, response, error) in 17 | if self.isCancelled { return} 18 | if let data = data, 19 | let imageData = UIImage(data: data) { 20 | if self.isCancelled { return } 21 | self.outputImage = imageData 22 | } 23 | self.state = .finished 24 | } 25 | task.resume() 26 | } 27 | } 28 | 29 | 30 | extension ImageLoadOperation: ImagePass { 31 | var image: UIImage? { 32 | return outputImage 33 | } 34 | } 35 | 36 | 37 | -------------------------------------------------------------------------------- /CancellationFourImages.playground/Sources/Operations/ImageOutputOperation.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | public class ImageOutputOperation: ImageTakeOperation { 4 | 5 | private let completion: (UIImage?) -> () 6 | 7 | public init(completion: @escaping (UIImage?) -> ()) { 8 | self.completion = completion 9 | super.init(image: nil) 10 | } 11 | 12 | override public func main() { 13 | if isCancelled { return } 14 | completion(inputImage) 15 | } 16 | } 17 | 18 | 19 | -------------------------------------------------------------------------------- /CancellationFourImages.playground/Sources/Operations/PostProcessingImage.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016 Razeware LLC 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | import Foundation 24 | 25 | public class PostProcessImageOperation: ImageTakeOperation { 26 | 27 | override public func main() { 28 | guard let inputImage = inputImage else { return } 29 | 30 | outputImage = postProcessImage(inputImage) 31 | 32 | } 33 | } 34 | 35 | -------------------------------------------------------------------------------- /CancellationFourImages.playground/Sources/PostProcess.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016 Razeware LLC 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | import CoreImage 24 | import UIKit 25 | 26 | 27 | // This is an implementation of a Core Image filter chain that returns a CGImage 28 | // backed UIImage. This is not usually the best approach - CIImage-backed 29 | // UIImages are more optimised to display straight on screen. 30 | func postProcessImage(_ image: UIImage) -> UIImage { 31 | 32 | guard let inputImage = CIImage(image: image) else { return image } 33 | 34 | // Create filter chain 35 | guard let photoFilter = CIFilter(name: "CIPhotoEffectInstant", 36 | withInputParameters: ["inputImage" : inputImage]), 37 | let photoOutput = photoFilter.outputImage, 38 | let vignetteFilter = CIFilter(name: "CIVignette", 39 | withInputParameters: ["inputRadius" : 1.75, "inputIntensity" : 1.0, "inputImage": photoOutput]), 40 | let filterOutput = vignetteFilter.outputImage else { return image } 41 | 42 | let ciContext = CIContext(options: nil) 43 | 44 | let cgImage = ciContext.createCGImage(filterOutput, from: inputImage.extent) 45 | return UIImage(cgImage: cgImage!) 46 | } 47 | 48 | 49 | -------------------------------------------------------------------------------- /CancellationFourImages.playground/Sources/StopClock.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | fileprivate var lastStartTime = Date() 4 | 5 | public func startClock(){ 6 | lastStartTime = Date() 7 | } 8 | 9 | public func stopClock() -> TimeInterval { 10 | return Date().timeIntervalSince(lastStartTime) 11 | } 12 | 13 | 14 | -------------------------------------------------------------------------------- /CancellationFourImages.playground/Sources/UIImageExtensions.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Accelerate 3 | 4 | extension UIImage { 5 | public func applyBlurWithRadius(_ blurRadius: CGFloat, maskImage: UIImage? = nil) -> UIImage? { 6 | // Check pre-conditions. 7 | if (size.width < 1 || size.height < 1) { 8 | print("*** error: invalid size: \(size.width) x \(size.height). Both dimensions must be >= 1: \(self)") 9 | return nil 10 | } 11 | if self.cgImage == nil { 12 | print("*** error: image must be backed by a CGImage: \(self)") 13 | return nil 14 | } 15 | if maskImage != nil && maskImage!.cgImage == nil { 16 | print("*** error: maskImage must be backed by a CGImage: \(String(describing: maskImage))") 17 | return nil 18 | } 19 | 20 | let __FLT_EPSILON__ = CGFloat( Float.ulpOfOne) 21 | let screenScale = UIScreen.main.scale 22 | let imageRect = CGRect(origin: CGPoint.zero, size: size) 23 | var effectImage = self 24 | 25 | let hasBlur = blurRadius > __FLT_EPSILON__ 26 | 27 | if hasBlur { 28 | func createEffectBuffer(_ context: CGContext) -> vImage_Buffer { 29 | let data = context.data 30 | let width = vImagePixelCount(context.width) 31 | let height = vImagePixelCount(context.height) 32 | let rowBytes = context.bytesPerRow 33 | 34 | return vImage_Buffer(data: data, height: height, width: width, rowBytes: rowBytes) 35 | } 36 | 37 | UIGraphicsBeginImageContextWithOptions(size, false, screenScale) 38 | let effectInContext = UIGraphicsGetCurrentContext()! 39 | 40 | effectInContext.scaleBy(x: 1.0, y: -1.0) 41 | effectInContext.translateBy(x: 0, y: -size.height) 42 | effectInContext.draw(self.cgImage!, in: imageRect) 43 | 44 | var effectInBuffer = createEffectBuffer(effectInContext) 45 | 46 | 47 | UIGraphicsBeginImageContextWithOptions(size, false, screenScale) 48 | let effectOutContext = UIGraphicsGetCurrentContext()! 49 | 50 | var effectOutBuffer = createEffectBuffer(effectOutContext) 51 | 52 | 53 | if hasBlur { 54 | // A description of how to compute the box kernel width from the Gaussian 55 | // radius (aka standard deviation) appears in the SVG spec: 56 | // http://www.w3.org/TR/SVG/filters.html#feGaussianBlurElement 57 | // 58 | // For larger values of 's' (s >= 2.0), an approximation can be used: Three 59 | // successive box-blurs build a piece-wise quadratic convolution kernel, which 60 | // approximates the Gaussian kernel to within roughly 3%. 61 | // 62 | // let d = floor(s * 3*sqrt(2*pi)/4 + 0.5) 63 | // 64 | // ... if d is odd, use three box-blurs of size 'd', centered on the output pixel. 65 | // 66 | 67 | let inputRadius = blurRadius * screenScale 68 | var radius = UInt32(floor(Double(inputRadius * 0.75 * sqrt(2.0 * .pi) + 0.5))) 69 | if radius % 2 != 1 { 70 | radius += 1 // force radius to be odd so that the three box-blur methodology works. 71 | } 72 | 73 | let imageEdgeExtendFlags = vImage_Flags(kvImageEdgeExtend) 74 | 75 | vImageBoxConvolve_ARGB8888(&effectInBuffer, &effectOutBuffer, nil, 0, 0, radius, radius, nil, imageEdgeExtendFlags) 76 | vImageBoxConvolve_ARGB8888(&effectOutBuffer, &effectInBuffer, nil, 0, 0, radius, radius, nil, imageEdgeExtendFlags) 77 | vImageBoxConvolve_ARGB8888(&effectInBuffer, &effectOutBuffer, nil, 0, 0, radius, radius, nil, imageEdgeExtendFlags) 78 | } 79 | 80 | effectImage = UIGraphicsGetImageFromCurrentImageContext()! 81 | 82 | UIGraphicsEndImageContext() 83 | UIGraphicsEndImageContext() 84 | } 85 | 86 | // Set up output context. 87 | UIGraphicsBeginImageContextWithOptions(size, false, screenScale) 88 | let outputContext = UIGraphicsGetCurrentContext() 89 | outputContext?.scaleBy(x: 1.0, y: -1.0) 90 | outputContext?.translateBy(x: 0, y: -size.height) 91 | 92 | // Draw base image. 93 | outputContext?.draw(self.cgImage!, in: imageRect) 94 | 95 | // Draw effect image. 96 | if hasBlur { 97 | outputContext?.saveGState() 98 | if let image = maskImage { 99 | //CGContextClipToMask(outputContext, imageRect, image.CGImage); 100 | let effectCGImage = effectImage.cgImage?.masking(image.cgImage!) 101 | if let effectCGImage = effectCGImage { 102 | effectImage = UIImage(cgImage: effectCGImage) 103 | } 104 | } 105 | outputContext?.draw(effectImage.cgImage!, in: imageRect) 106 | outputContext?.restoreGState() 107 | } 108 | 109 | // Output image is ready. 110 | let outputImage = UIGraphicsGetImageFromCurrentImageContext() 111 | UIGraphicsEndImageContext() 112 | 113 | return outputImage 114 | } 115 | } 116 | 117 | 118 | -------------------------------------------------------------------------------- /CancellationFourImages.playground/contents.xcplayground: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /CancellationFourImages.playground/playground.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /CancellationFourImages.playground/playground.xcworkspace/xcuserdata/tatianakornilova.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BestKora/Operation_OperatioQueue/fa74adad6dc9a725203002d1cb56bb888a4890a4/CancellationFourImages.playground/playground.xcworkspace/xcuserdata/tatianakornilova.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /CancellationFourImages.playground/timeline.xctimeline: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /CancellationGroup.playground/Contents.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | //: # Operation Cancellation 3 | //: Очень важно убедиться, что индивидуальные операции реагируют на свойство `isCancelled` и, следовательно, могут быть уничтожены. 4 | //: 5 | //: Тем не менее в дополнение к уничтожению отдельных операциям вы можете уничтожить все операции на определенной очереди операций. Это особенно полезно, если у вас есть набор операций, работающих на единую цель, при этом эта единая цель разделена на независимые операции, которые запускаются параллельно, или представляет собой граф зависимых операций, исполняемых одна за другой. 6 | //: 7 | //: В этой реализации мы достигнем того же самого, что и операция `ArraySumOperation`, которая берет массив кортежей (Int, Int), и, используя функцию медленного сложения slowAdd(), создает массив сумм чисел, составляющих кортеж. Но в нашем случае будет множество отдельных операций `SumOperation`: 8 | class SumOperation: Operation { 9 | let inputPair: (Int, Int) 10 | var output: Int? 11 | 12 | init(input: (Int, Int)) { 13 | inputPair = input 14 | super.init() 15 | } 16 | 17 | override func main() { 18 | if isCancelled { return } 19 | output = slowAdd(inputPair) 20 | } 21 | } 22 | //: Класс `GroupAdd` - это обычный класс, который управляет очередью операций и множеством операций `SumOperation` для того, чтобы посчитать сумму всех пар во входном массиве. 23 | class GroupAdd { 24 | private let queue = OperationQueue() 25 | private let appendQueue = OperationQueue() 26 | var outputArray = [(Int, Int, Int)]() 27 | 28 | init(input: [(Int, Int)]) { 29 | queue.isSuspended = true 30 | queue.maxConcurrentOperationCount = 2 31 | appendQueue.maxConcurrentOperationCount = 1 32 | generateOperations(input) 33 | } 34 | 35 | private func generateOperations(_ numberArray: [(Int, Int)]) { 36 | for pair in numberArray { 37 | let operation = SumOperation(input: pair) 38 | operation.completionBlock = { 39 | 40 | // Заметьте: добавляем в массив на очереди операций appendQueue. 41 | guard let result = operation.output else { return } 42 | self.appendQueue.addOperation { 43 | self.outputArray.append((pair.0, pair.1, result)) 44 | } 45 | } 46 | queue.addOperation(operation) 47 | } 48 | } 49 | 50 | func start() { 51 | queue.isSuspended = false 52 | } 53 | 54 | func cancel() { 55 | queue.cancelAllOperations() 56 | } 57 | 58 | func wait() { 59 | queue.waitUntilAllOperationsAreFinished() 60 | } 61 | } 62 | //: Входной массив 63 | let numberArray = [(1,2), (3,4), (5,6), (7,8), (9,10)] 64 | //: Создаем экземпляр класса `GroupAdd` 65 | let groupAdd = GroupAdd(input: numberArray) 66 | //: Стартуем группу 67 | startClock() 68 | groupAdd.start() 69 | //: __TEST:__ Попробуйте снять комментарий с `cancel()`, чтобы проверить, сработает ли этот метод корректно. После завершения операций останавливаем таймер `stopClock()` и увидим другую длину выходного массива. 70 | sleep(1) 71 | groupAdd.cancel() 72 | 73 | groupAdd.wait() 74 | stopClock() 75 | //: Проверяем результат 76 | groupAdd.outputArray 77 | -------------------------------------------------------------------------------- /CancellationGroup.playground/Sources/DelayUtil.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016 Razeware LLC 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | import Foundation 24 | 25 | public func delay(_ delay: Double, closure: @escaping () -> ()) { 26 | DispatchQueue.main.asyncAfter( 27 | deadline: DispatchTime.now() + Double(Int64(delay * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC), 28 | execute: closure) 29 | } 30 | -------------------------------------------------------------------------------- /CancellationGroup.playground/Sources/SlowSum.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Razeware LLC 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | import Foundation 24 | 25 | public func slowAdd(_ input: (Int, Int)) -> Int { 26 | sleep(1) 27 | return input.0 + input.1 28 | } 29 | 30 | public func slowAddArray(input: [(Int, Int)], progress: ((Double) -> (Bool))? = nil) -> [Int] { 31 | var results = [Int]() 32 | for pair in input { 33 | results.append(slowAdd(pair)) 34 | if let progress = progress { 35 | if !progress(Double(results.count) / Double(input.count)) { return results } 36 | } 37 | } 38 | return results 39 | } 40 | 41 | -------------------------------------------------------------------------------- /CancellationGroup.playground/Sources/StopClock.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Razeware LLC 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | import Foundation 24 | 25 | fileprivate var lastStartTime = Date() 26 | 27 | public func startClock(){ 28 | lastStartTime = Date() 29 | } 30 | 31 | public func stopClock() -> TimeInterval { 32 | return Date().timeIntervalSince(lastStartTime) 33 | } 34 | -------------------------------------------------------------------------------- /CancellationGroup.playground/contents.xcplayground: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /CancellationGroup.playground/playground.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /CancellationGroup.playground/playground.xcworkspace/xcuserdata/tatianakornilova.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BestKora/Operation_OperatioQueue/fa74adad6dc9a725203002d1cb56bb888a4890a4/CancellationGroup.playground/playground.xcworkspace/xcuserdata/tatianakornilova.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /CancellationGroup.playground/timeline.xctimeline: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 9 | 10 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /LoadAndFilter.playground/Contents.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import PlaygroundSupport 3 | PlaygroundPage.current.needsIndefiniteExecution = true 4 | //: ## Dependencies 5 | //: Операция загрузки `ImageLoadOperation` уже нам знакома: 6 | class ImageLoadOperation: AsyncOperation { 7 | var urlString: String? 8 | var outputImage: UIImage? 9 | 10 | init(urlString: String?) { 11 | self.urlString = urlString 12 | super.init() 13 | } 14 | override func main() { 15 | asyncImageLoad (urlString: self.urlString) { [unowned self] (image) in 16 | self.outputImage = image 17 | self.state = .finished 18 | } 19 | } 20 | } 21 | 22 | //: Передача данных в зависимую операцию c помощью протокола 23 | protocol ImagePass { 24 | var image: UIImage? { get } 25 | } 26 | 27 | extension ImageLoadOperation: ImagePass { 28 | var image: UIImage? { return outputImage } 29 | } 30 | //: Операция фильтрации `FilterOperation`: 31 | class FilterOperation: Operation { 32 | var outputImage: UIImage? 33 | private let _inputImage: UIImage? 34 | 35 | init(_ image: UIImage?) { 36 | _inputImage = image 37 | super.init() 38 | } 39 | 40 | var inputImage: UIImage? { 41 | // Определяем, задан ли у операции inputImage 42 | // Если НЕТ, то анализируем dependencies, 43 | // которые "подтверждают" протокол ImagePass 44 | var image: UIImage? 45 | if let inputImage = _inputImage { 46 | image = inputImage 47 | } else if let dataProvider = dependencies 48 | .filter({ $0 is ImagePass }) 49 | .first as? ImagePass { 50 | image = dataProvider.image 51 | } 52 | return image 53 | } 54 | 55 | override func main() { 56 | outputImage = filterImage(image: inputImage) 57 | } 58 | } 59 | //: Определяем две операции: 60 | let urlString = "http://www.planetware.com/photos-large/F/france-paris-eiffel-tower.jpg" 61 | let imageLoad = ImageLoadOperation(urlString:urlString ) 62 | let filter = FilterOperation(nil) 63 | //: Устанавливаем зависимость между операциями imageLoad и filter: 64 | filter.addDependency(imageLoad) 65 | //: Добавляем обе операции в очередь операций: 66 | let queue = OperationQueue() 67 | queue.addOperations([imageLoad, filter], waitUntilFinished: true) 68 | //: И смотрите, что у нас на выходе: 69 | imageLoad.outputImage 70 | filter.outputImage 71 | 72 | sleep(3) 73 | //PlaygroundPage.current.finishExecution() 74 | -------------------------------------------------------------------------------- /LoadAndFilter.playground/Resources/dark_road_small.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BestKora/Operation_OperatioQueue/fa74adad6dc9a725203002d1cb56bb888a4890a4/LoadAndFilter.playground/Resources/dark_road_small.jpg -------------------------------------------------------------------------------- /LoadAndFilter.playground/Resources/train_day.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BestKora/Operation_OperatioQueue/fa74adad6dc9a725203002d1cb56bb888a4890a4/LoadAndFilter.playground/Resources/train_day.jpg -------------------------------------------------------------------------------- /LoadAndFilter.playground/Resources/train_dusk.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BestKora/Operation_OperatioQueue/fa74adad6dc9a725203002d1cb56bb888a4890a4/LoadAndFilter.playground/Resources/train_dusk.jpg -------------------------------------------------------------------------------- /LoadAndFilter.playground/Resources/train_night.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BestKora/Operation_OperatioQueue/fa74adad6dc9a725203002d1cb56bb888a4890a4/LoadAndFilter.playground/Resources/train_night.jpg -------------------------------------------------------------------------------- /LoadAndFilter.playground/Sources/AsyncOperation.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016 Razeware LLC 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | 24 | import Foundation 25 | 26 | open class AsyncOperation: Operation { 27 | public enum State: String { 28 | case ready, executing, finished 29 | 30 | fileprivate var keyPath: String { 31 | return "is" + rawValue.capitalized 32 | } 33 | } 34 | 35 | public var state = State.ready { 36 | willSet { 37 | willChangeValue(forKey: newValue.keyPath) 38 | willChangeValue(forKey: state.keyPath) 39 | } 40 | didSet { 41 | didChangeValue(forKey: oldValue.keyPath) 42 | didChangeValue(forKey: state.keyPath) 43 | } 44 | } 45 | } 46 | 47 | 48 | extension AsyncOperation { 49 | // NSOperation Overrides 50 | override open var isReady: Bool { 51 | return super.isReady && state == .ready 52 | } 53 | 54 | override open var isExecuting: Bool { 55 | return state == .executing 56 | } 57 | 58 | override open var isFinished: Bool { 59 | return state == .finished 60 | } 61 | 62 | override open var isAsynchronous: Bool { 63 | return true 64 | } 65 | 66 | override open func start() { 67 | if isCancelled { 68 | state = .finished 69 | return 70 | } 71 | 72 | main() 73 | state = .executing 74 | } 75 | 76 | open override func cancel() { 77 | state = .finished 78 | } 79 | } 80 | 81 | -------------------------------------------------------------------------------- /LoadAndFilter.playground/Sources/Duration.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016 Razeware LLC 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | import Foundation 24 | 25 | public func duration(_ block: () -> ()) -> TimeInterval { 26 | let startTime = Date() 27 | block() 28 | return Date().timeIntervalSince(startTime) 29 | } 30 | -------------------------------------------------------------------------------- /LoadAndFilter.playground/Sources/Filter.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | public func filterImage(image: UIImage?) -> UIImage? { 4 | guard let image = image else { return .none } 5 | sleep(1) 6 | let mask = topAndBottomGradient(size: image.size) 7 | return image.applyBlur(radius: 6, maskImage: mask) 8 | } 9 | 10 | func filterAsync(image: UIImage?, callback: @escaping (UIImage?) ->()) { 11 | OperationQueue().addOperation { 12 | let result = filterImage(image: image) 13 | callback(result) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /LoadAndFilter.playground/Sources/GradientGenerator.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016 Razeware LLC 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | import UIKit 24 | 25 | func topAndBottomGradient(size: CGSize, clearLocations: [CGFloat] = [0.35, 0.65], innerIntensity: CGFloat = 0.5) -> UIImage { 26 | 27 | let context = CGContext(data: nil, width: Int(size.width), height: Int(size.height), bitsPerComponent: 8, bytesPerRow: 0, space: CGColorSpaceCreateDeviceGray(), bitmapInfo: CGImageAlphaInfo.none.rawValue) 28 | 29 | let colors = [ 30 | .white, 31 | UIColor(white: innerIntensity, alpha: 1.0), 32 | .black, 33 | UIColor(white: innerIntensity, alpha: 1.0), 34 | .white 35 | ].map { $0.cgColor } 36 | let colorLocations : [CGFloat] = [0, clearLocations[0], (clearLocations[0] + clearLocations[1]) / 2.0, clearLocations[1], 1] 37 | 38 | let gradient = CGGradient(colorsSpace: CGColorSpaceCreateDeviceGray(), colors: colors as CFArray, locations: colorLocations) 39 | 40 | let startPoint = CGPoint(x: 0, y: 0) 41 | let endPoint = CGPoint(x: 0, y: size.height) 42 | 43 | context?.drawLinearGradient(gradient!, start: startPoint, end: endPoint, options: CGGradientDrawingOptions()) 44 | let cgImage = context!.makeImage() 45 | 46 | return UIImage(cgImage: cgImage!) 47 | 48 | } 49 | 50 | -------------------------------------------------------------------------------- /LoadAndFilter.playground/Sources/ImageLoad.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | public func asyncImageLoad(urlString: String?,callback: @escaping (UIImage?) -> ()) { 4 | guard let urlString = urlString, 5 | let imageURL = URL(string: urlString) else { return callback (nil) } 6 | 7 | let task = URLSession.shared.dataTask(with: imageURL){ 8 | (data, response, error) in 9 | if let data = data { 10 | callback (UIImage(data: data)) 11 | } 12 | } 13 | task.resume() 14 | } 15 | -------------------------------------------------------------------------------- /LoadAndFilter.playground/Sources/UIImage+Blur.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016 Razeware LLC 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | import UIKit 24 | import Accelerate 25 | 26 | extension UIImage { 27 | public func applyBlur(radius: CGFloat, maskImage: UIImage? = nil) -> UIImage? { 28 | // Check pre-conditions. 29 | if (size.width < 1 || size.height < 1) { 30 | print("*** error: invalid size: \(size.width) x \(size.height). Both dimensions must be >= 1: \(self)") 31 | return nil 32 | } 33 | if self.cgImage == nil { 34 | print("*** error: image must be backed by a CGImage: \(self)") 35 | return nil 36 | } 37 | if maskImage != nil && maskImage!.cgImage == nil { 38 | print("*** error: maskImage must be backed by a CGImage: \(String(describing: maskImage))") 39 | return nil 40 | } 41 | 42 | let __FLT_EPSILON__ = CGFloat(Float.ulpOfOne) 43 | let screenScale = UIScreen.main.scale 44 | let imageRect = CGRect(origin: .zero, size: size) 45 | var effectImage = self 46 | 47 | let hasBlur = radius > __FLT_EPSILON__ 48 | 49 | if hasBlur { 50 | func createEffectBuffer(context: CGContext) -> vImage_Buffer { 51 | let data = context.data 52 | let width = vImagePixelCount(context.width) 53 | let height = vImagePixelCount(context.height) 54 | let rowBytes = context.bytesPerRow 55 | 56 | return vImage_Buffer(data: data, height: height, width: width, rowBytes: rowBytes) 57 | } 58 | 59 | UIGraphicsBeginImageContextWithOptions(size, false, screenScale) 60 | let effectInContext = UIGraphicsGetCurrentContext()! 61 | 62 | effectInContext.scaleBy(x: 1.0, y: -1.0) 63 | effectInContext.translateBy(x: 0, y: -size.height) 64 | effectInContext.draw(self.cgImage!, in: imageRect) 65 | 66 | var effectInBuffer = createEffectBuffer(context: effectInContext) 67 | 68 | 69 | 70 | UIGraphicsBeginImageContextWithOptions(size, false, screenScale) 71 | let effectOutContext = UIGraphicsGetCurrentContext()! 72 | 73 | var effectOutBuffer = createEffectBuffer(context: effectOutContext) 74 | 75 | 76 | if hasBlur { 77 | // A description of how to compute the box kernel width from the Gaussian 78 | // radius (aka standard deviation) appears in the SVG spec: 79 | // http://www.w3.org/TR/SVG/filters.html#feGaussianBlurElement 80 | // 81 | // For larger values of 's' (s >= 2.0), an approximation can be used: Three 82 | // successive box-blurs build a piece-wise quadratic convolution kernel, which 83 | // approximates the Gaussian kernel to within roughly 3%. 84 | // 85 | // let d = floor(s * 3*sqrt(2*pi)/4 + 0.5) 86 | // 87 | // ... if d is odd, use three box-blurs of size 'd', centered on the output pixel. 88 | // 89 | 90 | let inputRadius = radius * screenScale 91 | var radius = UInt32(floor(Double(inputRadius * 0.75 * sqrt(2.0 * .pi) + 0.5))) 92 | if radius % 2 != 1 { 93 | radius += 1 // force radius to be odd so that the three box-blur methodology works. 94 | } 95 | 96 | let imageEdgeExtendFlags = vImage_Flags(kvImageEdgeExtend) 97 | 98 | vImageBoxConvolve_ARGB8888(&effectInBuffer, &effectOutBuffer, nil, 0, 0, radius, radius, nil, imageEdgeExtendFlags) 99 | vImageBoxConvolve_ARGB8888(&effectOutBuffer, &effectInBuffer, nil, 0, 0, radius, radius, nil, imageEdgeExtendFlags) 100 | vImageBoxConvolve_ARGB8888(&effectInBuffer, &effectOutBuffer, nil, 0, 0, radius, radius, nil, imageEdgeExtendFlags) 101 | } 102 | 103 | effectImage = UIGraphicsGetImageFromCurrentImageContext()! 104 | 105 | UIGraphicsEndImageContext() 106 | UIGraphicsEndImageContext() 107 | } 108 | 109 | // Set up output context. 110 | UIGraphicsBeginImageContextWithOptions(size, false, screenScale) 111 | let outputContext = UIGraphicsGetCurrentContext() 112 | outputContext!.scaleBy(x: 1.0, y: -1.0) 113 | outputContext!.translateBy(x: 0, y: -size.height) 114 | 115 | // Draw base image. 116 | outputContext!.draw(self.cgImage!, in: imageRect) 117 | 118 | // Draw effect image. 119 | if hasBlur { 120 | outputContext!.saveGState() 121 | if let image = maskImage { 122 | let effectCGImage = effectImage.cgImage!.masking(image.cgImage!) 123 | if let effectCGImage = effectCGImage { 124 | effectImage = UIImage(cgImage: effectCGImage) 125 | } 126 | } 127 | outputContext!.draw(effectImage.cgImage!, in: imageRect) 128 | outputContext!.restoreGState() 129 | } 130 | 131 | // Output image is ready. 132 | let outputImage = UIGraphicsGetImageFromCurrentImageContext() 133 | UIGraphicsEndImageContext() 134 | 135 | return outputImage 136 | } 137 | } 138 | 139 | -------------------------------------------------------------------------------- /LoadAndFilter.playground/contents.xcplayground: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /LoadAndFilter.playground/playground.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /LoadAndFilter.playground/playground.xcworkspace/xcuserdata/tatianakornilova.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BestKora/Operation_OperatioQueue/fa74adad6dc9a725203002d1cb56bb888a4890a4/LoadAndFilter.playground/playground.xcworkspace/xcuserdata/tatianakornilova.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /LoadAndFilter.playground/timeline.xctimeline: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 9 | 10 | 14 | 15 | 19 | 20 | 24 | 25 | 29 | 30 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /Operation.playground/Contents.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import PlaygroundSupport 3 | PlaygroundPage.current.needsIndefiniteExecution = true 4 | //: # Operation 5 | //: `Operation` представляет собой 'задачу' 6 | //: ## простейшая `Operation` 7 | //: используется только совместно c `OperationQueue` с помощью метода `addOperation` 8 | let operation1 = { 9 | print ("Operation 1 started") 10 | print ("Operation 1 finished") 11 | } 12 | 13 | let queue = OperationQueue() 14 | queue.addOperation(operation1) 15 | //: Полноценная `Operation` может быть сконструирована как `BlockOperation` или как пользовательский subclass `Operation`. 16 | //: ## BlockOperation 17 | //: Создаем `BlockOperation` для конкатенации двух строк 18 | var result: String? 19 | 20 | let concatenationOperation = BlockOperation { 21 | result = "💧" + "☂️" 22 | sleep(2) 23 | } 24 | duration { 25 | concatenationOperation.start() 26 | } 27 | result 28 | 29 | //: Создаем `BlockOperation` с можеством блоков: 30 | let multiPrinter = BlockOperation() 31 | multiPrinter.completionBlock = { 32 | print("Finished multiPrinting!") 33 | } 34 | 35 | multiPrinter.addExecutionBlock { print("Каждый 🍎"); sleep(2) } 36 | multiPrinter.addExecutionBlock { print("Охотник 🍊"); sleep(2) } 37 | multiPrinter.addExecutionBlock { print("Желает 🍋"); sleep(2) } 38 | multiPrinter.addExecutionBlock { print("Знать 🍏"); sleep(2) } 39 | multiPrinter.addExecutionBlock { print("Где 💎"); sleep(2) } 40 | multiPrinter.addExecutionBlock { print("Сидит 🚙"); sleep(2) } 41 | multiPrinter.addExecutionBlock { print("Фазан 🍆"); sleep(2) } 42 | 43 | duration { 44 | multiPrinter.start() 45 | } 46 | //: ## Subclassing `Operation` 47 | //: Позволяет вам осуществлять большее управление над тем, что делает `Operation` 48 | let inputImage = UIImage(named: "dark_road_small.jpg") 49 | // DONE: Создайте и запустите FilterOperation 50 | 51 | class FilterOperation: Operation { 52 | var inputImage: UIImage? 53 | var outputImage: UIImage? 54 | 55 | override func main() { 56 | outputImage = filter(image: inputImage) 57 | } 58 | } 59 | 60 | let filterOp = FilterOperation() 61 | filterOp.inputImage = inputImage 62 | 63 | duration { 64 | filterOp.start() 65 | } 66 | filterOp.outputImage 67 | 68 | PlaygroundPage.current.finishExecution() 69 | -------------------------------------------------------------------------------- /Operation.playground/Resources/dark_road_small.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BestKora/Operation_OperatioQueue/fa74adad6dc9a725203002d1cb56bb888a4890a4/Operation.playground/Resources/dark_road_small.jpg -------------------------------------------------------------------------------- /Operation.playground/Sources/Duration.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016 Razeware LLC 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | import Foundation 24 | 25 | public func duration(_ block: () -> ()) -> TimeInterval { 26 | let startTime = Date() 27 | block() 28 | return Date().timeIntervalSince(startTime) 29 | } 30 | -------------------------------------------------------------------------------- /Operation.playground/Sources/Filter.swift: -------------------------------------------------------------------------------- 1 | 2 | import UIKit 3 | 4 | public func filter(image: UIImage?) -> UIImage? { 5 | guard let image = image else { return .none } 6 | sleep(1) 7 | let mask = topAndBottomGradient(size: image.size) 8 | return image.applyBlur(radius: 6, maskImage: mask) 9 | } 10 | 11 | func filterAsync(image: UIImage?, callback: @escaping (UIImage?) ->()) { 12 | OperationQueue().addOperation { 13 | let result = filter(image: image) 14 | callback(result) 15 | } 16 | } 17 | 18 | -------------------------------------------------------------------------------- /Operation.playground/Sources/GradientGenerator.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016 Razeware LLC 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | import UIKit 24 | 25 | func topAndBottomGradient(size: CGSize, clearLocations: [CGFloat] = [0.35, 0.65], innerIntensity: CGFloat = 0.5) -> UIImage { 26 | 27 | let context = CGContext(data: nil, width: Int(size.width), height: Int(size.height), bitsPerComponent: 8, bytesPerRow: 0, space: CGColorSpaceCreateDeviceGray(), bitmapInfo: CGImageAlphaInfo.none.rawValue) 28 | 29 | let colors = [ 30 | .white, 31 | UIColor(white: innerIntensity, alpha: 1.0), 32 | .black, 33 | UIColor(white: innerIntensity, alpha: 1.0), 34 | .white 35 | ].map { $0.cgColor } 36 | let colorLocations : [CGFloat] = [0, clearLocations[0], (clearLocations[0] + clearLocations[1]) / 2.0, clearLocations[1], 1] 37 | 38 | let gradient = CGGradient(colorsSpace: CGColorSpaceCreateDeviceGray(), colors: colors as CFArray, locations: colorLocations) 39 | 40 | let startPoint = CGPoint(x: 0, y: 0) 41 | let endPoint = CGPoint(x: 0, y: size.height) 42 | 43 | context?.drawLinearGradient(gradient!, start: startPoint, end: endPoint, options: CGGradientDrawingOptions()) 44 | let cgImage = context!.makeImage() 45 | 46 | return UIImage(cgImage: cgImage!) 47 | 48 | } 49 | 50 | -------------------------------------------------------------------------------- /Operation.playground/Sources/UIImageExtensions.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016 Razeware LLC 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | import UIKit 24 | import Accelerate 25 | 26 | extension UIImage { 27 | public func applyBlur(radius: CGFloat, maskImage: UIImage? = nil) -> UIImage? { 28 | // Check pre-conditions. 29 | if (size.width < 1 || size.height < 1) { 30 | print("*** error: invalid size: \(size.width) x \(size.height). Both dimensions must be >= 1: \(self)") 31 | return nil 32 | } 33 | if self.cgImage == nil { 34 | print("*** error: image must be backed by a CGImage: \(self)") 35 | return nil 36 | } 37 | if maskImage != nil && maskImage!.cgImage == nil { 38 | print("*** error: maskImage must be backed by a CGImage: \(String(describing: maskImage))") 39 | return nil 40 | } 41 | 42 | let __FLT_EPSILON__ = CGFloat( Float.ulpOfOne) 43 | let screenScale = UIScreen.main.scale 44 | let imageRect = CGRect(origin: .zero, size: size) 45 | var effectImage = self 46 | 47 | let hasBlur = radius > __FLT_EPSILON__ 48 | 49 | if hasBlur { 50 | func createEffectBuffer(context: CGContext) -> vImage_Buffer { 51 | let data = context.data 52 | let width = vImagePixelCount(context.width) 53 | let height = vImagePixelCount(context.height) 54 | let rowBytes = context.bytesPerRow 55 | 56 | return vImage_Buffer(data: data, height: height, width: width, rowBytes: rowBytes) 57 | } 58 | 59 | UIGraphicsBeginImageContextWithOptions(size, false, screenScale) 60 | let effectInContext = UIGraphicsGetCurrentContext()! 61 | 62 | effectInContext.scaleBy(x: 1.0, y: -1.0) 63 | effectInContext.translateBy(x: 0, y: -size.height) 64 | effectInContext.draw(self.cgImage!, in: imageRect) 65 | 66 | var effectInBuffer = createEffectBuffer(context: effectInContext) 67 | 68 | 69 | 70 | UIGraphicsBeginImageContextWithOptions(size, false, screenScale) 71 | let effectOutContext = UIGraphicsGetCurrentContext()! 72 | 73 | var effectOutBuffer = createEffectBuffer(context: effectOutContext) 74 | 75 | 76 | if hasBlur { 77 | // A description of how to compute the box kernel width from the Gaussian 78 | // radius (aka standard deviation) appears in the SVG spec: 79 | // http://www.w3.org/TR/SVG/filters.html#feGaussianBlurElement 80 | // 81 | // For larger values of 's' (s >= 2.0), an approximation can be used: Three 82 | // successive box-blurs build a piece-wise quadratic convolution kernel, which 83 | // approximates the Gaussian kernel to within roughly 3%. 84 | // 85 | // let d = floor(s * 3*sqrt(2*pi)/4 + 0.5) 86 | // 87 | // ... if d is odd, use three box-blurs of size 'd', centered on the output pixel. 88 | // 89 | 90 | let inputRadius = radius * screenScale 91 | var radius = UInt32(floor(Double(inputRadius * 0.75 * sqrt(2.0 * .pi) + 0.5))) 92 | if radius % 2 != 1 { 93 | radius += 1 // force radius to be odd so that the three box-blur methodology works. 94 | } 95 | 96 | let imageEdgeExtendFlags = vImage_Flags(kvImageEdgeExtend) 97 | 98 | vImageBoxConvolve_ARGB8888(&effectInBuffer, &effectOutBuffer, nil, 0, 0, radius, radius, nil, imageEdgeExtendFlags) 99 | vImageBoxConvolve_ARGB8888(&effectOutBuffer, &effectInBuffer, nil, 0, 0, radius, radius, nil, imageEdgeExtendFlags) 100 | vImageBoxConvolve_ARGB8888(&effectInBuffer, &effectOutBuffer, nil, 0, 0, radius, radius, nil, imageEdgeExtendFlags) 101 | } 102 | 103 | effectImage = UIGraphicsGetImageFromCurrentImageContext()! 104 | 105 | UIGraphicsEndImageContext() 106 | UIGraphicsEndImageContext() 107 | } 108 | 109 | // Set up output context. 110 | UIGraphicsBeginImageContextWithOptions(size, false, screenScale) 111 | let outputContext = UIGraphicsGetCurrentContext() 112 | outputContext!.scaleBy(x: 1.0, y: -1.0) 113 | outputContext!.translateBy(x: 0, y: -size.height) 114 | 115 | // Draw base image. 116 | outputContext!.draw(self.cgImage!, in: imageRect) 117 | 118 | // Draw effect image. 119 | if hasBlur { 120 | outputContext!.saveGState() 121 | if let image = maskImage { 122 | let effectCGImage = effectImage.cgImage!.masking(image.cgImage!) 123 | if let effectCGImage = effectCGImage { 124 | effectImage = UIImage(cgImage: effectCGImage) 125 | } 126 | } 127 | outputContext!.draw(effectImage.cgImage!, in: imageRect) 128 | outputContext!.restoreGState() 129 | } 130 | 131 | // Output image is ready. 132 | let outputImage = UIGraphicsGetImageFromCurrentImageContext() 133 | UIGraphicsEndImageContext() 134 | 135 | return outputImage 136 | } 137 | } 138 | 139 | -------------------------------------------------------------------------------- /Operation.playground/contents.xcplayground: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /Operation.playground/playground.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Operation.playground/playground.xcworkspace/xcuserdata/tatianakornilova.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BestKora/Operation_OperatioQueue/fa74adad6dc9a725203002d1cb56bb888a4890a4/Operation.playground/playground.xcworkspace/xcuserdata/tatianakornilova.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /Operation.playground/timeline.xctimeline: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 9 | 10 | 14 | 15 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /OperationQueue.playground/Contents.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import PlaygroundSupport 3 | PlaygroundPage.current.needsIndefiniteExecution = true 4 | //: # OperationQueue 5 | //: `OperationQueue` отвечает за планирование и запуск группы операций по умолчанию на background. 6 | //: ## Создание очереди операций 7 | //: Создание очереди операций очень просто с использованием default initializer; вы также можете установить максимальное число операций, которые будут выполняться одновременно, а также задать очередь, на которой будут выполняться операции 8 | // Создаем пустую очередь printerQueue 9 | let printerQueue = OperationQueue() 10 | 11 | //printerQueue.maxConcurrentOperationCount = 2 12 | //printerQueue.underlyingQueue = DispatchQueue(label: "...", qos: .userInitiated, attributes:.concurrent) 13 | //: ## Добавляем операции `Operations` на очередь операций 14 | /*: Операции `Operations` можно добавлять на очередь напрямую как замыкания 15 | - ВАЖНО: 16 | Добавление операций в очередь действительно "ничего не стоит"; хотя операции начинают выполняться как только окажутся в очереди, добавление их происходит полностью асинхронно. 17 | \ 18 | Вы можете это обнаружить по результатам работы функции `duration` : 19 | */ 20 | let concatenationOperation = BlockOperation { 21 | print ( "-------💧" + "☂️") 22 | sleep(2) 23 | } 24 | concatenationOperation.qualityOfService = .userInitiated 25 | 26 | // DONE: Добавляем 5 операций на очередь printerQueue 27 | 28 | duration { 29 | 30 | printerQueue.addOperation { print("Каждый 🍎"); sleep(2) } 31 | printerQueue.addOperation { print("Охотник 🍊");sleep(2) } 32 | printerQueue.addOperation { print("Желает 🍋"); sleep(2) } 33 | printerQueue.addOperation { print("Знать 🍏"); sleep(2) } 34 | printerQueue.addOperation { print("Где 💎"); sleep(2) } 35 | printerQueue.addOperation { print("Сидит 🚙"); sleep(2) } 36 | printerQueue.addOperation { print("Фазан 🍆"); sleep(2) } 37 | 38 | printerQueue.addOperation(concatenationOperation) 39 | 40 | } 41 | 42 | // DONE: Измеряем длительность всех операций 43 | duration { 44 | printerQueue.waitUntilAllOperationsAreFinished() 45 | } 46 | //: ## Фильтрация массива изображений 47 | //: Теперь более реальный пример. 48 | // Исходные изображения 49 | let images = ["city", "dark_road", "train_day", "train_dusk", "train_night"].map { UIImage(named: "\($0).jpg") } 50 | 51 | // Отфильтрованные изображения 52 | var filteredImages = [UIImage]() 53 | //: Создаем очередь filterQueue с помощью default initializer: 54 | let filterQueue = OperationQueue() 55 | //: Создаем последовательную очередь (serial queue) appendQueue для добавления элемента к массиву: 56 | let appendQueue = OperationQueue() 57 | appendQueue.maxConcurrentOperationCount = 1 58 | //: Создаем операцию фильтрации для каждого изображения, добавляя `completionBlock`: 59 | for image in images { 60 | 61 | let filterOp = FilterOperation() 62 | filterOp.inputImage = image 63 | filterOp.completionBlock = { 64 | guard let output = filterOp.outputImage else { return } 65 | appendQueue.addOperation { 66 | filteredImages.append(output) 67 | } 68 | } 69 | filterQueue.addOperation(filterOp) 70 | } 71 | //: Необходимо дождаться, пока закончатся операции на очереди прежде, чем проверять результаты 72 | // Ждем... 73 | duration { 74 | filterQueue.waitUntilAllOperationsAreFinished() 75 | } 76 | //: Проверяем отфильтрованные изображения 77 | filteredImages[0] 78 | 79 | filteredImages[1] 80 | filteredImages[4] 81 | //filterOp.outputImage 82 | PlaygroundPage.current.finishExecution() 83 | -------------------------------------------------------------------------------- /OperationQueue.playground/Resources/city.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BestKora/Operation_OperatioQueue/fa74adad6dc9a725203002d1cb56bb888a4890a4/OperationQueue.playground/Resources/city.jpg -------------------------------------------------------------------------------- /OperationQueue.playground/Resources/dark_road.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BestKora/Operation_OperatioQueue/fa74adad6dc9a725203002d1cb56bb888a4890a4/OperationQueue.playground/Resources/dark_road.jpg -------------------------------------------------------------------------------- /OperationQueue.playground/Resources/train_day.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BestKora/Operation_OperatioQueue/fa74adad6dc9a725203002d1cb56bb888a4890a4/OperationQueue.playground/Resources/train_day.jpg -------------------------------------------------------------------------------- /OperationQueue.playground/Resources/train_dusk.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BestKora/Operation_OperatioQueue/fa74adad6dc9a725203002d1cb56bb888a4890a4/OperationQueue.playground/Resources/train_dusk.jpg -------------------------------------------------------------------------------- /OperationQueue.playground/Resources/train_night.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BestKora/Operation_OperatioQueue/fa74adad6dc9a725203002d1cb56bb888a4890a4/OperationQueue.playground/Resources/train_night.jpg -------------------------------------------------------------------------------- /OperationQueue.playground/Sources/Duration.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016 Razeware LLC 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | import Foundation 24 | 25 | public func duration(_ block: () -> ()) -> TimeInterval { 26 | let startTime = Date() 27 | block() 28 | return Date().timeIntervalSince(startTime) 29 | } 30 | -------------------------------------------------------------------------------- /OperationQueue.playground/Sources/Filter.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | 4 | public class FilterOperation : Operation { 5 | public var inputImage: UIImage? 6 | public var outputImage: UIImage? 7 | 8 | override public func main() { 9 | guard let inputImage = inputImage else { return } 10 | outputImage = filter(image: inputImage) 11 | } 12 | } 13 | public func filter(image: UIImage?) -> UIImage? { 14 | guard let image = image else { return .none } 15 | sleep(1) 16 | let mask = topAndBottomGradient(size: image.size) 17 | return image.applyBlur(radius: 6, maskImage: mask) 18 | } 19 | 20 | func filterAsync(image: UIImage?, callback: @escaping (UIImage?) ->()) { 21 | OperationQueue().addOperation { 22 | let result = filter(image: image) 23 | callback(result) 24 | } 25 | } 26 | 27 | -------------------------------------------------------------------------------- /OperationQueue.playground/Sources/GradientGenerator.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016 Razeware LLC 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | import UIKit 24 | 25 | func topAndBottomGradient(size: CGSize, clearLocations: [CGFloat] = [0.35, 0.65], innerIntensity: CGFloat = 0.5) -> UIImage { 26 | 27 | let context = CGContext(data: nil, width: Int(size.width), height: Int(size.height), bitsPerComponent: 8, bytesPerRow: 0, space: CGColorSpaceCreateDeviceGray(), bitmapInfo: CGImageAlphaInfo.none.rawValue) 28 | 29 | let colors = [ 30 | .white, 31 | UIColor(white: innerIntensity, alpha: 1.0), 32 | .black, 33 | UIColor(white: innerIntensity, alpha: 1.0), 34 | .white 35 | ].map { $0.cgColor } 36 | let colorLocations : [CGFloat] = [0, clearLocations[0], (clearLocations[0] + clearLocations[1]) / 2.0, clearLocations[1], 1] 37 | 38 | let gradient = CGGradient(colorsSpace: CGColorSpaceCreateDeviceGray(), colors: colors as CFArray, locations: colorLocations) 39 | 40 | let startPoint = CGPoint(x: 0, y: 0) 41 | let endPoint = CGPoint(x: 0, y: size.height) 42 | 43 | context?.drawLinearGradient(gradient!, start: startPoint, end: endPoint, options: CGGradientDrawingOptions()) 44 | let cgImage = context!.makeImage() 45 | 46 | return UIImage(cgImage: cgImage!) 47 | 48 | } 49 | 50 | -------------------------------------------------------------------------------- /OperationQueue.playground/Sources/StopClock.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016 Razeware LLC 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | import Foundation 24 | 25 | fileprivate var lastStartTime = Date() 26 | 27 | public func startClock(){ 28 | lastStartTime = Date() 29 | } 30 | 31 | public func stopClock() -> TimeInterval { 32 | return Date().timeIntervalSince(lastStartTime) 33 | } 34 | -------------------------------------------------------------------------------- /OperationQueue.playground/Sources/UIImageExtensions.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016 Razeware LLC 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | import UIKit 24 | import Accelerate 25 | 26 | extension UIImage { 27 | public func applyBlur(radius: CGFloat, maskImage: UIImage? = nil) -> UIImage? { 28 | // Check pre-conditions. 29 | if (size.width < 1 || size.height < 1) { 30 | print("*** error: invalid size: \(size.width) x \(size.height). Both dimensions must be >= 1: \(self)") 31 | return nil 32 | } 33 | if self.cgImage == nil { 34 | print("*** error: image must be backed by a CGImage: \(self)") 35 | return nil 36 | } 37 | if maskImage != nil && maskImage!.cgImage == nil { 38 | print("*** error: maskImage must be backed by a CGImage: \(String(describing: maskImage))") 39 | return nil 40 | } 41 | 42 | let __FLT_EPSILON__ = CGFloat( Float.ulpOfOne) 43 | let screenScale = UIScreen.main.scale 44 | let imageRect = CGRect(origin: .zero, size: size) 45 | var effectImage = self 46 | 47 | let hasBlur = radius > __FLT_EPSILON__ 48 | 49 | if hasBlur { 50 | func createEffectBuffer(context: CGContext) -> vImage_Buffer { 51 | let data = context.data 52 | let width = vImagePixelCount(context.width) 53 | let height = vImagePixelCount(context.height) 54 | let rowBytes = context.bytesPerRow 55 | 56 | return vImage_Buffer(data: data, height: height, width: width, rowBytes: rowBytes) 57 | } 58 | 59 | UIGraphicsBeginImageContextWithOptions(size, false, screenScale) 60 | let effectInContext = UIGraphicsGetCurrentContext()! 61 | 62 | effectInContext.scaleBy(x: 1.0, y: -1.0) 63 | effectInContext.translateBy(x: 0, y: -size.height) 64 | effectInContext.draw(self.cgImage!, in: imageRect) 65 | 66 | var effectInBuffer = createEffectBuffer(context: effectInContext) 67 | 68 | 69 | 70 | UIGraphicsBeginImageContextWithOptions(size, false, screenScale) 71 | let effectOutContext = UIGraphicsGetCurrentContext()! 72 | 73 | var effectOutBuffer = createEffectBuffer(context: effectOutContext) 74 | 75 | 76 | if hasBlur { 77 | // A description of how to compute the box kernel width from the Gaussian 78 | // radius (aka standard deviation) appears in the SVG spec: 79 | // http://www.w3.org/TR/SVG/filters.html#feGaussianBlurElement 80 | // 81 | // For larger values of 's' (s >= 2.0), an approximation can be used: Three 82 | // successive box-blurs build a piece-wise quadratic convolution kernel, which 83 | // approximates the Gaussian kernel to within roughly 3%. 84 | // 85 | // let d = floor(s * 3*sqrt(2*pi)/4 + 0.5) 86 | // 87 | // ... if d is odd, use three box-blurs of size 'd', centered on the output pixel. 88 | // 89 | 90 | let inputRadius = radius * screenScale 91 | var radius = UInt32(floor(Double(inputRadius * 0.75 * sqrt(2.0 * .pi) + 0.5))) 92 | if radius % 2 != 1 { 93 | radius += 1 // force radius to be odd so that the three box-blur methodology works. 94 | } 95 | 96 | let imageEdgeExtendFlags = vImage_Flags(kvImageEdgeExtend) 97 | 98 | vImageBoxConvolve_ARGB8888(&effectInBuffer, &effectOutBuffer, nil, 0, 0, radius, radius, nil, imageEdgeExtendFlags) 99 | vImageBoxConvolve_ARGB8888(&effectOutBuffer, &effectInBuffer, nil, 0, 0, radius, radius, nil, imageEdgeExtendFlags) 100 | vImageBoxConvolve_ARGB8888(&effectInBuffer, &effectOutBuffer, nil, 0, 0, radius, radius, nil, imageEdgeExtendFlags) 101 | } 102 | 103 | effectImage = UIGraphicsGetImageFromCurrentImageContext()! 104 | 105 | UIGraphicsEndImageContext() 106 | UIGraphicsEndImageContext() 107 | } 108 | 109 | // Set up output context. 110 | UIGraphicsBeginImageContextWithOptions(size, false, screenScale) 111 | let outputContext = UIGraphicsGetCurrentContext() 112 | outputContext!.scaleBy(x: 1.0, y: -1.0) 113 | outputContext!.translateBy(x: 0, y: -size.height) 114 | 115 | // Draw base image. 116 | outputContext!.draw(self.cgImage!, in: imageRect) 117 | 118 | // Draw effect image. 119 | if hasBlur { 120 | outputContext!.saveGState() 121 | if let image = maskImage { 122 | let effectCGImage = effectImage.cgImage!.masking(image.cgImage!) 123 | if let effectCGImage = effectCGImage { 124 | effectImage = UIImage(cgImage: effectCGImage) 125 | } 126 | } 127 | outputContext!.draw(effectImage.cgImage!, in: imageRect) 128 | outputContext!.restoreGState() 129 | } 130 | 131 | // Output image is ready. 132 | let outputImage = UIGraphicsGetImageFromCurrentImageContext() 133 | UIGraphicsEndImageContext() 134 | 135 | return outputImage 136 | } 137 | } 138 | 139 | -------------------------------------------------------------------------------- /OperationQueue.playground/contents.xcplayground: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /OperationQueue.playground/playground.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /OperationQueue.playground/playground.xcworkspace/xcuserdata/tatianakornilova.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BestKora/Operation_OperatioQueue/fa74adad6dc9a725203002d1cb56bb888a4890a4/OperationQueue.playground/playground.xcworkspace/xcuserdata/tatianakornilova.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /OperationQueue.playground/timeline.xctimeline: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 9 | 10 | 14 | 15 | 19 | 20 | 24 | 25 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /OperationTableViewController/GCDTableViewController.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | E417AF641E324FEF00D8CF51 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E417AF631E324FEF00D8CF51 /* AppDelegate.swift */; }; 11 | E417AF691E324FEF00D8CF51 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = E417AF671E324FEF00D8CF51 /* Main.storyboard */; }; 12 | E417AF6B1E324FEF00D8CF51 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E417AF6A1E324FEF00D8CF51 /* Assets.xcassets */; }; 13 | E417AF6E1E324FEF00D8CF51 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = E417AF6C1E324FEF00D8CF51 /* LaunchScreen.storyboard */; }; 14 | E417AF761E32504500D8CF51 /* ImageTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E417AF751E32504500D8CF51 /* ImageTableViewController.swift */; }; 15 | E417AF791E32529C00D8CF51 /* ImageTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = E417AF781E32529C00D8CF51 /* ImageTableViewCell.swift */; }; 16 | E4448D8A1E4F7F7400B45C56 /* GradientGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4448D891E4F7F7400B45C56 /* GradientGenerator.swift */; }; 17 | E4448D8C1E4F7F8200B45C56 /* UIImageExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4448D8B1E4F7F8200B45C56 /* UIImageExtensions.swift */; }; 18 | E4448D8E1E4F7F8E00B45C56 /* PostProcess.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4448D8D1E4F7F8E00B45C56 /* PostProcess.swift */; }; 19 | E4448D911E4F7FD800B45C56 /* AsyncOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4448D901E4F7FD800B45C56 /* AsyncOperation.swift */; }; 20 | E4448D931E4F7FEE00B45C56 /* ImageTakeOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4448D921E4F7FEE00B45C56 /* ImageTakeOperation.swift */; }; 21 | E4448D951E4F7FFF00B45C56 /* Filter.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4448D941E4F7FFF00B45C56 /* Filter.swift */; }; 22 | E4448D971E4F800E00B45C56 /* ImageLoadOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4448D961E4F800E00B45C56 /* ImageLoadOperation.swift */; }; 23 | E4448D991E4F801E00B45C56 /* ImageOutputOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4448D981E4F801E00B45C56 /* ImageOutputOperation.swift */; }; 24 | E4448D9B1E4F802700B45C56 /* PostProcessingImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4448D9A1E4F802700B45C56 /* PostProcessingImage.swift */; }; 25 | E4448D9F1E4F806600B45C56 /* ImageProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4448D9E1E4F806600B45C56 /* ImageProvider.swift */; }; 26 | /* End PBXBuildFile section */ 27 | 28 | /* Begin PBXFileReference section */ 29 | E417AF601E324FEF00D8CF51 /* GCDTableViewController.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = GCDTableViewController.app; sourceTree = BUILT_PRODUCTS_DIR; }; 30 | E417AF631E324FEF00D8CF51 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 31 | E417AF681E324FEF00D8CF51 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 32 | E417AF6A1E324FEF00D8CF51 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 33 | E417AF6D1E324FEF00D8CF51 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 34 | E417AF6F1E324FEF00D8CF51 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 35 | E417AF751E32504500D8CF51 /* ImageTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageTableViewController.swift; sourceTree = ""; }; 36 | E417AF781E32529C00D8CF51 /* ImageTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ImageTableViewCell.swift; path = GCDTableViewController/ImageTableViewCell.swift; sourceTree = ""; }; 37 | E4448D891E4F7F7400B45C56 /* GradientGenerator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GradientGenerator.swift; sourceTree = ""; }; 38 | E4448D8B1E4F7F8200B45C56 /* UIImageExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIImageExtensions.swift; sourceTree = ""; }; 39 | E4448D8D1E4F7F8E00B45C56 /* PostProcess.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PostProcess.swift; sourceTree = ""; }; 40 | E4448D901E4F7FD800B45C56 /* AsyncOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AsyncOperation.swift; sourceTree = ""; }; 41 | E4448D921E4F7FEE00B45C56 /* ImageTakeOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageTakeOperation.swift; sourceTree = ""; }; 42 | E4448D941E4F7FFF00B45C56 /* Filter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Filter.swift; sourceTree = ""; }; 43 | E4448D961E4F800E00B45C56 /* ImageLoadOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageLoadOperation.swift; sourceTree = ""; }; 44 | E4448D981E4F801E00B45C56 /* ImageOutputOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageOutputOperation.swift; sourceTree = ""; }; 45 | E4448D9A1E4F802700B45C56 /* PostProcessingImage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PostProcessingImage.swift; sourceTree = ""; }; 46 | E4448D9E1E4F806600B45C56 /* ImageProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageProvider.swift; sourceTree = ""; }; 47 | /* End PBXFileReference section */ 48 | 49 | /* Begin PBXFrameworksBuildPhase section */ 50 | E417AF5D1E324FEF00D8CF51 /* Frameworks */ = { 51 | isa = PBXFrameworksBuildPhase; 52 | buildActionMask = 2147483647; 53 | files = ( 54 | ); 55 | runOnlyForDeploymentPostprocessing = 0; 56 | }; 57 | /* End PBXFrameworksBuildPhase section */ 58 | 59 | /* Begin PBXGroup section */ 60 | E417AF571E324FEF00D8CF51 = { 61 | isa = PBXGroup; 62 | children = ( 63 | E417AF621E324FEF00D8CF51 /* GCDTableViewController */, 64 | E417AF771E32508F00D8CF51 /* Supporting Files */, 65 | E417AF611E324FEF00D8CF51 /* Products */, 66 | E417AF781E32529C00D8CF51 /* ImageTableViewCell.swift */, 67 | ); 68 | sourceTree = ""; 69 | }; 70 | E417AF611E324FEF00D8CF51 /* Products */ = { 71 | isa = PBXGroup; 72 | children = ( 73 | E417AF601E324FEF00D8CF51 /* GCDTableViewController.app */, 74 | ); 75 | name = Products; 76 | sourceTree = ""; 77 | }; 78 | E417AF621E324FEF00D8CF51 /* GCDTableViewController */ = { 79 | isa = PBXGroup; 80 | children = ( 81 | E417AF751E32504500D8CF51 /* ImageTableViewController.swift */, 82 | E417AF671E324FEF00D8CF51 /* Main.storyboard */, 83 | E4448D9E1E4F806600B45C56 /* ImageProvider.swift */, 84 | E4448D9C1E4F803400B45C56 /* Operations */, 85 | E4448D8F1E4F7F9600B45C56 /* ImageFiltering */, 86 | ); 87 | path = GCDTableViewController; 88 | sourceTree = ""; 89 | }; 90 | E417AF771E32508F00D8CF51 /* Supporting Files */ = { 91 | isa = PBXGroup; 92 | children = ( 93 | E417AF6F1E324FEF00D8CF51 /* Info.plist */, 94 | E417AF631E324FEF00D8CF51 /* AppDelegate.swift */, 95 | E417AF6A1E324FEF00D8CF51 /* Assets.xcassets */, 96 | E417AF6C1E324FEF00D8CF51 /* LaunchScreen.storyboard */, 97 | ); 98 | name = "Supporting Files"; 99 | path = GCDTableViewController; 100 | sourceTree = ""; 101 | }; 102 | E4448D8F1E4F7F9600B45C56 /* ImageFiltering */ = { 103 | isa = PBXGroup; 104 | children = ( 105 | E4448D891E4F7F7400B45C56 /* GradientGenerator.swift */, 106 | E4448D8B1E4F7F8200B45C56 /* UIImageExtensions.swift */, 107 | E4448D8D1E4F7F8E00B45C56 /* PostProcess.swift */, 108 | ); 109 | name = ImageFiltering; 110 | sourceTree = ""; 111 | }; 112 | E4448D9C1E4F803400B45C56 /* Operations */ = { 113 | isa = PBXGroup; 114 | children = ( 115 | E4448D9D1E4F804300B45C56 /* Abstract */, 116 | E4448D961E4F800E00B45C56 /* ImageLoadOperation.swift */, 117 | E4448D941E4F7FFF00B45C56 /* Filter.swift */, 118 | E4448D981E4F801E00B45C56 /* ImageOutputOperation.swift */, 119 | E4448D9A1E4F802700B45C56 /* PostProcessingImage.swift */, 120 | ); 121 | name = Operations; 122 | sourceTree = ""; 123 | }; 124 | E4448D9D1E4F804300B45C56 /* Abstract */ = { 125 | isa = PBXGroup; 126 | children = ( 127 | E4448D901E4F7FD800B45C56 /* AsyncOperation.swift */, 128 | E4448D921E4F7FEE00B45C56 /* ImageTakeOperation.swift */, 129 | ); 130 | name = Abstract; 131 | sourceTree = ""; 132 | }; 133 | /* End PBXGroup section */ 134 | 135 | /* Begin PBXNativeTarget section */ 136 | E417AF5F1E324FEF00D8CF51 /* GCDTableViewController */ = { 137 | isa = PBXNativeTarget; 138 | buildConfigurationList = E417AF721E324FEF00D8CF51 /* Build configuration list for PBXNativeTarget "GCDTableViewController" */; 139 | buildPhases = ( 140 | E417AF5C1E324FEF00D8CF51 /* Sources */, 141 | E417AF5D1E324FEF00D8CF51 /* Frameworks */, 142 | E417AF5E1E324FEF00D8CF51 /* Resources */, 143 | ); 144 | buildRules = ( 145 | ); 146 | dependencies = ( 147 | ); 148 | name = GCDTableViewController; 149 | productName = GCDTableViewController; 150 | productReference = E417AF601E324FEF00D8CF51 /* GCDTableViewController.app */; 151 | productType = "com.apple.product-type.application"; 152 | }; 153 | /* End PBXNativeTarget section */ 154 | 155 | /* Begin PBXProject section */ 156 | E417AF581E324FEF00D8CF51 /* Project object */ = { 157 | isa = PBXProject; 158 | attributes = { 159 | LastSwiftUpdateCheck = 0820; 160 | LastUpgradeCheck = 0820; 161 | ORGANIZATIONNAME = "Tatiana Kornilova"; 162 | TargetAttributes = { 163 | E417AF5F1E324FEF00D8CF51 = { 164 | CreatedOnToolsVersion = 8.2.1; 165 | DevelopmentTeam = QLY879YR3Y; 166 | ProvisioningStyle = Automatic; 167 | }; 168 | }; 169 | }; 170 | buildConfigurationList = E417AF5B1E324FEF00D8CF51 /* Build configuration list for PBXProject "GCDTableViewController" */; 171 | compatibilityVersion = "Xcode 3.2"; 172 | developmentRegion = English; 173 | hasScannedForEncodings = 0; 174 | knownRegions = ( 175 | en, 176 | Base, 177 | ); 178 | mainGroup = E417AF571E324FEF00D8CF51; 179 | productRefGroup = E417AF611E324FEF00D8CF51 /* Products */; 180 | projectDirPath = ""; 181 | projectRoot = ""; 182 | targets = ( 183 | E417AF5F1E324FEF00D8CF51 /* GCDTableViewController */, 184 | ); 185 | }; 186 | /* End PBXProject section */ 187 | 188 | /* Begin PBXResourcesBuildPhase section */ 189 | E417AF5E1E324FEF00D8CF51 /* Resources */ = { 190 | isa = PBXResourcesBuildPhase; 191 | buildActionMask = 2147483647; 192 | files = ( 193 | E417AF6E1E324FEF00D8CF51 /* LaunchScreen.storyboard in Resources */, 194 | E417AF6B1E324FEF00D8CF51 /* Assets.xcassets in Resources */, 195 | E417AF691E324FEF00D8CF51 /* Main.storyboard in Resources */, 196 | ); 197 | runOnlyForDeploymentPostprocessing = 0; 198 | }; 199 | /* End PBXResourcesBuildPhase section */ 200 | 201 | /* Begin PBXSourcesBuildPhase section */ 202 | E417AF5C1E324FEF00D8CF51 /* Sources */ = { 203 | isa = PBXSourcesBuildPhase; 204 | buildActionMask = 2147483647; 205 | files = ( 206 | E417AF791E32529C00D8CF51 /* ImageTableViewCell.swift in Sources */, 207 | E4448D991E4F801E00B45C56 /* ImageOutputOperation.swift in Sources */, 208 | E417AF641E324FEF00D8CF51 /* AppDelegate.swift in Sources */, 209 | E4448D8C1E4F7F8200B45C56 /* UIImageExtensions.swift in Sources */, 210 | E4448D8E1E4F7F8E00B45C56 /* PostProcess.swift in Sources */, 211 | E4448D931E4F7FEE00B45C56 /* ImageTakeOperation.swift in Sources */, 212 | E417AF761E32504500D8CF51 /* ImageTableViewController.swift in Sources */, 213 | E4448D8A1E4F7F7400B45C56 /* GradientGenerator.swift in Sources */, 214 | E4448D951E4F7FFF00B45C56 /* Filter.swift in Sources */, 215 | E4448D911E4F7FD800B45C56 /* AsyncOperation.swift in Sources */, 216 | E4448D9B1E4F802700B45C56 /* PostProcessingImage.swift in Sources */, 217 | E4448D9F1E4F806600B45C56 /* ImageProvider.swift in Sources */, 218 | E4448D971E4F800E00B45C56 /* ImageLoadOperation.swift in Sources */, 219 | ); 220 | runOnlyForDeploymentPostprocessing = 0; 221 | }; 222 | /* End PBXSourcesBuildPhase section */ 223 | 224 | /* Begin PBXVariantGroup section */ 225 | E417AF671E324FEF00D8CF51 /* Main.storyboard */ = { 226 | isa = PBXVariantGroup; 227 | children = ( 228 | E417AF681E324FEF00D8CF51 /* Base */, 229 | ); 230 | name = Main.storyboard; 231 | sourceTree = ""; 232 | }; 233 | E417AF6C1E324FEF00D8CF51 /* LaunchScreen.storyboard */ = { 234 | isa = PBXVariantGroup; 235 | children = ( 236 | E417AF6D1E324FEF00D8CF51 /* Base */, 237 | ); 238 | name = LaunchScreen.storyboard; 239 | sourceTree = ""; 240 | }; 241 | /* End PBXVariantGroup section */ 242 | 243 | /* Begin XCBuildConfiguration section */ 244 | E417AF701E324FEF00D8CF51 /* Debug */ = { 245 | isa = XCBuildConfiguration; 246 | buildSettings = { 247 | ALWAYS_SEARCH_USER_PATHS = NO; 248 | CLANG_ANALYZER_NONNULL = YES; 249 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 250 | CLANG_CXX_LIBRARY = "libc++"; 251 | CLANG_ENABLE_MODULES = YES; 252 | CLANG_ENABLE_OBJC_ARC = YES; 253 | CLANG_WARN_BOOL_CONVERSION = YES; 254 | CLANG_WARN_CONSTANT_CONVERSION = YES; 255 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 256 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 257 | CLANG_WARN_EMPTY_BODY = YES; 258 | CLANG_WARN_ENUM_CONVERSION = YES; 259 | CLANG_WARN_INFINITE_RECURSION = YES; 260 | CLANG_WARN_INT_CONVERSION = YES; 261 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 262 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 263 | CLANG_WARN_UNREACHABLE_CODE = YES; 264 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 265 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 266 | COPY_PHASE_STRIP = NO; 267 | DEBUG_INFORMATION_FORMAT = dwarf; 268 | ENABLE_STRICT_OBJC_MSGSEND = YES; 269 | ENABLE_TESTABILITY = YES; 270 | GCC_C_LANGUAGE_STANDARD = gnu99; 271 | GCC_DYNAMIC_NO_PIC = NO; 272 | GCC_NO_COMMON_BLOCKS = YES; 273 | GCC_OPTIMIZATION_LEVEL = 0; 274 | GCC_PREPROCESSOR_DEFINITIONS = ( 275 | "DEBUG=1", 276 | "$(inherited)", 277 | ); 278 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 279 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 280 | GCC_WARN_UNDECLARED_SELECTOR = YES; 281 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 282 | GCC_WARN_UNUSED_FUNCTION = YES; 283 | GCC_WARN_UNUSED_VARIABLE = YES; 284 | IPHONEOS_DEPLOYMENT_TARGET = 10.2; 285 | MTL_ENABLE_DEBUG_INFO = YES; 286 | ONLY_ACTIVE_ARCH = YES; 287 | SDKROOT = iphoneos; 288 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 289 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 290 | }; 291 | name = Debug; 292 | }; 293 | E417AF711E324FEF00D8CF51 /* Release */ = { 294 | isa = XCBuildConfiguration; 295 | buildSettings = { 296 | ALWAYS_SEARCH_USER_PATHS = NO; 297 | CLANG_ANALYZER_NONNULL = YES; 298 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 299 | CLANG_CXX_LIBRARY = "libc++"; 300 | CLANG_ENABLE_MODULES = YES; 301 | CLANG_ENABLE_OBJC_ARC = YES; 302 | CLANG_WARN_BOOL_CONVERSION = YES; 303 | CLANG_WARN_CONSTANT_CONVERSION = YES; 304 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 305 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 306 | CLANG_WARN_EMPTY_BODY = YES; 307 | CLANG_WARN_ENUM_CONVERSION = YES; 308 | CLANG_WARN_INFINITE_RECURSION = YES; 309 | CLANG_WARN_INT_CONVERSION = YES; 310 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 311 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 312 | CLANG_WARN_UNREACHABLE_CODE = YES; 313 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 314 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 315 | COPY_PHASE_STRIP = NO; 316 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 317 | ENABLE_NS_ASSERTIONS = NO; 318 | ENABLE_STRICT_OBJC_MSGSEND = YES; 319 | GCC_C_LANGUAGE_STANDARD = gnu99; 320 | GCC_NO_COMMON_BLOCKS = YES; 321 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 322 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 323 | GCC_WARN_UNDECLARED_SELECTOR = YES; 324 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 325 | GCC_WARN_UNUSED_FUNCTION = YES; 326 | GCC_WARN_UNUSED_VARIABLE = YES; 327 | IPHONEOS_DEPLOYMENT_TARGET = 10.2; 328 | MTL_ENABLE_DEBUG_INFO = NO; 329 | SDKROOT = iphoneos; 330 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 331 | VALIDATE_PRODUCT = YES; 332 | }; 333 | name = Release; 334 | }; 335 | E417AF731E324FEF00D8CF51 /* Debug */ = { 336 | isa = XCBuildConfiguration; 337 | buildSettings = { 338 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 339 | DEVELOPMENT_TEAM = QLY879YR3Y; 340 | INFOPLIST_FILE = GCDTableViewController/Info.plist; 341 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 342 | PRODUCT_BUNDLE_IDENTIFIER = home.GCDTableViewController; 343 | PRODUCT_NAME = "$(TARGET_NAME)"; 344 | SWIFT_VERSION = 3.0; 345 | }; 346 | name = Debug; 347 | }; 348 | E417AF741E324FEF00D8CF51 /* Release */ = { 349 | isa = XCBuildConfiguration; 350 | buildSettings = { 351 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 352 | DEVELOPMENT_TEAM = QLY879YR3Y; 353 | INFOPLIST_FILE = GCDTableViewController/Info.plist; 354 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 355 | PRODUCT_BUNDLE_IDENTIFIER = home.GCDTableViewController; 356 | PRODUCT_NAME = "$(TARGET_NAME)"; 357 | SWIFT_VERSION = 3.0; 358 | }; 359 | name = Release; 360 | }; 361 | /* End XCBuildConfiguration section */ 362 | 363 | /* Begin XCConfigurationList section */ 364 | E417AF5B1E324FEF00D8CF51 /* Build configuration list for PBXProject "GCDTableViewController" */ = { 365 | isa = XCConfigurationList; 366 | buildConfigurations = ( 367 | E417AF701E324FEF00D8CF51 /* Debug */, 368 | E417AF711E324FEF00D8CF51 /* Release */, 369 | ); 370 | defaultConfigurationIsVisible = 0; 371 | defaultConfigurationName = Release; 372 | }; 373 | E417AF721E324FEF00D8CF51 /* Build configuration list for PBXNativeTarget "GCDTableViewController" */ = { 374 | isa = XCConfigurationList; 375 | buildConfigurations = ( 376 | E417AF731E324FEF00D8CF51 /* Debug */, 377 | E417AF741E324FEF00D8CF51 /* Release */, 378 | ); 379 | defaultConfigurationIsVisible = 0; 380 | defaultConfigurationName = Release; 381 | }; 382 | /* End XCConfigurationList section */ 383 | }; 384 | rootObject = E417AF581E324FEF00D8CF51 /* Project object */; 385 | } 386 | -------------------------------------------------------------------------------- /OperationTableViewController/GCDTableViewController.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /OperationTableViewController/GCDTableViewController.xcodeproj/project.xcworkspace/xcuserdata/tatianakornilova.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BestKora/Operation_OperatioQueue/fa74adad6dc9a725203002d1cb56bb888a4890a4/OperationTableViewController/GCDTableViewController.xcodeproj/project.xcworkspace/xcuserdata/tatianakornilova.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /OperationTableViewController/GCDTableViewController.xcodeproj/xcuserdata/tatianakornilova.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /OperationTableViewController/GCDTableViewController.xcodeproj/xcuserdata/tatianakornilova.xcuserdatad/xcschemes/GCDTableViewController.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /OperationTableViewController/GCDTableViewController.xcodeproj/xcuserdata/tatianakornilova.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | GCDTableViewController.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | E417AF5F1E324FEF00D8CF51 16 | 17 | primary 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /OperationTableViewController/GCDTableViewController/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // GCDTableViewController 4 | // 5 | // Created by Tatiana Kornilova on 1/20/17. 6 | // Copyright © 2017 Tatiana Kornilova. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 18 | // Override point for customization after application launch. 19 | return true 20 | } 21 | 22 | func applicationWillResignActive(_ application: UIApplication) { 23 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 24 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 25 | } 26 | 27 | func applicationDidEnterBackground(_ application: UIApplication) { 28 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 30 | } 31 | 32 | func applicationWillEnterForeground(_ application: UIApplication) { 33 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 34 | } 35 | 36 | func applicationDidBecomeActive(_ application: UIApplication) { 37 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 38 | } 39 | 40 | func applicationWillTerminate(_ application: UIApplication) { 41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 42 | } 43 | 44 | 45 | } 46 | 47 | -------------------------------------------------------------------------------- /OperationTableViewController/GCDTableViewController/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | } 33 | ], 34 | "info" : { 35 | "version" : 1, 36 | "author" : "xcode" 37 | } 38 | } -------------------------------------------------------------------------------- /OperationTableViewController/GCDTableViewController/AsyncOperation.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | open class AsyncOperation: Operation { 4 | public enum State: String { 5 | case ready, executing, finished 6 | 7 | fileprivate var keyPath: String { 8 | return "is" + rawValue.capitalized 9 | } 10 | } 11 | 12 | public var state = State.ready { 13 | willSet { 14 | willChangeValue(forKey: newValue.keyPath) 15 | willChangeValue(forKey: state.keyPath) 16 | } 17 | didSet { 18 | didChangeValue(forKey: oldValue.keyPath) 19 | didChangeValue(forKey: state.keyPath) 20 | } 21 | } 22 | } 23 | 24 | 25 | extension AsyncOperation { 26 | // NSOperation Overrides 27 | override open var isReady: Bool { 28 | return super.isReady && state == .ready 29 | } 30 | 31 | override open var isExecuting: Bool { 32 | return state == .executing 33 | } 34 | 35 | override open var isFinished: Bool { 36 | return state == .finished 37 | } 38 | 39 | override open var isAsynchronous: Bool { 40 | return true 41 | } 42 | 43 | override open func start() { 44 | if isCancelled { 45 | state = .finished 46 | return 47 | } 48 | 49 | main() 50 | state = .executing 51 | } 52 | 53 | open override func cancel() { 54 | super.cancel() 55 | state = .finished 56 | } 57 | } 58 | 59 | -------------------------------------------------------------------------------- /OperationTableViewController/GCDTableViewController/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 | -------------------------------------------------------------------------------- /OperationTableViewController/GCDTableViewController/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /OperationTableViewController/GCDTableViewController/Filter.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | public class Filter : ImageTakeOperation { 4 | 5 | override public func main() { 6 | if isCancelled { return } 7 | guard let inputImage = inputImage else { return } 8 | 9 | if isCancelled { return } 10 | let mask = topAndBottomGradient(inputImage.size) 11 | 12 | if isCancelled { return } 13 | outputImage = inputImage.applyBlurWithRadius(6, maskImage: mask) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /OperationTableViewController/GCDTableViewController/GradientGenerator.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016 Razeware LLC 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | import UIKit 24 | 25 | public func topAndBottomGradient(_ size: CGSize, clearLocations: [CGFloat] = [0.4, 0.6], innerIntensity: CGFloat = 0.5) -> UIImage { 26 | 27 | let context = CGContext(data: nil, width: Int(size.width), height: Int(size.height), bitsPerComponent: 8, bytesPerRow: 0, space: CGColorSpaceCreateDeviceGray(), bitmapInfo: CGImageAlphaInfo.none.rawValue) 28 | 29 | let colors = [ 30 | UIColor.white, 31 | UIColor(white: innerIntensity, alpha: 1.0), 32 | UIColor.black, 33 | UIColor(white: innerIntensity, alpha: 1.0), 34 | UIColor.white 35 | ].map { $0.cgColor } 36 | let colorLocations : [CGFloat] = [0, clearLocations[0], (clearLocations[0] + clearLocations[1]) / 2.0, clearLocations[1], 1] 37 | 38 | let gradient = CGGradient(colorsSpace: CGColorSpaceCreateDeviceGray(), colors: colors as CFArray, locations: colorLocations) 39 | 40 | let startPoint = CGPoint(x: 0, y: 0) 41 | let endPoint = CGPoint(x: 0, y: size.height) 42 | context?.drawLinearGradient(gradient!, start: startPoint, end: endPoint, options: CGGradientDrawingOptions()) 43 | 44 | let cgImage = context?.makeImage() 45 | 46 | return UIImage(cgImage: cgImage!) 47 | } 48 | -------------------------------------------------------------------------------- /OperationTableViewController/GCDTableViewController/ImageLoadOperation.swift: -------------------------------------------------------------------------------- 1 | 2 | import UIKit 3 | 4 | public class ImageLoadOperation: AsyncOperation { 5 | private var url: URL? 6 | fileprivate var outputImage: UIImage? 7 | 8 | public init(url: URL?) { 9 | self.url = url 10 | super.init() 11 | } 12 | 13 | override public func main() { 14 | if self.isCancelled { return} 15 | guard let imageURL = url else {return} 16 | let task = URLSession.shared.dataTask(with: imageURL){(data, response, error) in 17 | if self.isCancelled { return} 18 | if let data = data, 19 | let imageData = UIImage(data: data) { 20 | if self.isCancelled { return } 21 | self.outputImage = imageData 22 | } 23 | self.state = .finished 24 | } 25 | task.resume() 26 | } 27 | } 28 | 29 | 30 | extension ImageLoadOperation: ImagePass { 31 | var image: UIImage? { 32 | return outputImage 33 | } 34 | } 35 | 36 | 37 | -------------------------------------------------------------------------------- /OperationTableViewController/GCDTableViewController/ImageOutputOperation.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | public class ImageOutputOperation: ImageTakeOperation { 4 | 5 | private let completion: (UIImage?) -> () 6 | 7 | public init(completion: @escaping (UIImage?) -> ()) { 8 | self.completion = completion 9 | super.init(image: nil) 10 | } 11 | 12 | override public func main() { 13 | if isCancelled { completion(nil)} 14 | completion(inputImage) 15 | } 16 | } 17 | 18 | 19 | -------------------------------------------------------------------------------- /OperationTableViewController/GCDTableViewController/ImageProvider.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImageProvider.swift 3 | // ConcurrencyDemo 4 | // 5 | // Created by Tatiana Kornilova on 2/8/17. 6 | // 7 | 8 | import UIKit 9 | class ImageProvider { 10 | 11 | private var operationQueue = OperationQueue () 12 | let imageURLString: String 13 | 14 | init(imageURLString: String,completion: @escaping (UIImage?) -> ()) { 15 | self.imageURLString = imageURLString 16 | operationQueue.maxConcurrentOperationCount = 2 17 | 18 | guard let imageURL = URL(string: imageURLString) else {return} 19 | 20 | // Создаем операции 21 | let dataLoad = ImageLoadOperation(url: imageURL) 22 | let filter = Filter(image: nil) 23 | let output = ImageOutputOperation(completion: completion) 24 | 25 | let operations = [dataLoad, filter, output] 26 | 27 | // Добавляем dependencies 28 | filter.addDependency(dataLoad) 29 | output.addDependency(filter) 30 | 31 | operationQueue.addOperations(operations, waitUntilFinished: false) 32 | } 33 | 34 | func cancel() { 35 | operationQueue.cancelAllOperations() 36 | } 37 | 38 | } 39 | 40 | extension ImageProvider: Hashable { 41 | var hashValue: Int { 42 | return (imageURLString).hashValue 43 | } 44 | } 45 | 46 | func ==(lhs: ImageProvider, rhs: ImageProvider) -> Bool { 47 | return lhs.imageURLString == rhs.imageURLString 48 | } 49 | 50 | 51 | -------------------------------------------------------------------------------- /OperationTableViewController/GCDTableViewController/ImageTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // imageTableViewCell.swift 3 | // Smashtag 4 | // 5 | // Created by Tatiana Kornilova on 7/6/15. 6 | // Copyright (c) 2015 Stanford University. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | class ImageTableViewCell: UITableViewCell { 11 | 12 | @IBOutlet weak var tImage: UIImageView! 13 | @IBOutlet weak var spinner: UIActivityIndicatorView! 14 | 15 | // MARK: - Public API 16 | var imageURLString: String? { 17 | didSet { 18 | if imageURLString != nil { 19 | updateImageViewWithImage(nil) 20 | } 21 | } 22 | } 23 | 24 | func updateImageViewWithImage(_ image: UIImage?) { 25 | if let image = image { 26 | tImage.image = image 27 | tImage.alpha = 0 28 | UIView.animate(withDuration: 0.2, animations: { 29 | self.tImage.alpha = 1.0 30 | self.spinner.alpha = 0 31 | }, completion: { _ in 32 | self.spinner.stopAnimating() 33 | }) 34 | 35 | } else { 36 | tImage.image = nil 37 | tImage.alpha = 0 38 | spinner.alpha = 1.0 39 | spinner.startAnimating() 40 | } 41 | } 42 | } 43 | 44 | -------------------------------------------------------------------------------- /OperationTableViewController/GCDTableViewController/ImageTableViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImageTableViewController.swift 3 | // GCDTableViewController 4 | // 5 | // Created by Tatiana Kornilova on 1/20/17. 6 | // Copyright © 2017 Tatiana Kornilova. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ImageTableViewController: UITableViewController { 12 | var imageURLs: [String] = 13 | ["http://www.planetware.com/photos-large/F/france-paris-eiffel-tower.jpg", 14 | "http://adriatic-lines.com/wp-content/uploads/2015/04/canal-of-Venice.jpg", 15 | "http://bestkora.com/IosDeveloper/wp-content/uploads/2016/12/Screen-Shot-2017-01-17-at-9.33.52-PM.png", 16 | "http://www.picture-newsletter.com/arctic/arctic-02.jpg", 17 | "http://www.planetware.com/photos-large/F/france-paris-eiffel-tower.jpg", 18 | "http://www.picture-newsletter.com/arctic/arctic-16.jpg", 19 | "http://www.picture-newsletter.com/arctic/arctic-15.jpg", 20 | "http://www.picture-newsletter.com/arctic/arctic-12.jpg"] 21 | 22 | var imageProviders = Set() 23 | 24 | // MARK: - Table view data source 25 | 26 | override func tableView(_ tableView: UITableView, 27 | numberOfRowsInSection section: Int) -> Int { 28 | return imageURLs.count 29 | } 30 | 31 | override func tableView(_ tableView: UITableView, 32 | cellForRowAt indexPath: IndexPath) -> UITableViewCell { 33 | let cell = tableView.dequeueReusableCell(withIdentifier: "imageOperation", 34 | for: indexPath) 35 | 36 | if let cell = cell as? ImageTableViewCell { 37 | cell.imageURLString = imageURLs [indexPath.row] 38 | } 39 | return cell 40 | } 41 | } 42 | 43 | extension ImageTableViewController { 44 | override func tableView(_ tableView: UITableView, 45 | willDisplay cell: UITableViewCell, 46 | forRowAt indexPath: IndexPath) { 47 | guard let cell = cell as? ImageTableViewCell else { return } 48 | 49 | let imageProvider = ImageProvider(imageURLString: 50 | imageURLs[(indexPath as NSIndexPath).row]) { 51 | image in 52 | OperationQueue.main.addOperation { 53 | cell.updateImageViewWithImage(image) 54 | } 55 | } 56 | imageProviders.insert(imageProvider) 57 | } 58 | 59 | override func tableView(_ tableView: UITableView, 60 | didEndDisplaying cell: UITableViewCell, 61 | forRowAt indexPath: IndexPath) { 62 | guard let cell = cell as? ImageTableViewCell else { return } 63 | for provider in 64 | imageProviders.filter({ $0.imageURLString == cell.imageURLString }){ 65 | 66 | provider.cancel() 67 | imageProviders.remove(provider) 68 | } 69 | } 70 | } 71 | 72 | -------------------------------------------------------------------------------- /OperationTableViewController/GCDTableViewController/ImageTakeOperation.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | protocol ImagePass { 4 | var image: UIImage? { get } 5 | } 6 | 7 | open class ImageTakeOperation: Operation { 8 | var outputImage: UIImage? 9 | private let _inputImage: UIImage? 10 | 11 | public init(image: UIImage?) { 12 | _inputImage = image 13 | super.init() 14 | } 15 | 16 | public var inputImage: UIImage? { 17 | var image: UIImage? 18 | if let inputImage = _inputImage { 19 | image = inputImage 20 | } else if let dataProvider = dependencies 21 | .filter({ $0 is ImagePass }) 22 | .first as? ImagePass { 23 | image = dataProvider.image 24 | } 25 | return image 26 | } 27 | } 28 | 29 | extension ImageTakeOperation: ImagePass { 30 | var image: UIImage? { 31 | return outputImage 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /OperationTableViewController/GCDTableViewController/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | NSAppTransportSecurity 32 | 33 | NSAllowsArbitraryLoads 34 | 35 | 36 | UISupportedInterfaceOrientations 37 | 38 | UIInterfaceOrientationPortrait 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /OperationTableViewController/GCDTableViewController/PostProcess.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016 Razeware LLC 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | import CoreImage 24 | import UIKit 25 | 26 | 27 | // This is an implementation of a Core Image filter chain that returns a CGImage 28 | // backed UIImage. This is not usually the best approach - CIImage-backed 29 | // UIImages are more optimised to display straight on screen. 30 | func postProcessImage(_ image: UIImage) -> UIImage { 31 | 32 | guard let inputImage = CIImage(image: image) else { return image } 33 | 34 | // Create filter chain 35 | guard let photoFilter = CIFilter(name: "CIPhotoEffectInstant", 36 | withInputParameters: ["inputImage" : inputImage]), 37 | let photoOutput = photoFilter.outputImage, 38 | let vignetteFilter = CIFilter(name: "CIVignette", 39 | withInputParameters: ["inputRadius" : 1.75, "inputIntensity" : 1.0, "inputImage": photoOutput]), 40 | let filterOutput = vignetteFilter.outputImage else { return image } 41 | 42 | let ciContext = CIContext(options: nil) 43 | 44 | let cgImage = ciContext.createCGImage(filterOutput, from: inputImage.extent) 45 | return UIImage(cgImage: cgImage!) 46 | } 47 | -------------------------------------------------------------------------------- /OperationTableViewController/GCDTableViewController/PostProcessingImage.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016 Razeware LLC 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | import Foundation 24 | 25 | public class PostProcessImageOperation: ImageTakeOperation { 26 | 27 | override public func main() { 28 | if isCancelled { return} 29 | guard let inputImage = inputImage else { return } 30 | outputImage = postProcessImage(inputImage) 31 | } 32 | } 33 | 34 | -------------------------------------------------------------------------------- /OperationTableViewController/GCDTableViewController/UIImageExtensions.swift: -------------------------------------------------------------------------------- 1 | /* 2 | File: UIImage+ImageEffects.m 3 | Abstract: This is a category of UIImage that adds methods to apply blur and tint effects to an image. This is the code you’ll want to look out to find out how to use vImage to efficiently calculate a blur. 4 | Version: 1.0 5 | Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple 6 | Inc. ("Apple") in consideration of your agreement to the following 7 | terms, and your use, installation, modification or redistribution of 8 | this Apple software constitutes acceptance of these terms. If you do 9 | not agree with these terms, please do not use, install, modify or 10 | redistribute this Apple software. 11 | In consideration of your agreement to abide by the following terms, and 12 | subject to these terms, Apple grants you a personal, non-exclusive 13 | license, under Apple's copyrights in this original Apple software (the 14 | "Apple Software"), to use, reproduce, modify and redistribute the Apple 15 | Software, with or without modifications, in source and/or binary forms; 16 | provided that if you redistribute the Apple Software in its entirety and 17 | without modifications, you must retain this notice and the following 18 | text and disclaimers in all such redistributions of the Apple Software. 19 | Neither the name, trademarks, service marks or logos of Apple Inc. may 20 | be used to endorse or promote products derived from the Apple Software 21 | without specific prior written permission from Apple. Except as 22 | expressly stated in this notice, no other rights or licenses, express or 23 | implied, are granted by Apple herein, including but not limited to any 24 | patent rights that may be infringed by your derivative works or by other 25 | works in which the Apple Software may be incorporated. 26 | The Apple Software is provided by Apple on an "AS IS" basis. APPLE 27 | MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION 28 | THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS 29 | FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND 30 | OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. 31 | IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL 32 | OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 33 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 34 | INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, 35 | MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED 36 | AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), 37 | STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE 38 | POSSIBILITY OF SUCH DAMAGE. 39 | Copyright (C) 2013 Apple Inc. All Rights Reserved. 40 | Copyright © 2013 Apple Inc. All rights reserved. 41 | WWDC 2013 License 42 | NOTE: This Apple Software was supplied by Apple as part of a WWDC 2013 43 | Session. Please refer to the applicable WWDC 2013 Session for further 44 | information. 45 | IMPORTANT: This Apple software is supplied to you by Apple Inc. 46 | ("Apple") in consideration of your agreement to the following terms, and 47 | your use, installation, modification or redistribution of this Apple 48 | software constitutes acceptance of these terms. If you do not agree with 49 | these terms, please do not use, install, modify or redistribute this 50 | Apple software. 51 | In consideration of your agreement to abide by the following terms, and 52 | subject to these terms, Apple grants you a non-exclusive license, under 53 | Apple's copyrights in this original Apple software (the "Apple 54 | Software"), to use, reproduce, modify and redistribute the Apple 55 | Software, with or without modifications, in source and/or binary forms; 56 | provided that if you redistribute the Apple Software in its entirety and 57 | without modifications, you must retain this notice and the following 58 | text and disclaimers in all such redistributions of the Apple Software. 59 | Neither the name, trademarks, service marks or logos of Apple Inc. may 60 | be used to endorse or promote products derived from the Apple Software 61 | without specific prior written permission from Apple. Except as 62 | expressly stated in this notice, no other rights or licenses, express or 63 | implied, are granted by Apple herein, including but not limited to any 64 | patent rights that may be infringed by your derivative works or by other 65 | works in which the Apple Software may be incorporated. 66 | The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES 67 | NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE 68 | IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR 69 | A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND 70 | OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. 71 | IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL 72 | OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 73 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 74 | INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, 75 | MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED 76 | AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), 77 | STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE 78 | POSSIBILITY OF SUCH DAMAGE. 79 | EA1002 80 | 5/3/2013 81 | */ 82 | 83 | // 84 | // UIImage.swift 85 | // Today 86 | // 87 | // Created by Alexey Globchastyy on 15/09/14. 88 | // Copyright (c) 2014 Alexey Globchastyy. All rights reserved. 89 | // 90 | 91 | 92 | import UIKit 93 | import Accelerate 94 | 95 | extension UIImage { 96 | public func applyBlurWithRadius(_ blurRadius: CGFloat, maskImage: UIImage? = nil) -> UIImage? { 97 | // Check pre-conditions. 98 | if (size.width < 1 || size.height < 1) { 99 | print("*** error: invalid size: \(size.width) x \(size.height). Both dimensions must be >= 1: \(self)") 100 | return nil 101 | } 102 | if self.cgImage == nil { 103 | print("*** error: image must be backed by a CGImage: \(self)") 104 | return nil 105 | } 106 | if maskImage != nil && maskImage!.cgImage == nil { 107 | print("*** error: maskImage must be backed by a CGImage: \(String(describing: maskImage))") 108 | return nil 109 | } 110 | 111 | let __FLT_EPSILON__ = CGFloat(Float.ulpOfOne) 112 | let screenScale = UIScreen.main.scale 113 | let imageRect = CGRect(origin: CGPoint.zero, size: size) 114 | var effectImage = self 115 | 116 | let hasBlur = blurRadius > __FLT_EPSILON__ 117 | 118 | if hasBlur { 119 | func createEffectBuffer(_ context: CGContext) -> vImage_Buffer { 120 | let data = context.data 121 | let width = vImagePixelCount(context.width) 122 | let height = vImagePixelCount(context.height) 123 | let rowBytes = context.bytesPerRow 124 | 125 | return vImage_Buffer(data: data, height: height, width: width, rowBytes: rowBytes) 126 | } 127 | 128 | UIGraphicsBeginImageContextWithOptions(size, false, screenScale) 129 | let effectInContext = UIGraphicsGetCurrentContext()! 130 | 131 | effectInContext.scaleBy(x: 1.0, y: -1.0) 132 | effectInContext.translateBy(x: 0, y: -size.height) 133 | effectInContext.draw(self.cgImage!, in: imageRect) 134 | 135 | var effectInBuffer = createEffectBuffer(effectInContext) 136 | 137 | 138 | UIGraphicsBeginImageContextWithOptions(size, false, screenScale) 139 | let effectOutContext = UIGraphicsGetCurrentContext()! 140 | 141 | var effectOutBuffer = createEffectBuffer(effectOutContext) 142 | 143 | 144 | if hasBlur { 145 | // A description of how to compute the box kernel width from the Gaussian 146 | // radius (aka standard deviation) appears in the SVG spec: 147 | // http://www.w3.org/TR/SVG/filters.html#feGaussianBlurElement 148 | // 149 | // For larger values of 's' (s >= 2.0), an approximation can be used: Three 150 | // successive box-blurs build a piece-wise quadratic convolution kernel, which 151 | // approximates the Gaussian kernel to within roughly 3%. 152 | // 153 | // let d = floor(s * 3*sqrt(2*pi)/4 + 0.5) 154 | // 155 | // ... if d is odd, use three box-blurs of size 'd', centered on the output pixel. 156 | // 157 | 158 | let inputRadius = blurRadius * screenScale 159 | var radius = UInt32(floor(Double(inputRadius * 0.75 * sqrt(2.0 * .pi) + 0.5))) 160 | if radius % 2 != 1 { 161 | radius += 1 // force radius to be odd so that the three box-blur methodology works. 162 | } 163 | 164 | let imageEdgeExtendFlags = vImage_Flags(kvImageEdgeExtend) 165 | 166 | vImageBoxConvolve_ARGB8888(&effectInBuffer, &effectOutBuffer, nil, 0, 0, radius, radius, nil, imageEdgeExtendFlags) 167 | vImageBoxConvolve_ARGB8888(&effectOutBuffer, &effectInBuffer, nil, 0, 0, radius, radius, nil, imageEdgeExtendFlags) 168 | vImageBoxConvolve_ARGB8888(&effectInBuffer, &effectOutBuffer, nil, 0, 0, radius, radius, nil, imageEdgeExtendFlags) 169 | } 170 | 171 | effectImage = UIGraphicsGetImageFromCurrentImageContext()! 172 | 173 | UIGraphicsEndImageContext() 174 | UIGraphicsEndImageContext() 175 | } 176 | 177 | // Set up output context. 178 | UIGraphicsBeginImageContextWithOptions(size, false, screenScale) 179 | let outputContext = UIGraphicsGetCurrentContext() 180 | outputContext?.scaleBy(x: 1.0, y: -1.0) 181 | outputContext?.translateBy(x: 0, y: -size.height) 182 | 183 | // Draw base image. 184 | outputContext?.draw(self.cgImage!, in: imageRect) 185 | 186 | // Draw effect image. 187 | if hasBlur { 188 | outputContext?.saveGState() 189 | if let image = maskImage { 190 | //CGContextClipToMask(outputContext, imageRect, image.CGImage); 191 | let effectCGImage = effectImage.cgImage?.masking(image.cgImage!) 192 | if let effectCGImage = effectCGImage { 193 | effectImage = UIImage(cgImage: effectCGImage) 194 | } 195 | } 196 | outputContext?.draw(effectImage.cgImage!, in: imageRect) 197 | outputContext?.restoreGState() 198 | } 199 | 200 | // Output image is ready. 201 | let outputImage = UIGraphicsGetImageFromCurrentImageContext() 202 | UIGraphicsEndImageContext() 203 | 204 | return outputImage 205 | } 206 | } 207 | --------------------------------------------------------------------------------