Skip to content

Latest commit

 

History

History
209 lines (146 loc) · 11 KB

Лекция-10.md

File metadata and controls

209 lines (146 loc) · 11 KB

Лекция №10 (18.04.2019)

Шаблони

--

Шаблоните позволяват да прилагаме общо решение на проблем към частни проблеми. Това редуцира количеството код, което трябва да пишем, но от друга страна повишава трудността да дизайнваме шаблонни решения.

Какъв проблем решават шаблоните?

Как да напишем една фукнция, която има различни форми само веднъж и тя да може да се използва в различни моменти.

Шаблонни функции

Това са функции, които заместват конкретни имплементации на други функции. Примерно, можем да дефинираме шаблонна функция, която "събира" два обекта от определен тип. Типовете, над които може да бъде приложена функцията, трябва да имат имплементирана операцията "+".

Трябва да използваме шаблонните типове

Шаблонните типове се заместват с типове от вида T, K, U, като винаги са с главна буква и ако имат някаква релация, може да се използват думи, които да подсказват релацията. Пример: Dictionary<Key, Value>

Шаблоните се добавят след името на функцията в < > скоби. После този тип, който е шаблонен се използва въе функцията, за да се определи типа на параметрите и типа на връщания резултат.

Ето и един пример:

protocol Summable {
    static func + (left: Self, right:Self) -> Self
    
}

// изискванията, които поставя тази темплейтна функция са:
// 1. двата параметъра да са от един тип
// 2. резултатът да е от същия тип
// 3. типът да има реализирана операция + или събирането да е дефинирано. Това става, чрез наложеното ограничение в темплейтите

func sum<T:Summable>(_ a:T, _ b: T) -> T {
    return a + b
}


struct Vector:Summable {
   var x = 10
    
    static func + (left: Vector, right:Vector) -> Vector {
        return Vector(x: left.x + right.x)
    }
}

let vX = Vector(x: 10)
let vY = Vector(x: 10)
let sumV = sum(vX, vY)

extension String:Summable {}
let hw  = sum("Hello ", "World!")

Можем да ползваме шаблоните не само при функциите, но и при дефинирането на нови класове.

Шаблонни класове

Шаблонните класове наподобяват шаблонните функции, защото шаблонните типове се задават пак в < > скоби след името на класа.

Можем да дефинираме и шаблонни протоколи, като използваме запазената дума: associatedtype.

Ето и един пример:

protocol CollectionEquatable {
    associatedtype Element
    var count:Int { get }
    subscript (i:Int) -> Element {get}
}

Това е протокол, който трябва да борави с шаблонен тип Element. Така определената версия на протокола, може да конкретизира типа, който ще се свърже с Element. Т.е. навсякъде вместо този тип, ще се използва конкретния валиден тип.

Ето и пример за шаблонна опашка:

class Queue<Item>:CollectionEquatable {
    var items:[Item]
    
    init() {
        items = []
    }
    
    func insert(item:Item) {
       items.append(item)
    }
    
    func get() -> Item? {
        if items.count > 0 {
            return items.removeFirst()
        }
        
        return nil
    }
    
    subscript (i:Int) -> Item {
        return items[i]
    }
    
    var count:Int {
        return items.count
    }
}

extension Array : CollectionEquatable {}

Нека сега да напишем шаблонна функция, която сравнява две колекции, които могат да бъдат сравнявани по следните правила:

  1. Двете колекции трябва да имплментират протокола CollectionEquatable
  2. Да имат еднакъв брой елементи
  3. Всеки елемент да е еднакъв на съответния елемент от другата колекция

Ето и код, който илюстрира това:

func isEqual<T:CollectionEquatable, U:CollectionEquatable>(left:T, right:U) -> Bool where T.Element:Equatable, U.Element == T.Element {
    if left.count == right.count {
        for i in 0..<right.count {
            if left[i] != right[i] {
                return false
            }
        }
        
        return true
    }
    
    return false
}

Функцията има WHERE, чрез която се задава зависимостта елементите на всяка колекция да са от един и същи тип - U.Element == T.Element и типът на тези елементи да позволява да сравняване обекти.

Тези ограничения са важни, за да можем да запишем коректно условията в тялото на функцията. Важно е да има два типа, за да позволим различни типове колекции, които имплементират този интерфейс да могат да бъдат сравнявани - <T:CollectionEquatable, U:CollectionEquatable>. T.Element:Equatable и U.Element == T.Element ни позволява да напишем left[i] != right[i], понеже елементите ще са от един и същи тип и този тип ще е сравним.

Добре е да знаем, че можем да използваме разширения, за да добавяме допълнителни функции към шаблонните класове.

Ограниченията над шаблонните класове ги задаваме по няколко различни начина:

  • чрез протоколи, които трябва да бъдат имплементирани от класовете. (Добре е да си припомним, че можем да фиксираме даден протокол да се имплементира само от класове)
  • чрез WHERE клауза и зависимости между класовете.
  • чрез associatedtype и протоколи

Асоциирани типове и протоколи

Трябва да използваме associatedtype Element като част от протокола. В последствие, можем да използваме типа Element.

При имплементиране на определен протокол с асоцииран тип, Swift сам успява да определи реалният тип, с който трябва да замести асоциацията. Т.е. Element с кой реален тип трябва да бъде заместен.

Where условия при шаблоните

Условието трябва да се добави преди началото на функцията или типа от данни, които описваме. Може да видите правилото в действие в кода по-горе.

Атрибути *

-- Вече сме се сблъсквали с няколко приложения на атрибутите. Добре е да разберем тяхното основно приложение.

За какво се ползват атрибутите

Това е механизъм, който добавя допълнителна информация към декларация или към тип. В Swift се използват за да дадем допълнителна информация за параметрите на функция. Ето и пример:

func funcAutoclosureComplex(pred: @autoclosure () -> ()) {
    print("body of \(#function)")
}

Общият вид е следния:

@attributeName
//или
@attributeName(attribute arguments)

Популярни атрибути

Популярните атрибути, които можете да срещнете в Swift код са:

  • available - използва се да покажем даден метод от кога е достъпен. За коя платформа и от коя версия:
@available(platformName versionNumber, *)
//пример
@available(iOS 10.1, macOS 10.12, *)
  • discardableResult - прилага се при функции, за да не се изписва предупреждението, когато резултата от функцията не се използва.
  • GKInspectable - използва се при атрибути, които са свързани с iOS фреймуорка SpriteKit.
  • objc - използва се, когато искаме да направим код-а от swift достъпен за obj-C. Има своите специфики.
  • nonobjc - използва се, когато задължително искаме да скрием декларация от Obj-C.
  • NSApplicationMain - прилага се към основният делегат. Приложимо е в iOS.

Атрибутите, които се прилагат над параметрите са:

  • escaping - добре познат атрибут свързан с клоужърите. Трябва да се използва в случай, когато клоужъра ще напусне функцията.
  • autoclosure - конвертира израза в клоужър
  • convention - изполва се, когато се смесват различни типове функции - objc, c или swift
//int (*)(void) in C 
//in Swift looks like this
@convention(c) () -> Int32

Повече информация може да намерите в официалната документация от Apple (https://developer.apple.com/library/content/documentation/Swift/Conceptual/BuildingCocoaApps/InteractingWithCAPIs.html).

--

  • няма да е част от тест 1