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))
}
}
В мире языков программирования существует парадигма обобщенной разработки. Она заключается в возможности создавать алгоритмы, одинаково работающие с разными, заранее неизвестными типами входных данных.
Для ее реализации 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...
}
Разработчики iOS SDK не так уж часто добавляют новые компоненты пользовательского интерфейса, но в 9-й версии это случилось, был представлен UIStackView.
В прямом смысле, UIStackView не является графическим элементом и не обладает визуальным представлением, поэтому изменение таких его свойств, как backgroundColor, не имеет эффекта. Единственная задача UIStackView — организация расположения объектов. По сути, он лишь выступает контейнером для других UIView (в том числе и UIStackView, что бывает полезно), которые размещает по определенным правилам.
Основные настройки призводятся свойствами:
- axis — ось (направление) расположения элементов, значение UILayoutConstraintAxis. По умолчанию — Horizontal.
- alignment — выравнивание по оси, перпендикулярной axis, значение UIStackViewAlignment. По умолчанию — Fill.
- distribution — распределение объектов вдоль оси axis, значение UIStackViewDistribution. По умолчанию — Fill.
- spacing — расстояние (для некоторых distribution минимальное) между объектами. По умолчанию — 0.
Любое из этих свойств можно изменять динамически, при этом объекты передвинутся автоматически.
Также, разработчики постарались, добавив в Interface Builder практически все настройки UIStackView, поэтому работать там с ним очень удобно, особенно, если необходимо привязывать значения к сайзклассам.
Практически у каждого разработчика наступает момент, когда все наработки становится неудобно держать в виде исходных файлов. Возникает обоснованное желание скомпилировать это добро в библиотеки, динамически подключаемые по мере надобности. Начиная с Xcode 6 такое реально.
В отличие от статических библиотек, фреймворк кроме кода может содержать весь набор ресурсов: иконки, изображения, звуки, конфигурационные файлы и прочие данные.
Наполнение проекта фреймворка ничем не отличается от обычного. Разве что в коде нужно правильно выставлять уровни доступа элементов. Если уровень не задан явно, то примет значение по умолчанию — internal, запрещающее доступ извне модуля, поэтому весь открытый интерфейс должен быть public.
После успешного построения в каталоге появится и сам фреймворк.
Его, конечно, можно и вручную каждый раз копировать отсюда в зависимые проекты, но лучше настроить автокопирование в общую папку (где потом и забирать). Для этого понадобится скрипт постобработки.
Фреймворк готов.
Чтобы им воспользоваться, в зависимых от него проектах необходимо выполнить два действия.
Первое — в General настройках добавить фреймворк в Embedded Binaries, убрав в опциях подтверждения флажок Copy items if needed для связи по ссылке, без физического копирования. При этом Linked Frameworks and Libraries заполнится автоматически.
Второе — в настройках Build Settings включить каталог фреймворка в Framework Search Paths.
Теперь фреймворк можно импортировать и использовать в коде.
В случае визуальной привязки объекта к классу фреймворка, следует явно указывать модуль.
Мобильные устройства уже давно содержат многоядерные процессоры, открывающие доступ к многопоточности. Но не все так просто. Так как система сама не в состоянии верно, с учетом всех зависимостей разделить код по потокам, эта возможность вынесена в API и доступна разработчикам. Существует несколько способов распараллеливания задач.
NSThread
Низкоуровневый подход, наиболее сложный и наименее предпочтительный из всех — прямая работа с потоками через NSThread. В подавляющем большинстве случаев этот способ нецелесообразен, лучше сразу перейти к технологиям более высокого уровня, основанным на очередях.
Grand Central Dispatch
Преимущества конкурентного программирования с применением очередей огромны. Разработчик избавляется от ручного управления потоками, учета системных ресурсов, доступности, а также приоритезации и распределения задач.
Одна из таких технологий — Grand Central Dispatch, сокращенно GCD. Ее особенностью является портированная как есть Си-подобная стилистика: все реализовано на функциях со специфичными названиями и типами.
Работая с GCD, надо знать ряд вещей.
- Хотя очереди можно создавать, лучше воспользоваться готовыми системными.
- Есть два вида очередей: последовательные — задания в них выполняются по одному в порядке добавления, и конкурентные (все системные такие, кроме исключения ниже) — включающие распараллеливание.
- Существует единственная главная последовательная очередь, в которой должны производиться изменения, связанные с графическим интерфейсом — это нельзя забывать.
- Задания представляют собой блоки. Добавлять их в очередь можно либо синхронно, с ожиданием завершения блока, либо асинхронно, с возвращением контроля сразу.
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 используется в качестве инструмента.
Эти очереди хорошо подходят как для простых случаев, когда операциями могут выступать банальные объектные обертки блоков.
import Foundation
let queue = NSOperationQueue()
let operation = NSBlockOperation {
print(NSOperationQueue.currentQueue())
}
queue.addOperation(operation)
// Or equivalently
queue.addOperationWithBlock {
print(NSOperationQueue.currentQueue())
}
Так и сложных, требующих создания дочерних классов от NSOperation. И здесь важны моменты.
- Стартовая логика должна находиться в методе start операций. Он будет вызываться очередью.
- У операций есть несколько статусных свойств:
- ready — готовность к началу выполнения. По умолчанию — true.
- executing — нахождение в процессе работы. По умолчанию — false.
- finished — окончание, вследствие завершения или отмены. По умолчанию — false.
- cancelled — отмена. По умолчанию — false.
В подклассах необходимо вовремя и верно задавать как минимум finished и executing, потому что они участвуют в работе очередей и зависимостей. Остальные необязательно: ready является лишь дополнительным условием старта операции, основное — завершение всех зависимостей (finished у них должно быть true), если они есть; cancelled устанавливается при вызове метода cancel. Также, при изменении значений этих свойств, потребуется рассылка уведомлений методами willChangeValueForKey и didChangeValueForKey механизма KVO для соответствующих ключей, иначе очереди и зависимые операции не узнают об этом.
- Все операции в очереди выполняются конкурентно. Если же нужна определенная последовательность, это делается через зависимости (по-простому — ожидания одними операциями завершения других).
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
Дополнительные материалы: