Skip to content

Latest commit

 

History

History
executable file
·
452 lines (328 loc) · 22.4 KB

Lecture 2 - Swift.md

File metadata and controls

executable file
·
452 lines (328 loc) · 22.4 KB

Swift (xCode continue)

В предишната лекция се запознахме с Xcode - основната среда за разработка на приложения за еко системата на Apple. Ще се фокусираме над последната версия на това IDE(Integrated Developement Environmet) и на интеграцията на Swift с него.

Към момента последната версия на Swift е 5.5. Сега ще минем през основните елементи на езика, като препоръчваме да прочетете записките от курса във ФМИ Програмиране със Swift, за да добиете пълна и изчерпателна представа за езика.

Основни елементи на езика Swift

Ще започнем с променливите и константите. В Swift имаме ключови думи и за двата вида елементи. var използваме, когато искаме да боравим с нещо, което ще може да се променя в хода на нашата програма, а с let - константа - когато то ще е устойчиво на промени.

Бележка: Променливите не могат да започват с число. Могат да съдържат всякакви символи. Може да се изпозлват ` (обратни кавички), за да се изпозлват и запазени думи от езика за имена на променливи.

Swift e силно типизиран език. Това означава, че всяка променлива трябва да има конкретен тип, който да е ясен при компилиране. В много случаи не е нужно да определяте типа на променливите, тъй като Swift може да направи това вместо вас. Това се нарича type inferal или извличане на типа. Това са множество правила, които са заложени в компилатора и му позволяват да направи логическо следствие за типа. Когато не може еднозначно да се определи типа, възниква грешка при компилирането.

Бележка: Не е нужно да използваме ";" (точка и запетая) след всеки оператор (ред). Изпозлваме ";", ако имаме повече от един оператор на ред, за да помогнем на Swift да раздели операторите един от друг.

В Swift можем да ползваме следните оператори:

If else

Операторът if за проверка на условие е базова единица в програмирането. Той се използва за разделяне на логиката на програмата на два основни клона. Един, в който условието е изпълнено и друг, в който то не е (може да не съществува такъв - else).

Пример:

    var temperature = -1
    if temperature <= 0 {
    	//клон 1
    	print("Много е студено. Облечете се топло.")
    }
    var temperature = 10
    if temperature <= 0 {
	   //клон 1
    	print("Много е студено. Облечете се топло.")
    } else {
    	//клон 2
    	print("Не е много студено, но си вземи шапка.")
    }

Винаги точно един от двата "клона" на програмата ще бъдат изпълнени.

Важно е да запомним следните основни характеристики на оператора if в Swift:

  • Скобите около условието не са задължителни. Можем да ги изпозлваме при необходимост или когато самото условие е твърде сложно.
  • Къдравите скоби за отделянето на всеки "клон" от код са задължителни.
  • Имаме специална форма на if, когато работим с optional типове.

Възможно е да добавяме и допълнителни условия, като изпозлваме структурата else if.

    var temperature = 12
    if temperature <= 0 {
	   //клон 1
    	print("Много е студено. Облечете се топло.")
    } else if temperature <= 10 {
    	//клон 2
    	print("Не е много студено, но си вземи шапка.")
    } else {
    	//клон 3
    	print("Не е студено. Нямам нужда от шапка.")
    }

Краткият оператор ?: и ??.

	var temperature = 12 
	let status = temperature < 0 ? "Много е студено. Облечете се топло." : "Не е много студено, но си вземи шапка."
    	print(status)
	
	var x:Int? = 5
	let y = x ?? 15

switch

Операторът switch се изпозлва, когато имаме много възможности, които не се пресичат. Той има следния общ вид:

switch стойност за проверка {
	case стойност 1:
		отговор за стойност 1
	case стойност 2, стойност 3:
		отговор за стойностите 2 и 3
	default:
		в противен случай, изпълни друг израз
}

Важно е да запомним следните основни характеристики на оператора switch в Swift:

  • case клаузите нямат нуждата от break. Има неявна такава след края на всеки блок.
  • default е задължителен, ако не са разгледани всички случаи
  • Всеки switch трябва да е изчерпателен.
  • Има специална дума/оператор за пропадане в следващата клауза fallthrough.
  • В тялото на всяка клауза трябва да има поне един изпълним оператор или break, ако няма нищо.
  • Можем да използваме умни case клаузи с присвояване на стойности.
  • Можем да обединяваме няколко случая в един case.
  • Можем да използваме последователности (интервали).
  • Можем да използваме _ (wildcard), когато стойността не ни трябва.

Примери:

    let someNumber: Int = 3
    switch someNumber {
    case 1,  3:
        print("Едно или три")
    case 4...10:
    	 print("Между 3 и 10")
    default:
        print("Някакво друго число")
    }

Това е пример с интервали (Range<Int>).

    let count = 34
    let things = "ябълки"
    var expression: String
    switch count {
    case 0:
        expression = "николко"
    case 1..<10:
        expression = "няколко"
    case 10..<100:
        expression = "десетки"
    case 100..<1000:
        expression = "стотици"
    default:
        expression = "много"
    }
    print("\(count) са \(expression) \(things)")

Това е пример, който ще разгледаме, когато се запознаем с тип tuple или n-торка (вързопче от няколко различни типа, чиито полета може да са именовани).

    let point = (0, 1)
    switch point {
    case (let x, 0):
        print("точка (\(x), 0) се намира на абсциса х")
    case (0, let y):
        print("точка (0, \(y)) се намира на ордината у")
    case let (x, y):
        print("точка (\(x), \(y)) е някъде другаде")
    }

Умнотото разпознаване ни дава възможност да дефинираме сложни закономерности.

for

Един от основните инструменти за работа с колекции от данни. Може да обхождаме елементи в масив, двойки в Dictionary, символи в низ или диапазон от числа.

    for index in 1...5 {
        print("\(index) по 5 е \(index * 5)")
    }

Следва пример със списък от имена.

    let names = ["Боян", "Драго", "Емил", "Петко", "Спас"]
    for name in names {
        print("Здравей, \(name)!")
    }

Бележка: Съдържанието в Dictionary няма наредба по ключовете и резултатът от тази програма може да е различен по различно време.

    let numberOfLegs = ["паяци": 8, "мравки": 6, "котки": 4]
    for (animalName, legCount) in numberOfLegs {
        print("\(animalName)те имат \(legCount) крака”)
    }

Освен популярния for ще се запознаем и с while and repeat-while циклите.

while

Използва се, когато не знаем конкретния брой пъти, които трябва да се изпълни цикъла. Но знаем, че може и да не се изпълни нито един път.

while условие {
	изрази
}

repeat while

Използва се, когато не знаем конкретния брой пъти, които трябва да се изпълни цикъла. Но знаем, че трябва да се изпълни поне веднъж.

repeat {
    изрази
} while условие

Имаме и функции, които ще разгледаме в тяхния общ вариант Closures (Клоужърс) по-късно.

Swift разполага със стандартните типове данни, като:

  • Int
  • Int32
  • Int64
  • Double
  • Float
  • String
  • Bool
  • tuple (n-торка)

Бележка: Основните типове се пишат винаги с главна буква.

Това са и основните колекции.

Бележка: Колекция е тип данни, която може да съхранява повече от един елемент. (tuple e граничен случай, тъй като може да се разглежда и като колекция подобно на Dictionary)

  • Array
  • Set
  • Dictionary

Като посочените горе типове данни могат да съхраняват различни под видове. Това се постига с шаблони Generics.

Преди да преминем към типовете данни, които ние сами можем да декларирам, трябва да изясним optional типовете. Всеки нормален тип данни има братче, което е optional тип. Например: Int?

Основната черта тези типове данни е, че може да нямат сотйност или ако имат, то тя е от съответния основен тип. В горния пример това е Int.

Има няколко начина да работим с optional променливи.

  1. if let
  2. guard
  3. ! -

Можем да дефинираме собствени типове данни, които трябва да попадат в един от няколкото основни класа:

  • Изброим тип
  • Протокол
  • Структура
  • Клас
  • Функция/Клоужър

Ще разгледаме основните видове данни, които можем да използваме.

Изброим тип - enum

Дефинираме по следния начин:

enum Color {
	case pink
	case green
	case black
	case blue
	case white
	case noColor
}

Основното предимство е, че има фиксиран брой стойности. Можем да си съдадем съответствие с друг тип, наприемер Int или String. Възможно е да добавяме и фунции към нашите изброими типове или към вече съществуващи, използвайки разширения(Extensions).

Протокол - protocol

Дефинираме по следния начин:

protocol Sellable {
	var pricePerUnit: Double { get }
	var isAvailable: Bool { set get }
} 

Протоколите(protocols) се използват, за да определи публичен договор между различните видове типове/данни. Те са различен тип данни, който ни позволява да разшираваме езика и да дефинираме сложни връзки между другите типове. Може да се наследяват протоколи, като резултата е обединението на интерфейсите им. Чрез тях можем да симулираме множествено наследяване.

Структура - struct

Дефинираме по следния начин:

struct Merchandise {
	var name: String
   	var pricePerUnit: Double
	var isAvailable: Bool
   
   //конструктор
   init(name: String, pricePerUnit: Double, isAvailable:Bool) {
		self.name = name
   	   	self.pricePerUnit = pricePerUnit
   		self.isAvailable = isAvailable 
	}
}

Структурите(struct) се използват, когато искаме да моделираме обекти от реалния свят. Те позволяват капсулирането на представянето и определяне на възможните действия с даден тип. Чрез тях можем да свързваме данни с операции.

Следните елементи са от съществено значение:

  • Конструктори
  • Пропъртита - getter & setter, (read-only), lazy
  • Член методи
  • Наблюдатели на пропъртита
  • Subscripts - достъп до елементи, използвайки синтаксиса []

Характерно за обектите от тип структура е, че се предават по стойност.

Забележка: Има голяма разлика при предаването по стойност и по референция. По сотйност се предават всички базови типове и става копиране, което е оптимизирано за тях. При предаване по референция става споделяне на паметта, където се съхранява оригиналния обект.

Разширения - extensions

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

Повече може да прочетете тук.

Класове - class

Дефинираме по следния начин:

class Media {
	var name: String = ""
	var sizeInBytes: Double = 0.0
}

Класове(class) се използват, когато искаме да моделираме обекти от реалния свят, но искаме те да се предават по референция. Класовете са много близки до структурите, но са референтен тип. Тук трябва да внимаваме за тях и за паметта, която те ползват. При класовете имаме наследяване, но нямаме множествено наследяване. Това може да симулираме чрез протоколите.

Поведението на let е различно, когато става въпрос за типове предавани по референция. Пример:

let movie = Media()
movie.name = "X-Men"
print("Media name: \(movie.name)")
let ref = movie
print("Media ref name: \(ref.name)")
movie.name = "X-Men 2"
print("Media ref name: \(ref.name)") 
	
//Media name: X-Men
//Media ref name: X-Men
//Media ref name: X-Men 2

Кложъри - closure

Дефинираме по няколко начина:

  • като глобални функции
  • като анонимни функции
  • като член методи

Кложърите(closures) имат различни форми в Swift. Ще говорим най-често за тях, когато са в общия си анонимен вид. Всички останали видове имат други наименования и въпреки че са клоужъри, често използваме другите им имена като функции.

Пример:

//Нека да напишем първата функция, която обединява няколко действия.
func severalActions() {
	print("Action 1")
	print("Action 2")
	print("Action 3")
}

//тук ще активираме нашата функция. Може да мислим, че я "извикваме" (call)
severalActions()
severalActions()

Следва общия вид на фукнция с един параметър.

func functionName(labelName variableName:String) -> String {
	let returnedValue = variableName + " was passed"
	return returnedValue
}

//ето и извикването на функцията
functionName(labelName: "Nothing")

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

func printAllNames(names: [String], printFunc: (String) -> Void) {
    for name in names {
        printFunc(name)
    }
}

func createVeryFancyPrintFunction() -> (String)-> Void {
    
    func fancyPrint(name: String)   {
        print("@****************************@")
        print("@$$$$$$$$$$ \(name) $$$$$$$$$@")
        print("@****************************@")
    }
    
    
    return fancyPrint
}

printAllNames(names: ["Иван", "Гошо"], printFunc: createVeryFancyPrintFunction())

Ето и един пример с клоужъри:

let names = ["aaa", "ccc", "bbb"]
//сортиране
print(names.sorted(by: { $0 > $1 }))
print(names.sorted() { $0 > $1 })
//сортираме, като подаваме функция от тип (String, String) -> Bool ( т.е. оператор за сравнение)
print(names.sorted(by: >))

Какво е capturing?

Това е процес, при който става запомняне на контекста, в който възниква даден клоужър (closure).

Повече по темата може да прочетете тук.

Шаблоните

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

Примерно, списък/масив от цели числа.

let names:Array<String> = ["aaa", "ccc", "bbb"]
let numbers:Array<Int> = [1, 2, 3]

Какво остава неразгледано?

Има няколко въпроса, които остават незасегнати в тази лекция за Swift. Един от тях е управлението на паметта. Другият е правилното мениджиране на паметта, когато се изпозлват клоужъри.

Семинар

  1. Задачи на тема - Outlets and actions
  2. Свързване на хендлъри - Target-action (Interface Builder related)
  3. Да се имплементира - логин форма в стартов проект от github.