Pull to refresh

Практический пример использования перегрузки операторов для библиотеки CoreGraphics

Reading time 4 min
Views 6.3K

Предистория


Под впечатлением статей про Sprite Kit и про GestureRecognizer возникла идея портировать простенькое приложение себе на телефон. А так как там достаточно часто используются структуры CGPoint, то озадачился изучением перегрузки операторов в Swift и подсмотрел тут.

Реализация


В несколько шагов создал себе некоторую «библиотеку» операторов перегрузки для удобной работы с выражениями для структур CGPoint. Заодно на каждом шаге потестировал тестирование в XCode.

Фактически реализована полная алгебра операций для линейного пространства векторов (структуры CGPoint), скаляров (CGFloat) и частично операторов (CGAffineTransform).

Так как, данные функции перегрузки операторов описаны глобально, то функционал добавляется в любой проект легко — через добавление исходника.

Конечно, примеров использования перегрузки на вымышленных ситуациях достаточно много, но данная «библиотека операторов» возможно окажется:

  1. чуть более конкретным примером,
  2. готовым решением, дающим экономии пары часов рутинного набора и отладки простейшего кода (листинг исходника целиком приведен в конце).

Шаг за шагом


Шаг 1: операторы сложения/вычитания векторов
Стоит обратить внимание, как во втором случае с помощью prefix указывается на унарность оператора-префикса.

func +(left: CGPoint, right: CGPoint)->CGPoint{
	return CGPoint(x: left.x+right.x, y: left.y+right.y)
}
prefix func -(right: CGPoint)->CGPoint{
	return CGPoint(x: -right.x, y: -right.y)
}
func -(left: CGPoint, right: CGPoint)->CGPoint{
	return left + (-right)
}


Шаг 2: умножение и деление вектора на скаляр
Здесь стоит обратить внимание на то, что первые два оператора образуют «симметрию» умножения скаляра и вектора.

func *(left: CGPoint, right: CGFloat)->CGPoint{
	return CGPoint(x: left.x*right, y: left.y*right)
}
func *(left: CGFloat, right: CGPoint)->CGPoint{
	return right*left
}
func /(left: CGPoint, right: CGFloat)->CGPoint{
	return left*(1/right)
}


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

func +=(inout left:CGPoint, right:CGPoint){
	left = left+right
}
func -=(inout left:CGPoint, right:CGPoint){
	left = left-right
}
func *=(inout left:CGPoint, right:CGFloat){
	left = left*right
}
func /=(inout left:CGPoint, right:CGFloat){
	left = left/right
}


Шаг 4: скалярное и 'смешанное' произведение
Очень полезные вещи, так как первое произведение дает скаляр и пропорционально косинусу угла между векторами, а второе произведение является псевдоскаляром (то есть меняет знак при перестановке множителей) и пропорционально синусу угла между векторами (жест «вращение» вычисляет величину угла вероятнее всего как раз на базе этого выражения).

func *(left: CGPoint, right: CGPoint)->CGFloat{
	return left.x*right.x + left.y*right.y
}
func ^(left: CGPoint, right: CGPoint)->CGFloat{
	return left.x*right.y - left.y*right.x
}


Шаг 5: произведение вектора на оператор аффинного преобразования
Здесь порядка ради вводится только умножение вектора на оператор справа.

func *(left: CGPoint, right: CGAffineTransform)->CGPoint{
	return CGPointApplyAffineTransform(left, right)
}
func *=(inout left:CGPoint, right:CGAffineTransform){
	left = left*right
}


Шаг 6: произведение операторов аффинного преобразования
func *(left: CGAffineTransform, right: CGAffineTransform)->CGAffineTransform{
	return CGAffineTransformConcat(left, right)
}
func *=(inout left:CGAffineTransform, right:CGAffineTransform){
	left = left*right
}


Заключение


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

Хочется отметить еще, что данные решения являются настолько очевидными и удобными, что будет неудивительным, если со временем подобная перегрузка войдет как часть стандартной библиотеки CoreGraphics.

Весь код целиком
Это можно уже копировать непосредственно в проект.

import Foundation

func +(left: CGPoint, right: CGPoint)->CGPoint{
	return CGPoint(x: left.x+right.x, y: left.y+right.y)
}
prefix func -(right: CGPoint)->CGPoint{
	return CGPoint(x: -right.x, y: -right.y)
}
func -(left: CGPoint, right: CGPoint)->CGPoint{
	return left + (-right)
}
///////////////////////////////////////////////////

func *(left: CGPoint, right: CGFloat)->CGPoint{
	return CGPoint(x: left.x*right, y: left.y*right)
}
func *(left: CGFloat, right: CGPoint)->CGPoint{
	return right*left
}
func /(left: CGPoint, right: CGFloat)->CGPoint{
	return left*(1/right)
}
///////////////////////////////////////////////////

func +=(inout left:CGPoint, right:CGPoint){
	left = left+right
}
func -=(inout left:CGPoint, right:CGPoint){
	left = left-right
}
func *=(inout left:CGPoint, right:CGFloat){
	left = left*right
}
func /=(inout left:CGPoint, right:CGFloat){
	left = left/right
}
///////////////////////////////////////////////////
///////////////////////////////////////////////////

func *(left: CGPoint, right: CGPoint)->CGFloat{
	return left.x*right.x + left.y*right.y
}
func /(left: CGPoint, right: CGPoint)->CGFloat{
	return left.x*right.y - left.y*right.x
}
///////////////////////////////////////////////////
///////////////////////////////////////////////////

func *(left: CGPoint, right: CGAffineTransform)->CGPoint{
	return CGPointApplyAffineTransform(left, right)
}
func *=(inout left:CGPoint, right:CGAffineTransform){
	left = left*right
}
///////////////////////////////////////////////////

func *(left: CGAffineTransform, right: CGAffineTransform)->CGAffineTransform{
	return CGAffineTransformConcat(left, right)
}
func *=(inout left:CGAffineTransform, right:CGAffineTransform){
	left = left*right
}

Tags:
Hubs:
+10
Comments 8
Comments Comments 8

Articles