Атрибуты IBInspectable и IBDesignable

Xcode 6 обзавелся новыми атрибутами, позволяющими вживую отображать ваши подклассы UIView через Interface Builder, и там же определять значения свойств. Все, что для этого нужно — пометить класс как @IBDesignable, а свойства как @IBInspectable, поддерживаемые типы: Int, CGFloat, Double, String, Bool, CGPoint, CGSize, CGRect, UIColor, UIImage.

import UIKit

@IBDesignable
class FlagView: UIView {
    @IBInspectable var firstColor: UIColor = UIColor.clearColor() {
        didSet {
           setNeedsDisplay()
        }
    }

    @IBInspectable var secondColor: UIColor = UIColor.clearColor() {
        didSet {
            setNeedsDisplay()
        }
    }

    @IBInspectable var thirdColor: UIColor = UIColor.clearColor() {
        didSet {
            setNeedsDisplay()
        }
    }

    override func drawRect(rect: CGRect) {
        let context = UIGraphicsGetCurrentContext()
        let width = bounds.width
        let height = bounds.height / 3
  
        firstColor.setFill()
        CGContextFillRect(context, CGRect(x: 0, y: 0, width: width, height: height))

        secondColor.setFill()
        CGContextFillRect(context, CGRect(x: 0, y: height, width: width, height: height))

        thirdColor.setFill()
        CGContextFillRect(context, CGRect(x: 0, y: 2 * height, width: width, height: height))
    }
}
Set FlagView class Set custom attributes

Дженерики

В мире языков программирования существует парадигма обобщенной разработки. Она заключается в возможности создавать алгоритмы, одинаково работающие с разными, заранее неизвестными типами входных данных.

Для ее реализации Swift обладает дженериками, которые уже в полной мере задействованы в стандартной библиотеке. Например, массивы и словари, где тип элементов неизвестен вплоть до инициализации, построены с ними.

Можно сказать, что методы, структуры и классы, написанные с применением дженериков, способны принимать типы в качестве параметров и использовать их в коде.

import UIKit

// Generic struct
struct SuperSet<T> {
    let array = [T]()
}

// Generic function
func swapTwoValues<T>(inout a: T, inout _ b: T) {
    let temporaryA = a
    a = b
    b = temporaryA
}

let superset = SuperSet<Int>()

var a: Int = 1
var b: Int = 2

swapTwoValues(&a, &b)

var c: CGFloat = 3.0
var d: CGFloat = 4.0

swapTwoValues(&c, &d)

В дополнение, параметры дженериков допускают ограничение по наследованию от определенных классов и соответствию протоколам. Для этого предназначена конструкция where.

import UIKit

protocol Swiftable {
    func swift()
}

func addSwiftableView<T where T: UIView, T: Swiftable>(swiftableView: T) {
    // Some code...
}

Основы UIStackView

Разработчики iOS SDK не так уж часто добавляют новые компоненты пользовательского интерфейса, но в 9-й версии это случилось, был представлен UIStackView.

В прямом смысле, UIStackView не является графическим элементом и не обладает визуальным представлением, поэтому изменение таких его свойств, как backgroundColor, не имеет эффекта. Единственная задача UIStackView — организация расположения объектов. По сути, он лишь выступает контейнером для других UIView (в том числе и UIStackView, что бывает полезно), которые размещает по определенным правилам.

Основные настройки призводятся свойствами:

Любое из этих свойств можно изменять динамически, при этом объекты передвинутся автоматически.

Также, разработчики постарались, добавив в Interface Builder практически все настройки UIStackView, поэтому работать там с ним очень удобно, особенно, если необходимо привязывать значения к сайзклассам.

Создание фреймворка

Практически у каждого разработчика наступает момент, когда все наработки становится неудобно держать в виде исходных файлов. Возникает обоснованное желание скомпилировать это добро в библиотеки, динамически подключаемые по мере надобности. Начиная с Xcode 6 такое реально.

Cocoa Touch Framework creation

В отличие от статических библиотек, фреймворк кроме кода может содержать весь набор ресурсов: иконки, изображения, звуки, конфигурационные файлы и прочие данные.

Наполнение проекта фреймворка ничем не отличается от обычного. Разве что в коде нужно правильно выставлять уровни доступа элементов. Если уровень не задан явно, то примет значение по умолчанию — internal, запрещающее доступ извне модуля, поэтому весь открытый интерфейс должен быть public.

Framework sample code

После успешного построения в каталоге появится и сам фреймворк.

Framework file

Его, конечно, можно и вручную каждый раз копировать отсюда в зависимые проекты, но лучше настроить автокопирование в общую папку (где потом и забирать). Для этого понадобится скрипт постобработки.

Edit scheme menu Build post-action script

Фреймворк готов.

Чтобы им воспользоваться, в зависимых от него проектах необходимо выполнить два действия.

Первое — в General настройках добавить фреймворк в Embedded Binaries, убрав в опциях подтверждения флажок Copy items if needed для связи по ссылке, без физического копирования. При этом Linked Frameworks and Libraries заполнится автоматически.

Add framework Copy framework as link Added framework

Второе — в настройках Build Settings включить каталог фреймворка в Framework Search Paths.

Framework search path setting

Теперь фреймворк можно импортировать и использовать в коде.

Framework code usage

В случае визуальной привязки объекта к классу фреймворка, следует явно указывать модуль.

Framework storyboard usage

Конкурентное программирование

Мобильные устройства уже давно содержат многоядерные процессоры, открывающие доступ к многопоточности. Но не все так просто. Так как система сама не в состоянии верно, с учетом всех зависимостей разделить код по потокам, эта возможность вынесена в API и доступна разработчикам. Существует несколько способов распараллеливания задач.

NSThread

Низкоуровневый подход, наиболее сложный и наименее предпочтительный из всех — прямая работа с потоками через NSThread. В подавляющем большинстве случаев этот способ нецелесообразен, лучше сразу перейти к технологиям более высокого уровня, основанным на очередях.

Grand Central Dispatch

Преимущества конкурентного программирования с применением очередей огромны. Разработчик избавляется от ручного управления потоками, учета системных ресурсов, доступности, а также приоритезации и распределения задач.

Одна из таких технологий — Grand Central Dispatch, сокращенно GCD. Ее особенностью является портированная как есть Си-подобная стилистика: все реализовано на функциях со специфичными названиями и типами.

Работая с GCD, надо знать ряд вещей.

  1. Хотя очереди можно создавать, лучше воспользоваться готовыми системными.
  2. Есть два вида очередей: последовательные — задания в них выполняются по одному в порядке добавления, и конкурентные (все системные такие, кроме исключения ниже) — включающие распараллеливание.
  3. Существует единственная главная последовательная очередь, в которой должны производиться изменения, связанные с графическим интерфейсом — это нельзя забывать.
  4. Задания представляют собой блоки. Добавлять их в очередь можно либо синхронно, с ожиданием завершения блока, либо асинхронно, с возвращением контроля сразу.
import UIKit

class ViewController: UIViewController {
    var label: UILabel!
    var animator: UIDynamicAnimator!
    var gravity: UIGravityBehavior!
    var collision: UICollisionBehavior!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        label = UILabel(frame: CGRect(x: 100, y: 50, width: 100, height: 50))
        
        animator = UIDynamicAnimator(referenceView: view)
        
        gravity = UIGravityBehavior(items: [self.label])
        
        collision = UICollisionBehavior(items: [self.label])
        collision.translatesReferenceBoundsIntoBoundary = true
        
        view.addSubview(label)
        
        // Asynchronously submit long calculations to the system global concurrent queue
        dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0)) {
            let result = "\(self.complexCalculation())"
            
            // Back to the main serial queue for update GUI
            dispatch_async(dispatch_get_main_queue()) {
                self.label.text = result

                self.animator.addBehavior(self.gravity)
                self.animator.addBehavior(self.collision)
            }
        }
    }
    
    // Long running method
    func complexCalculation() -> Double {
        return M_PI
    }
}

NSOperationQueue

Другая технология — очереди операций. В отличие от GCD, она построена по модели ООП: очереди — объекты NSOperationQueue, задания — объекты NSOperation. Но в то же время GCD используется в качестве инструмента.

Relationships between operations, queues, GCD and threads

Эти очереди хорошо подходят как для простых случаев, когда операциями могут выступать банальные объектные обертки блоков.

import Foundation

let queue = NSOperationQueue()
let operation = NSBlockOperation {
    print(NSOperationQueue.currentQueue())
}

queue.addOperation(operation)

// Or equivalently
queue.addOperationWithBlock {
    print(NSOperationQueue.currentQueue())
}

Так и сложных, требующих создания дочерних классов от NSOperation. И здесь важны моменты.

  1. Стартовая логика должна находиться в методе start операций. Он будет вызываться очередью.
  2. У операций есть несколько статусных свойств:
    • ready — готовность к началу выполнения. По умолчанию — true.
    • executing — нахождение в процессе работы. По умолчанию — false.
    • finished — окончание, вследствие завершения или отмены. По умолчанию — false.
    • cancelled — отмена. По умолчанию — false.
    В подклассах необходимо вовремя и верно задавать как минимум finished и executing, потому что они участвуют в работе очередей и зависимостей. Остальные необязательно: ready является лишь дополнительным условием старта операции, основное — завершение всех зависимостей (finished у них должно быть true), если они есть; cancelled устанавливается при вызове метода cancel. Также, при изменении значений этих свойств, потребуется рассылка уведомлений методами willChangeValueForKey и didChangeValueForKey механизма KVO для соответствующих ключей, иначе очереди и зависимые операции не узнают об этом.
  3. Все операции в очереди выполняются конкурентно. Если же нужна определенная последовательность, это делается через зависимости (по-простому — ожидания одними операциями завершения других).
import UIKit

// Utility class for image data
class Picture {
    var url: NSURL
    var originalImage: UIImage!
    var filteredImage: UIImage!

    init(url: String) {
        self.url = NSURL(string: url)!
    }
}

// Base image operations class
class ImageOperation: NSOperation {
    // MARK: - Properties
    // Private executing and finished properties with KVO notifications
    private var isExecuting = false {
        willSet {
            willChangeValueForKey("isExecuting")
        }
        didSet {
            didChangeValueForKey("isExecuting")
        }
    }
    
    private var isFinished = false {
        willSet {
            willChangeValueForKey("isFinished")
        }
        didSet {
            didChangeValueForKey("isFinished")
        }
    }
    
    override var executing: Bool {
        get {
            return isExecuting
        }
        set {
            isExecuting = newValue
        }
    }

    override var finished: Bool {
        get {
            return isFinished
        }
        set {
            isFinished = newValue
        }
    }
}

class ImageDownloadOperation: ImageOperation {
    // MARK: - Properties
    private var picture: Picture!
    
    lazy var imageDownloadTask: NSURLSessionDataTask = {
        return NSURLSession.sharedSession().dataTaskWithURL(self.picture.url,
            completionHandler:
            {
                (data: NSData?,
                response: NSURLResponse?,
                error: NSError?) -> Void in
                if error == nil {
                    self.picture.originalImage = UIImage(data: data!)!
                    self.executing = false
                    self.finished = true
                } else {
                    print(error)
                    self.executing = false
                    self.finished = true
                    self.cancel()
                }
        })
    } ()
    
    // MARK: - Initialization
    init(picture: Picture) {
        super.init()
        self.picture = picture
    }
    
    override func start() {
        executing = true
        
        // If operation is cancelled, stop process
        guard !cancelled else {
            executing = false
            finished = true
            return
        }
        
        imageDownloadTask.resume()
    }
}

class ImageFilterOperation: ImageOperation {
    // MARK: - Properties
    private var picture: Picture!
    
    // MARK: - Initialization
    init(picture: Picture) {
        super.init()
        self.picture = picture
    }
    
    override func start() {
        executing = true
        
        // If operation is cancelled, stop process
        guard !cancelled else {
            executing = false
            finished = true
            return
        }
        
        // Make filtered image
        let inputImage = CIImage(image: picture.originalImage)
        
        let context = CIContext(EAGLContext: EAGLContext(API: EAGLRenderingAPI.OpenGLES2))
        let filter = CIFilter(name: "CIPhotoEffectMono")!
        filter.setValue(inputImage, forKey: kCIInputImageKey)
        let outputImage = filter.outputImage
        let outputCGImage = context.createCGImage(outputImage!, fromRect: outputImage!.extent)
        
        guard !cancelled else {
            executing = false
            finished = true
            return
        }
        
        picture.filteredImage = UIImage(CGImage: outputCGImage)
        
        executing = false
        finished = true
    }
}

class ViewController: UIViewController {
    let queue = NSOperationQueue()
    
    let picture = Picture(url: 
      "http://valery.bashkatov.org/images/concurrent-programming/sample.jpg")
    
    let originalImageView = UIImageView(frame: CGRect(x: 0, y: 0, width: 300, height: 200))
    let filteredImageView = UIImageView(frame: CGRect(x: 0, y: 200, width: 300, height: 200))
    
    override func viewDidLoad() {
        super.viewDidLoad()

        view.addSubview(originalImageView)
        view.addSubview(filteredImageView)
        
        /*
        Create operations with dependencies: originalImageDownload - originalImageShow
                                                     |
                                             originalImageFilter - filteredImageShow
        */
        let originalImageDownload = ImageDownloadOperation(picture: picture)
        
        let originalImageShow = NSBlockOperation {
            NSOperationQueue.mainQueue().addOperationWithBlock {
                self.originalImageView.image = self.picture.originalImage
            }
        }
        originalImageShow.addDependency(originalImageDownload)
        
        let originalImageFilter = ImageFilterOperation(picture: picture)
        originalImageFilter.addDependency(originalImageDownload)
        
        let filteredImageShow = NSBlockOperation {
            NSOperationQueue.mainQueue().addOperationWithBlock {
                self.filteredImageView.image = self.picture.filteredImage
            }
        }
        filteredImageShow.addDependency(originalImageFilter)
        
        queue.addOperations([originalImageDownload,
                             originalImageShow,
                             originalImageFilter,
                             filteredImageShow], waitUntilFinished: false)
    }
}

Скачать Concurrency.zip

Дополнительные материалы: