├── LICENSE ├── README.md ├── Лекция-01.md ├── Лекция-02.md ├── Лекция-03.md ├── Лекция-04.md ├── Лекция-05.md ├── Лекция-06.md ├── Лекция-07.md ├── Лекция-08.md ├── Лекция-09.md ├── Лекция-10.md ├── Лекция-11.md └── Лекция-12.md /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SwiftLectures -------------------------------------------------------------------------------- /Лекция-01.md: -------------------------------------------------------------------------------- 1 | # Лекция №1 2 | 3 | В тази лекция ще се запознаем с историята на `Swift`. Ще се научим къде и как можем да пишем нашите първи програми. Ще надникнем във вселената на open-source (проектите с отворен код). Ще се запознаем с github и различните среди за разработка. 4 | 5 | Преди да се гмурнем в детайли от къде идва `Swift` е добре да се запознаем с различните езици, които са популярни в момента. 6 | 7 | ## Популярни езици за програмиране 8 | 9 | В днешно време има много езици за програмиране. Някои от тях имат доста дълга история, други са по-млади, но не така популярни. 10 | 11 | Следващата статистика ни подсказва тенденциите в програмирането през последните няколко години. 12 | 13 | |Language|| 14 | |------------:|-------| 15 | |JavaScript|71.5%| |HTML|69.4%| |CSS|66.2%| |SQL|58.5%| |Java|45.4%| |Bash/Shell|40.4%| |Python|37.9%| |C#|35.3%| |PHP|31.4%| |C++|24.6%| |C|22.1%| |TypeScript|18.3%| |Ruby|10.3%| |Swift|8.3%| |Objective-C|7.3%| |Go|7.2%| |Assembly|6.9%| |VB.NET|6.9%| |R|6.0%| |Matlab|5.5%| |VBA|4.8%| |Kotlin|4.7%| |Groovy|4.5%| |Scala|4.5%| |Perl|4.2%| 16 | -- 17 | Статистиката е взета от анкетата на [StackOverFlow](https://insights.stackoverflow.com/survey/2018) за 2018 година. Отговори са дали 73,248 запитани. 18 | 19 | Всички програмни езици различна история. Можете да разгледате кога са създадени те [тук](https://en.wikipedia.org/wiki/History_of_programming_languages). 20 | Ясно се вижда, че `Swift` изпреварва доста езици като популярност. 21 | 22 | ### Защо точно трябва да учим един нов език? 23 | 24 | Новите езици са близки до старите, но не еднакви. Те вземат добрите им страни. Издигат нови ценности и стават популярни, като правят работата на разработчиците лесна. Крайният резултат е по-добър, процесът по-разработване е по-лесен, кодът е по-стабилен. Някои нови езици позволяват изграждането на сложни системи с лекота и за по-кратко време. 25 | ## Кратка история на `Swift` 26 | Въпреки че `Swift` е само на няколко години (през 2014г. е представен пред публика), вече има  пета версия (Swift 5), а от трета е с отворен код. (Трета версия е първата основна след публикуването на `swift` като open-source език.) Първоначално, Apple наемат Chris Lattner, който е основният автор на [LLVM] (https://en.wikipedia.org/wiki/LLVM), да развие системите за разработка на софтуер в компанията. Така, Apple постепенно преминава към LLVM, т.е. LLVM е интегриран в инструментите за разработка на приложения за macOS и iOS. На кратко - LLVM измества [GNU Compiler Collection (GCC)](https://en.wikipedia.org/wiki/GNU_Compiler_Collection) (най-популярения по това време компилатор) и се налага като стандарт, успявайки същевременно да направи прехода от GCC към LLVM плавен и безболезнен. 27 | 28 | Първа версия на езика (Swift 1.0) е публично достъпна на 9 септември 2014г. Официално версия 1.1 е пусната на 22 октомври 2014г. Трета версия, представена на 13 септември 2016г., след който момент се замислихме за стартиране на подходящ курс по програмиране във ФМИ. В този курс ще разгледаме версия 5, която беше пусната на 25 март 2019г. 29 | 30 | През януари 2017 Chris Lattner, считан за създател на Swift, прехвърля водещата роля на Ted Kremenek, но обещава да следи развитието на езика. 31 | 32 | ## Open-Source & Github 33 | 34 | Какво е приложение с отворен код (Open-source)? Не трябва да бъркаме "свободно приложение" (free ware) с "приложение с отворен код" (Open-source), въпреки че има припокриване. Приложенията с отворен код може да са платени, може и да са безплатни. Всичко това се определя от лиценза, под който се разпространява програмният код, съпътстващ приложенито. Характерно за софтуера с отворен код (СОК) е, че може да се разгледа самият програмен код, който го изгражда. 35 | 36 | Отвореният код като термин се налага след 1998г., като има различни инициативи в негова подкрепа. Тази идеология се възприема и сред най-големите софтуерни компании на пазара, които постепенно обикват модела и започват да го прилагат. Повече информация за цялостното движение в подкрепа на отворениея код можем да намерим [тук](https://bg.wikipedia.org/wiki/%D0%A1%D0%BE%D1%84%D1%82%D1%83%D0%B5%D1%80_%D1%81_%D0%BE%D1%82%D0%B2%D0%BE%D1%80%D0%B5%D0%BD_%D0%BA%D0%BE%D0%B4). 37 | 38 | Важно е да отбележим, че отвореният код осигурява и гаранира високи нива на: 39 | 40 | * сигурност 41 | * достъпност 42 | * прозрачност 43 | * разширяемост (наследственост) 44 | * оперативна съвместимост 45 | * гъвкавост 46 | * и други 47 | 48 | Повече за отворения код можем да намерим във есето от 1997г. - ["Катедралата и базарът"](http://catb-bg.sourceforge.net/index.html#thanks) на Ерик Реймънд, където той публично дискутира двата различни подхода при разработка на софтуер - класическия затворен модел и отворения модел. 49 | 50 | Нека сега да приложим наученото към Swift и да се опитаме да посочим позитивните страни на езика, след като е възприел идеологията на отворения код. Ще представим позитивните страни в списък, като същите не са подредени по важност: 51 | 52 | * езикът ще се развива под въздействието на екип, които може да взаимства идеи от всеки заинтересован, готов да предложи идея и да се обоснове. 53 | * свободата на езика позволява да се разширява във всякакви посоки 54 | * езикът става платформено независим, което го прави свободен и готов да се използва и на други операционни системи 55 | * достъпен е 56 | * вътрешната организация на езика е прозрачна и всеки може да се запознае с нея 57 | * вземането на решения, касаещи бъдещето на технологията, става на базата на ясен процес 58 | * когато много разработчици са включени, процесът е по-бавен, но за сметката на това е по-сигурен 59 | 60 | Всички горни точки гарантират, че Swift ще го има докато има общество, което да го поддържа. На базата на темповете, с които се развива, можем да сме сигурни, че езикът ще продължава да расте и да "краде" от дяла на другите програмни езици. 61 | 62 | #### Github 63 | Github е едно от най-популярните места за публикуване и съхранение на отворен код в момента. Това, че даден проект е публикуван в Github не означава, че той автоматично става достъпен безплатно. Хората имат достъп до кода, но нямат правото да го ползват, освен ако това не е изрично уредено с лиценз. Лицензът, под който се разпространява даден код, определя начинът, по който може да се използва в други проекти. Например, ако даден проект е под лиценз [GPL](https://bg.wikipedia.org/wiki/GNU_General_Public_License), той не може пряко да участва в друг комерсиален продукт, защото GPL-лицензът задължава всеки, който използва части от този код, да разпространява напълно безплатно продукта. Преди да качите Вашия проект, помислете какъв лиценз би пасвал най-добре. Когато ползвате чужди проекти, първо трябва да съобразите лицензите им. 64 | Лицензи, които са благоприятни за ползване, са [Apache](https://www.apache.org/licenses/LICENSE-2.0), [MIT](https://en.wikipedia.org/wiki/MIT_License), [BSD](https://en.wikipedia.org/wiki/BSD_licenses), разновидности на Creative commons. 65 | 66 | Можете да намерите всичко свързано със `swift` във github на следния адрес [https://github.com/apple/swift](https://github.com/apple/swift). 67 | 68 | 69 | ### От кога Swift е отворена технология? 70 | 71 | [Swift 2.2 е опънсорс](https://swift.org/blog/swift-2-2-released/) 72 | 73 | [Swift 3.0 е първата голяма версия (3), която е опънсорс.](https://swift.org/blog/swift-3-0-released/) 74 | 75 | ## Версия 5 76 | Версия 5 (Swift 5) е официално пусната на 25 март 2019. Няма синтактични разлики в езика от версия 4. Ще разгледаме част от нововъведенията в хода на курса. Повече информация какво е добавено със версия 5 може да намерите [тук](https://swift.org/blog/swift-5-released//). 77 | 78 | ## Къде и как да пишем `Swift`? 79 | Swift е платформено независим език, но официално няма версия за Windows. Такава обаче съществува на следния адрес: [Swift for Windows](https://swiftforwindows.github.io/) 80 | 81 | ### Swift for Windows 82 | Първо трябва да инсталирате специален пакет, който може да свалите от следния [сайт](https://swiftforwindows.github.io/). 83 | 84 | След успешното инсталиране може да използвате произволни редактори на код (със съответните разширения за `swift`) за да пишете вашите програми. 85 | 86 | __Не е нужно да имате Mac компютър за да може да пишете програми на `Swift`!__ 87 | 88 | ### Xcode (IDE) 89 | 90 | Това е стандартната среда за разработване от Apple, която можете да свалите от Appstore. Безплатна е, но върви само на `macOS`. 91 | 92 | 93 | ### AppCode (IDE) 94 | 95 | Това е допълнителна програма за лесно разработване на swift програми, било то мобилни или не. За съжаление, няма пълно препокриване със Xcode поради липсата на InterfaceBuilder частта. За съжаление и тя е само за `macOS`. Средата е базирана на така популярние `IntellyJ`, но не е безплатна. Има безплатна версия, до която може да получите достъп, ако сте [студенти](https://www.jetbrains.com/student/). 96 | 97 | ### Atom (editor) 98 | 99 | [Атом](https://atom.io/) е доста популярен редактор на код. Той е платформено независим и е с отворен код. Има много разширения, които позволяват да го превърнете в пълнофункционално IDE. 100 | 101 | ### VSCode (editor) 102 | [VSCode](https://code.visualstudio.com/) е популярен редактор на код разработен от Microsoft. Той е платформено независим и е с отворен код. Има много разширения, които позволяват да го превърнете в пълнофункционално IDE. Има и разширения, които ни дават възможност да пишем `ѕwift`. Не трябва да го бъркате със стандартната версия на Visual Studio. 103 | 104 | Следват инструкции как да настроите вашата среда за програмиране. Преди това трябва да сте инталирали: 105 | 106 | * [Swift for Windows](https://swiftforwindows.github.io/) 107 | * [VisualStudio Code](https://code.visualstudio.com/) 108 | 109 | [![Как да изпозлваме Swift и VisualStudioCode?](https://img.youtube.com/vi/K-0ciVsxoLQ/0.jpg)](https://www.youtube.com/watch?v=K-0ciVsxoLQ) 110 | 111 | ### Microsoft VisualStudio (IDE) 112 | 113 | Това е популярното IDE разработено от Microsoft за писане на 114 | 115 | Положението не е много по-различно със стандартното IDE за програмиране предоставено от Microsoft. От скоро има и [версия за `macOS`](https://www.visualstudio.com/vs/mac/). 116 | 117 | ###  Swift Playgrounds 118 | 119 | Swift Playgrounds e мобилно приложение за iPad и мак компютри, което ни учи как да боравим с прогмания език `Swift`. Това предоставя доста по интерактивен и привлекателен начин да се учим на чрез приложение от Apple. Връзки за сваляне на приложението, демо и друга полезна информация можете да намерите на [официалната страница на  Swift Playgrounds](https://www.apple.com/swift/playgrounds/). 120 | 121 | А сега е време да вникнем в основите на `Swift`. 122 | 123 | ## Основни типове данни 124 | 125 | Сега ще се запознаем с основните структурни единици на езика. Ще дискутираме различните видове и ще дадем примери за тяхната употреба. 126 | 127 | ### Променливи и константи 128 | 129 | `Swift` ни позволява да свързваме място в паметта (оперативната такава) с определени данни (стойност/и). Едно и също мястото в паметта може да има различни имена или казано по друг начин - различни имена могат да сочат едно и също място в паметта. 130 | 131 | Има два начина за такова свързване: _константно_ и _променливо_. 132 | 133 | - Константното свързване (“константа”) е еднократно и не може да бъде променяно. __let__ е запазената дума за константно свързване в паметта. За яснота в бъдеще константно свързване на дадено място в паметта с определени данни ще наричаме “константа”, като константата се свързва с името, което следва след __let__. За разлика от него, при 134 | - Променливото свързване могат да се записват различни данни в бъдещето. __var__ е запазена дума за променливото свързване. Отново, за яснота, променливо свързване на място в паметта с данни ще наричаме “променлива”, като променливата се свързва с името, което следва след запазената дума __var__. 135 | 136 | Ето един примерен програмен код, за да онагледим гореизложеното: 137 | 138 | ```swift 139 | let oneValue = 1 140 | var integerValue = 42 141 | ``` 142 | 143 | В примера по-горе константно свързваме мястото в паметта, което място е с име: “oneValue”, със следните данни: стойността 1. Аналогично, променливо свързваме мястото в паметта, което място е с име: “integerValue”, със следните данни: стойността 42. 144 | 145 | #### Метафоричен поглед към програмирането 146 | 147 | Нека, метафорично, да си представим че паметта на компютъра представлява огромен скрин с твърде много чекмеджета. При изпълнението на нашата програма, някой се грижи да ни казва кои чекмеджета може да ползваме, а след краят на програмата, ще мине и ще почисти всичко ненужно останало в тях. Всяко чекмедже може да съдържа нещо, т.е. то си има _тип_ предмети, които може да съхранява. Тук намесваме, константното свързване (let) - това е, чекмедже, на което даваме име и в което можем да поставим нещо само веднъж и то винаги ще съдържа единствено и само това нещо. Няма да можем да го заменим. От друга страна, променливото свързване (var) - това е чекмедже, на което даваме име и което може да ползваме напълно нормално. Т.е. можем да му сменяме съдържанието в произволен момент, да заменяме предмета/тите в чекмеджето. Хубавото, е че когато искаме да боравим с чекмеджетата можем да използваме техните имена (имената на променливите или константите.) 148 | 149 | ### Деклариране 150 | 151 | `Swift` позволява да декларираме по няколко проемнливи на един ред. 152 | 153 | Пример: 154 | ```swift 155 | var x = 0, y = 5 156 | let a = 1, b = 3 157 | ``` 158 | 159 | __Как да именуваме променливите?__ 160 | 161 | Променливите трябва да имат описателни имена. Не е добре да ги съкращаваме излишно. Кодът трябва да е само описателен и да няма нужда от излишни коментари, ако сложността му го позволява. 162 | 163 | Ето какво трябва да избягваме, когато програмираме. 164 | 165 | let a = "hello" 166 | let b = "world" 167 | let r = "\(a) \(b)!" 168 | 169 | 170 | И към какво да се стремим. 171 | 172 | let greeting = "hello" 173 | let subject = "world" 174 | let result = "\(greeting) \(subject)!" 175 | 176 | 177 | ___Добра практика___ 178 | 179 | _Когато променлива няма да бъде променяна във Вашия код, добре е да я заменяте с константа, използвайки запазената дума `let`._ 180 | 181 | ### Коментари 182 | 183 | Коментарите са част от програмния код, който създаваме, но се игнорират от Swift-компилатора. Накратко, те нямат влияние върху финалната програма. Тяхната роля е да поставят пояснителни бележки в процеса на писане на програмния код. Има два вида коментари: 184 | Коментари на един ред. Същите започват с две наклонени черти ```//``` и всичко до края на реда се счита за коментар; 185 | Коментари на много редове. Те започват с ```/*``` и завършват с ```*/```. 186 | 187 | Ето един пример за коментар: 188 | 189 | ```swift 190 | //декларираме константата 0 191 | let zeroValue = 0 192 | 193 | /*а това е коментар на 194 | няколко 195 | реда*/ 196 | ``` 197 | 198 | Swift предоставя версия на основните типове данни: Int, Double, Float, Bool, String. 199 | Всеки от основните типове данни разполага с подвариации в зависимост от платформата, за която е [компилирана](https://bg.wikipedia.org/wiki/%D0%9A%D0%BE%D0%BC%D0%BF%D0%B8%D0%BB%D0%B0%D1%82%D0%BE%D1%80) съответната програма . 200 | 201 | Например: на 32-bit-ова платформа Int е същият размер като Int32. На 64-bit-ова платформа, Int е същият размер като Int64. 202 | 203 | 204 | ## Типове 205 | 206 | Понеже Swift е силно типизиран език (strongly typed), това означава, че всяка променлива или константа трябва да има тип данни. Тук може да навлезем в детайли за сигурността на езика по време на компилация, но предлагаме на любопитния читател да потърси сам повече информация за видовете програмни езици и за тяхната сигурност относно типовете - type safety. Това е една обширна тема, по която няма единно мнение, кои езици са по-добри - [силно или слабо типизираните](https://en.wikipedia.org/wiki/Strong_and_weak_typing), но има ясно очертани граници. Ясно е, че дискутирайки Swift, става въпроз за случая, в който компилаторът ще ни помага преди да изпълним нашата програма. 207 | 208 | Характерът и природата на Swift не позволяват смесването на типове данни без експлицитното му обявяване (конвертиране от тип в тип). 209 | 210 | __Въпрос:__ _Как да обявим, че дадена променлива/константа е от определен тип (т.е. че тя ще съдържа данни от съответния обявен тип)?_ 211 | 212 | Всяка променлива или константа може да има изрично зададен тип. Това става чрез обявяване на типа след името на променливата. Ето и примерен код: 213 | 214 | ```swift 215 | var sum: Int 216 | ``` 217 | 218 | На компютърен език двоеточието означава: “от тип”, т.е. можем да прочетем горния код като: декларираме променлива с име “sum” от тип цяло число (__Int__eger). 219 | 220 | ___Добра практика___ 221 | 222 | _В практиката много рядко се пишат типове данни. Ако имаме начална стойност на променливите, Swift може да определи типа на променливата или константата автоматично._ 223 | ### Целите числа (Int) като тип 224 | 225 | Нека първо да се запознаем с целите числа (Integers). 226 | Ето и няколко примера: 42, 0, -1. 227 | 228 | Целите числа се делят на __два__ основни __подтипа__ в Swift: 229 | _такива __със знак__ (signed)_ и 230 | _такива __без знак__ (unsigned)_ 231 | Тези със знак са: позитивни, нулата и негативните, а тези без знак са само позитивните и нулата. 232 | __Всички типове__ цели числа __в Swift започват с големи букви__. Ето и няколко примера: Int, Int32, UInt8, UInt32, UInt64. 233 | Езикът ни предоставя достъп до минималната и максималната стойност на целочисления тип. 234 | Например: 235 | ```swift 236 | UInt8.min // минимална стойност 237 | UInt64.max // максимална стойност 238 | ``` 239 | 240 | ### Реални числа (дробни числа) - Double и Float 241 | 242 | Дробните числа в езика за програмиране са представени с два основни типа данни - Float и Double. Първият тип (Float) използва по-малко памет, но дава възможност за по-малко точност при представянето на дробното число в паметта. Защо? Накратко - съхранява по-малко цифри след десетичната точка. Когато декларираме променлива или константа и присвоим дробно число, типът, който Swift определя за тази променлива, е Double. Той се характеризира с по-голяма точност и в тази връзка, когато извършваме операции, като делене, можем да покажем повече цифри след десетичния разделител (десетичната запетая). 243 | 244 | Подобно на типовете цели числа, съществуват различни типове дробни числа в зависимост от операционната система, на която ще стартираме програмата. Имаме два типа данни, които представят дробни числа в паметта на компютъра: Float32 и Float6, като Float32 е Float, а Float64 е Dоuble. Double типът няма различни варианти, за разлика от целочислените типове. 245 | 246 | 247 | ### Символен низ (String) 248 | 249 | Това е тип данни, които съхранява в паметта на компютъра последователност от текст. Интересно е представянето на символните низове в паметта поради наличието на различни естествени говорими и писмени езици. За да може боравим с различни езици, още в началото авторите на езика са заложили възможността му да работи с Unicode. За да въведем концепцията за различни писмени езици, ще си мислим, че Uni–code е таблица, която определя на кой код (число) кой символ да бъде изобразен. Нарича се Unicode, защото съдържа кодовете на различни писмени езици като Българския, Арабския, Китайския и т.н. Кратък поглед в историята на компютрите показва, че преди Unicode да се наложи е било необходимо всеки компютър да зарежда своя таблица с кодове, но това води до проблеми. Може да прочетете много за ASCII и произлизащите проблеми от тази кодова таблица/стандарт. Проблеми, с които сме се сблъсквали и ние - понякога не можем да видим какво е написано, най-често когато искаме да гледаме филм с български субтитри. Често това се случва поради грешна употреба на кодовата таблица при превръщане на числата(кодовете) в символи. 250 | 251 | Примерно: 252 | ```swift 253 | let codeOfA = ("A" as Unicode.Scalar).value 254 | //(стойността на символа __А__) 255 | 256 | let codeOfB = codeOfA + 1 257 | // тези каствания (прехвърляния през различните типове са необходими, за да можем да отпечатаме буква по зададен код (номер) 258 | print(Unicode.Scalar(codeOfB)!) 259 | ``` 260 | 261 | Всеки един символен низ, съхранява конкретен набор от UTF символи и на базата на това, коя версия на UTF използва, дължината на символния низ в паметта е различна. Ето няколко примера, които подчертават това и дискутират последствията от това. 262 | 263 | Примерно: 264 | ```swift 265 | let helloWorld = "Hello 🌍!" 266 | // синтаксиса ?? се оценява, с дясната стойност на израза, само ако първата част е nil. За повече информация виж частта за Optionals 267 | //utf-8 268 | print(Unicode.Scalar(helloWorld.utf8.dropFirst(0).first!))//H 269 | print(helloWorld.utf8.dropFirst(6).first!)//част от емоджито 270 | print(helloWorld.utf8.dropFirst(7).first!)//част от емоджито 271 | print(helloWorld.utf8.dropFirst(8).first!)//част от емоджито 272 | print(helloWorld.utf8.dropFirst(9).first!)//част от емоджито 273 | print(Unicode.Scalar(helloWorld.utf8.dropFirst(10).first!)) //! 274 | 275 | //utf-16 276 | print(helloWorld.utf16.dropFirst(6).first!) 277 | print(helloWorld.utf16.dropFirst(7).first!) 278 | print(helloWorld.utf16.dropFirst(8))//! 279 | 280 | //utf-32 281 | print(helloWorld.unicodeScalars.dropFirst(6).first!)//емоджи 282 | print(helloWorld.unicodeScalars.dropFirst(7))//! 283 | ``` 284 | 285 | 286 | Трябва да отбележим една специфика на езика, че тук Символният низ се предава по стойност във функции. Това може да ви звучи странно, но когато се сблъскаме със функции, ще направим разлика с другите езици, където се предава по референция (примерно C++). 287 | За да можем да използваме символните низове и да представяме цифрова информация, е добре да можем да знаем как да инициализираме символен низ (string) използвайки променливи от други типове. 288 | 289 | ```swift 290 | let realNumber = String(12.35) 291 | let integerNumber = String(3 + 4) 292 | ``` 293 | 294 | ### Опционален тип (Optionals) 295 | 296 | В хода на нашата програма всяка променлива може да има стойност или да няма такава. Какво ще рече, да няма стойност? Това е специално състояние, което показва, че променливата няма стойност (или по скоро има запазената стойност nil). Това въвежда неудобстовото, всеки път да се проверява дали дадена променлива има стойност и ако има такава да се преминава към действие, ако няма да се действа по друг начин. За да се улесни боравенето със това специално състояние на липса на стойност във Swift са въведени опционалните типове. Те са като стандартните типове, но завършват с ?. Да, точно така, завършват с ? за да ни подскачат визуално, че има възможност дадена променлива да няма стойност. 297 | 298 | Примерно: 299 | ```swift 300 | let a: Int? = 5 301 | ``` 302 | 303 | Основното правило за работа с опционален тип е, че трябва да стигнете до стойността му. Затова е въведена __let__ конструкцията и с нейна помощ, може много лесно и четимо да боравите със стойностите на променливи. 304 | 305 | Сега ще разгледаме, каква е структурата на такъв опционален тип. Той има две състояния - да има стойност или да няма стойност (nil) Тук е добре да се прави разлика от nil в Objective-C. 306 | 307 | [Инфо](https://developer.apple.com/reference/swift/optional) 308 | 309 | Когато срещаме Int? ние виждаме краткият запис за Optional. Всички опционални типове използват един и същ шаблонен клас, но имат различни данни. По-късно в лекциите ще стане ясно, какво точно се има предвид със записа Optional в детайли. За да изясним ситуацията с опционалният тип данни, е добре да представим и следния пример: 310 | 311 | ```swift 312 | let numberOne: Int? = Optional.some(1) // има стойснот 1 313 | let nothing: Int? = Optional.none // няма стойност 314 | ``` 315 | 316 | ### Операции 317 | 318 | __! За операторите ще говорим в детайли следващите лекции.__ 319 | 320 | ##Съвети: 321 | 322 | Ако имате въпроси, може да ги задавате по e-mail - swiftfmi [at] gmail.com. 323 | 324 | Хубаво е да споделяте идеи с колегите си относно решенията на някакви задачи, но не и самите решения. Университетът има за цел, да изгради начин на мислене, което става със споделянето на идеи, а не на цели решения. 325 | 326 | Тези, които познават Objective-C или Swift и искат да завършат курса, трябва да покажат добро владеене на езика. Това може да стане с разработката на проект, който е по-тежък и изисква повече усилия или по стандартният начин, като преминат стъпка по стъпка през курса. Моля разгледайте началната страница на курса, където ще нъдат обявени и проектите. 327 | -------------------------------------------------------------------------------- /Лекция-02.md: -------------------------------------------------------------------------------- 1 | # Лекция №2 2 | -- 3 | В тази лекция ще се запознаем с различните езикови структури в `Swift`. Ще говорим за основните запазени думи, операторите и началните езиковите средства, които да ни послужат, когато пишем програми на `Swift`. Ще представим и възможността да пишем отделни функционални фрагменти, които да можем да изпозваме многократно в нашите програми. 4 | 5 | Swift предоставя набор от условни оператори като if, guard и switch, използвани за сравнение на стойностите на променливи и извършване на действия спрямо резултата. 6 | 7 | На първо място, нека да разгледаме условния оператор, който ни позволява да правим избор и да задействаме различни части от програмата си, в зависимост от това, дали условието, което проверяваме, е вярно или не. 8 | 9 | ## `if` оператор 10 | В най-простата си форма оператора `if` има само едно условие. Той изпълнява зададените му изрази само, ако условието му е изпълнено (`true`). 11 | 12 | ```swift 13 | var temperature = -1 14 | if temperature <= 0 { 15 | print("Много е студено. Облечете се топло.") 16 | } 17 | ``` 18 | 19 | Кодът проверява дали температурата е по-ниска или равна на 0 градуса по целзий. Ако условието е изпълнено, принтираме съобщение, ако ли не - не принтираме нищо, а кодът продължава след затварящата скоба (}) на `if` оператора. 20 | 21 | ### `else` оператор 22 | Операторът `if` може да предостави алтернативен набор от условия или `else`, за ситуации в които условието на `if` оператора е грешно (`false`). 23 | 24 | ```swift 25 | var temperature = 6 26 | if temperature <= 0 { 27 | print("Много е студено. Облечете се топло.") 28 | } else { 29 | print("Не е чак толкова студено. Защо не облечете тениска?") 30 | } 31 | ``` 32 | 33 | Винаги точно едно от условията ще бъде изпълнено. Температурата беше повишена до 6 градуса, затова не е толкова студено, че да препоръчаме шал и израза в `else` ще бъде изпълнен. 34 | ### `else if` оператор 35 | Можете да навържете няколко `if` оператора за да проверите допълнителни условия. 36 | 37 | ```swift 38 | var temperature = 32 39 | if temperature <= 0 { 40 | print("Много е студено. Облечете се топло.") 41 | } else if temperature >= 30 { 42 | print("Жега е. Да не забравите да си сложите слънцезащитен крем.") 43 | } else { 44 | print("Не е чак толкова студено. Защо не облечете тениска?") 45 | } 46 | ``` 47 | 48 | Тук беше добавен допълнителен `if` оператор, за да можем да дадем коректна информация при високи температури. Последият `else` оператор остава, както до сега и ще принтира информация, когато температурите не са нито прекалено високи, нито прекалено ниски. 49 | 50 | Последният `else` оператор не е задължителен и може да бъде пропуснат, ако всички условия не трябва да бъдат проверявани. 51 | 52 | ```swift 53 | var temperature = 24 54 | if temperature <= 0 { 55 | print("Много е студено. Облечете се топло.") 56 | } else if temperature >= 30 { 57 | print("Жега е. Да не забравите да си сложите слънцезащитен крем.") 58 | } 59 | ``` 60 | 61 | Поради причината, че температурата не е нито твърде ниска, нито твърде висока, за да задейства `if` или `else if` операторите, няма да бъде принтирано никакво съобщение. 62 | 63 | ## Условен оператор `switch` 64 | `switch` операторът проверява стойността на променлива и я сравнява с различни възможни варианти, а след това изпълнява изразите, принадлежащи на първото съответствие. `switch`-ът е алтернатива на `if` оператора, която е предназначена за работа с голям брой възможни съвпадения. 65 | В най-простата си форма `switch` операторът сравнява стойност с една или повече стойности от същият тип. 66 | 67 | ```swift 68 | switch стойност за проверка { 69 | case стойност 1: 70 | отговор за стойност 1 71 | case стойност 2, стойност 3: 72 | отговор за стойностите 2 и 3 73 | default: 74 | в противен случай, изпълни друг израз 75 | } 76 | ``` 77 | 78 | `switch` операторът съдържа в себе си различни възможни съответствия (`case`). Освен сравнението със специфични променливи, `switch` операторът може да бъде използван за сравнение с по-сложни изрази. 79 | Всеки `case` бива сравняван със зададената, с инициализацията на `switch`-a, променлива. Ако условието е изпълнено (`true`), се изпълнява набора от изрази, зададени в тялото на съответният `case`. В тялото на всеки дефиниран `case` трябва да има поне един изпълняем израз. 80 | Всеки `switch` трябва да бъде изчерпателен. Всяка възможна стойност на променливата, която се проверява, трябва да бъде описана в `case`. Ако това не е възможно, може да бъде дефиниран основен (`default`) `case`, за да могат да бъдат покрити всички останали стойности, които не са описани в отделен `case`. `default` `case`-ът се намира винаги в края на `switch` оператора. 81 | 82 | ```swift 83 | let someNumber: Int = 3 84 | switch someNumber { 85 | case 1: 86 | print("Едно") 87 | case 3: 88 | print("Три") 89 | default: 90 | print("Някакво друго число") 91 | } 92 | ``` 93 | 94 | В този пример ще бъде сравнявано число от тип `Int`, наречено `someNumber`. В първия случай (`case`) `someNumber` ще бъде сравнено с числото 1, а във втория - с 3. Тъй като не можем да сравним `someNumber` с всички възможни числа, използваме `default` `case`, за да покрием останалите числа, които не сме изброили в отделни `case`-ове. По този начин се уверяваме, че `switch` операторът е изчерпан. 95 | В повечето езици, като `C` и `Objective-C`, изпълнението на кода след проверката на `case` продължава към следващия `case` до прекратяване чрез `break`. Вместо това, в `Swift` `switch` операторът прекратява изпълнението си след първото съвпадение без нуждата да бъде спряно чрез `break`. Това прави използването на оператора по-лесно и безопасно, избягвайки възможността да се изпълни повече от един `case` по грешка. И все пак, това поведение може да бъде избегнато, като използваме запазената дума `fallthrough` в края на избран или всеки описан `case`. 96 | 97 | В един `case` можем да сравняваме повече от един случай. В примера по-горе можем да комбинираме използваните случаи 1 и 3 в един `case`, като ги разделим чрез запетая. 98 | 99 | ```swift 100 | let someNumber: Int = 3 101 | switch someNumber { 102 | case 1, 3: 103 | print("Едно или три") 104 | default: 105 | print("Някакво друго число") 106 | } 107 | ``` 108 | ### Сравнение с последователности 109 | Стойността на сравняваната променлива може да бъде сравнена и с диапазон. 110 | 111 | ```swift 112 | let count = 34 113 | let things = "ябълки" 114 | var expression: String 115 | switch count { 116 | case 0: 117 | expression = "николко" 118 | case 1..<10: 119 | expression = "няколко" 120 | case 10..<100: 121 | expression = "десетки" 122 | case 100..<1000: 123 | expression = "стотици" 124 | default: 125 | expression = "много" 126 | } 127 | print("\(count) са \(expression) \(things)") 128 | ``` 129 | 130 | В примера константата `count` се сравнява с число или диапазон от числа. Тъй като стойността на `count` се намира между 10 и 100, `expression` заема стойността на `“десетки”` и изпълнението излиза извън `switch` оператора, като принтира `“34 са десетки ябълки”`. 131 | ### Сравнение на няколко стойности (n-торки) 132 | В един `switch` можем да сравним стойностите на повече от една променлива. Стойността на всяка подадена променлива може да бъде сравнена с друга стойност или диапазон от стойности. А всяка стойност в `case` може да бъде заместена с долна черта (`_`), позната като "маска", която ще съвпадне с всяка подадена стойност. 133 | В примера ще проверим дали по дадена точка `(x, y)` в координатната система, изразена чрез `(Int, Int)`, се намира в квадрат 4 на 4, средата на който се намира в точка `(0, 0)`. 134 | 135 | ```swift 136 | let point = (1, 1) 137 | switch point { 138 | case (0, 0): 139 | print("точка (0, 0) е в началото на координатната система") 140 | case (_, 0): 141 | print("точка (\(point.0), 0) се намира на абсциса х") 142 | case (0, _): 143 | print("точка (0, \(point.1)) се намира на абсциса у") 144 | case (-2...2, -2...2): 145 | print("точка (\(point.0), \(point.1)) е в квадрата") 146 | default: 147 | print("точка (\(point.0), \(point.1)) е извън квадрата") 148 | } 149 | ``` 150 | 151 | Този `switch` оператор проверява, дали подадената точка се намира: в началото на координатната система, на абсциса х, на ордината у, в квадрат 4 на 4 или извън него. Ако бе подадена точка `(0, 0)`, щяхме да имаме 3 съвпадения, тъй като първите 3 case-a отговарят на подадената стойност. В такъв случай, първата проверка (`case (0, 0)`) щеше да бъде изпълнана, което ще прекрати изпълнението на `switch`-a и всички останали проверки щяха да бъдат игнорирани. 152 | 153 | ### Присвояване на стойност 154 | Стойността или стойностите на `case` могат да бъдат присвоени във временна константа или променлива, която да се използва в тялото на случая (`case`). В следващия пример ще подадем точка `(x, y)` и ще категоризираме позицията й върху координатната система. 155 | 156 | ```swift 157 | let point = (0, 1) 158 | switch point { 159 | case (let x, 0): 160 | print("точка (\(x), 0) се намира на абсциса х") 161 | case (0, let y): 162 | print("точка (0, \(y)) се намира на ордината у") 163 | case let (x, y): 164 | print("точка (\(x), \(y)) е някъде другаде") 165 | } 166 | ``` 167 | 168 | В примера проверяваме дали подадената точка се намира на абсциса х, ордината у или някъде другаде. В дефинираните случаи (`case`) проверяваме една от точките, а другата я присвояваме към временна константа. След като временната константа е дефинирана, тя може да бъде използвана в тялото на `case`-a, в този случай я използваме, за да принтираме нейната стойност. 169 | Както виждате, в примера няма `default` `case`. Последният случай (`case let (x, y):`) присвоява и двете подадени стойности, което позволява съвпадението с всяка подадена точка. Тъй като `point` съдържа винаги 2 стойности, последният случай (`case`) ще съвпадне с всички останали възможни стойности и `default` `case`-a не е нужен, за да изчерпим този `switch` `case`. 170 | 171 | ## Цикли 172 | 173 | ### Цикъл for in 174 | Можете да използвате цикъла `for in`, за да обиколите елементи в масиви, символи в низ или диапазон от числа. 175 | Този пример принтира първите няколко вписвания в масив от 5 числа. 176 | 177 | ```swift 178 | for index in 1...5 { 179 | print("\(index) по 5 е \(index * 5)") 180 | } 181 | ``` 182 | 183 | В примера се създава диапазон от числата (от 1 до 5) чрез използването на оператора за затворен диапазон (`...`). Стойността на `index` се задава от първото число в диапазона (1) и изразите в цикъла биват изпълнени. В този случай, цикълът съдържа само един израз, който принтира текущата стойност на `index`. След като изразът бъде изпълнен, стойността на `index` се обновява, така че да съдържа следващата стойност от зададеният диапазон (2) и `print` функцията се задейства отново. Този процес се повтаря до края на диапазона. 184 | В примера по-горе `index` е константа, която автоматично се задава в началото на всяка итерация от цикъла. Поради тази причина не е нужно `index` да бъде деклариран преди да бъде използван. Задава се просто чрез включването му в декларацията на цикъла без използването на запазената дума `let`. 185 | 186 | Ако не Ви е нужно да използвате стойността на променливата в цикъла, можете да игнорирате стойността й чрез използването на долна черта на мястото на името на променливата. 187 | 188 | ```swift 189 | let base = 3 190 | let power = 10 191 | var answer = 1 192 | for _ in 1...power { 193 | answer *= base 194 | } 195 | print("\(base) на степен \(power) е \(answer)") 196 | ``` 197 | 198 | Примерът горе изчислява стойността на едно число на степентта на друго (в този случай: 3 на степен 10). Това умножава началната стойност 1 (пропускаме 3 на степен 0) по 3, десет пъти, използвайки затворен диапазон започващ с 1 и завършващ с 10. За това изчисление не е нужна стойност за отброяване на пътите, в които е преминато през израза в цикъла, кодът просто се изпълнява правилният брой пъти. Долната черта (`_`) се използва на мястото на променливата в цикъла, което ни позволява да игнорираме индивидуалните стойности на променливата по време на изпълнението му. 199 | 200 | Можете да използвате `for in`, за да обиколите елементите в масив. 201 | 202 | ```swift 203 | let names = ["Емил", "Спас", "Иван", "Гошо"] 204 | for name in names { 205 | print("Здравей, \(name)!") 206 | } 207 | ``` 208 | 209 | Също така можете да обикаляте масиви с ключ и стойност (dictionary), за да достъпите двойките ключ-стойност. Всеки елемент от масива се връща като (key, value) при всяка итерация на масива. Можете да декомпозирате стойностите на (key, value) с различни имена, които да използвате в изразите на цикъла. В примера ключовете на масива са декомпозирани в константи, наименовани `animalName`, а стойностите - `legCount` 210 | 211 | ```swift 212 | let numberOfLegs = [“паяци”: 8, “мравки”: 6, “котки”: 4] 213 | for (animalName, legCount) in numberOfLegs { 214 | print("\(animalName)те имат \(legCount) крака”) 215 | } 216 | ``` 217 | 218 | Не е нужно елементите в dictionary да бъдат итерирани по реда, в който са вкарани. Съдържанието на dictionary не е подредено и когато го обхождаме, нямаме гаранция за реда, в който ще бъдат върнати стойностите му. 219 | 220 | ### Цикли тип while 221 | `while` цикълът изпълнява набор от изрази, докато условието му не стане `false`. Този вид цикли се използват, когато не се знае колко пъти трябва да се изпълни даден израз преди началото на първата итерация. `Swift` предлага два вида `while` цикли: 222 | `while`, който проверява условието в началото на всяка итерация на цикъла 223 | `repeat while`, който проверява условието в края на всяка итерация на цикъла 224 | #### while 225 | `while` започва, като проверява едно единствено условие. Ако условието е вярно (`true`), набор от изрази се повтарят, докато условието стане грешно (`false`). 226 | Това представлява един типичен `while` цикъл: 227 | 228 | ```swift 229 | while условие { 230 | изрази 231 | } 232 | ``` 233 | 234 | В този пример ще използваме играта “Змии и стълби”. 235 | 236 | Правилата са следните: 237 | Игровата дъска съдържа 25 квадрата и целта на играта е да се достигне или премине 25-тият квадрат; 238 | Всеки ход играча хвърля зар и премества своята пионка според числото, което е хвърлил. Посоката на преместване се определя по поредността на номерата изписани на дъската; 239 | Ако ходът Ви завърши в началото на стълба, трябва да се качите по нея; 240 | Ако ходът Ви завърши на главата на змия, трябва да слезете до опашката й. 241 | 242 | Игровата дъска ще бъде представлявана от масив с елементи от тип `Int`. Големината на дъската (броят квадрати) ще зависи от константа `finalSquare`, която ще се използва за инициализация на масива и за да проверим дали има победител по-късно. Дъската ще бъде инициализирана с 26 нули от тип `Int`, а не 25 (от 0 за начална позиция до 25). 243 | 244 | ```swift 245 | let finalSquare = 25 246 | var board = [Int](repeating: 0, count: finalSquare + 1) 247 | ``` 248 | 249 | Някои квадрати ще бъдат с различна стойност от 0, които ще представляват змиите и стълбите. Квадратите със стълби ще имат позитивни стойности, за да отведат играча по-нагоре по дъската, а квадратите със змийски глави ще съдържат негативни стойности, за да върнат играча назад по дъската. 250 | 251 | ```swift 252 | board[03] = +08; board[06] = +11; board[09] = +09; board[10] = +02 253 | board[14] = -10; board[19] = -11; board[22] = -02; board[24] = -08 254 | ``` 255 | 256 | Квадрат 3 съдържа долната част на стълба, която ви отвежда до квадрат 11. За да представим това, `board[03]` ще бъде равен на +08, което е еквавилент на integer стойността 8 (разликата между 3 и 11). 257 | Играчът започва от квадрат 0, което е точно до долният ляв ъгъл на дъската. Първото хвърляне на зара винаги премества пионката на играча върху дъската. 258 | 259 | ```swift 260 | var square = 0 261 | var diceRoll = 0 262 | while square < finalSquare { 263 | // хвърляне на зар 264 | diceRoll += 1 265 | if diceRoll == 7 { diceRoll = 1 } 266 | // преместване спрямо стойността на зара 267 | square += diceRoll 268 | if square < board.count { 269 | // ако пионката на играча все още е върху дъската, преместваме пионката нагоре или надолу за змия или стълба 270 | square += board[square] 271 | } 272 | } 273 | print(“Край на играта!”) 274 | ``` 275 | 276 | Примерът горе използва доста опростен вариант за хвърляне на зар. Вместо да генерираме случайно число, `diceRoll` винаги започва със стойност 0. При всяка итерация на цикъла `diceRoll` се инкрементира с единица, а след това се проверява дали стойността не е станала прекалено голяма. Когато стойносттна й достигне 7, стойността на зара е станала прекалено голяма и се връща до 1. Като резултат стойностите на зара стават 1, 2, 3, 4, 5, 6, 1, 2 и така нататък. 277 | След като зарът е хвърлен, пионката на играча се придвижва напред според стойността на `diceRoll`. Възможно е хвърлянето да отведе играча на по-горна позиция от 25-тия квадрат, което значи край на играта. За да можем да изпълним този сценарий, кодът проверява дали square е с по-ниска стойност от броя на елементи на board преди стойността на елемента в `board` да бъде добавена към текущата стойност на square, което ще придвижи играча според това дали е попаднал на змия или стълба. 278 | След това текущата итерация на цикъла завършва и зададеното условие се проверява отново, за да се разбере дали е нужна следваща итерация. Ако играчът е стъпил или преминал квадрат 25, проверката на условието ще доведе до `false` и играта ще приключи. 279 | Цикълът `while` е подходящ в този пример, защото продължителността на играта не е ясна преди началото на цикъла, а вместо това цикълът се изпълнява докато условието не е задоволено. 280 | 281 | #### Цикли тип repeat while 282 | Другият вариант на цикъла `while` (или цикъл `repeat while`) изпълнява първоначална итерация на цикъла преди да провери условието. След това продължава да изпълнява цикъла докато условието не стане грешно (`false`). 283 | `repeat while` в `Swift` е аналог на `do while` цикъла в други езици. 284 | Това представлява един типичен `repeat while` цикъл: 285 | 286 | ```swift 287 | repeat { 288 | изрази 289 | } while условие 290 | ``` 291 | 292 | Ето същия пример на “Змии и стълби” реализиран с `repeat while` цикъл. Стойностите на `finalSquare`, `board`, `square`, и `diceRoll` се задават по същият начин както в `while` цикъла. 293 | 294 | ```swift 295 | let finalSquare = 25 296 | var board = [Int](repeating: 0, count: finalSquare + 1) 297 | board[03] = +08; board[06] = +11; board[09] = +09; board[10] = +02 298 | board[14] = -10; board[19] = -11; board[22] = -02; board[24] = -08 299 | var square = 0 300 | var diceRoll = 0 301 | ``` 302 | 303 | В този вариант на играта първото нещо, което се изпълнява в цикъла е да се провери за змия или стълба. Няма стълба на дъската, която да отведе играча директно до 25тият квадрат, затова не е възможно да се спечели играта докато се изкачва стълба. Следователно е безопасно да се провери за змия или стълба още в първият израз в цикъла. 304 | В началото на играта играчът е на квадрат 0. `board[0]` винаги е равен на 0 и няма никакъв допълнителен ефект. 305 | 306 | ```swift 307 | repeat { 308 | // преместване спрямо стойността на зара 309 | square += board[square] 310 | // хвърляне на зара 311 | diceRoll += 1 312 | if diceRoll == 7 { diceRoll = 1 } 313 | // преместване спрямо стойността на зара 314 | square += diceRoll 315 | } while square < finalSquare 316 | print("Край на играта") 317 | ``` 318 | 319 | След като кода провери за змии или стълби, зарът се хвърля и играчът се мести напред с брой квадрати равен на стойността на `diceRoll`. След това текущата итерация на цикала приключва. 320 | Условието на цикъла (`while square < finalSquare`) е същато, като в предния пример, но този път не се проверява докато не се изпълни първата итерация. Структората на `repeat while` е по-подходяща за тази игра от `while`. В `repeat while` цикъла, `square += board[square]` се изпълнява винаги след като е проверено, че `square` все още е на дъската. Това поведение ни дава възможността да премахнем проверката `square < board.count` от предният пример. 321 | 322 | 323 | Нека сега да представим и възможността за дефиниране на преизползваеми фрагменти от програми, които можем да ползваме многократно. 324 | -------------------------------------------------------------------------------- /Лекция-03.md: -------------------------------------------------------------------------------- 1 | # Лекция №3 2 | -- 3 | 4 | В тази лекция научаваме за фукнциите. Допълнително задълбаваме в операторите, които са специален вид функции и представяме начин как да дефинираме собствен тип (структура). 5 | 6 | Преди да започнем с функциите и тяхните особенности, е добре да можете да отговорите на следните въпроси. 7 | 8 | ## Въпроси от предишната лекция 9 | 10 | Следват няколко основни въпроса, които маркират основните ключови моменти от предишната лекция. 11 | 12 | - Какво е характерно за `if` оператор в `swift`? 13 | - Какво е условен оператор `switch` 14 | - Какво са последователности? 15 | - Кои са основните цикли? 16 | 17 | ## Функции 18 | Как се справяме с големите програми? 19 | 20 | Преди време, когато компютрите са били големи чудовищини машини, отнемащи пространство повече от една нормална стая (~15 м. кв.), изчислителната им мощ е била хиляди пъти по-малка от тази на съвременните "умни" телефони. Размера на оперативната им памет е бил малък, много по-малък от 1 МB (за справка, днешните домашни компютри имат средно около 4 до 8 GB, 1GB = 1024 MB). Това налага ограничение на размера на програмите, които могат да се пишат, за да могат да се управляват от хардуера. Трудността в писането на компютърни програми по това време идва и от липсата на хубави програмни езици. Какво поражда липсата на хубави програмни езици по това време - липсата на [компилатори](https://en.wikipedia.org/wiki/History_of_compiler_construction#First_compilers). т.е. преди всичко се е пишело на по-ниско ниво. Отнемало много време и е било трудно да се следи за корекността на програмите, понеже кодът е бил сложен за четене и анализ от програмистта, но пък е бил по-лесен за превод към устройстовото. Днес инструкциите, до които се превежда написаният от нас код на Swift, се наричат инструкции на ниско ниво, асемблер и машинен език. Те остават скрити за съвременният широк кръг от програмисти, но това са били първите варианти на езиците за прогрмаиране. 21 | С течение на времето, сложността на програмите започва да нараства и нуждата от нови парадигми в програмирането ражда нови езици и начини на организране на код-а. От програмиране на Асемблер се минава към проргамни езици, които се компилират. Има движение, което издига функционалните езици. Това е друга категория програмни езици, които поритежават хубави логически свойства в сравнение с класическите императивни езици. Последните еволюират, като се появява Обектно ориентираният подход (C++). Можем да говорим много за [историята на програмните езици](https://en.wikipedia.org/wiki/History_of_programming_languages), но е важно да отбележим, че се заражда едно движение за презиползване на вече написаните неща. Т.е. появава се процес по надграждане, какъвто има и в научните среди в различните индустрии. Натрупват се знания, които тласкат прогреса и човечеството напред. 22 | 23 | В тази статия ще се запознаем с функциите в Swift, които ни позволяват да преизползваме вече написан код в нашите програми. Те ни дават възможността да решим общ проблем еднократно и в последствие да приложим решението на много места, спестявайки време, съкращавайки дължината на програмата, подобрявайки нейната корекност (по-малко код => по-малка вероятност за бъгове). 24 | 25 | Как се дефинират функции в Swift? Ще започнем с базов пример - функция, която изпълнява набор от стъпки. В последствие ще разширим примера докато достигнем пълния потенциал на функциите като изразно средство. 26 | 27 | ```swift 28 | //Нека да напишем първата функция, която обединява няколко действия. 29 | func severalActions() { 30 | print("Action 1") 31 | print("Action 2") 32 | print("Action 3") 33 | } 34 | 35 | //тук ще активираме нашата функция. Може да мислим, че я "извикваме" (call) 36 | severalActions() 37 | severalActions() 38 | ``` 39 | 40 | Примерът по-горе прави 3 последователни извиквания на функцията `print()` с различни стойности. Дефиницията на функцията сама по себе си определя какво ще прави дадена функция, но тя не се активира автоматично. Трябва да направим правилното "извикване". 41 | 42 | Ще опишем общия вариант на функция в Swift. 43 | 44 | ```swift 45 | func functionName(labelName variableName:String) -> String { 46 | let returnedValue = variableName + " was passed" 47 | return returnedValue 48 | } 49 | 50 | //ето и извикването на функцията 51 | functionName(labelName: "Nothing") 52 | ``` 53 | 54 | Всичко започва с думата `func`, която е запазената дума в Swift за деклариране на функция. След това следва името на функцията - `functionName`, като тук следваме правилата за име на променлива, като внимаваме да няма многозначност (т.е. да не се повтаря името с име на променлива и някоя друга запазена дума!). Продължаваме с параметрите, които може да са повече от един. Всеки параметър има име(label ), което се използва при извикване на функцията и име (variable), което се среща в тялото на функцията. Следва запазеният символ `->` и типа на връщания резултат. После в `{ }` е отделено тялото на функцията, като трябва да има `return` случай, който указва каква стойност ще връща функцията (може да е конкретна стойност от обявения по-горе тип, може да е и променлива или израз, стига оценяването му да дава правилния тип стойност). 55 | 56 | Ако погледнем приемра по-горе, виждаме, че функцията ще връща `String` стойност. Тя получава един параметър, който трябва да бъде подаден със своето име `labelName:`. Резултатът от извикването на функцията се запомня в константата `resultOfFunctionCall` и е от тип __String__, както и типа на връщания резултат от функцията. 57 | 58 | Нека да представим няколко разновидности на тази функция. 59 | 60 | ```swift 61 | //вижте разновидностите на функцията 62 | 63 | //ако променим името на аргумента, swift счита това за нова различна функция. Т.е. имената на аргумента 64 | func functionName(labelNameNew variableName:String) -> String { 65 | let returnedValue = variableName + " NEW was passed" 66 | return returnedValue 67 | } 68 | 69 | //когато изпуснем името на аргумента, името на променливата се изпозлва за негово име. т.е. двете съвпадат 70 | func functionName(variableName:String) -> String { 71 | let returnedValue = variableName + " was passed. v2" 72 | return returnedValue 73 | } 74 | ``` 75 | 76 | _Какво ни прави впечатление?_ Променливите имат имена. 77 | 78 | Имената на променливите са нещо нетрадиционно за класическите езици като Java, C, C++. Ако искаме да симулираме старото поведение, трябва да използваме `_`(undescore) - долна черта (wildcard), за да маркираме името на параметара като празен. Вече сме запознати с този символ. Той влизаше в полза, когато запазваме стойността в променлива, която няма участие в кода, който следва. Подобна е и логиката тук. Не ни трябва име на параметъра. 79 | 80 | _Какви са останалите случаи, когато имаме параметър с име?_ Името на параметъра участва в крайното име на функцията. Това позволява, да имаме функции с еднакви имена, но с различни имена на параметри (примерите следват). (В другите езици е важно и типовете да са различни или броят на променливите да е различен, т.е. за да имаме правилен overloading. - Компилаторът подбира правилната функция на базата на типовете на данните, които са използвани при извикването й.) 81 | 82 | Нека сега да разгледаме различните типове функции: 83 | 84 | ### 1. функции без параметри, които връщат резултат 85 | 86 | ```swift 87 | //функция, която връща резултат 88 | func seven() -> Int { 89 | return 7 90 | } 91 | 92 | let s = seven() 93 | print("Това е числото \(s).") 94 | ``` 95 | 96 | Всяка функция може да връща резултат от определен тип или да не връща резултат. При дефинирането й ние решаваме това. В примера по-горе става ясно, че функцията връща резултат, но той не зависи от парамтерите в конкретния случая. Често връщаният резултат зависи от параметрите, но понякога зависи от текущото състояние на системата (устройството, на което се изпълнява кодът). Примерно: можем да напишем функция, която връща различни стойности в зависимост от това дали кодът се изпълнява на мобилно устройство или на настолен компютър. 97 | 98 | ### 2. функция с параметри (един или повече, но без да връща резултат) 99 | 100 | ```swift 101 | //сума на две числа 102 | func printSum(a:Int, b:Int) { 103 | print("Sum \(a) + \(b) = \(a + b)") 104 | } 105 | 106 | //извикване на функцията 107 | printSum(a:3, b:5) 108 | ``` 109 | Тези функции се използват, за да изпълнят някакво действие или да представят информация на потребителя. Хубаво е да използваме имената на параметрите, за да можем да представим по-изчерпателно какво ще прави тази функция пред разработчика в момента на използване. 110 | 111 | _Публичните функции, които можем да използваме при разработката на мобилни приложения за iOS следват това правило и са един прекрасен пример за това как е правилно да се именуват функциите и параметрите, така че да няма излишни думи, но и да е напълно разбираемо._ 112 | 113 | ### 3. функция с няколко резултата 114 | 115 | За да върнем повече от един тип резултат, ние трябва да използваме N-торки (tuples). Това са пакетчета от няколко различни типа данни, които са окомплектовани в едно. Те може да имат имена, асоциирани с всеки член на N-торката. Ако такива имена липсват, тогава можем да използваме индексите. Данните в пакетчето се предават по стойност. Това означава, че те се копират в паметта (в следващата лекция ще разгледаме разликата между референтините типове (reference types) и тези, които се копират по стойност (value types)). Нека да видим няколко примера, за да знаем как да ги ползваме: 116 | 117 | ```swift 118 | /* N-торки */ 119 | let person = (name: "Иван", familyName: "Петров", age: 25) 120 | let p:(String, String, age: Int)? = person 121 | print("Здравей, \(person.name)!") 122 | 123 | print("Здравей, г-н \(person.familyName)!") 124 | 125 | print("Здравей, г-н \(person.1)!") 126 | if let pp = p, pp.age > 20 { 127 | print("Г-н \(pp.1), Вие сте на \(pp.age) години.") 128 | } 129 | ``` 130 | 131 | Ето и пример за функция, която ги използва активно. 132 | 133 | ```swift 134 | func maxItemIndex(numbers:[Int]) -> (item:Int, index:Int) { 135 | var index = -1 136 | var max = Int.min 137 | 138 | for (i, val) in numbers.enumerated() { 139 | if max < val { 140 | max = val 141 | index = i 142 | } 143 | } 144 | 145 | return (max, index) 146 | } 147 | 148 | let maxItemTuple = maxItemIndex(numbers: [12, 2, 6, 3, 4, 5, 2, 10]) 149 | if maxItemTuple.index >= 0 { 150 | print("Max item is \(maxItemTuple.item).") 151 | } 152 | ``` 153 | 154 | ### 4. функция с __inout__ параметри 155 | 156 | ```swift 157 | func updateVar(oldVariable x: inout Int, newValue: Int = 5) { 158 | x = newValue 159 | } 160 | 161 | var ten = 10 162 | print(ten) 163 | updateVar(oldVariable: &ten, newValue: 15) 164 | print(ten) 165 | ``` 166 | 167 | В примера по-горе виждаме още един начин как да връщаме "резултат" от функция. Трябва да спазим някои основни правила. __inout__ се поставя преди типа параметъра `x: inout Int`, когато дефинираме функцията. В момента на извикване на функцията можем да подаваме само променливи където се изискват __inout__ параметри. Това е нужно, за да може функцията да запише стойността в тази част от паметта. Това се отбелязва визуално с `&` пред името на променливата. 168 | 169 | Интересно е да се отбележи, че всички променливи, които са част от декларацията на функцията и не са __inout__, са константи. Т.е. не може в тялото на функцията да бъде присвоена стойност, освен ако не са обявени като __inout__. Ето и един пример: 170 | 171 | ```swift 172 | var sumAB:Int = 0 173 | sum(a:3, b:4, in:&sumAB) 174 | ``` 175 | 176 | Променливата `sumAB` трябва да е инициализирана преди да се подаде като параметър. Предаването й става по референция. В действителност се подава мястото в паметта, което може да бъде използвано от функцията, да запише стойсност. 177 | 178 | Кои функции са по-добри? Тези, които връщат няколко резултата или тези които променят стойностите на параметрите си? И двата случая имат предимства и недостатъци. Тези, които връщат няколко параметъра, използват повече място в паметта, но не изискват допълнителна подготовка. Подаваме си параметрите и си получаваме резултата. Те не променят "средата" в която се изпълняват. 179 | Алтернативата - функции с __inout__ параметри - могат да променят стойността на параметрите си. Трябва да внимаваме в подаването на правилните неща (тук компилатора ни помага). Също така, трябва да знаем кога една функция може да направи промяна с данните и да отчитаме съответнана промяна. Тази техника има предимство, ако се борави с големи обеми от данни. Ще разгледаме доста примери след като се запознаем с референтните типове през следващите лекции. 180 | Примери: 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | Преминаваме към едни по-специални функции - оператори. Те ни позволяват да дефинираме операции над стандартните типове и да добавяме операции над типове, дефинирани от нас. 191 | ## Оператори 192 | 193 | 194 | 195 | 196 | 197 | 198 | ### prefix и postfix 199 | Класовете и структурите могат да имплементират стандартните унарни оператори. Този тип оператори въздействат само на един обект от там и унарни. Това става, като се използват запазените думи преди записа на функция (func): 200 | 201 | -**prefix**, ако операторът се изписва преди името на инстанцията на класа или структурата (Например: +x) 202 | 203 | -**postfix**, ако операторът се изписва след името на инстанцията на класа или структурата (Например: x+). 204 | 205 | ### infix 206 | Класовете и структурите могат да дефинират собствени оператори. 207 | В примера по-долу показваме как да добавим аритметичният оператор за умножение (*) към String. Този тип оператор се нарича бинарен, тъй като въздейства на два обекта и е от тип infix, защото се изписва между тях. 208 | 209 | ```swift 210 | extension String { 211 | static func * (left: String, right: Int) -> String { 212 | if right <= 0 { 213 | return "" 214 | } 215 | 216 | var result = left 217 | for _ in 1..", "&", "|", или "~". Също така и Unicode символи от ["Mathematical operators"](https://en.wikipedia.org/wiki/Mathematical_Operators) сета. 254 | 255 | 256 | ### Приоритети на операторите 257 | 258 | 259 | 260 | 261 | Всичко което научихме за операторите може да бъде пиложено над различни типове от данни, които са вече дефинирани или ние ще дефинираме. За да можем да създаваме по-сложни данни, трябва да научим повече за структурите. 262 | 263 | ## Структури 264 | 265 | Примери: -------------------------------------------------------------------------------- /Лекция-04.md: -------------------------------------------------------------------------------- 1 | # Лекция №4 2 | -- 3 | 4 | В тази лекция, ще разгледаме структурите като тип данни в детайли. Ще решим и няколко задачи с рекурсия. 5 | 6 | ## Въпроси от предишната лекция 7 | 8 | Следват няколко основни въпроса, които целят да проверят и затвърдят основните ключови моменти от предишната лекция. 9 | 10 | - Какво помним за функциите в `swift`? 11 | - за имената 12 | - за параметрите 13 | - за връщаните типове 14 | - за ? 15 | 16 | - Какви видове оператори има? 17 | 18 | 19 | ## Комбинирани типове данни 20 | 21 | До сега се запознахме с основните типове данни в програмния език Swift - `Int`, `Double`, `String`, `Bool`, `Array`. Това ни дава възможността да решаваме много задачи, но е време да въведем по-сложни типове данни, които ще ни позволят да моделираме сложни системи от реалността с лекота в нашите програми. Например, за да реализираме система за онлайн магазин - ние трябва да можем да представим всеки един продукт. Един продукт има различни характеристики. Нека да изброим някои основни - име на продукта (`String`), цена на бройка (`Double`), наличен ли е или не (`Bool`). Със знанията ни до момента най-близкият начин за моделиране е да използваме N-торките (вързопчетата с данни от различни типове). Нека да въведем един възможен начин да дефинираме сложен тип от данни. Такъв който да има няколко характеристики и възможни операции над него. 22 | 23 | ## Структури 24 | 25 | Структурите са основна единица носител на данни в програмите. По значимост те се нареждат до функциите, основните типове и изброимите типове. По-късно ще се запознаем с класовете, които са близки до структурите, но имат някои основни различия. 26 | 27 | Структурите са сложен тип от данни, който обединява няколко различни типа данни (характеристи) и дава възможност за лесно боравене с тях. До сега сме се запознали с N-торките (tuples), но към структурите можем да добавяме операции, т.е. можем да определяме функции, които са част от интерфейса на структура, която да борави с данните в самата структура. Функциите, които познаваме до момента, могат да изпълняват тези задачи, но е много по-лесно и интуитивно да се разсъждава от гледна точка на структурата в сравнение с глобалната гледна точка, която е типична за глобалните функции. 28 | 29 | При някои програмни езици декларацията на интерфейса на структура и дефиницията (логиката и организацията) стоят в различни файлове. Това не е характерно за Swift. Тук всичко се намира в един файл. 30 | 31 | > Интерфейс на тип данни е съвкупността от полета и методи, които определят възможните начини за работа с конкретния тип. 32 | 33 | Нека да да разгледаме един пример за структура, която може да опише една стока от онлайн магазин. 34 | 35 | ```swift 36 | struct Merchandise { 37 | var name: String 38 | var pricePerUnit: Double 39 | var isAvailable: Bool 40 | } 41 | ``` 42 | 43 | > Интерфейсът се състои от 3-те различни полета до които имаме достъп чрез точкова `.` нотация. 44 | 45 | Ето и къс фрагмент, който показва дефиниранта горе структура в действие. 46 | 47 | ```swift 48 | var phone:Merchandise = Merchandise(name:"Nokia", pricePerUnit:200, isAvailable:false) 49 | 50 | func printInfoFor(merchandise:Merchandise) { 51 | print("Product : \(merchandise.name) - 52 | \(merchandise.pricePerUnit) - 53 | \(merchandise.isAvailable ? "available" : "unavailable")") 54 | } 55 | 56 | printInfoFor(merchandise: phone) 57 | //Product : Nokia - 200.0 - unavailable 58 | ``` 59 | 60 | Това, което забелязваме, е че можем да се обръщаме към всяко поле с неговото име, т.е. можем да си мислим, че декларирайки променлива от тази структура, ние получаваме място в паметта, което има няколко адреса. Използвайки нотацията с . можем да достъпваме всяко под поле от променливата. Ето и един пример как можем да боравим с отделните части на структурата. 61 | 62 | Създаваме променлива `phone` от вече дефинираната структура `Merchandise` (стока). В помощната функция `printInfoFor(merchandise:Merchandise)` ние използваме нотацията с . `merchandise.name`, за да достъпим името на стоката. 63 | 64 | Впечатление прави, че при инициализиране на променливата от новия тип имаме функция, в която можем да зададем стойност на всяко поле (елемент от структурата). 65 | 66 | Не можем да създадем променлива от типа `Merchandise`, която да не е инициализирана без да подадем данни. Това е характерно за Swift - всяко поле трябва да има стойност. 67 | 68 | Тази специална функция ще наричаме конструктор или `init` метод. Метод за инициализиране на променливи от този тип. 69 | 70 | Когато опишем полетата (елементите) на една структура, тогава имаме неявен конструктор, който включва всички полета като аргументи. Ето какво получваме на готово в случая с нашия тип: 71 | 72 | ```swift 73 | init(name: String, pricePerUnit: Double, isAvailable:Bool) { 74 | self.name = name 75 | self.pricePerUnit = pricePerUnit 76 | self.isAvailable = isAvailable 77 | } 78 | ``` 79 | 80 | Ако се сблъсквате с подобен код за пръв път, тогава вероятно вече сте си задали следните въпроси: 81 | 82 | _Какво означава `self`?_ 83 | 84 | `self` е запазена дума, която ни позволява да се обръщаме към мястото в паметта, където се съхраняват данните. Посредством `self`, можем да адресираме отделните полета и да боравим със стойностите им. В горния пример, правим просто копиране на данните в полетата от структурата. Интересно е да отбележим, че `self` се среща в други езици както и `this`. `this` не е запазена дума в езика Swift. 85 | 86 | _`init` функция ли е?_ 87 | 88 | Приемаме, че 'init' е специална функция (както я определихме по-горе - конструктор). Тя няма тип на резултата, който връща и не използва запазената дума `func`. Можем да имаме много разновидности на `init`. 89 | 90 | _Защо не извикваме явно функцията `init`?_ 91 | 92 | Функцията се извика, но няма пряко съответствие между името й и извикването й. За да я извикамеq трябва да използваме името на типа данни или в нашия случай - името на структурата. В този случай, цялостния вид на структурата ще изглежда така: 93 | 94 | ```swift 95 | struct Merchandise { 96 | var name: String 97 | var pricePerUnit: Double 98 | var isAvailable: Bool 99 | 100 | init(name: String, pricePerUnit: Double, isAvailable:Bool) { 101 | self.name = name 102 | self.pricePerUnit = pricePerUnit 103 | self.isAvailable = isAvailable 104 | } 105 | } 106 | ``` 107 | 108 | Ако искаме да имаме конструктор без параметри, трябва да декларираме такъв. Това е друга "версия" на конструктора - такава без параметри. Ето как можем да направим това: 109 | 110 | ```swift 111 | init() { 112 | self.name = "Noname" 113 | self.pricePerUnit = 0 114 | self.isAvailable = false 115 | } 116 | ``` 117 | 118 | Нужно да се отбележи, че всяко поле трябва да има стойност или да му бъде присвоена такава в конструктора `init` метода. _Изключение са полетата със стойност по подразбиране._ Можем да дадем такава на всяко поле в нашата структура. Ето как можем да си спестим конструктора без параметри: 119 | 120 | ```swift 121 | struct Merchandise { 122 | var name: String = "noname" 123 | var pricePerUnit: Double = 0.0 124 | var isAvailable: Bool = false 125 | } 126 | ``` 127 | 128 | След като имаме такъв конструктор можем да го използваме по следния начин: 129 | 130 | ```swift 131 | var newPhone:Merchandise = Merchandise() 132 | 133 | printInfoFor(merchandise: newPhone) 134 | //Product : Noname - 0.0 - unavailable 135 | ``` 136 | 137 | Логично е да си зададем въпроса: ако имаме инициализиране, какво става, когато освобождаваме паметта. Процеса можем да наричаме деинициализация. Структурите __нямат__ деинициализиращ метод `deinit`. Ако се опитате да декларирате такъв, компилаторът ще ви подскаже, че това е метод, който се среща само при класовете. Така че, когато ползваме структури, трябва да мислим за тяхната иницализация, а всичко останало оставяме на Swift. 138 | 139 | Нека сега разгледаме как можем да добавим операции (или собствени функции) към една структура. Операциите са функции, които ни позволяват да боравим по-лесно със структурите. 140 | 141 | Ще добавим функция `printInfo`, която отпечатва данните за един продукт. Ще взаимстваме логиката от вече познатата ни функция `func printInfoFor(merchandise:Merchandise)` 142 | 143 | ```swift 144 | func printInfo() { 145 | print("Product : \(name) - \(pricePerUnit) - \(isAvailable ? "available" : "unavailable")") 146 | } 147 | ``` 148 | 149 | Цялостния вид на нашата структура е следния: 150 | 151 | ```swift 152 | struct Merchandise { 153 | var name: String 154 | var pricePerUnit: Double 155 | var isAvailable: Bool 156 | 157 | init() { 158 | self.name = "Noname" 159 | self.pricePerUnit = 0 160 | self.isAvailable = false 161 | } 162 | 163 | init(name: String, pricePerUnit: Double, isAvailable:Bool) { 164 | self.name = name 165 | self.pricePerUnit = pricePerUnit 166 | self.isAvailable = isAvailable 167 | } 168 | 169 | func printInfo() { 170 | print("Product : \(name) - \(pricePerUnit) - \(isAvailable ? "available" : "unavailable")") 171 | } 172 | } 173 | ``` 174 | 175 | Нека сега да сравним тази функция с горната. Нямаме резултат, който да връщат. Това е еднакво. Едната взима параметър, а другата не взима. Това е така, защото функцията, която е част от структурата, може да оперира с данните в структурата. Това обяснява защо и от къде идват полетата `name`, `pricePerUnit`, `isAvailable`, респективно данните в тях. Да, можем да използваме `self` пред всяко поле, т.е. `self.name` и `name` означават едно и също. 176 | 177 | Множеството от полета и функции (операции) над структурата определят интерфейса за боравене с този тип данни. 178 | 179 | Можем да използваме всички неща, които знаем за функциите от предходната лекция и да ги прилагаме, когато дефинираме функции като част от структура. Можем да имаме __inout__ параметри. Може да имаме неопределен брои параметри, използвайки __...__ след типа на променливата. Променливата се преобразува до масив от параметри, от типа преди __...__. Ето и един пример за такава функция: 180 | 181 | 182 | ```swift 183 | func maxValue(params numbers:Int...) -> Int { 184 | var max = Int.min 185 | for v in numbers { 186 | if max < v { 187 | max = v 188 | } 189 | } 190 | 191 | return max 192 | } 193 | ``` 194 | 195 | Можем и да връщаме резултат от определен тип. 196 | 197 | Нека направим леко отклонение и да разясним някои базови типове като `Array`. Това е колекция от еднакви по тип данни. 198 | 199 | Ето пример за списък от цели числа: 200 | 201 | ```swift 202 | let integers = [1, 2, 3] 203 | print(integers) 204 | let reversed = integers.reversed() 205 | //print(reversed) //this is from strange type 206 | print([Int](reversed)) 207 | 208 | //[1, 2, 3] 209 | //[3, 2, 1] 210 | ``` 211 | 212 | Тук извикваме функцията `reversed()`, която връща копие на списъка, но елементите са подредени в обратен ред. 213 | 214 | 215 | ### Предаване по стойност 216 | 217 | Структурите, както и стандартните типове данни, които познаваме, се предават по стойност, когато подаваме параметри на функция. 218 | 219 | _Какво е предаване по стойност?_ 220 | 221 | Това означава, че когато имаме нова променлива и направим присвояване, компютърът извършва копиране на данните в паметта. Ето и един пример, който може да онагледи това поведение: 222 | 223 | ```swift 224 | var a = 1 225 | var b = a 226 | print("Initial values:") 227 | print("a = \(a)") 228 | print("b = \(b)") 229 | 230 | a += 5 231 | print("Modify a += 5.") 232 | print("a = \(a)") 233 | print("b = \(b)") 234 | 235 | func modify(value:inout Int) { 236 | value = 3 237 | } 238 | 239 | print("Example with functions.") 240 | print("b = \(b)") 241 | modify(value: &b) 242 | print("Modify b = 3.") 243 | print("b = \(b)") 244 | 245 | //изход в конзолата: 246 | //Initial values: 247 | //a = 1 248 | //b = 1 249 | //Modify a += 5. 250 | //a = 6 251 | //b = 1 252 | //Example with functions. 253 | //b = 1 254 | //Modify b = 3. 255 | //b = 3 256 | ``` 257 | 258 | Същият пример, реализиран и с променлива от тип структура (тип, който ние сме си дефинирали по-горе): 259 | 260 | ```swift 261 | var nokia = Merchandise(name: "Nokia 3310", pricePerUnit: 100, isAvailable: false) 262 | nokia.printInfo() 263 | var nokiaDiscounted = nokia 264 | nokiaDiscounted.pricePerUnit *= (100.0 - 20.0) / 100.0 // -20% 265 | nokiaDiscounted.isAvailable = true 266 | nokiaDiscounted.printInfo() 267 | nokia.printInfo() 268 | ``` 269 | 270 | Какво остава да научим за структурите? 271 | 272 | `Subscripts` - достъп до елементи, използвайки синтаксиса `[]` 273 | 274 | Индексирането е лесен начин за достъп до елементите на колекция, списък или последователност от елементи. Употребява се при работата с колекции от стандартните типове, като масив или речник(dictionary). 275 | 276 | ```swift 277 | var goods = [Merchandise(), Merchandise(), Merchandise()] 278 | goods[1].isAvailable = true // достъп до първия елемент 279 | ``` 280 | 281 | Класическата форма е: 282 | 283 | ```swift 284 | subscript (index:Int) -> Any { 285 | get { 286 | //връщаме подходяща стойност 287 | } 288 | 289 | set(newValue) { 290 | //променяме данните в структурата 291 | } 292 | } 293 | ``` 294 | 295 | Имаме `get` и `set` част. Като различна част от кода се задейства в зависимост от това дали ще присвояваме стойност с `=` или ще използваме стойността връщана от `get`. Можем да дефинираме произволни форми, които взимат параметър от тип различен от цяло число. 296 | 297 | Примерно: 298 | 299 | ```swift 300 | subscript (index:(Int,Int)) -> Any { 301 | return "tuple nothing" 302 | } 303 | ``` 304 | 305 | Достъпът до елементите може да има различно значение в зависимост от типа на данните, върху които се прилага. 306 | 307 | Трябва да отбележим, че `subscript` е специален вид функция, която не позволява употребата на __inout__ параметри и параметри със стойности по подразбиране. 308 | 309 | ### Extensions или разширения 310 | 311 | Разширенията (Extensions) дават възможност да добавяме допълнителна функционалност към структури и класове. Можем да добавим: 312 | 313 | * методи към обекта (функции) или към типа данни (static) 314 | * да дефинираме достъп чрез subscript - [] 315 | * добавяне на __вложените типове__ 316 | 317 | Повече детайли за `extenssions` има в следващата лекция. Споменаваме ги тук, за да можем да ги изпозлваме със структурите. 318 | 319 | Протоколи - лесен начин да дефинираме интерфейс. Приложими са към структури, към класове и към изброими типове. За тях ще кажем повече в следващата лекция. 320 | 321 | Обект от тип структура, който е присвоена на константа, използвайки `let`, не може да модифицира своите пропъртита, т.е. това е константна структура. Обясняваме го с това, че когато един тип, предаван по стойност, е използван, за да инициализра константа, тогава всички негови член данни стават константни, независимо дали са били променливи. 322 | 323 | Ето и пример: 324 | 325 | ```swift 326 | let g1 = Merchandise() 327 | //това предизвиква грешка 328 | g1.name = "New name" //note: change 'let' to 'var' to make it mutable 329 | ``` 330 | 331 | ### Модифициращи методи 332 | 333 | Структурите имат член данни, които можем да достъпваме с точкова нотация `newPhone.printInfo()`. Ако искаме да напишем функция, която модифицира член данните, трябва да използваме запазената дума `mutating`, за да подскажем на компилатора, че дадената функция ще модифицира структурата, използвайки `self`. Това става, като слагаме `mutating` преди `func`. 334 | 335 | ### Пропъртита 336 | 337 | Член данните които дефинираме във нашата структура се наричат пропъртита. Те са една от основните черти на интерфейса определящ нашата структура. Такива пропъртита можем да наричаме член данни или пропъртита съхраняващи данни. Това не е единствения вид пропъртита. 338 | 339 | > Всички пропъртита можем да ги достъпваме с точковата нотация. 340 | 341 | Пропъртитата може да са променливи (които да променяме) или константи - такива, които не можем да променяме. Те могат да бъдат изчислими и да се изчисляват динамично. 342 | 343 | Изчислимите пропъртита са такива, които позволяват лесен достъп до данните, като можем да добавим логика, която да извършва допълнителни действия. Тези пропъртита могат да записват данни в други член данни, но самите те добавят допълнителна логика и улесняват достъпа до данните. 344 | 345 | Съшествуват и мързеливите пропъртита, които се оценяват, когато се използват за пръв път. Те не може да са константи `let`. 346 | 347 | Пропъртита, които са част от обект, дефинираме без да добавим ключовата дума __static__. Ако я използваме, тогава пропъртита стават част от типа (от клас или структурата). 348 | 349 | ### Изчислими пропъртита 350 | 351 | Това са пропъртита, които се изчисляват в момента на обръщение към тях. По идея приличат на функция, която връща резултат, но няма параметри. Разликата е в това, че в кода изглеждат като нормални пропъртита. Ето и един пример, който показва такива пропъртита. 352 | 353 | _Изчислимото пропърти е добавено в отделно разширение._ 354 | 355 | ```swift 356 | extension Merchandise { 357 | //изчислими пропъртита 358 | var incomePerUnit:Double { 359 | get { 360 | return self.pricePerUnit * 0.2 361 | } 362 | } 363 | } 364 | 365 | var macBookPro = Merchandise(name:"MacBook Pro 15\"", pricePerUnit: 3200.0, isAvailable: true) 366 | print("Income per product: \(macBookPro.incomePerUnit)") 367 | ``` 368 | 369 | #### Изчислими пропъртита с допълнителна логика 370 | 371 | Пропъртитата може да връщат стойност, но могат и да приемат стойност. Добре е да знаем, че можем да предефинираме стандартното им поведение, Т.е. може да имаме пропърти, което да записва стойности в различни части от структурата, както и да връща резултат (да бъде изчислимо), базиран на няколко член данни (други пропъртита или функции). Ето един пример: 372 | 373 | ```swift 374 | extension Merchandise { 375 | var realPricePerUnit:Double { 376 | get { 377 | return self.pricePerUnit * 0.8 378 | } 379 | 380 | set (newValue) { 381 | self.pricePerUnit = newValue * 1.25 382 | } 383 | } 384 | } 385 | ``` 386 | 387 | Има и къс вариант на тази дефиниция: 388 | 389 | ```swift 390 | extension Merchandise { 391 | var realPricePerUnit:Double { 392 | get { 393 | return self.pricePerUnit * 0.8 394 | } 395 | 396 | set { 397 | //newValue е името на параметъра. Типът е Double 398 | self.pricePerUnit = newValue * 1.25 399 | } 400 | } 401 | } 402 | ``` 403 | 404 | Когато имаме само `get` метод, тогава говорим за read–only пропъртита. Такива, които можем само да използваме като част от израз, но не и да им присвояваме някаква стойност. Такъв пример имаме по-горе `incomePerUnit`. 405 | Можем да използваме по-кратък синтаксис, за да го запишем така: 406 | 407 | ```swift 408 | extension Merchandise { 409 | 410 | //изчислими пропъртита 411 | var incomePerUnit:Double { 412 | return self.pricePerUnit * 0.2 413 | } 414 | } 415 | ``` 416 | 417 | #### Мързеливи пропъртита 418 | 419 | Това са пропъртита, които са инициализирани при тяхната първа употреба. Те трябва да се маркират с ключовата дума `lazy`. Те трябва винаги да са променливи `var`. 420 | 421 | ```swift 422 | struct LazyStruct { 423 | var count: Int 424 | init (count:Int) { 425 | print("\(LazyStruct.self) се конструира чрез -> \(#function)") 426 | self.count = count 427 | } 428 | } 429 | 430 | struct NormalStruct { 431 | var count: Int 432 | init (count:Int) { 433 | print("\(NormalStruct.self) се конструира чрез -> \(#function)") 434 | self.count = count 435 | } 436 | } 437 | 438 | struct ExampleLazyProperty { 439 | lazy var s:LazyStruct = LazyStruct(count: 5) 440 | var normal:NormalStruct = NormalStruct(count: 10) 441 | var regularInt = 5 442 | 443 | init() { 444 | print("\(ExampleLazyProperty.self) се конструира чрез -> \(#function)") 445 | } 446 | } 447 | 448 | var lazyPropExample = ExampleLazyProperty() 449 | lazyPropExample.regularInt = 15 450 | print("Стойноста в нормалното пропърти 'regularInt' e \(lazyPropExample.regularInt)") 451 | print("Стойноста в нормалното пропърти 'normal' e \(lazyPropExample. normal.count)") 452 | print("Стойноста на мързеливото пропърти е \(lazyPropExample.s.count)") 453 | print("Стойноста в нормалното пропърти 'regularInt' е \(lazyPropExample.regularInt)") 454 | ``` 455 | 456 | Ако изпълним примера, виждаме че пропъртито `s` от тип `LazyStruct` не се инициализира незабавно след като сме инициализирали обекта. 457 | 458 | #### Наблюдатели на пропъртита 459 | 460 | Наблюдателите на пропъртита са функции, които ни позволяват да наблюдаваме промени в сойността на дадено пропърти и да реагираме на такива промени. 461 | 462 | > Може да се добавят наблюдатели към всички видове пропъртита освен към мързеливите - `lazy`. 463 | 464 | Има два основни вида наблюдатели: 465 | 466 | * `willSet` - извиква се преди да се запише новата стойност. Позволява ни да направим промени преди записване на стойността 467 | * `didSet` - извиква се след записване на новата стойност. Позволява ни да направим промени след запива на новата стойност. 468 | 469 | ```swift 470 | struct Merchandise { 471 | var name: String 472 | var pricePerUnit: Double { 473 | willSet { 474 | print("Сменяме цената с нова \(newValue)") 475 | print("Старата цена е \(pricePerUnit)") 476 | } 477 | didSet(oldPrice) { 478 | if oldPrice > pricePerUnit { 479 | print("Намаление!") 480 | } else { 481 | print("Всичко поскъпва!") 482 | } 483 | } 484 | } 485 | var isAvailable: Bool 486 | } 487 | ``` 488 | 489 | Можем да именуваме променливата, която получава в наблюдателите. Съществена е разликата, че когато сме в `willSet` променливата съдържа новата стойност, а чрез пропъртито можем да достъпим старта. 490 | В `didSet` нещата стоят огледално. Новата стойност е вече в пропъртита, а старта е в параметъра. 491 | 492 | Разгледаният механизъм за наблюдение на стойностите на пропъртита тук е приложим и за класове, но с леки уточнения. 493 | 494 | #### Видимост на член-данните и функциите 495 | 496 | Чрез разлини модификатори можем да ограничим нивото на достъп до части от нашите типове. 497 | 498 | Когато пишете само едно приложение всички типове данни и тяхните елементи са в един модул. Те имат подразбиращо се ниво на защита и могат да се изпозлват само в рамките на този модул. Това опростява многократно задачата с дефинирането на правата за достъп. 499 | 500 | > Модул е множество от класове, структури, функции съпровожащите ги типове данни. Те са логически свързани и единни. 501 | > 502 | > Модул е едно приложение. Модул е и един фреймуорк (framework). 503 | 504 | Едим модул е изграден от множество файлове със код, но може и да включва и ресурси. Както ни е ясно, често един тип се дефинира в отделен файл, но е възможно да имаме няколко типа дефинирани в един файл. 505 | 506 | > Често срещана практика е да се разделя дефиницията в няколко файла чрез `extensions`. 507 | 508 | Нивата на достъп са пет, като ще ги изброим от най-отворените (най-свободните) към най-затворените (най-ограничените): 509 | 510 | - `open` - използва се когато се дефинира интерфейса на `framework`. Разликата между `open` и `public` е в това, че класовете маркирани като `open` могат да се наследяват извън рамките на един модул. 511 | - `public` - използва се когато се дефинира интерфейс на `framework` и позовлява директното изпозлване на типа данни. Не не е позволено да се наследяват извън рамките на модула. Наследяването е възможно само в рамките на текущия модул. Това е валидно и за всички по-ограничени нива на достъп. 512 | - `internal` - ограничава достъпа до рамките на текущия модул. Изполва се за дефиниране на вътрешната структура и скриване на детайлите и от външните модули. Това е нивото за достъп, което се подразбира, ако не са ползвани други модификатори. 513 | - `fileprivate` - достъпът е ограничен само до нивото на файлва, в който е дефинира типа или функцията. Може да се използва за да се скрие вътрешната организация на даден тип за остналата част от модула. 514 | - `private` - пълно информационно скриване. Всичко извън рамките на дадената дефиниця няма достъп. 515 | 516 | Самите нива за достъп от горе `open`, `public`, `internal`, `fileprivate` и `private` са ключови думи, които използваме за да дефинираме нивото на достъп в типвое данни и тяхните член данни или методи. 517 | 518 | ```swift 519 | internal struct Merchandise { 520 | internal var name: String 521 | public var pricePerUnit: Double 522 | fileprivate var isAvailable: Bool 523 | 524 | private var realPricePerUnit:Double { 525 | get { 526 | return self.pricePerUnit * 0.8 527 | } 528 | 529 | set (newValue) { 530 | self.pricePerUnit = newValue * 1.25 531 | } 532 | } 533 | 534 | public func printInfo() { 535 | print("Product : \(name) - \(pricePerUnit) - \(isAvailable ? "available" : "unavailable")") 536 | } 537 | } 538 | ``` 539 | 540 | > Използването на `open` определя кои класове могат да бъдат изпозлвани и предполага, че това действие е обмислено детайлно и какви ще са последствията от него. 541 | > 542 | > Дизйана на API (на публичните интерфейс) не е лесна задача и трябва да се обмисля детайлно преди да се финализира. 543 | 544 | Когато сме в случа на N-торки(tuples), тогава нивото на достъп се определя от елемента с най-ниско ниво на достъп. При N-торки(tuples) няма как да се определи нивото на достъп експлицитно както при останалите типове данни. 545 | 546 | По подобен начин се определя и нивото на достъп за дадена функция. Взема се най-ниското нивото на достъп от всички аргументи и връщания тип. Тук обаче трябва експлицитно да го опишем. 547 | 548 | > Можем да изпозлваме и по-ограничаващо ниво на достъп, но не и по-свободно. 549 | 550 | ```swift 551 | internal func printInfo(m: Merchandise) { 552 | //... 553 | } 554 | ``` 555 | 556 | Ако се опитаме да ползваме `public` или `open` кода няма да се компилира. 557 | 558 | При наследяване, класът наследник не може да има по-свободно ниво на достъп понеже поризлиза от базовия клас, който му налага ограничението. Само достъпните елементи в съответния контекст могат да се предефинират. Тук зависи дали сме в един модул или в различни, в един файл или в различни. 559 | 560 | > Възможно е при предефиниране да се променя нивото на достъп към по-високо спрямо бащиния клас. 561 | 562 | При стандартните пропъртита: 563 | 564 | > Може да намаляваме нивото на достъп за setter и да имаме друго ниво да за достъп на getter-ите. 565 | > 566 | > Това е възможно дори и при нормалните член данни, където компилатора ни генерира set и get метод. Става с експлицитно обявяване на нивото на достъп `internal(set)`. 567 | 568 | ```swift 569 | struct Merchandise { 570 | public var name: String 571 | fileprivate(set) public var pricePerUnit: Double 572 | private(set) public var isAvailable: Bool = false 573 | } 574 | ``` 575 | 576 | Конструкторите трябва да са от същото или по-ниско ниво на достъп спрямо нивото на достъп на типа. Задължителните конструктури трябва да имат същото ниво на достъп както типа. Тук можем да приложим правилото която е валидно за функциите. Не може да има параметри, които да имат по-строго ниво на достъп спрямо конструктора. 577 | 578 | > Конструкторите по подразбиране имат същото ниво на достъп каквото е нивото на типа данни. 579 | 580 | ### Полезни ресурси 581 | 582 | __Ето къде можем да прочетем повече за дизайна на API-та и генерализирането на [функции](http://www.generic-programming.org/)__ 583 | 584 | __Тук може да намерим примерни [playground](https://github.com/kushtaneja/iPad_Swift_Playgrounds)-и__ 585 | 586 | 587 | ## Задачи върху рекурсия 588 | 589 | 1. Бързо повдигане на степен? 590 | 591 | 2. Задачи с рекурсия върху символни низове? 592 | 1. Да се напише рекурсивна функция, която определя дължината на символен низ 593 | 1. Да се напише рекурсивна функция, която намира позицията на първо срещане на символен низ в символен низ. 594 | 1. Да се напише рекурсивна фунцкия, която намира последното срещане на символен низ в друг символен низ. 595 | 596 | 6. Преброяване на цветовите фрагменти върху квадратна мрежа. Един фрагмен се простира на всички квадрати, които са съседни по хоризонтала или вертикала и имат един и същи знак. 597 | 598 | $$$$$$$$$$^^^^^ 599 | $$$$$оооо^^^^^^ 600 | $$$$$оо^^^^хххх 601 | ххххххххххххххх 602 | ххххххххххххххх 603 | 604 | отговор: 4 605 | 606 | > Решения на задачите може да намерите в репозиторито, в секцията `playgrounds`. -------------------------------------------------------------------------------- /Лекция-05.md: -------------------------------------------------------------------------------- 1 | # Лекция №5 2 | -- 3 | В тази лекция ще разгледаме класовете и ще ги съпоставим с наученото за структурите. Ще въвдем основните принципи на обектно-ориентираното програмиране (ООП). 4 | 5 | ## Въпроси от предишната лекция 6 | 7 | Следват няколко основни въпроса, които маркират ключови моменти от предишната лекция. 8 | 9 | - Какви видове са структурите? 10 | - Какви видове пропъртита знаем? 11 | - Какво са `init` методите? 12 | - Какво е `subscript`? 13 | - Как можем да променяме части от структурата? 14 | - Какво е предимството на `lazy`? 15 | 16 | ## Класове 17 | 18 | Към момента познаваме основните типове от данни като `Int`, `Double`, `Bool`, `Float`, `String`, изброимите типове, колекциите (`Array` и `Dictionary`) и структурите като тип в Swift. Всички тези типове са типове, които се предават по стойност (за повече информация, разгледайте [лекция 4](./2018-03-22-Swift-4-Лекция-4.md)). 19 | 20 | Днес ще се запознаем със значително по-различен тип данни, които имат различно поведение. Класовете и типовете, които ще дефинираме на базата на тях се наричат референтни типове. Ще стане ясно какво е характерно за тях и какви са основните разлики с типовете, предавани по стойност. 21 | 22 | За да декларираме собствен клас (тип данните), ние използваме запазената дума `class`. После даваме име на типа данни, което трябва е уникално. Прието е имената на класовете да започват с голяма буква, тъй като декларираме тип данни, подобен на `Int` или `String`. 23 | 24 | Ето и един клас: 25 | 26 | ```swift 27 | class Media { 28 | var name: String = "" 29 | var sizeInBytes: Double = 0.0 30 | } 31 | ``` 32 | 33 | Очевидната разлика между класовете и структурите е в запазената дума. 34 | 35 | Както и при структурите, така и тук можем да говорим за обекти от дефинирания тип, само когато направим променлива и я инициализираме, използвайки класа. Това изглежда така: 36 | 37 | ```swift 38 | let movie = Media() 39 | movie.name = "X-Men" 40 | print("Media name: \(movie.name)") 41 | ``` 42 | 43 | Използваме пропъртитата (член данните), използвайки точковата нотация, както и при структурите. 44 | 45 | _Интересно е, че ако опитаме да сменим типа от клас на структура, тогава кодът няма да работи. Причината за това се крие в разликата между референтните и стойностните типове._ 46 | 47 | Ако имаме структура и направим константа, в която съхраним обект от тип структура, тогава не можем да модифицираме пропъртитата, независимо от това какви са те - `var` или `let`. 48 | 49 | Това, обаче, не е вярно за класовете (виж примера по-горе). Нека си представим, че константата е свързана с точно конкретно място в паметта, но тази част от паметта (понеже е обект от референтен тип) сочи клетка, която може да се променя. 50 | 51 | Малко по-различно стоят нещата при структурите - константа съдържа директно копие на всички полета и така те не могат да бъдат променяни. 52 | 53 | Какво още можем да научим за референтните типове? 54 | 55 | * Само когато създаваме нов обект, се заделя ново място в паметта. 56 | * Когато имаме присвояване на променливи, тогава се получава само едно свързване на променливата с мястото в паметта, където се намира обекта. 57 | 58 | Пример: 59 | 60 | ```swift 61 | let ref = movie 62 | print("Media ref name: \(ref.name)") 63 | movie.name = "X-Men 2" 64 | print("Media ref name: \(ref.name)") 65 | 66 | //Media name: X-Men 67 | //Media ref name: X-Men 68 | //Media ref name: X-Men 2 69 | ``` 70 | 71 | При структури щяхме да имаме една и съща стойност за `ref.name`. 72 | 73 | За разлика от структурите, при класовете е възможно в няколко променливи да има един и същ обект. (Променливите или константите да сочат към едно и също място в паметта) За да можем да сравним две променливи и тяхните референции (места към които съхраняват), е въведен операторът за идентитет `===` (identical to) и съответно - допълващият го `!==`. 74 | 75 | Трябва да правим разлика между оператора `==`за еквивалентност, който е приложим над структурите (__но трябва да си го дефинираме__) и оператора за идентитет `===`, който е приложим над класовете. 76 | 77 | Преди да преминем към детайли относно класовете е добре да знаем кога следва да позлваме структури и кога класове. 78 | 79 | Целта на структурите е да представяме малки обеми от данни в паметта. Такива, които ще бъдат копирани при всички действия. Добре е да изпозлваме структура, ако всички полета са структури. Когато смесваме структури с класове, е доста трудно да проследим закономерността - какво става при копиране. А какво става, когато имаме константа? Следва пример, който скицира проблема: 80 | 81 | ```swift 82 | var newMediaList2 = mediaList 83 | //това работи, понеже newMediaList2 е променлива 84 | newMediaList2.count = 5 85 | //това също работи 86 | newMediaList2.item.name = "X-Men 2.5" 87 | 88 | let constMediaList = mediaList 89 | //това също работи 90 | constMediaList.item.name = "X-Men 3" 91 | ``` 92 | 93 | Ако имаме поведение, което трябва да е споделено между различни типове и то трябва да се определя в наследниците, тогава трябва да предпочетем клас пред структура. Ако нямаме нужда от наследяване, тогава се спираме на структури. 94 | 95 | Класовете ни дават пълна свобода и могат да се използват във всички случаи. Ако сме наясно, че можем да използваме структура, тогава е удачно да я използваме вместо клас. 96 | 97 | 98 | ## Наследяване 99 | 100 | Наследяването е механизъм, който може да се прилага над класовете. Той позволява споделянето на характеристиките (пропъртита, методи и други) от базови класове към наследници. Той е основната причина да има __полиморфизъм__ - динамично определяне коя функция трябва да бъде извикана в зависимост от типа на обекта. Ще стане ясно, когато разберем повече как можем да предефинираме методи и пропъртита. 101 | 102 | ### Какво е базов клас? 103 | 104 | Базовият клас е клас, който не наследява други класове. Базов е, защото основава нова йерархия. Той може да бъде наследяван. 105 | 106 | В Swift, ако не е посочен базов клас в клас дефиниран от нас, то няма подразбиращ се базов клас, от който всички типове произхождат. Разлика с Java - `Object` (https://docs.oracle.com/javase/tutorial/java/IandI/subclasses.html). 107 | 108 | 109 | ```swift 110 | enum Color { 111 | case pink 112 | case green 113 | case black 114 | case blue 115 | case white 116 | case noColor 117 | } 118 | 119 | //базов клас 120 | class Show { 121 | var name: String 122 | var color: Color 123 | var country: String 124 | var language: String 125 | var duration: Double 126 | 127 | init() { 128 | self.name = " no name " 129 | color = .noColor 130 | self.country = "no country" 131 | language = "BG" 132 | duration = 0.0 133 | } 134 | 135 | var durationInMinutes:String { 136 | let minutes:Int = Int(round(duration)) 137 | let minutesInHour = 60 138 | return "\(minutes / minutesInHour) : \(minutes % minutesInHour) min" 139 | } 140 | 141 | func shortDescription() -> String { 142 | return "\(name) - \(language) : Duration: \(durationInMinutes)" 143 | } 144 | 145 | deinit { 146 | print("deinit \(#function)" ) 147 | } 148 | } 149 | 150 | extension Show { 151 | convenience init(duration:Double) { 152 | self.init() 153 | self.duration = duration 154 | } 155 | } 156 | ``` 157 | 158 | Тук дефинираме клас наследник на `Show`, който се казва `TVShow`. Той има допълнително пропърти `series` - брой епизоди. 159 | 160 | Определяме, че един клас наследява друг, като изписваме ` : Името на базовия клас` след името на класа. В нашия случай, `class TVShow : Show`. 161 | 162 | __Всеки клас може да има само един клас, който да наследява.__ 163 | __Ако не наследява нито един клас, тогава този клас е базов.__ 164 | Всеки клас, който може да бъде наследен (по-късно ще изясним как можем да предотвратим наследяването), може да бъде наследен безброй пъти. Т.е. нямаме ограничение колко пъти един клас, който може да бъде наследен, ще бъде наследен. 165 | 166 | 167 | ```swift 168 | class TVShow : Show { 169 | var series: Int 170 | 171 | override init() { 172 | series = 0 173 | //извиква конструиращия метод на бащиния клас 174 | super.init() 175 | } 176 | 177 | init(name:String) { 178 | series = 0 179 | //извиква конструиращия метод на бащиния клас 180 | super.init() 181 | super.name = name 182 | } 183 | 184 | var isTurkish:Bool { 185 | return self.language == "TR" && self.series > 100 186 | } 187 | 188 | override func shortDescription() -> String { 189 | return super.shortDescription() + " v.2" 190 | } 191 | 192 | deinit { 193 | print("deinit TVShow" ) 194 | } 195 | } 196 | ``` 197 | В класовете наследници можем да добавяме допълнителни полета, стига те да не се препокриват с тези, които получаваме наготово от наследения клас. 198 | 199 | Например 200 | : Ако в `TVShow` добавим `duration`, тогава компилаторът ще ни подскаже, че това не е възможно, тъй като базовият клас съдържа такова пропърти. 201 | 202 | Ето и как можем да използваме класа `TVShow`. 203 | 204 | 205 | ```swift 206 | var blackSails:TVShow = TVShow(name: "Balck Sails", duration: 123) 207 | print(blackSails.shortDescription()) 208 | //създаваме нов обект и старият бива деинициализиран 209 | blackSails = TVShow() 210 | ``` 211 | 212 | ### Инициализатори 213 | 214 | Всеки тип (клас или структура) трябва да има инициализиращ метод, който се грижи за инициализирането на паметта. Както дискутирахме при структурите и тук важат правилата за инициализаторите (конструктoрите), но с малки разлики. Инициализаторите (`init` методите) трябва те да се грижат да попълнят всички полета, които изграждат класа или структурата. Не е нужно да се инициализират полетата, които имат стойност по подразбиране. Optional (опционалните) полета имат стойност по подразбиране, която е - "липсата на стойност" или `nil`. 215 | 216 | При класовете, получаваме конструктор по подразбиране (т.е. без никакви параметри). При структурите освен този, получаваме и конструктор по всички полета, който можем да използваме, за да инициализираме всички полета с нови данни. Ето и един пример, който илюстрира автоматично генерираните методи. 217 | 218 | ```swift 219 | struct StructBook { 220 | var pageCount = 0 221 | var title = "no title" 222 | var publishDate:Date? = nil 223 | } 224 | 225 | class Book { 226 | var pageCount = 0 227 | var title = "no title" 228 | var publishDate:Date? = nil 229 | } 230 | 231 | var book = Book() 232 | book.pageCount = 100 233 | 234 | var book2 = StructBook(pageCount: 1000, title: "Swift 3", publishDate: nil) 235 | ``` 236 | 237 | 238 | Понякога ще ни трябва повече от един конструктор. И преди да се втурнем към директното имплементиране, е добре да знаем, че има два основни типа конструктори. 239 | 240 | Основните конструктори са тези, които се грижат за конструиране на обекта. Известни са в литературата като ```designated```. Характерното за тях, е че се грижат напълно за инициализирането на обекта от даден тип. Т.е. няма поле, което да не е инициализирано и да не се знае каква стойност ще има. 241 | 242 | Другият тип конструктори е "удобните" `convеnience` конструкторите [convenient - удобен, пригоден, подходящ]. Те се маркират с тази запазена дума. 243 | 244 | Пример: 245 | 246 | 247 | ```swift 248 | class Book { 249 | var pageCount = 0 250 | var title = "no title" 251 | var publishDate:Date? = nil 252 | 253 | convenience init(pages:Int) { 254 | self.init() 255 | self.pageCount = pages 256 | } 257 | } 258 | ``` 259 | 260 | 261 | Тези конструктури са задължени да извикват директно или индиректно един основен конструктор. "Удобните" конструктори трябва да делегират конструирането на обекта на друг основен конструктор или могат да делегират на друг "удобен", но винаги един от "удобните" конструктори трябва да се обръща към основен конструктор. 262 | 263 | Добра практика е да се дефинират допълнителни конструктури, но те да се добавят в `extension`. Трябва да знаем, че __можем__ да дефинираме само "удобни" конструктори в разширението. Ако искаме да добавим основен конструктор, то трябва да допишем неговата дефиниция в началната (основната) дефиниция на типа данни (клас или структура). 264 | Трябва да ни е ясно, че всеки тип данни трябва да има поне един основен конструктор, който да може да се използва за конструирането на обекти. 265 | 266 | #### Задължителни конструктори 267 | 268 | Ако обявим един конструктор със запазената дума `required`, то всеки един наследник трябва да го имплементира. 269 | 270 | (виж протоколи) Ако конструктор идва като правило от протокол, тогава той трябва да използва запазената дума `required` при всяка имплементация в някои тип данни. 271 | 272 | #### Как да ограничим наследяването 273 | 274 | Можем да използваме запазената дума `final` като модификатор на функция или клас. Ако маркираме клас, тогава няма да можем да го наследим. Това означава, че ограничаваме йерархията. 275 | 276 | Ако я изпозлваме върху метод, тогава няма да можем да го предифинираме в нито един от наследниците. 277 | -------------------------------------------------------------------------------- /Лекция-06.md: -------------------------------------------------------------------------------- 1 | # Лекция №7 2 | -- 3 | 4 | ## Протоколи 5 | 6 | ### Какво са протоколите? 7 | 8 | Протоколът е "договор", който всеки тип данни (клас, структура или изброим тип) се съгласява да удовлетвори. "Договор", е списък от изисквания, които определят начина по който ще изглежда даденият тип. 9 | 10 | ### Синтаксис 11 | ```swift 12 | protocol Sellable { 13 | var pricePerUnit: Double { get } 14 | 15 | var isAvailable: Bool { set get } 16 | } 17 | ``` 18 | 19 | ### Пропъртита - properties 20 | Протоколът може да задължава всички имплементиращи го да добавят пропъртита. Има два варианта: 21 | 22 | #### Гетъри - get 23 | -- 24 | Когато имаме гетър, тогава наложените ограничения са най-малки. Те не ни задължават да имаме сетър. Можем да имаме изчислимо пропърти или стандартно (set и get). Дори можем да имаме и `read–only` пропърти. 25 | 26 | #### Сетъри и гетъри - Set & Get 27 | -- 28 | В този случай трябва да имаме и двата метода - `get` и `set`. Т.е. ограниченията са такива, че можем да имаме или стандартно пропърти `var` (то има гет и сет) или да имплементираме изчислимо, но и с двата му варианта, за да има `set` и `get`. 29 | 30 | Като от всеки от горе изброените два варианта, може да се прилага към типа (`static`) или към инстанцията - тук говорим за член пропъртита. 31 | 32 | Трябва да знаем, че не можем да дадем имплементация по-подразбиране на статичните функции. 33 | 34 | Пример: 35 | 36 | ```swift 37 | protocol Printable { 38 | var description: String { get } 39 | 40 | static var version: String { get } 41 | } 42 | ``` 43 | 44 | ### Методи: 45 | В протокола може да изискваме различни методи, като фиксираме името на метода, имената и типовете на параметрите (дали имат име или не) и типа на връщания резултат. 46 | 47 | #### 1. към инстанцията (методи към обект) 48 | Това са методи, които ще са характерни за инстанция (обект) от типа, който имплементира протокола. 49 | 50 | #### 2. към типа (статични методи) 51 | Това са методи, които са характерни за типа, който имплементира протокола. Тези функции (можем спокойно да си мислим и за пропъртита) 52 | 53 | Пример: 54 | 55 | ```swift 56 | protocol PersonalComputer { 57 | func getRamSize() -> Int 58 | 59 | // Convert X bytes to "KB" or "MB" or "GB" or "TB" 60 | static convert(bytes: Int, to:String) -> Double 61 | } 62 | ``` 63 | 64 | ### Инициализиращи методи 65 | 66 | Пишем ги по стандартния начин, без самата имплементация. 67 | 68 | Когато ги имплементираме, добавяме ключовата дума `required` 69 | 70 | Пример: 71 | 72 | ```swift 73 | protocol Printable { 74 | var description: String { get } 75 | static var version: String { get set} 76 | 77 | init() 78 | 79 | init(a:Int, b:Int) 80 | } 81 | 82 | class Machine: Printable { 83 | var description = "" 84 | var powerConsumption = 0 85 | var name = "Missing name" 86 | static var version: String = "v. 2.0" 87 | 88 | //без този конструктор не се компилира 89 | required init() { } 90 | 91 | required init(a:Int, b:Int) { 92 | print("Machine") 93 | } 94 | } 95 | ``` 96 | 97 | ### Протоколи, които могат да се имплементират само от класове 98 | 99 | Понякога имаме нужда даден протокол да се използва само от типове данни, които са класове. Това можем да се направи, като използваме ключовата дума `class`. 100 | Ето и един пример: 101 | 102 | ```swift 103 | protocol PersonalComputer: class { 104 | func getRamSize() -> Int 105 | static func convert(bytes: Int, to:String) -> Double 106 | } 107 | ``` 108 | 109 | ### Протоколи в стандартната библиотека на Swift 110 | 111 | Може да групираме протоколите в стандартната Swift библиотека в три групи: 112 | 113 | “Can-do” протоколи. 114 | 115 | “Is-a” протоколи. 116 | 117 | “Can-be” протоколи. 118 | 119 | ### Can-do Протоколи 120 | 121 | Те описват неща, които типът може да прави. Обикновено завършват на "-able", което ги прави лесни за разпознаване. 122 | 123 | Например, когато тип имплементира Hashable протокола, то значи, че може да го хешираме до Int. Equatable and Comparable протоколите указват, че два обекта от този тип могат да бъдат сравнявани с операторите за еквивалентност (==) и сравнение (>/<). 124 | 125 | Малка част от протоколите в тази група се грижат и за алтернативни изгледи за типовете. Например CustomPlaygroundQuickLookable - означава, че обект от този тип може да бъде разглеждат чрез функцията "quick look" на Playground, като за целта трябва да предоставим този изглед/формат. 126 | 127 | Идеята е, че самата стойност остава същата, а погледнат "отвън", обектът е различен (неговата презентация). Операцията в случая е "надникване"/quick-look. 128 | 129 | ### Is-a протоколи 130 | 131 | Те описват какъв видът. За разлика от Can-do протоколите те по-скоро придават идентичност на типа. 132 | 133 | Всъщност по-голямата част от протоколите в стандартната библиотека са от тази група. В предишни версии те завършваха с "Type" и бяха много лесни за разпознаване, но конвенцията се промени. 134 | 135 | Collection(Type), който Array, Dictionary и Set имплементират. И Sequence(Type) and Generator(Type) пък се ползват, ако искаме да придадем итеративност на дадена колекция (да може да се обхожда). Имаме и Error(Type), който се ползва в модела за грешките в Swift. За него имаме и цяла лекция. 136 | 137 | Съществува и MirrorPath(Type), който има забавен коментар към документацията си: 138 | 139 | /// A protocol for legitimate arguments to `Mirror`'s `descendant` 140 | /// method. 141 | /// 142 | /// Do not declare new conformances to this protocol; they will not 143 | /// work as expected. 144 | 145 | ### Can-be протоколи 146 | 147 | Последната група протоколи от стандартната библиотека означава, че един тип може да бъде конвертиран от и към нещо друго. Обикновено имат имена, съдържащи ExpressibleBy...Literal (ExpressibleByNilLiteral, ExpressibleByStringLiteral и др.). 148 | 149 | Има такива, които изискват имплементирането на един инициализатор, който връща конкретния тип, като например ExpressibleByFloatLiteral - при подаден Float, бихме могли да конструираме нашия тип; init(floatLiteral value: Self.FloatLiteralType) 150 | 151 | Протоколите в тази категория са доста праволинейни. Когато дефинираме свой тип, който може да бъде създаден от обект от друг тип или да бъде преобразуван в друг, то е добре да спазваме конвенцията на протокол от стандартната библиотека и така да имаме чист и познат интерфейс и консистентност. 152 | 153 | 154 | ### В обобщение 155 | В стандартната библиотека имаме основно три групи протоколи, които са свързани с дееспособност, идентичност и конверсия. 156 | 157 | Ето и някои общи модели, за които да мислим, пишейки своя код:: 158 | 159 | Операции - Ако има общ набор от операции, които бихме извършвали с/върху нашите типове, обмислете да ги дефинирате с протокол(и). 160 | 161 | Свързани с алтернативни изгледи - Ако от типовете ни се изисква да имат алтернативен изглед и презентация, които не са чиста конверсия (необходими са ни само за определено представяне без модификация на стойността) 162 | 163 | За идентичност - това е единственият ви шанс да се доближите до множествено наследяване. Мислете за същността на вашите типове и начин за групирането им чрез протоколи. 164 | 165 | Конверсии - При често конвертиране от и към други типове, обмислете дали не можете да имплементиране някои често срещани конверсии, за да сте консистентни. 166 | 167 | ## Разширения 168 | 169 | ### Какво са разширенията? 170 | 171 | Разширенията (extensions, в други езици - категории) позволяват добавянето на нова функционалност към тип, който вече е дефиниран. Могат да се използват за разширяване/допълване на функционалности - добавяне на пропъртита и функции, на типовете от стандартната Swift библиотека `Int`, `String`, `Bool` и т.н. или на дефинирани от нас типове. Разширенията могат да са полезни и за организиране на кода в логически свързани блокове. Най-често ще използваме разширения за добавяне на функционалности към типове, които не можем да редактираме директно и организиране на кода. Да се има предвид, че разширенията добавят функционалност към всички инстанции на този тип във вашата програма. 172 | 173 | ### Синтаксис 174 | 175 | Използваме запазената дума `extension`, последвана от името на типа, който искаме да разширим. 176 | 177 | ```swift 178 | extension SomeType { 179 | // Code goes here 180 | } 181 | ``` 182 | 183 | ### Добавяне на пропъртита (изчислими) 184 | 185 | Добавяме статична променлива, която връща любимото ни цяло число: 186 | 187 | ```swift 188 | extension Int { 189 | static var favoriteNumber: Int { 190 | return 23 191 | } 192 | } 193 | ``` 194 | 195 | ### Добавяне на методи към инстанция 196 | 197 | Може да използваме разширенията, за да добавим нови методи към инстанция на даден тип по кратък и ясен начин: 198 | 199 | ```swift 200 | extension Int { 201 | func squared() -> Int { 202 | return self * self 203 | } 204 | } 205 | ``` 206 | 207 | ### Органициране на кода 208 | 209 | При разработката на нетривиална програма е изключително важно кодът да е организиран и лесен за четене. Прост, но ефективен пример е разделянето на декларацията на типа и неговите пропъртита от функциите и изчислимите пропъртита. Така декларацията на типа остава по-кратка и четима. 210 | 211 | ```swift 212 | class Person { 213 | let firstName: String 214 | var lastName: String 215 | var age: Int 216 | var phoneNumber: String 217 | } 218 | 219 | extension Person { 220 | var fullName: String { 221 | return "\(firstName) \(lastName)" 222 | } 223 | } 224 | 225 | extension Person { 226 | func growOlder() { 227 | age += 1 228 | } 229 | 230 | func greet() { 231 | print("Hello, my name is \(fullName)") 232 | } 233 | } 234 | ``` 235 | 236 | ### Имплементиране на протоколи 237 | 238 | Честа практика е имплементирането на някой протокол да става в разширение на класа: 239 | 240 | ```swift 241 | extension Person: Equatable { 242 | static func ==(lhs: Person, rhs: Person) -> Bool { 243 | return lhs.firstName == rhs.firstName && lhs.lastName == rhs.lastName && lhs.age == rhs.age && lhs.phoneNumber == rhs.phoneNumber 244 | } 245 | } 246 | ``` 247 | -------------------------------------------------------------------------------- /Лекция-07.md: -------------------------------------------------------------------------------- 1 | # Лекция №7 2 | -- 3 | 4 | В тази лекция ще разгледаме общия вид на функциите - closures. Ще започнем с кратък преговор на това, което знаем за функциите и ще се впуснем в детайли за closure-ите. 5 | 6 | ## Какво са функциите? 7 | 8 | Функциите са блок от код, които може да се употреби (активира) на различни места в нашата програма. Една функция има параметри, от които зависи и може да връща резултат. Т.е. функцията в общия вид е действие над определени данни (параметри), което има резултат. 9 | 10 | Това е съществено, за да можем да разберем, че функциите имат и определен тип. Т.е. следните две функции са от един тип. 11 | 12 | ```swift 13 | func sum(a:Int, b:Int) -> Int { 14 | return a + b 15 | } 16 | 17 | func product(a:Int, b:Int) -> Int { 18 | return a * b 19 | } 20 | ``` 21 | 22 | ### Функциите като тип 23 | 24 | Всяка функция има определен тип, който се формира от типа на параметрите и от типа на резултата, връщан от функцията. Т.е. типа данни, над които ще работи и типа на резултата формират цялостния тип на функцията. (Има паралел с tuples от гледна точка на това, че самият тип е съставен от няколко части, но пък функцията съдържа и закодирана последователност от действия). 25 | 26 | Типът на функциите от примера по-горе е `(Int, Int) -> Int`. 27 | 28 | Имайки възможността да обединяваме различни функции по тяхния тип, ние можем да пишем програми, в които да заместваме дадени фукции една с друга. Всичко това може да става динамично, а кодът, който борави с тях, да е лесен, компактен и в обща форма. 29 | 30 | Примерно: Ако разработваме приложение, което реализира софтуерен калкулатор, при натискане на бутон с операция (x, /, ^, +, -) ние можем да запомним вида на операцията (най-често бинарна операция от вида `(Int, Int) -> Int`). Tака няма да навлизаме над детайли какви операции ще има конкретно калкулатора при реализиране на логиката за изчисление, а ще можем да подходим абстрактно. 31 | 32 | Типът на функция е пълноправен тип данни. Използваме го както останалите типове. Можем да имаме, променливи, константи, параметри от такъв тип и функциите могат да връщат резултат от такъв тип. Т.е. можем да подаваме функция като параметър и да връщаме резултат, който да е функция. 33 | 34 | Ето и един пример как функция може да взима друга като параметър: 35 | 36 | ```swift 37 | func printAllNames(names: [String], printFunc: (String) -> Void) { 38 | for name in names { 39 | printFunc(name) 40 | } 41 | } 42 | ``` 43 | Вторият параметър е от тип `(String) -> Void`. Това е типът на функция, която има един параметър и не връща резултат. 44 | 45 | А това е пример как дадена функция може да връща функция като резултат: 46 | 47 | ```swift 48 | //: Да се даде пример за функция, която връща функция като параметър. 49 | 50 | func createVeryFancyPrintFunction() -> (String)-> Void { 51 | 52 | func fancyPrint(name: String) { 53 | print("@****************************@") 54 | print("@$$$$$$$$$$ \(name) $$$$$$$$$@") 55 | print("@****************************@") 56 | } 57 | 58 | 59 | return fancyPrint 60 | } 61 | ``` 62 | Типът на връщаната функция е `(String) -> Void`. Интересното е, че декларираме функция във функция. Това се нарича вместване на функции. Функции, които са част от други функции, се наричат nested (вместени). 63 | 64 | ### Nested (вместени) функции 65 | 66 | До сега сме се запознали с функции, които са глобални (методите, които са част от класовете, са по-различен вид функции, които познаваме). Вместените функции са такива функции, които се появяват като част от тялото на друга функция. Ето и един пример: 67 | 68 | ```swift 69 | func globalFunc() { 70 | var divisor = 3 71 | 72 | func nestedFunction(i: Int) -> Bool { 73 | print("\(i) is printed from the nested function"); 74 | //как можем да ползваме променлива от външната функция? 75 | return i % divisor == 0 76 | } 77 | //извикване на вместената функция (calling the nested function) 78 | nestedFunction(i: 5) 79 | } 80 | 81 | //извикване на глобалната функция 82 | globalFunc() 83 | //вместената функция не е видима тук и следващият ред предизвиква компилационна грешка 84 | //nestedFunction(3) 85 | ``` 86 | 87 | Припомняйки какво бяха функциите, ние се запознахме с един комплексен тип данни - типа на дадена функция и възможността да влагаме функции (nested функциите). Сега ще престъпим към дефинирането на общия вид на клоужърите. 88 | 89 | ## Closures (Клоужъри) 90 | 91 | ### Какво са клоужърите? 92 | 93 | В Swift, клоужърите (closure) са: 94 | 95 | * глобалните функции с имена, които вече познаваме; 96 | * вместените функции са клоужъри, които имат видимост до променливите в функцията, която ги съдържа (процеса по запомняне/прихващане на променливите се нарича capturing) 97 | * клоужър израз - без име, записан по специфичен начин, прихващащ (capture) стойности от прилежащия му контекст. 98 | 99 | Клоужърите имат прост и компактен синтаксис. Ще го видим в различни примери, които следват. Техният механизъм позволява определянето на типовете на параметрите автоматично и на връщания резултат. Ако имаме клоужър само с един израз, тогава не е нужно да използваме `return`, за да обявим, че връщаме резултат. 100 | Клоужърите имат съкратен вариант и в него можем лесно да се обръщаме към параметрите. 101 | 102 | 103 | Клоужърите могат да се подават като последен параметър към функция. В този случай можем да ги изписваме в къдрави скоби след функцията, като изпуснем последния параметър. 104 | 105 | ```swift 106 | func trailingClosure(i:Int, predicate: (Int)->Bool) { 107 | print("Проверка за \(i)") 108 | if predicate(i) { 109 | print("Предикатът е удволетворен.") 110 | } 111 | } 112 | ``` 113 | 114 | Тук имаме дефинирана функция, чийто втори параметър е от тип функция (клоужър) `(Int) -> Bool`. Ще използваме експлицитно извикване на тази функция, като винаги ще подаваме еквивалентен клоужър по функционалност, но записан по различен начин. 115 | 116 | ```swift 117 | //нормалното извикване 118 | trailingClosure(i: 1, predicate: { (a) -> Bool in 119 | return a % 2 == 0 120 | }) 121 | 122 | //няма нужда от определяне на типа, който връща клоужъра, той може да бъде определен автоматично 123 | trailingClosure(i: 1, predicate: { (a) in 124 | return a % 2 == 0 125 | }) 126 | 127 | //клоужърът е последен параметър, затова се подава като блок код след извикване на функцията 128 | trailingClosure(i: 4) { (a) in 129 | return a % 2 == 0 130 | } 131 | 132 | //съкратен вид 133 | trailingClosure(i: 4) { (a) in 134 | //без return понеже имаме само един ред 135 | a % 2 == 0 136 | } 137 | 138 | //подразбиращи се имена на параметрите 139 | //кодът е къс, но трудно разбираем за всички 140 | trailingClosure(i: 8) { 141 | //използваме автоматичното име, за да достъпим първия аргумент на клоужъра 142 | $0 % 2 == 0 143 | } 144 | ``` 145 | Когато нямаме явни имена на параметрите, не трябва да използваме запазената дума `in`. 146 | 147 | Имената на параметрите следват следния шаблон - започват с `$` и следва число. Индексирането е от `0`. За да достъпим първия параметър, подаден на клоужър, трябва да използваме `$0`. 148 | 149 | Ето и нещо любопитно: как можем да използваме оператор, за да сортираме масив от данни. 150 | 151 | ```swift 152 | let names = ["aaa", "ccc", "bbb"] 153 | //сортиране 154 | print(names.sorted(by: { $0 > $1 })) 155 | print(names.sorted() { $0 > $1 }) 156 | //сортираме, като подаваме функция от тип (String, String) -> Bool ( т.е. оператор за сравнение) 157 | print(names.sorted(by: >)) 158 | ``` 159 | 160 | ### Какво е capturing (запомняне/прихващане)? 161 | 162 | Това е процес, при който става запомняне на контекста, в който възниква даден клоужър (closure). В примера по-горе nested (вместената) функция прихваща променливата `divisor`. Прихващането позволява използването на такива променливи или константи в тялото на фукцията по всяко едно време. Ето и един по-комплексен пример, който можем да използваме за генератор на четните числа. 163 | 164 | ```swift 165 | //пример за запомняне на променливи от контекста (JS програмиране) 166 | 167 | 168 | func createGen(start: Int, modify: @escaping (Int) -> Int ) -> ()->(Int) { 169 | 170 | var myStart = start 171 | 172 | return { 173 | myStart = modify(myStart) 174 | return myStart 175 | } 176 | 177 | } 178 | 179 | var next = createGen(start: 0) { 180 | $0 + 2 181 | } 182 | 183 | print(next()) //2 184 | print(next()) //4 185 | print(next()) //6 186 | print(next()) //8 187 | ``` 188 | Не трябва да забравяме, че клоужърите са референтен тип. 189 | Важно е да отбележим, че компилаторът очаква всеки клоужър (подаден като аргумент) да не напуска тялото на функцията, към която е подаден. Но това не винаги е правилното нещо. Затова ние можем да маркираме параметър от тип функция(клоужър) като `@escaping`. 190 | 191 | Ето и един кратък пример, който показва как дадена функция може да напусне границите на друга, към която е подадена. 192 | 193 | ```swift 194 | var handlers:[()->Void] = [] 195 | //трябва да добавим атрибута @escaping иначе няма да се компилира 196 | func escapingClosure(f: @escaping ()->Void) { 197 | handlers.append(f) 198 | } 199 | 200 | handlers.append { 201 | print("test") 202 | } 203 | 204 | escapingClosure { 205 | print("test 2") 206 | } 207 | 208 | //активираме всички функции 209 | for f in handlers { 210 | f() 211 | } 212 | ``` 213 | 214 | Възможно е да маркираме даден аргумент като `@autoclosure`. Това ще ни позволи да подадем код, който автоматично ще бъде превърнат във функция. Активацията ще настъпи в правилното време, когато параметърът бива активиран с нужните параметри. В конкретния случай само извиква `pred()`. 215 | 216 | ```swift 217 | func funcAutoclosure(pred: @autoclosure () -> Bool) { 218 | if pred() { 219 | print("It's true") 220 | } else { 221 | print("It's НОТ true") 222 | } 223 | } 224 | 225 | funcAutoclosure(pred: 11 > 12) 226 | funcAutoclosure(pred: { () -> Bool in return 2 > 1}()) 227 | 228 | //допълнителен пример 229 | func funcAutoclosureComplex(pred: @autoclosure () -> ()) { 230 | print("body of \(#function)") 231 | } 232 | 233 | 234 | func funcAutoclosureComplexVoid(pred:()) { 235 | print("body of \(#function)") 236 | } 237 | 238 | funcAutoclosureComplex(pred: print("the function is wrapped in a closure and it's never called.")) 239 | 240 | funcAutoclosureComplexVoid(pred: print("the function print() is called")) 241 | //Това е изходът от горния код: 242 | //body of funcAutoclosureComplex(pred:) 243 | //the function print() is called 244 | //body of funcAutoclosureComplexVoid(pred:) 245 | ``` 246 | 247 | Възможно е да комбинираме и двете анотации `@escaping` и `@autoclosure`. Ето и един пример, който показва това: 248 | 249 | ```swift 250 | 251 | //: () и Void са еквивалетни записа за липсата на резултат 252 | var predicates:[()->Void] = [] 253 | func funcEscapeAutoclosure(pred:@escaping @autoclosure () -> ()) { 254 | predicates.append(pred) 255 | } 256 | 257 | funcEscapeAutoclosure(pred: print("body 2")) 258 | funcEscapeAutoclosure(pred: print("body 3")) 259 | funcEscapeAutoclosure(pred: print("body 1")) 260 | //няма да има нищо отпечатано на екрана, понеже функцията print() е вкарана автоматично в closure, но той не е извикан. 261 | 262 | ``` 263 | 264 | Трябва да запомним че `@autoclosure` ни задължава да изпозлваме нормален код вместо функция и `Swift` ще "опакова" този код в функция, която може да бъде оценена по-късно. -------------------------------------------------------------------------------- /Лекция-08.md: -------------------------------------------------------------------------------- 1 | # Лекция №8 2 | ## Изброени типове (enums) в Swift 3 | 4 | Подобно на `switch` оператора в Swift, `изброените` типове на пръв поглед изглеждат като "леко подобрена версия на добре познатия изброен тип в **C**". Например бихме могли да ги определим като "начин да дефинираме единица, която е част от по-общо крайно множество". Всъщност `изброените` типове, както са имплементирани в Swift, дават решения на много повече сценарии от практиката, отколкото **C** изброените типове. 5 | 6 | Ще дадем една дефиниция за `изброените` типове: 7 | 8 | "Изброените типове (enums) декларират вид крайни множества и техните възможни значения, заедно с придружаващите ги стойности. Чрез влагания, функции, асоциирани стойности и `pattern matching`, изброените типове позволяват да се изгради организирана йерархия от данни". 9 | 10 | `Изброените типове се предават стойност, също както и структурите.` 11 | 12 | ## Основи на изброените типове 13 | 14 | Да предположим, че работим по игра и играчът има ограничени възможности за движение - наляво и надясно. 15 | 16 | ```swift 17 | if movement == "left" { ... } 18 | else if movement == "right" { ...} 19 | ``` 20 | 21 | Тук има голяма опасност да сгрешим, докато изписваме стойностите на символните низове и вместо `left` или `right`, да имаме `rigth`. Вместо това, ще разчитаме на компилатора да прави проверката: 22 | 23 | ```swift 24 | let moveLeft = 0 25 | let moveRight = 1 26 | if movement == moveLeft { ... } 27 | else if movement == moveRight { ... } 28 | ``` 29 | 30 | Това решава проблема с грешното изписване, но при добавяне на нови възможности за движение, може да изпуснем да проверим за определена стойност и да имаме **бъг** 31 | 32 | ```swift 33 | let moveUp = 0 34 | let moveDown = 1 35 | ``` 36 | 37 | Ако програмистът забрави на промени `if`-a, ще имаме нов брой движения, които няма да обработваме. Отново може да разчитаме на компилатора за тези проверки. Той може да ни предупреди, ако има възможност, която не обработваме. Тук на помощ влизат `изброените` типове. 38 | 39 | ### Синтаксис 40 | 41 | В един изрбоим тип `enum`, дефинираме възможностите като `case` - "частен случай". 42 | 43 | ```swift 44 | enum Movement { 45 | case left 46 | case right 47 | } 48 | ``` 49 | 50 | Със `switch` можем да проверяваме всички възможности: 51 | 52 | ```swift 53 | let movement = Movement.left 54 | switch movement { 55 | case Movement.left: player.goLeft() 56 | case Movement.right: player.goRight() 57 | } 58 | ``` 59 | 60 | Ако добавим и `up`, то кодът няма да се компилира, защото `switch` в Swift **трябва да бъде изчерпателен**, а нямаме `default` клауза. 61 | 62 | В Swift е приета конвенцията да се ползва съкратен запис на изброените типове, като се пропуска типа. Това се счита за добра практика, докато пишем Swift код: 63 | 64 | ```swift 65 | switch movement { 66 | case .left: 67 | player.goLeft() 68 | case .right: 69 | player.goRight() 70 | } 71 | ``` 72 | 73 | ## Стойности на изброените типове 74 | 75 | В някои случаи може да ни е необходима стойност за всеки възможен случай. В **C** можем да даваме числени стойности, докато в Swift имаме много по-голяма гъвкавост. 76 | 77 | ```swift 78 | // Напълно безполезен изброен тип 79 | enum Binary { 80 | case zero = 0 81 | case one = 1 82 | } 83 | 84 | // Можем да използваме и символни низове за стойности 85 | enum House: String { 86 | case baratheon = "Ours is the Fury" 87 | case greyjoy = "We Do Not Sow" 88 | case martell = "Unbowed, Unbent, Unbroken" 89 | case stark = "Winter is Coming" 90 | case tully = "Family, Duty, Honor" 91 | case tyrell = "Growing Strong" 92 | } 93 | 94 | // Или пък числа с плаваща запетая, за да изпишем често ползвани математически константи 95 | enum Constants: Double { 96 | case π = 3.14159 97 | case e = 2.71828 98 | case φ = 1.61803398874 99 | case λ = 1.30357 100 | } 101 | ``` 102 | 103 | Ако стойността е от тип `Int` или `String`, то тези стойности се генерират от компилатора: 104 | 105 | ```swift 106 | // mercury = 1, venus = 2, ... neptune = 8 107 | enum Planet: Int { 108 | case mercury = 1, venus, earth, mars, jupiter, saturn, uranus, neptune 109 | } 110 | 111 | // north = "north", ... west = "west" 112 | enum CompassPoint: String { 113 | case north, south, east, west 114 | } 115 | ``` 116 | 117 | Swift поддържа следните типове за стойности: 118 | 119 | - `Целочислени - Integer` - Int, UInt 120 | - `Числа с плаваща запетая - Floating Point` - Float, Double 121 | - `Символни низове`- String 122 | - `Bool` 123 | 124 | Можем да поддържаме и други типове чрез имплементацията на протокол, но за него по-късно. 125 | 126 | Ако искаме да достъпим стойността, то използваме `rawValue` пропъртито. 127 | 128 | ```swift 129 | let bestHouse = House.stark 130 | print(bestHouse.rawValue) 131 | ``` 132 | 133 | Получаваме и инициализатор за конструиране на изброен тип от стойност: 134 | 135 | ```swift 136 | enum Movement: Int { 137 | case left = 0 138 | case right 139 | case top 140 | case bottom 141 | } 142 | // Създаваме movement.right, чиято стойност е 1 143 | let rightMovement = Movement(rawValue: 1) 144 | ``` 145 | 146 | Обърнете внимание, че този инициализатор е `failable` и връщаната стойнос те **Optional**. Какво става в този случай? 147 | 148 | ```swift 149 | let aMovement = Movement(rawValue: 42) 150 | ``` 151 | 152 | ## Влагане (nesting) на изброените типове 153 | 154 | Ако искаме да спазим определени изисквания за по-типове, можем да вложим изброените типове в други изброени типове. Това ни позволява да отделим логическа група от типове. Да вземем за пример играч от ролева игра (RPG). Всеки герой си има оръжие и всички герои имат достъп до един и същи набор от оръжия. Всички други обекти в играта нямат достъп до тези оръжия (например троловете носят само тояги). 155 | 156 | ```swift 157 | enum Character { 158 | enum Weapon { 159 | case bow 160 | case sword 161 | case spear 162 | case dagger 163 | } 164 | 165 | enum Helmet { 166 | case leather 167 | case wooden 168 | case iron 169 | } 170 | 171 | case thief 172 | case warrior 173 | case knight 174 | } 175 | ``` 176 | 177 | Така имаме йерарфична система, която описва различните предмети, които вашият герой може да носи. 178 | 179 | ```swift 180 | let character = Character.thief 181 | let weapon = Character.Weapon.bow 182 | let helmet = Character.Helmet.iron 183 | ``` 184 | 185 | Можем да добавим и инициализатор за по-удобно инстанцииране, като избягваме изписването на пълния тип. 186 | 187 | А можем да имаме и функция, чиито параметри са от вложените изброени типове, при която също ще запишем стойностите съкратено: 188 | 189 | ```swift 190 | func strength(of character: Character, 191 | with weapon: Character.Weapon, 192 | and armor: Character.Helmet) { 193 | 194 | return 9001 195 | } 196 | 197 | // You can still call it like this: 198 | strength(of: .thief, with: .bow, and: .leather) 199 | ``` 200 | 201 | Изброените типове мога да се влагат и в други типове - `структури` и `класове`. Използвайки влагане на изброените типове, свързаните данни остават групирани на едно място. 202 | 203 | ```swift 204 | struct Character { 205 | enum CharacterClass { 206 | case thief 207 | case warrior 208 | case knight 209 | } 210 | 211 | enum Weapon { 212 | case bow 213 | case sword 214 | case spear 215 | case dagger 216 | } 217 | 218 | let class: CharacterClass 219 | let weapon: Weapon 220 | } 221 | 222 | let warrior = Character(class: .warrior, weapon: .sword) 223 | ``` 224 | 225 | ## Асоциирани стойности (Associated Values) 226 | 227 | Чрез асоциираните стойности, можем да "прикрепим" допълнителна информация към случай в изброен тип (`enum case`). Ако предположим, че правим програма за търгуване на акции, то имаме само две възможности - `купи` и `продай`. Всяка от двете възможности работи с определена акция и количество: 228 | 229 | ```swift 230 | enum Trade { 231 | case buy 232 | case sell 233 | } 234 | func executeTrade(_ tradeType: Trade, stock: String, amount: Int) { ... } 235 | ``` 236 | 237 | В този случай ясно се вижда, че акцията и количеството принадлежат на търговията. Можем да ги обединим в структура, но по-лесен начин е да използваме асоциирани стойности: 238 | 239 | ```swift 240 | enum Trade { 241 | case buy(stock: String, amount: Int) 242 | case sell(stock: String, amount: Int) 243 | } 244 | 245 | func executeTrade(_ trade: Trade) { ... } 246 | ``` 247 | 248 | Така дефинираме два случая - `купи` и `продай`, като всеки от тях има прикрепена информация за акцията и количеството, което се търгува. Тези случаи не мога да съществуват без тази информация и не можем да ги запишем така: 249 | 250 | ```swift 251 | let trade = Trade.buy 252 | ``` 253 | 254 | Винаги ги инициализираме с прилежащите асоциирани стойности: 255 | 256 | ```swift 257 | let trade = Trade.buy("APPL", 500) 258 | ``` 259 | 260 | ### Четене на асоциираните стойности и "pattern matching" 261 | 262 | Ако искаме да достъпим данните от асоциираните стойности, то ползваме "pattern matching" в Swift: 263 | 264 | ```swift 265 | let trade = Trade.buy(stock: "AAPL", amount: 500) 266 | 267 | if case let Trade.buy(stock, amount) = trade { 268 | print("buy \(amount) of \(stock)") 269 | } 270 | ``` 271 | 272 | В този случай казваме на компилатора "ако търговията е от тип `купи`, присвои стойностите на двете променливи `stock` и `amount`". 273 | 274 | Горното има и алтернативен запис 275 | 276 | ```swift 277 | if case Trade.buy(let stock, let amount) = trade { 278 | ... 279 | } 280 | ``` 281 | 282 | Естествено можем да ползваме и добре познатия `switch`: 283 | 284 | ```swift 285 | switch trade { 286 | case .buy(let stock, let amount): 287 | ... 288 | case .sell(let stock, let amount): 289 | ... 290 | } 291 | ``` 292 | 293 | ### Имената на асоциираните стойности, може да се пропускат 294 | 295 | Не е нужно да давате имена на асоциираните стойности 296 | 297 | ```swift 298 | enum Trade { 299 | case buy(String, Int) 300 | case sell(String, Int) 301 | } 302 | 303 | let trade = Trade.sell("APPL", 500) 304 | ``` 305 | 306 | В случай, че им дадем имена, обаче, то е задължително да ги използваме при конструирането на обекта. 307 | 308 | Асоциираните стойности можем да използваме в много различни сценарии: 309 | 310 | ```swift 311 | // Типовете на асоциираните стойности може да са различни 312 | enum UserAction { 313 | case openURL(url: Strig) 314 | case switchProcess(processId: UInt) 315 | case restart(time: String?, intoCommandLine: Bool) 316 | } 317 | 318 | // Или пък при имплеметирането на текстови редактор, който има множествено селектиране 319 | // Като например Sublime Text: 320 | // https://www.youtube.com/watch?v=i2SVJa2EGIw 321 | enum Selection { 322 | case none 323 | case single(Range) 324 | case multiple([Range]) 325 | } 326 | 327 | // Или пък да различаваме различ 328 | ните видове идентификационни кодове и тяхната информация 329 | enum Barcode { 330 | case UPCA(numberSystem: Int, manufacturer: Int, product: Int, check: Int) 331 | case QRCode(productCode: String) 332 | } 333 | 334 | // И по-сложен пример: да адаптираме C библиотека, като Kqeue BSD/Darwin нотификации 335 | // system: https://www.freebsd.org/cgi/man.cgi?query=kqueue&sektion=2 336 | enum KqueueEvent { 337 | case userEvent(identifier: UInt, fflags: [UInt32], data: Int) 338 | case readFD(fd: UInt, data: Int) 339 | case writeFD(fd: UInt, data: Int) 340 | case vnodeFD(fd: UInt, fflags: [UInt32], data: Int) 341 | case errorEvent(code: UInt, message: String) 342 | } 343 | 344 | // Като се върнем на примера ни с ролевата игра - всички предмети може да се изброят с допълнителна информация за тях (като тегло). 345 | // С това добавянето на нов материал е само един ред код. 346 | enum Wearable { 347 | enum Weight: Int { 348 | case light = 1 349 | case mid = 4 350 | case heavy = 10 351 | } 352 | 353 | enum Armor: Int { 354 | case light = 2 355 | case strong = 8 356 | case heavy = 20 357 | } 358 | 359 | case helmet(weight: Weight, armor: Armor) 360 | case breastplate(weight: Weight, armor: Armor) 361 | case shield(weight: Weight, armor: Armor) 362 | } 363 | 364 | let woodenHelmet = Wearable.helmet(weight: .light, armor: .light) 365 | ``` 366 | 367 | ## Функции и пропъртита 368 | 369 | Изброените типове в Swift могат да имат "прикрепени" функции и пропъртита. Записът е идентичен с `класовете` и `структурите`. 370 | 371 | ```swift 372 | enum Transportation { 373 | case car(Int) 374 | case train(Int) 375 | 376 | func distance() -> String { 377 | switch self { 378 | case .car(let kms): return "\(kms) километра, пропътувани с кола" 379 | case .train(let kms): return "\(kms) километра, пропътувани с влак" 380 | } 381 | } 382 | } 383 | ``` 384 | 385 | Основната разлика с `класовете` и `структурите` е, че тук може да използваме `switch` оператора върху `self`, за да изчислим резултата. 386 | 387 | ```swift 388 | enum Wearable { 389 | enum Weight: Int { 390 | case light = 1 391 | } 392 | 393 | enum Armor: Int { 394 | case light = 2 395 | } 396 | 397 | case helmet(weight: Weight, armor: Armor) 398 | 399 | func attributes() -> (weight: Int, armor: Int) { 400 | switch self { 401 | case .helmet(let w, let a): 402 | return (weight: w.rawValue * 2, armor: a.rawValue * 4) 403 | } 404 | } 405 | } 406 | 407 | let woodenHelmet = Wearable.helmet(weight: .light, armor: .light) 408 | let woodenHelmetProperties = woodenHelmet.attributes() 409 | ``` 410 | 411 | ### Пропъртита 412 | 413 | Изброените типове **НЕ** могат да имат `stored properties`. 414 | 415 | Те могат да имат **САМО** `изчислими` пропъртита. 416 | 417 | Този запис е невалиден: 418 | 419 | ```swift 420 | enum Device { 421 | case iPad 422 | case iPhone 423 | 424 | let introduced: Int 425 | } 426 | ``` 427 | 428 | Тук опитваме да дадем година, в която устройството е обявено от Епъл, но този код **НЯМА** да се компилира. 429 | 430 | Можем да добавим изчислими пропъртита, чиито резултат може да зависи от случая или дори от асоциираната стойност: 431 | 432 | ```swift 433 | enum Device { 434 | case iPad, 435 | case iPhone 436 | 437 | var introduced: Int { 438 | switch self { 439 | case .iPhone: return 2007 440 | case .iPad: return 2010 441 | } 442 | } 443 | } 444 | ``` 445 | 446 | Този пример работи, защото тази година никога не се променя! 447 | 448 | В случай, че тази информация е променлива, можем да използваме асоциирани стойности, като добавяме пропъртита за по-лесно достъпване на тези стойности: 449 | 450 | ```swift 451 | enum Character { 452 | case wizard(name: String, level: Int) 453 | case warior(name: String, level: Int) 454 | } 455 | 456 | extension Character { 457 | var level: Int { 458 | switch self { 459 | case .wizard(_, let level): 460 | return level 461 | case .warior(_, let level): 462 | return level 463 | } 464 | } 465 | } 466 | ``` 467 | 468 | Можем да добавяме и статични функции и пропъртита: 469 | 470 | ```swift 471 | enum Device { 472 | static var newestDevice: Device { 473 | return .airPods 474 | } 475 | 476 | case iPad, 477 | case iPhone 478 | case appleWatch 479 | case airPods 480 | } 481 | ``` 482 | 483 | Някои функции могат да променят обекта, като тогава те се маркират с `mutating`. Те могат да подменят `self` . 484 | 485 | ```swift 486 | enum TriStateSwitch { 487 | case off, low, bright 488 | 489 | mutating func next() { 490 | switch self { 491 | case .off: 492 | self = low 493 | case .low: 494 | self = .bright 495 | case high: 496 | self = off 497 | } 498 | } 499 | } 500 | 501 | var ovenLight = TriStateSwitch.low 502 | ovenLight.next() 503 | // self == .bright 504 | ovenLight.next() 505 | // self вече е .off 506 | ``` 507 | 508 | # Изброени типове в дълбочина 509 | 510 | Изброените типове са една от най-отличителните функционалности на Swift. В горните примери видяхме много начини за използването им, но има още много неща, които изброените типове могат да правят. 511 | 512 | Могат да се използват в съчетание с протоколи, също като всички останали типове в Swift. Могат да имат и разширения. 513 | 514 | ### Протоколи 515 | 516 | Имплементирането на протоколи е същото, както и при другите типове в Swift. Например протоколът от стандартната библиотека `CustomStringConvertible`: 517 | 518 | ```swift 519 | protocol CustomStringConvertible { 520 | var description: String { get } 521 | } 522 | ``` 523 | 524 | Единственото му условие е изчислимото пропърти `description` и може да се имплементира лесно в наш изброен тип: 525 | 526 | ```swift 527 | enum Trade: CustomStringConvertible { 528 | case buy, sell 529 | var description: String { 530 | switch self { 531 | case .buy: 532 | return "Купуваме нещо" 533 | case .sell: 534 | return "Продаваме нещо" 535 | } 536 | } 537 | } 538 | ``` 539 | 540 | Имплементацията на някои протоколи може да изисква запазването на стойности. Като например протокол за управлението на банкова сметка: 541 | 542 | ```swift 543 | protocol AccountCompatible { 544 | var remainingFunds: Int { get } 545 | mutating func addFunds(amount: Int) throws 546 | mutating func removeFunds(amount: Int) throws 547 | } 548 | ``` 549 | 550 | Със структура, имплементацията на този протокол е тривиална, но с изброен тип - не толкова. Решението са асоциираните стойности: 551 | 552 | ```swift 553 | enum Account { 554 | case empty 555 | case funds(remaining: Int) 556 | case credit(amount: Int) 557 | 558 | var remainingFunds: Int { 559 | switch self { 560 | case .empty: 561 | return 0 562 | case .funds(let remaining): 563 | return remaining 564 | case .credit(let amount): 565 | return amount 566 | } 567 | } 568 | } 569 | ``` 570 | 571 | За по-прегледно, добавяме имплементацията на протокола в разширение. 572 | 573 | ```swift 574 | extension Account: AccountCompatible { 575 | 576 | mutating func addFunds(amount: Int) { 577 | var newAmount = amount 578 | if case let .funds(remaining) = self { 579 | newAmount += remaining 580 | } 581 | if newAmount < 0 { 582 | self = .credit(newAmount) 583 | } else if newAmount == 0 { 584 | self = .empty 585 | } else { 586 | self = .funds(remaining: newAmount) 587 | } 588 | } 589 | 590 | mutating func removeFunds(amount: Int) throws { 591 | try self.addFunds(amount * -1) 592 | } 593 | } 594 | 595 | var account = Account.funds(remaining: 20) 596 | try? account.addFunds(amount:10) 597 | try? account.removeFunds(amount:15) 598 | ``` 599 | 600 | В резултат на тази имплементация, можем много лесно да проверим дали в дадена банкова сметка има средства, без да се налага да достъпваме баланса й. 601 | 602 | ```case 603 | if case Account.empty = account { 604 | print("Банковата сметка е празна!") 605 | } 606 | 607 | ``` 608 | 609 | ### Разширения 610 | 611 | ```swift 612 | enum Entity { 613 | case soldier(x: Int, y: Int) 614 | case tank(x: Int, y: Int) 615 | case player(x: Int, y: Int) 616 | } 617 | ``` 618 | 619 | Както видяхме по-горе, изброените типове могат да бъдат разширявани. Можем да имплементираме протоколи в тях: 620 | 621 | ```swift 622 | extension Entity: CustomStringConvertible { 623 | var description: String { 624 | switch self { 625 | case let .soldier(x, y): return "\(x), \(y)" 626 | case let .tank(x, y): return "\(x), \(y)" 627 | case let .player(x, y): return "\(x), \(y)" 628 | } 629 | } 630 | } 631 | ``` 632 | 633 | Или да използваме разширенията, за да поддържаме кода си по-подреден и прегледен и да отделим функциите от декларацията на случаите в изброения тип: 634 | 635 | ```swift 636 | extension Entity { 637 | mutating func move(dist: CGVector) { ... } 638 | mutating func attack() { ... } 639 | } 640 | ``` 641 | 642 | Можем да разширяваме и типове от стандартната Swift библиотека. Като например изброения тип *Optional*: 643 | ```swift 644 | extension Optional { 645 | var isNone: Bool { 646 | return self == .none 647 | } 648 | } 649 | ``` 650 | 651 | Можем да декларираме и разширения с друг обхват, за да енкапсулираме логика: 652 | 653 | ```swift 654 | fileprivate extension Entity { 655 | mutating func replace(to: Entity) { 656 | self = entity 657 | } 658 | } 659 | ``` 660 | 661 | ### Рекурсивни/индиректни типове 662 | Индиректните типове позволяват да дефинираме изброени типове, в които асоциираната стойност е от същия вид като самия изброен тип. Като пример ще разгледаме файлова система с директории и файлове. Като ако и файловете и директориите са случаи на изборения тип, то директорията ще има асоциирана стойност масив от файлове. 663 | 664 | ```swift 665 | enum FileNode { 666 | case file(name: String) 667 | indirect case folder(name: String, files: [FileNode]) 668 | } 669 | ``` 670 | 671 | Запазената дума `indirect` позволява декларирането на такива рекурсивни изброени типове. Тя може да се отнася и до целия изброен тип. 672 | 673 | ```swift 674 | indirect enum FileNode { 675 | case file(name: String) 676 | case folder(name: String, files: [FileNode]) 677 | } 678 | ``` 679 | 680 | Това е много мощна функционалност, която позволява изграждането на комплексни структури от данни по много чист начин, използвайки изброени типове. 681 | 682 | ### Частни типове на стойностите 683 | 684 | Ако се абстрахираме от асоциираните стойности, то стойността на отделен случай от изброени тип може да бъде само `Integer, Floating Point, String, или Boolean`. Ако се налага да поддържаме друг тип, то той трябва да имплементира протокола `ExpressibleByStringLiteral`, който прозволява сериализацията и десериализацията на типа от и до `String`. 685 | 686 | ```swift 687 | struct Size { 688 | let width: Double 689 | let height: Double 690 | } 691 | 692 | enum Devices: Size { 693 | case iPhone3GS = Size(width: 320, height: 480) 694 | case iPhone5 = Size(width: 320, height: 568) 695 | case iPhone6 = Size(width: 375, height: 667) 696 | case iPhone6Plus = Size(width: 414, height: 736) 697 | } 698 | ``` 699 | 700 | Горният код няма да се компилира, защото Size структурата не е `литерал` и не може да се ползва за стойност на изброения тип. `ExpressibleByStringLiteral` изисква имплементацията на инициализатор с параметър String. Не всеки символен низ е валиден формат, затова ще се подсигурим. 701 | 702 | ```swift 703 | extension Size: ExpressibleByStringLiteral { 704 | public init(stringLiteral value: String) { 705 | let components = rawValue.split(separator: ",") 706 | guard components.count == 2, 707 | let width = Double(components[0]), 708 | let height = Double(components[1]) 709 | else { 710 | return fatalError("Невалиден формат \(value)") 711 | } 712 | 713 | self.init(width: size.width, height: size.height) 714 | } 715 | } 716 | ``` 717 | 718 | Така можем да запишем нашия изброен тип по следния начин: 719 | 720 | ```swift 721 | enum Devices: Size { 722 | case iPhone3GS = "320,480" 723 | case iPhone5 = "320,568" 724 | case iPhone6 = "375,667" 725 | case iPhone6Plus = "414,736" 726 | } 727 | ``` 728 | 729 | Можем да достъпим стойността чрез `rawValue` пропъртито. 730 | 731 | ```swift 732 | let device = Devices.iPhone5 733 | let size = a.rawValue 734 | print("Телефонът \(device) е широк \(size.width) пиксела и висок \(size.height) пиксела.") 735 | ``` 736 | 737 | Алтернативата е да имплементираме протокола `RawRepresentable`, с който ще се сблъскаме след малко. 738 | 739 | ### Сравняване на изброени типове 740 | 741 | Също като символните низове, може да използваме оператора за еквивалентност `==`. 742 | 743 | ```swift 744 | enum Toggle { 745 | case on, off 746 | } 747 | 748 | Toggle.on == Toggle.off 749 | ``` 750 | 751 | При сравняването на по-сложни изброени типове с асоциирани стойности? 752 | 753 | ```swift 754 | enum Character { 755 | case warrior(name: String, level: Int, strength: Int) 756 | case wizard(name: String, magic: Int, spells: [String]) 757 | } 758 | ``` 759 | 760 | Ако отипаме да сравним обекти от този тип, кодът няма да се компилира, защото не знае как да сравни асоциираните стойности. Можем експлицитно да кажем да Swift как да сравни всеки два случая, като със Swift 4.2 вече можем да добавим празна имплементация на протокола `Equatable` и ако всички асоциирани стойности имплементират този протокол, то получаваме функционалността "на готово". 761 | 762 | ```swift 763 | enum Character: Equatable { 764 | case warrior(name: String, level: Int, strength: Int) 765 | case wizard(name: String, magic: Int, spells: [String]) 766 | } 767 | ``` 768 | 769 | С частни типове, трябва да имплементираме протокола: 770 | 771 | ```swift 772 | struct Weapon: Equatable { 773 | let name: String 774 | } 775 | 776 | enum Character: Equatable { 777 | case warrior(name: String, level: Int, strength: Int, weapon: Weapon) 778 | case wizard(name: String, magic: Int, spells: [String]) 779 | } 780 | ``` 781 | 782 | Ако някоя от асоциираните стойности не може да имплементира протокола по една или друга причина, то можем "ръчно" да правим сравнението: 783 | 784 | ```swift 785 | struct Stock { ... } 786 | 787 | enum Trade { 788 | case buy(stock: Stock, amount: Int) 789 | case sell(stock: Stock, amount: Int) 790 | } 791 | 792 | func ==(lhs: Trade, rhs: Trade) -> Bool { 793 | switch (lhs, rhs) { 794 | case let (.buy(stock1, amount1), .buy(stock2, amount2)) 795 | where stock1 == stock2 && amount1 == amount2: 796 | return true 797 | case let (.sell(stock1, amount1), .sell(stock2, amount2)) 798 | where stock1 == stock2 && amount1 == amount2: 799 | return true 800 | default: return false 801 | } 802 | } 803 | ``` 804 | 805 | В горния пример сравняваме два случая чрез `switch` и само ако съвпадат, сравняваме асоциираните им стойности. 806 | 807 | ### Инициализатори 808 | 809 | Ако искаме да инстанциираме обект от изброен тип с произволен тип данни, като например нестандартни имена на Епъл устройства: 810 | 811 | ```swift 812 | enum Device { 813 | case appleWatch 814 | } 815 | ``` 816 | 817 | То можем да напишем инициализатор за това. 818 | 819 | ```swift 820 | enum Device { 821 | case appleWatch 822 | init?(term: String) { 823 | if term == "iWatch" { 824 | self = .appleWatch 825 | } else { 826 | return nil 827 | } 828 | } 829 | } 830 | ``` 831 | 832 | Горният инициализатор е `failable`, но можем да дефинираме "нормални" такива: 833 | 834 | ```swift 835 | enum NumberCategory { 836 | case small 837 | case medium 838 | case big 839 | case huge 840 | 841 | init(number n: Int) { 842 | if n < 10000 { 843 | self = .small 844 | } else if n < 1000000 { 845 | self = .medium 846 | } else if n < 100000000 { 847 | self = .big 848 | } else { 849 | self = .huge 850 | } 851 | } 852 | } 853 | ``` 854 | 855 | ### Итериране на всички случаи на изброения тип 856 | 857 | ```swift 858 | enum Drink: String { 859 | case coke = "кола", beer = "бира", water = "вода", soda = "сода", lemonade = "лимонада", wine = "вино" 860 | } 861 | ``` 862 | 863 | Ако искаме да визуализираме всички случаи на горния изброен тип в списък, бихме искали да го направим с `for-each` цикъл, обхождащ всички случаи. Изброените типове не предлагат тази функционалност по подразбиране. За целта можем да имплементираме протокола `CaseIterable`. Като имплементацията му може и да е празна. 864 | 865 | ```swift 866 | enum Drink: String, CaseIterable { 867 | case coke, beer, water, soda, lemonade, wine, vodka, gin 868 | } 869 | 870 | for drink in Drink.allCases { 871 | print("За пиене бих искал \(drink).") 872 | } 873 | ``` 874 | 875 | Това ще сработи **САМО** в случаите, когато нямаме асоциирани стойности. 876 | 877 | ```swift 878 | enum Drink: CaseIterable { 879 | case beer 880 | case cocktail(ingredients: [String]) 881 | } 882 | ``` 883 | 884 | Този код няма да се компилира, защото не бихме могли да изброен всички възможни стойности на случая `.coctail(_)`. 885 | 886 | ### Изброени типове в стандартната библиотека 887 | 888 | - [FloatingPointClassification](https://developer.apple.com/documentation/swift/floatingpointclassification#//apple_ref/swift/enumelt/FloatingPointClassification/s:FOSs27FloatingPointClassification12SignalingNaNFMS_S_) Този изброен тип съдържа възможните "класове" числа с плаваща запетая според стандарта IEEE 754. Като например **negativeInfinity**, **positiveZero**, **signalingNaN** 889 | - [Optional](https://developer.apple.com/library/watchos/documentation/Swift/Reference/Swift_Optional_Enumeration/index.html#//apple_ref/swift/enum/s:Sq) Шаблонен изброен тип, за който вече сме говорили. случаите му са **none** и **some(Wrapped)** 890 | 891 | 892 | # Изброените типове могат да бъдат и Шаблонни, но за Шаблоните в следващата лекция! 893 | 894 | Примерите в лекцията са адаптация от [статия на AppVenture](https://appventure.me/guides/advanced_practical_enum_examples/introduction.html). 895 | 896 | # Опции 897 | 898 | Опциите (Option set или OptionSet) са подобни на Изброените типове, но са предназначени да работят като набор, в който може да има повече от един обект. Например, когато използваме ```range(of:)``` методът на String, то може да използваме опцията `.caseInsensitive` за игнориране на главни и малки букви, както и `.backwards`, за да започне търсенето ни отзад напред или пък комбинация от двете: 899 | 900 | ```swift 901 | let string = "В град София вали СНЯГ" 902 | let range = string.range(of: "сняг", options: [.caseInsensitive, .backwards]) 903 | ``` 904 | 905 | За да дефинираме собствен набор от Опции, трябва да имаме структура, която да имплементира протокола OptionSet: 906 | 907 | 1. Да дефинираме константа, която да служи за същинската стойност на обекта. Обикновено е Int, но трябва да се укаже. 908 | 2. Да се дефинират статични инстанции на структурата със стойности за всяка опция. 909 | 3. Всяка такава трябва да има уникална стойност, като обикновено използваме побитово изместване, за да избегнем грешки. 910 | 4. Да се дефинират групи от горните обекти в статични колекции (незадължително). 911 | 912 | За да демонстрираме, ще използваме горен пример с движението на персонаж: 913 | ```swift 914 | class GameEngine { 915 | struct Position { 916 | var x: Int 917 | var y: Int 918 | } 919 | 920 | struct MovementDirection: OptionSet { 921 | let rawValue: Int 922 | 923 | static let up = MovementDirection(rawValue: 1 << 0) 924 | static let down = MovementDirection(rawValue: 1 << 1) 925 | static let left = MovementDirection(rawValue: 1 << 2) 926 | static let right = MovementDirection(rawValue: 1 << 3) 927 | 928 | static let all: MovementDirection = [.up, .down, .left, .right] 929 | static let positiveCoordinates: MovementDirection = [.up, .right] 930 | } 931 | 932 | var position = Position(x: 0, y: 0) 933 | 934 | func move(by movementValue: Int, direction: MovementDirection) { 935 | if direction.contains(.up) { 936 | position.y -= movementValue 937 | } else if direction.contains(.down) { 938 | position.y += movementValue 939 | } 940 | 941 | if direction.contains(.left) { 942 | position.x -= movementValue 943 | } else if direction.contains(.right) { 944 | position.x += movementValue 945 | } 946 | } 947 | } 948 | 949 | let engine = GameEngine() 950 | ``` 951 | 952 | При подаването им като параметър, може да използваме: 953 | 1. Единична стойност - ```engine.move(by: 1, direction: .up)``` 954 | 2. Много стойности под формата на сет - ```engine.move(by: 10, direction: [.down, .left])``` 955 | 3. От дефинираните групи - ```engine.move(by: 20, direction: .positiveCoordinates)``` 956 | 4. За празна стойност използваме празен сет - ```string.range(of: "сняг", options: [])``` 957 | -------------------------------------------------------------------------------- /Лекция-09.md: -------------------------------------------------------------------------------- 1 | # Лекция №8 (11.04.2019) 2 | -- 3 | 4 | В тази лекция ще разгледаме вътрешната организация на паметта, когато пишем нашата програма, за да можем да спазваме законите, определени от Swift. Така приложенията ще могат да пестят системни ресурси и да работят по-бързо. Също така ще обърнем внимание на шабноните (функции, класове, структури и изброими типове) в Swift. 5 | 6 | ## Кога можем да говорим за паметта? 7 | 8 | Всяка програма използва памет. Образно можем да си мислим, че това е работната площадка на компютъра (процесора му) и той чете данни от там и ги записва там. Ние като потребители на тази умна система трябва да спазваме правилата, за да може да използваме всички ресурси, които тя ни предоставя. Ако разхищаваме и не следваме правилата, вероятността да стигнем до непредсказуемо състояние е голяма. 9 | 10 | Променливите, които ползваме в нашите програми до сега, се пазят в паметта. В зависимост вида им, те се съхраняват в различни части от паметта. Стойностните типове се записват в програмния стек - това е памет, която се управлява от Swift автоматично. Ние нямаме контрол над нея. Приемаме, че Swift се грижи вместо нас. По-интересни са референтните типове, които се записват в хийп-а. Това е друга памет, която се използва за съхранение на големи обеми от данни. Можем да си мислим, че е неизчерпаема, понеже операционната система има механизми, чрез които може да я разширява, но все пак е с крайно голям обем. 11 | 12 | Важно е да знаем, че класовете са референтни типове. Те се заделят в хийпа (този тип памет) и имаме референция (знание, къде се намират в паметта) към тях. 13 | 14 | ## ARC (Automatic Reference Counting) 15 | 16 | Това е механизмът, който се ползва от Swift за управление на паметта. 17 | 18 | Една инстанция се пази в паметта докато има референции към нея. Ако няма повече референции, тогава тя бива деинициализирана. Стандартните референции са силни референции (strong), защото задържат паметта сочена от тях и тя няма да бъде деинициализирана. 19 | 20 | Можем да говорим за ARC при класовете (т.е. референтните типове) и клоужърите. Типовете, които се предават по стойнонст като структури и изброими типове, не са част от ARC управлението на паметта. Те се управляват от друг различен механизъм, които приемаме за даденост. 21 | 22 | ### Защо ни трябва автоматично управление? 23 | 24 | Автоматичното управление на паметта ни позволява да се фокусриаме над истинските проблеми, а не над управлението на паметта в компютъра. Има различни механизми за управление на памет. Първият - най-базовият е ръчно управление на памет. Среща се в езиците като C, C++. Характерно е, че всяка динамично заделена инстанция заема памет и тази памет трябва експлицитно да бъде освободена след като няма да бъде използвана за напред. Второ ниво на автоматизиране е ARC (механизмът използван от Swift). Характерно за него е, че всяка истанция знае броя референции към нея. Т.е. ако имам две променливи, които сочат конкретна инстанция, то тази инстанция знае, че има поне две референции. Освобождаването на паметта настъпва автоматично, когато броят на референциите стане равен на нула и вече никой няма да използва обекта. Последното ниво на автоматизация (пълна автоматизация) е механизъм, който разчита на garbage collector (гарбидж-колектор). Това е алгоритъм, който се грижи за автоматичното разпознаване на ненужните обекти и освобождаването им. Предимството му е, че програмистът не трябва да се занимава с управлението на паметта - което не е напълно вярно за ARC. Непредсказуемостта му на изпълнение (кога ще се стартира) е основен недостатък. 25 | 26 | За да илюстрираме освобождаването на памет, ще отпечатване текст при автоматичното извикване не 27 | deinit метода на класа. 28 | 29 | ```swift 30 | class Car { 31 | private var name:String 32 | 33 | init(name:String) { 34 | self.name = name 35 | print("Initalize a car instance with name: \(name)") 36 | } 37 | 38 | deinit { 39 | print("Deinit a car instance with name \(self.name) ") 40 | } 41 | } 42 | 43 | var tesla:Car? = Car(name: "Tesla") 44 | tesla = nil 45 | 46 | ``` 47 | ### Алокиране и деалокиране 48 | Заделянето на памет настъпва когато инициализраме нова инстанция - обект от даден тип. Това става, когато неявно извикаме `init` метода на един клас. 49 | 50 | Освобождаването на заетата памет става, когато нямаме повече референции към даден обект. Това е факт, когато занулим съответните променливи, както в примера по горе. 51 | 52 | В общия случай ARC се справя с управлението на паметта, с изключения когато се получи цикъл от референции. Тогава броячите на всяка инстанция не стигат до 0 и паметта не може да бъде освободена. 53 | 54 | Точно такива цикли са причината за memory leak-ове ("изтичане на памет"). Следва пример, който илюстрира цикъл от референции. 55 | 56 | Сега ще дадем пример за референтен цикъл. 57 | 58 | Нека да реализираме следните два класа: 59 | 60 | - Книга (има точно един автор) 61 | - Автор (има точно една книга) 62 | 63 | Това е частен случай на реалността, но е напълно достатъчен да покаже проблема. 64 | 65 | ```swift 66 | class Book { 67 | let title:String 68 | let author:Author 69 | var genre:String? 70 | var pages:Int = 0 71 | 72 | init(title:String, author:Author) { 73 | self.title = title 74 | self.author = author 75 | } 76 | 77 | deinit { 78 | print("Deinit a book instance with title \(self.title) ") 79 | } 80 | } 81 | 82 | class Author { 83 | let name:String 84 | //трябва да добавим weak пред пропъртито, за да 85 | //можем да прекъснем цикъла 86 | //weak var book:Book? 87 | var book:Book? 88 | var age:Int 89 | var isAlive:Bool 90 | 91 | init(name:String, age:Int, isAlive:Bool) { 92 | self.name = name 93 | self.age = age 94 | self.isAlive = isAlive 95 | } 96 | 97 | deinit { 98 | print("Deinit an Auhtor instance with name \(self.name) ") 99 | } 100 | } 101 | 102 | 103 | var author:Author? = Author(name: "Достоевски",age: 73, isAlive: false) 104 | 105 | var book:Book? = Book(title: "53",author:author! ) 106 | 107 | author!.book = book 108 | //не можем да го прекъснем 109 | book = nil 110 | author = nil 111 | ``` 112 | 113 | Можем да направим следните изводи: 114 | 115 | - ARC е добър, когато няма цикли. 116 | - Ако имаме цикли от референции, трябва да използваме съответните механизми, за да ги разрешим. 117 | - ARC има нужда от малка подсказка, за да може да реши проблема. 118 | 119 | ### Да използваме `weak` референция 120 | 121 | `weak` реферeцията е такава, която позволява на ARC да деинициализира променливата, сочена от референцията. В резултат на това, тази променлива има стойност `nil`. Не можем да направим константа и типът е винаги опционален (optional). 122 | Трябва да използваме такава референция, когато реферираният елемент може да бъде заменен. 123 | 124 | Наблюдателите (observers) на пропъртита не се активират, когато ARC промени стойността на `nil`. 125 | 126 | При езиците с гарбидж-колектор (алтернативен механизъм на ARC) weak референциите имат друго значение. Те често се ползват, когато се реализира кеш от обекти, който трябва да се освободи, само когато няма достатъчно памет. Освобождаването се извършва от гарбидж-колектора. При ARC `weak` се различава и не може да бъде ползвана по този начин, тъй като референциите(паметта) биват освободени веднага. 127 | 128 | ### Да използваме `unowned` референция 129 | 130 | `unowned` реферeнцията е такава, която позволява на ARC да деинициализира променливата, но тук интересното е, че 'дължината на живот' на тази променлива е същата или по-дълга. Т.е. няма да има случай в който тя да сочи към мястото в паметта, а то да е `nil`. 131 | 132 | Трябва да се използва `unowned` когато сме сигурни, че референцията ще сочи инстанция, която няма да е деинициализирана. Ако се опитате да достъпите такава инстанция ще се получи грешка при изпълнение (runtime грешка). 133 | Ето и пример, който можем да разрешим с помощта на `unowned` модификатора. 134 | 135 | Да се реализира примерна йерархия: 136 | 137 | - Студент (който има студентска книжка) 138 | - Студентска книжка (StudentCard, която има студент) 139 | 140 | ```swift 141 | class Student { 142 | let name: String 143 | var age = 19 144 | var card:StudentCard? 145 | 146 | init(name:String, age:Int) { 147 | self.name = name 148 | self.age = age 149 | print("Init a student instance.") 150 | } 151 | 152 | func printStrudent() { 153 | print("Name: \(name) ") 154 | print("Age: \(age) ") 155 | } 156 | 157 | deinit { 158 | print("deInit a student instance") 159 | } 160 | } 161 | 162 | class StudentCard { 163 | let university: String 164 | let number: String 165 | 166 | 167 | // unowned(unsafe) let student:Student 168 | // unowned let student:Student 169 | let student:Student 170 | 171 | init(uni:String, number:String, student:Student) { 172 | university = uni 173 | self.number = number 174 | self.student = student 175 | } 176 | 177 | deinit { 178 | print("deInit a student-card instance - \(self.number)") 179 | } 180 | } 181 | 182 | var student:Student? = Student(name: "Г. Петров", age: 21) 183 | var studentId:StudentCard? = StudentCard(uni: "СУ св. 'Климент Охридски'", number: "35123", student: student!) 184 | 185 | student?.card = studentId 186 | 187 | studentId = nil 188 | student = nil 189 | ``` 190 | 191 | Можем да използваме варианта `unowned(unsafe)`, където проверката дали паметта не е занулена, е изключена. Този вариант е по-бърз от стандартния, но носи рискове в случаите, когато инстанцията е деалокирана. 192 | 193 | Референтни цикли можем да получим когато използваме и други референтни типове, примерно клоужъри. 194 | 195 | ## Цикли от референции при клоужъри (closures) 196 | 197 | Понеже тялото на клоужър (closure) запомня (capture) променливи и ако го използваме в клас - запомня `self`, тогава можем да стигнем до цикъл. Тъй като клоужърите и те са референтен тип, те могат да образуват цикъл от референции. 198 | 199 | Сега ще разгледаме следния пример: 200 | 201 | ```swift 202 | class DataType { 203 | let name:String 204 | var properties: Array //[String] 205 | 206 | let prettyPrint = true 207 | 208 | init(name:String) { 209 | self.name = name 210 | properties = [] 211 | } 212 | 213 | lazy var toSwift: () -> String = { 214 | //списъка с променливите в клоужъра ни 215 | //позволява да упражним допълнителен контрол 216 | // [unowned self, name = self.name] in 217 | 218 | var swiftCode:String = "class \(self.name) {" 219 | 220 | if self.prettyPrint { 221 | swiftCode += "\n" 222 | } 223 | 224 | for property in self.properties { 225 | if self.prettyPrint { 226 | swiftCode += "\t" 227 | } 228 | swiftCode += property 229 | } 230 | 231 | if self.prettyPrint { 232 | swiftCode += "\n" 233 | } 234 | 235 | swiftCode += "}" 236 | 237 | return swiftCode 238 | } 239 | 240 | deinit { 241 | print("Deinit dataType instance \(self.name)") 242 | } 243 | } 244 | 245 | var student:DataType? = DataType(name: "Student") 246 | student?.properties.append("var name:String = \"Без име\"") 247 | print(student!.toSwift()) 248 | student = nil 249 | ``` 250 | 251 | За да решим проблема трябва да използваме списъка с променливите, които клоужъра зaпомня и да ги определим като `unowned` или `weak`. Този списък се нарича capture list - или списък със запомнени променливи, които се използват в тялото на клоужъра. Той позволява добавяне на допълнителни модификатори към променливите и дори дефиниране на нови, които пази клоужъра. 252 | 253 | Ето и един пример, който показва каква е разликата между клоужър с и без такъв списък. 254 | 255 | ```swift 256 | var myA = 0 257 | var myB = 0 258 | 259 | let f: () -> () = { [myA] in 260 | print("A = \(myA)") 261 | print("B = \(myB)") 262 | } 263 | 264 | myA = 7 265 | myB = 7 266 | 267 | f() 268 | 269 | //A = 0 270 | //B = 7 271 | ``` 272 | 273 | Ето как изглежда примера за референтни типове. 274 | 275 | ```swift 276 | var myA = Car(name: "Tesla A 1.0") 277 | var myB = Car(name: "Tesla B 1.0") 278 | 279 | let f: () -> () = { [weak myA] in 280 | print("A = \(myA?.name ?? "???")") 281 | print("B = \(String(describing: myB.name))") 282 | } 283 | 284 | 285 | myA.name = "Tesla A 2.0" 286 | myB.name = "Tesla B 2.0" 287 | //"Tesla A 2.0" 288 | //"Tesla B 2.0" 289 | ``` 290 | 291 | На базата на примера можем да направим промяна, като добавим `[unowned self]` преди параметрите на клоужъра. -------------------------------------------------------------------------------- /Лекция-10.md: -------------------------------------------------------------------------------- 1 | # Лекция №10 (18.04.2019) 2 | 3 | # Шаблони 4 | -- 5 | 6 | Шаблоните позволяват да прилагаме общо решение на проблем към частни проблеми. Това редуцира количеството код, което трябва да пишем, но от друга страна повишава трудността да дизайнваме шаблонни решения. 7 | 8 | ## Какъв проблем решават шаблоните? 9 | 10 | Как да напишем една фукнция, която има различни форми само веднъж и тя да може да се използва в различни моменти. 11 | 12 | ## Шаблонни функции 13 | 14 | Това са функции, които заместват конкретни имплементации на други функции. Примерно, можем да дефинираме шаблонна функция, която "събира" два обекта от определен тип. Типовете, над които може да бъде приложена функцията, трябва да имат имплементирана операцията "+". 15 | 16 | ### Трябва да използваме шаблонните типове 17 | 18 | Шаблонните типове се заместват с типове от вида T, K, U, като винаги са с главна буква и ако имат някаква релация, може да се използват думи, които да подсказват релацията. Пример: `Dictionary` 19 | 20 | Шаблоните се добавят след името на функцията в `< >` скоби. После този тип, който е шаблонен се използва въе функцията, за да се определи типа на параметрите и типа на връщания резултат. 21 | 22 | Ето и един пример: 23 | 24 | ```swift 25 | protocol Summable { 26 | static func + (left: Self, right:Self) -> Self 27 | 28 | } 29 | 30 | // изискванията, които поставя тази темплейтна функция са: 31 | // 1. двата параметъра да са от един тип 32 | // 2. резултатът да е от същия тип 33 | // 3. типът да има реализирана операция + или събирането да е дефинирано. Това става, чрез наложеното ограничение в темплейтите 34 | 35 | func sum(_ a:T, _ b: T) -> T { 36 | return a + b 37 | } 38 | 39 | 40 | struct Vector:Summable { 41 | var x = 10 42 | 43 | static func + (left: Vector, right:Vector) -> Vector { 44 | return Vector(x: left.x + right.x) 45 | } 46 | } 47 | 48 | let vX = Vector(x: 10) 49 | let vY = Vector(x: 10) 50 | let sumV = sum(vX, vY) 51 | 52 | extension String:Summable {} 53 | let hw = sum("Hello ", "World!") 54 | ``` 55 | 56 | Можем да ползваме шаблоните не само при функциите, но и при дефинирането на нови класове. 57 | 58 | ## Шаблонни класове 59 | 60 | Шаблонните класове наподобяват шаблонните функции, защото шаблонните типове се задават пак в `< >` скоби след името на класа. 61 | 62 | Можем да дефинираме и шаблонни протоколи, като използваме запазената дума: `associatedtype`. 63 | 64 | Ето и един пример: 65 | ```swift 66 | protocol CollectionEquatable { 67 | associatedtype Element 68 | var count:Int { get } 69 | subscript (i:Int) -> Element {get} 70 | } 71 | ``` 72 | 73 | Това е протокол, който трябва да борави с шаблонен тип `Element`. Така определената версия на протокола, може да конкретизира типа, който ще се свърже с `Element`. Т.е. навсякъде вместо този тип, ще се използва конкретния валиден тип. 74 | 75 | Ето и пример за шаблонна опашка: 76 | 77 | ```swift 78 | class Queue:CollectionEquatable { 79 | var items:[Item] 80 | 81 | init() { 82 | items = [] 83 | } 84 | 85 | func insert(item:Item) { 86 | items.append(item) 87 | } 88 | 89 | func get() -> Item? { 90 | if items.count > 0 { 91 | return items.removeFirst() 92 | } 93 | 94 | return nil 95 | } 96 | 97 | subscript (i:Int) -> Item { 98 | return items[i] 99 | } 100 | 101 | var count:Int { 102 | return items.count 103 | } 104 | } 105 | 106 | extension Array : CollectionEquatable {} 107 | ``` 108 | 109 | Нека сега да напишем шаблонна функция, която сравнява две колекции, които могат да бъдат сравнявани по следните правила: 110 | 1. Двете колекции трябва да имплментират протокола `CollectionEquatable` 111 | 2. Да имат еднакъв брой елементи 112 | 3. Всеки елемент да е еднакъв на съответния елемент от другата колекция 113 | 114 | Ето и код, който илюстрира това: 115 | 116 | ```swift 117 | func isEqual(left:T, right:U) -> Bool where T.Element:Equatable, U.Element == T.Element { 118 | if left.count == right.count { 119 | for i in 0..`. 134 | `T.Element:Equatable` и `U.Element == T.Element` ни позволява да напишем `left[i] != right[i]`, понеже елементите ще са от един и същи тип и този тип ще е сравним. 135 | 136 | Добре е да знаем, че можем да използваме разширения, за да добавяме допълнителни функции към шаблонните класове. 137 | 138 | Ограниченията над шаблонните класове ги задаваме по няколко различни начина: 139 | 140 | - чрез протоколи, които трябва да бъдат имплементирани от класовете. (Добре е да си припомним, че можем да фиксираме даден протокол да се имплементира само от класове) 141 | - чрез `WHERE` клауза и зависимости между класовете. 142 | - чрез `associatedtype` и протоколи 143 | 144 | ### Асоциирани типове и протоколи 145 | 146 | Трябва да използваме `associatedtype Element` като част от протокола. В последствие, можем да използваме типа `Element`. 147 | 148 | При имплементиране на определен протокол с асоцииран тип, Swift сам успява да определи реалният тип, с който трябва да замести асоциацията. Т.е. `Element` с кой реален тип трябва да бъде заместен. 149 | 150 | ### Where условия при шаблоните 151 | 152 | Условието трябва да се добави преди началото на функцията или типа от данни, които описваме. Може да видите правилото в действие в кода по-горе. 153 | 154 | 155 | # Атрибути * 156 | -- 157 | Вече сме се сблъсквали с няколко приложения на атрибутите. Добре е да разберем тяхното основно приложение. 158 | 159 | ## За какво се ползват атрибутите 160 | 161 | Това е механизъм, който добавя допълнителна информация към декларация или към тип. В Swift се използват за да дадем допълнителна информация за параметрите на функция. 162 | Ето и пример: 163 | 164 | ```swift 165 | func funcAutoclosureComplex(pred: @autoclosure () -> ()) { 166 | print("body of \(#function)") 167 | } 168 | ``` 169 | 170 | Общият вид е следния: 171 | 172 | ```swift 173 | @attributeName 174 | //или 175 | @attributeName(attribute arguments) 176 | ``` 177 | 178 | ## Популярни атрибути 179 | 180 | Популярните атрибути, които можете да срещнете в Swift код са: 181 | 182 | - available - използва се да покажем даден метод от кога е достъпен. За коя платформа и от коя версия: 183 | ```swift 184 | @available(platformName versionNumber, *) 185 | //пример 186 | @available(iOS 10.1, macOS 10.12, *) 187 | ``` 188 | - discardableResult - прилага се при функции, за да не се изписва предупреждението, когато резултата от функцията не се използва. 189 | - GKInspectable - използва се при атрибути, които са свързани с iOS фреймуорка SpriteKit. 190 | - objc - използва се, когато искаме да направим код-а от swift достъпен за obj-C. Има своите специфики. 191 | - nonobjc - използва се, когато задължително искаме да скрием декларация от Obj-C. 192 | - NSApplicationMain - прилага се към основният делегат. Приложимо е в iOS. 193 | 194 | Атрибутите, които се прилагат над параметрите са: 195 | 196 | - escaping - добре познат атрибут свързан с клоужърите. Трябва да се използва в случай, когато клоужъра ще напусне функцията. 197 | - autoclosure - конвертира израза в клоужър 198 | - convention - изполва се, когато се смесват различни типове функции - objc, c или swift 199 | 200 | ```swift 201 | //int (*)(void) in C 202 | //in Swift looks like this 203 | @convention(c) () -> Int32 204 | ``` 205 | Повече информация може да намерите в официалната документация от Apple (https://developer.apple.com/library/content/documentation/Swift/Conceptual/BuildingCocoaApps/InteractingWithCAPIs.html). 206 | 207 | -- 208 | * няма да е част от тест 1 209 | 210 | -------------------------------------------------------------------------------- /Лекция-11.md: -------------------------------------------------------------------------------- 1 | # Грешки (Errors) в Swift 2 | 3 | Без значение колко добър е, обработката и справянето с грешки е често срещана задача за всеки програмист. Въпреки че имаме пълен контрол над кода, който пишем, и функционалностите, които създаваме, не можем да контролираме всичко в нашата програма и може да възникнат неочаквани събития или да получим неочаквани резултати. Това, което не можем да контролираме, е начинът, по който потребителят работи с нашата програма или дали необходимите ни ресурси са налични. В тази лекция ще разгледаме начините в Swift да се справяме с такива ситуации и възникналите грешки. 4 | 5 | В Swift определението за грешка е: 6 | 7 | ...неуспешна проверка на даден израз, която води до отклонение в нормалното изпълнение на нашия код. 8 | 9 | ## Условия за възникване на грешки 10 | 11 | Според условията за възникването си, грешките попадат в една от три категории: 12 | 13 | * Логически 14 | * Прости 15 | * Възвратими 16 | 17 | ### Логически грешки 18 | 19 | Логическите грешки възникват в резултат от проблеми в кода, който пишем. Тази категория за грешки включва неща като случайно извикване на функция на обект, който смятаме, че съществува, а всъщност е **nil** или се опитваме да достъпим елемент в колекция, използвайки индекс извън обхвата. В повечето случаи Swift (с помощта на компилатора) прави всичко възможно, за да ни помогне да избегнем тези видове грешки, но тази система не е перфектна и в тези грешки обикновено водят до принудително спиране на изпълнението на нашата пограма - краш (crash). 20 | 21 | ### Прости грешки 22 | 23 | Простите грешки възникват в резултат на извършване на някаква операция, която може да се провали по очевиден начин. Условията за грешка в тази категория обикновено са прости по своята същност и поради това те са лесно разбираеми и обикновено не се нуждаят от допълнителна информация (ако има такава), за да се разбере какво се е случило. 24 | 25 | Конвертирането на **String** в **Int** е един такъв пример: 26 | 27 | ```swift 28 | let value: Int? = Int ("1") // 1 29 | let value2: Int? = Int ("Здравейте") // nil 30 | ``` 31 | 32 | Стойността на **String** може да се преобразува в **Int** или пък не. Няма междинни случаи и не ни е необходима допълнителна информация, за да разберем какво се е случило. В Swift обикновено се справяме с простите грешки, като използваме **Optional** - връщане на желаната стойност при успех или **nil** в случай на грешка. 33 | 34 | ### Възвратими грешки 35 | 36 | Последната категория грешки са възвратимите грешки, които възникват в резултат на операции, които се провалят по не толкова очевиден начини и ни е необходима допълнителна информация или са резултат от външни услуги, които нашата програма ползва. Да вземем за пример четенето на файл от файловата система: 37 | 38 | ```siwft 39 | func contentsOf(file filename: String) -> String? { 40 | // ... 41 | } 42 | ``` 43 | 44 | Въпреки че резултатът от функцията е дефиниран като **Optional** (String?), има много причини, поради които четенето може да се провали - файлът не съществува, нямаме права да го достъпим, има грешен (не-текстови) формат и т.н. Въпреки че можем да определим дали функцията е изпълнена успешно по резултата й, ни е необходима информация да диагностицираме условията за грешката и как да се справим с нея. Трябва ни механизъм, който да използваме, за да капсулираме тази допълнителна информация. Swift предоставя няколко начина за решаване на проблема, но първо да разгледаме как се представят грешките в Swift. 45 | 46 | ## Error протоколът 47 | 48 | В Swift грешките са обекти от типове, които имплементират **Error** протокола. Това е празен протокол, който няма функции или член данни. За да дефинираме грешка, е необходимо да дефинираме тип, който имплементира протокола. Въпреки че този тип може да е всякакъв, в практиката най-често се използват Изброените типове (enums). Например: 49 | 50 | ```swift 51 | enum FileError: Error { 52 | case notFound 53 | case permissionDenied 54 | case unknownFormat 55 | } 56 | ``` 57 | 58 | В този пример грешката, която ще обработваме ще е от тип **FileError** и има следните случаи - файлът не съществува (**.notFound**), нямаме права да го достъпим (**.permissionDenied**) и непознат формат или неподдържан/непознат формат (**.unknownFormat**). 59 | 60 | Можем да използваме и Изброени типове с асоциирани стойности, за да предаваме допълнителната информация от грешката. 61 | 62 | ```swift 63 | enum IntParsingError: Error { 64 | case overflow 65 | case invalidInput(Character) 66 | } 67 | ``` 68 | 69 | След като вече знаем как да различаваме възникналата грешка, ни е необходим начин да я предаваме като резултат от нашата функция. Това може да се направи чрез *резултатен* тип. 70 | 71 | ### Резултатни типове (Result types) 72 | 73 | Да вземем за пример **Optional**: 74 | 75 | ```swift 76 | enum Optional { 77 | case none 78 | case some(Wrapped) 79 | } 80 | ``` 81 | Резултатните типове са много подобни - шаблонен Изброен тип с два възможни случая: 82 | 83 | * неуспех с асоциирана стойност обект от тип, който имплементира **Error** протокола 84 | * успех с асоциирана стойност обект от очаквания тип 85 | 86 | ```swift 87 | enum ResultType { 88 | case failure(Error) 89 | case success(T) 90 | } 91 | 92 | ``` 93 | 94 | Прилагайки идеята към горния пример: 95 | 96 | ```swift 97 | func contentsOf(file filename: String) -> String? { 98 | // ... 99 | } 100 | ``` 101 | 102 | Вместо функция, която връща **Optinal**, за да покаже успех или неуспех при четене на файл, можем да я предефинираме като функция, която връща нашия **ResultType** със съответната информация за грешка или символния низ. 103 | 104 | ```swift 105 | func contentsOf(file filename: String) -> ResultType { 106 | //... 107 | } 108 | ``` 109 | 110 | При извикване на функцията може да обработваме резултата или грешката по следния начин: 111 | 112 | ```swift 113 | let filename = "source.txt" 114 | let result = contentsOf(file: filename) 115 | 116 | switch result { 117 | case .success(let content): 118 | print(content) 119 | case .failure(let error): 120 | switch error { 121 | case FileError.notFound: 122 | print("Unable to locate file \(filename).") 123 | case FileError.permissionDenied: 124 | print("You do not have the required permissions to access the file \(filename).") 125 | case FileError.unknownFormat: 126 | print("Unable to open file \(filename) - incompatible format.") 127 | default: 128 | print("Unknown error") 129 | } 130 | } 131 | ``` 132 | 133 | Обърнете внимание на **default** клаузата в switch-a. Всеки switch оператор в Swift трябва да е изчерпателен, затова обработваме *всички* грешки, които имплементират протокола **Error**, а не само FileError. Можем обаче да добавим допълнителни ограничения, за да си го спестим, като добавим втори шаблонен тип при неуспех: 134 | 135 | ```swift 136 | enum ResultType { 137 | case failure(E) 138 | case success(T) 139 | } 140 | 141 | func contentsOf(file filename: String) -> ResultType { 142 | //... 143 | return .failure(.notFound) 144 | } 145 | ``` 146 | 147 | В Swift, по подобие на Optional изброеният тип е дефиниран и ```Result where Failure : Error```, който може да използваме. 148 | 149 | ## "Хвърляне" на грешки чрез throw 150 | 151 | В Swift можем да окажем, че в една функция или клоужър е възникнала грешка чрез запазената дума **throw**. Така използваме вградения механизъм за обработка на грешки. Хвърлянето на грешка е същото като връщане на резултат неуспех (например **.failure(FileError.notFound)**), позволявайки ни също да прикачим допълнителна информация. Използваме **throw** оператора, следван от обект, който имплементира **Error** протокола. 152 | 153 | ```swift 154 | throw FileError.notFound 155 | ``` 156 | 157 | Грешката може да се хвърли по време на изпълнение в тялото на функция или клоужър. При извикване на **throw** оператора, изпълнението на функцията спира веднага (подобно на **return**) и грешката се прехвърля към обкръжаващия блок (scope). 158 | 159 | Обработването на грешката може да се извърши в извикващата функция или да се предаде нагоре по веригата. 160 | 161 | ### Предаване на грешката чрез throws 162 | 163 | Дали една функция хвърля грешка или не е част от нейната дефиниция. По подразбиране функциите в Swift *не* хвърлят грешки и не могат да ги предават нагоре по веригата. Това означава, че в общия случай, ако възникне грешка в тялото на функцията, трябва да я прихванем и обработим. Понякога не можем (нямаме достатъчно информация) или не е логично да обработим грешката в текущата функция. Тогава е необходимо да я предадем нагоре по веригата, за да се обработи от някоя от извикващите функции. За да окажем, че една функция може да хвърля и предава грешки, използваме запазената дума **throws** към дефиницията на функцията. Тогава казваме, че тази функция прехвърля (**rethrows**) или предава грешките, възникнали по време на нейното изпълнение. Запазената дума се записва **след** списъка с параметри на функцията и **преди** стрелката за връщане на резултат (->): 164 | 165 | ```swift 166 | func contentsOf(file filename: String) throws -> String { 167 | //... 168 | } 169 | ``` 170 | 171 | ### Извикване на хвърлящи/предаващи функции 172 | 173 | От дефиницията на горната функция става ясно, че тя хвърля грешка по време на изпълнението си. От гледна точка на извикването й обаче нещата не изглеждат толкова ясни на пръв поглед: 174 | 175 | ```swift 176 | let content = contentsOf(file: "test.txt") 177 | ``` 178 | 179 | От тук няма как да сме сигурни дали извиканата функция хвърля грешка или не. Тук на помощ идва компилаторът, който ни предупреждава и изисква да използваме запазената дума **try** (**try?** или пък **try!**) преди извикването на функцията. 180 | 181 | ```swift 182 | let content = try contentsOfFile(file: "test.txt") 183 | ``` 184 | 185 | Добавянето на **try** извършва две функции - сигнализира на компилатора, че извикваме функция, която може да предава грешка; а на четящия кода става ясно, че извикваната функция хвърля грешка, която трябва да се обработи. 186 | 187 | ### Предаване на грешките чрез rethrows 188 | 189 | Да вземем за пример функция **log**, която изписва съобщения (в случая в конзолата). 190 | 191 | ```swift 192 | func log(description: () -> String) { 193 | print(description()) 194 | } 195 | ``` 196 | 197 | Така записана, функцията е напълно валидна и работеща, но ако си представим, че клоужъра **description** може да хвърля грешки, то самата функция **log** може да ги обработва или да ги предава за обработка на извикващия чрез **rethrows**. В Swift **rethrows** се използва за функции, които хвърлят грешка само когато някой от параметрите им хвърля грешка: 198 | 199 | ```swift 200 | func log(description: () throws -> String) rethrows -> () { 201 | print(try description()) 202 | } 203 | 204 | func nonThrowingDescription() -> String { 205 | return "Hello" 206 | } 207 | 208 | enum LogError : Error { 209 | case someError 210 | } 211 | 212 | func throwingDescription() throws -> String { 213 | //... 214 | throw LogError.someError 215 | } 216 | 217 | log(nonThrowingDescription) 218 | try log(throwingDescription) 219 | ``` 220 | 221 | Първото извикване на функцията е с клоужър **nonThrowingDescription**, който не хвърля грешки и тъй като използваме анотацията **rethrows**, цялата функция не би могла да хвърли грешка при извикването си. Затова не е необходим **try**. При второто извикване на функцията с параметър **throwingDescription**, имаме хвърляне на грешки и затова е необходим **try**. 222 | 223 | Две важни неща за функциите с анотация **rethrows**: 224 | 225 | 1. Те **трябва** да имат поне един параметър, който е функция/клоужър, която да хвърля грешки (**throws**). 226 | 2. Те могат да предават само грешки, които са хвърляни от функциите, които получават като параметър, но сами по себе си не могат да хвърлят грешки 227 | 228 | ```swift 229 | //1 230 | func doSomething(completion: () -> String) rethrows -> String { 231 | return completion() 232 | } 233 | // ERROR: 'rethrows' function must take a throwing function argument 234 | 235 | //2 236 | enum ExampleError : Error { 237 | case someError 238 | case someOtherError 239 | } 240 | 241 | func doSomethingElse(completion: () -> String) rethrows -> String { 242 | throw ExampleError.someError 243 | } 244 | // ERROR: a function declared 'rethrows' may only throw if its parameter does 245 | ``` 246 | 247 | ## Обработка на грешките 248 | 249 | В Swift можем да обработим грешките по един от три начина: 250 | 251 | * Чрез **do-catch** оператора 252 | * Използвайки **Optinal** стойност 253 | * Прекратяване на изпълнението на програмата 254 | 255 | ### Обработка на грешките чрез do-catch оператора 256 | В Swift **do** операторът създава нов блок (scope). Подобен е на къдравите скоби (**{}**) в **C**. Този блок е като всеки друг, което означава, че в него можем да дефинираме променливи и константи, които ще бъдат достъпни само в този блок и ще са живи до края на блока дефиниран с **do**. В допълнение, **do** блокът може да съдържа един или повече **catch** оператора, като всеки **catch** съдържа израз, който може да бъде сравняван с очаквани грешки. Чрез **catch** *хващаме* хвърляните грешки от **throw** функциите: 257 | 258 | ```swift 259 | do { 260 | try expression 261 | } 262 | catch errorPattern1 { 263 | //. 264 | } 265 | catch errorPattern2 where condition { 266 | //.. 267 | } 268 | catch { 269 | //... 270 | } 271 | ``` 272 | 273 | Можем да използваме **do-catch** в различните му форми, както и в комбинации, за да обработваме различни грешки: 274 | 275 | ```swift 276 | enum InputError : Error { 277 | case makeMissing 278 | case mileageTooLow(Int) 279 | case mileageTooHigh(Int) 280 | } 281 | 282 | func buyCar(make: String, mileage: Int) throws { 283 | guard make.isEmpty == false else { 284 | throw InputError.makeMissing 285 | } 286 | 287 | switch mileage { 288 | case mileage where mileage < 1_000: 289 | throw InputError.mileageTooLow(mileage) 290 | case mileage where mileage > 150_000: 291 | throw InputError.mileageTooHigh(mileage) 292 | default: 293 | print("Buy it!") 294 | } 295 | } 296 | 297 | do { 298 | try buyCar(make: "Honda", mileage: 175_000) 299 | } 300 | catch InputError.makeMissing { // проверяваме за конкретна грешка 301 | print("Missing make") 302 | } 303 | catch let InputError.mileageTooHigh(x) where x > 300_000 { // асоциирани стойности и допълнителни условия 304 | print("Mileage way way too high...") 305 | } 306 | catch let InputError.mileageTooHigh(x) { // асоциирани стойности 307 | print("Mileage too high") 308 | } 309 | catch { // хващаме всички останали грешки. 310 | print("\(error)") 311 | } 312 | 313 | // Mileage too high 314 | 315 | ``` 316 | 317 | Обърнете внимание на сравняването на типа грешка и използването на асоциирана стойност. 318 | 319 | При използване на catch без условия, по подразбиране получаваме обект error от тип Error! 320 | 321 | ### Конвертиране на грешките към Optional чрез try? 322 | 323 | Освен `do-catch` оператора, в Swift може да използваме **try?** оператора, за да получим резултата от функцията или **nil** при грешка: 324 | 325 | ```swift 326 | func throwing() throws -> String { 327 | // ... 328 | } 329 | 330 | let result = try? throwing() // -> String? 331 | ``` 332 | 333 | Недостатъкът на този подход е, че губим допълнителната информация, асоциирана с грешката. 334 | 335 | ### Прекратяване на програмата при грешка чрез try! 336 | 337 | Последният начин за обработване на грешките в Swift е чрез оператора **try!**. Той е много по-задължаващ от **try** и **try?**. Използваме го, когато *знаем*, че функцията, която пишем не би трябвало да хвърля грешка при изпълнението си, въпреки че е маркирана с **throw**. Например при предварително валидирани потребителски данни, при които сме сигурни, че няма грешка. 338 | 339 | ```swift 340 | enum ValueError : Error { 341 | case negativeValue 342 | } 343 | 344 | func squarePositive(value: Int) throws -> Int { 345 | guard value >= 0 else { 346 | throw ValueError.negativeValue 347 | } 348 | 349 | return value * value 350 | } 351 | 352 | let output : Int 353 | let input = 10 354 | if input >= 0 { 355 | output = try! squarePositive(value: input) 356 | print(output) 357 | } 358 | ``` 359 | 360 | ### Други типове, имплементиращи Error протокола 361 | 362 | Понякога искаме да имаме различни грешки, но с еднакви допълнителни данни, като например позицията във файл или пък статусът на изпълняваната програма. 363 | За предпочинате е да използваме Структура в този случай, вместо Изборен тип. 364 | 365 | Ето пример за грешка при прочитането на XML документ, в която се съдържа редът и колоната, където е възникнала грешката: 366 | 367 | ```swift 368 | struct XMLParsingError: Error { 369 | enum ErrorKind { 370 | case invalidCharacter 371 | case mismatchedTag 372 | case internalError 373 | } 374 | 375 | let line: Int 376 | let column: Int 377 | let kind: ErrorKind 378 | } 379 | 380 | func parse(_ source: String) throws -> XMLDoc { 381 | // ... 382 | throw XMLParsingError(line: 19, column: 5, kind: .mismatchedTag) 383 | // ... 384 | } 385 | ``` 386 | 387 | Обработката е същата, като при Изброените типове. Ето как може да хванем, която и да е `XMLParsingError`, хвърлена от функцията `parse(_:)`: 388 | 389 | ```swift 390 | do { 391 | let xmlDoc = try parse(myXMLData) 392 | } catch let e as XMLParsingError { 393 | print("Parsing error: \(e.kind) [\(e.line):\(e.column)]") 394 | } catch { 395 | print("Other error: \(error)") 396 | } 397 | 398 | // "Parsing error: mismatchedTag [19:5]" 399 | ``` 400 | -------------------------------------------------------------------------------- /Лекция-12.md: -------------------------------------------------------------------------------- 1 | # От Swift към SwiftUI 2 | 3 | В тази лекция ще разгледаме основните механизми на Swift, които се ползват активно в реализацията на SwiftUI `фреймуорка`. 4 | > Фреймуорк - множество от типове данни и/или алгоритми (в това число включваме всичките познати неща от езика като - функци, класове, структури, разширения, изброими типове, протоколи, грешки и други), които позволяват по-лесното решаване на определени задачи. 5 | 6 | ## KeyPath 7 | 8 | Ще запознем с кийпат (keypath). 9 | > Keypath е механизъм, който позволява да дефинираме пътя към пропърти или събскрипт (subscript `[]`) 10 | ```swift 11 | \Име на тип данни.път //общ вид 12 | 13 | \Car.name //конкретен пример 14 | \Person.age //конкретен пример 15 | \Car.owner?.name //по-сложен пример 16 | ``` 17 | 18 | > Път (path) наричаме последователността от различни пропъртита разделени с `.` или с оператор за индексен достъп (subscript). Mоже да учасва и опшънал (optional-chaining expression) свързване с `?` и форсирано разопаковане (force unwrapping) със `!`. 19 | 20 | Примерно: 21 | 22 | ```swift 23 | struct Person { 24 | let name: String 25 | let age: Int 26 | } 27 | 28 | struct Car { 29 | let name: String 30 | let owner: Person? 31 | } 32 | 33 | let person = Person(name: "Ivan", age: 30) 34 | let car = Car(name: "Tesla") 35 | car.owner = person //car.owner е път 36 | print(car.owner?.name) //car.owner?.name е път с optional синтаксис 37 | ``` 38 | 39 | При компилиране, конкретния кийпат е заменен с инстанция на `KeyPath` класа. 40 | 41 | За да достъпите стойността през кийпат, трябва да се изполва метода 42 | `subscript(keyPath:)`, който е достъпен от всички типове. 43 | 44 | ```swift 45 | let batman = Person(name: "Bruce Wayne", age: 30) 46 | let pathToName = \Person.name 47 | let value = batman[keyPath: pathToName] //стойността е "Bruce Wayne" 48 | ``` 49 | 50 | С този механизъм можем да достъпваме стойността за четене и за запис. Това позволява да променяме стойностите, ако това е възможно. 51 | 52 | Кийпат-ът може да използва запазената дума `\.self`, за да реферира към себе си (или по-точно към самата инстанция). 53 | 54 | ```swift 55 | var joker = Person(name: "Joker", age: 30) 56 | joker[keyPath: \.self] = Person(name: "The Joker", age: 30) 57 | print(joker[keyPath: \.name]) // отпечатва "The Joker" 58 | ``` 59 | 60 | Може да се създават по-сложни кийпатове чрез комбинирането на няколко кийпата. 61 | > Кобинирането на кийпатове, които трябва да ползват опционални полета не работи. 62 | 63 | ```swift 64 | // пример без optional 65 | struct Car { 66 | let name: String 67 | // ако owner е опционално пропърти, комбинирането не работи 68 | // понеже не може да се създаде KeyPath 69 | let owner: Person 70 | 71 | init(name: String, owner: Person) { 72 | self.name = name 73 | self.owner = owner 74 | } 75 | } 76 | 77 | let carOwner = \Car.owner 78 | let p = Person(name: "Elon", age: 50) 79 | let tesla = Car(name: "Tesla", owner: p) 80 | let personNameKeyPath = \Person.name 81 | let combinedKeyPath = carOwner.appending(path: personNameKeyPath) 82 | 83 | let personName = tesla[keyPath: combinedKeyPath] 84 | print(personName) // отпечатва Elon 85 | ``` 86 | 87 | Кийпат-ът може да се изпозлва на места, където се изисква клужър, чиито тип на резултата съвпада с типа на кийпада. Ето и един такъв пример, ако искаме да трансформираме колекиця от обекти и да достъпим само определено пропърти от самите обекти. 88 | 89 | ```swift 90 | let cars = [Car(name: "Tesla"), Car(name: "Mercedes"), Car(name: "Lada"), Car(name: "BMW")] 91 | let names = cars.map(\.name) 92 | print(names) // отпечатва ["Tesla", "Mercedes", "Lada", "BMW"] 93 | ``` 94 | 95 | Можем да изпозлваме функции и по-сложни изрази при инициализирането на кийпатове. 96 | 97 | > Кийпат-ът се оценява в момент, в който се инициализра проемнливата от тип `KeyPath`, а не при всяка употреба. 98 | 99 | 100 | ```swift 101 | let cars = [Car(name: "Tesla"), Car(name: "Mercedes"), Car(name: "Lada"), Car(name: "BMW")] 102 | var i = 0 103 | func getIndex() -> Int { 104 | print("Get exact index!") 105 | return i 106 | } 107 | 108 | let keyPathIndex = \[Car][getIndex()] // отпечатва Get exact index! 109 | i += 1 110 | 111 | let car = cars[keyPath:keyPathIndex] // не отпечатва нищо 112 | print(car) // отпечатва Car(name: "Tesla", owner: nil) 113 | ``` 114 | 115 | > Кийпат-овете може да се ползват и в случай с Obj-C. Тогава употребата им става със следната запазаена дума `#keyPath()`. Ето и един пример: 116 | `#keyPath(Car.name)` Пропъртито `name` трябва да е достъпно в Objective-C среадата. 117 | 118 | ## Property Wrapper 119 | 120 | Пропъртирапър-ът е абстракция, която позволява лесно управление на начина на съхраняване на определено пропърти от кодът, който го дефинира. 121 | 122 | Можем да имплементираме пропертирапър като структура, изброим тип или клас. За целта трябва да добавим пропъртито `var wrappedValue: <Тип>` и атрибута `@propertyWrapper` преди самия тип. 123 | 124 | Чрез пропъртирапъри можем да редуцираме сложността на имплементацията на нашите типове и получаваме възможност за пренасяне на логиката между различни пропъртита с помощта на единствен антрибут. 125 | 126 | Ето и един пример, който илюстрита какво е пропъртирапър: 127 | 128 | ```swift 129 | @propertyWrapper 130 | struct Trace { 131 | private var value: String = "" 132 | var wrappedValue: String { 133 | get { 134 | print("=> Get value \(value)") 135 | return value 136 | } 137 | 138 | set { 139 | print("=> Set value \(newValue)") 140 | value = newValue 141 | } 142 | } 143 | } 144 | 145 | struct ElectricCar { 146 | //Important: A constructor is required! 147 | init(model: String, speed: Int) { 148 | self.speed = speed 149 | self.model = model 150 | } 151 | 152 | init(model: String) { 153 | self.speed = 0 154 | self.model = model 155 | } 156 | 157 | @Trace var model: String 158 | var speed:Int 159 | } 160 | var tesla = ElectricCar(model:"Tesla", speed: 300) // отпечатва => Set value Tesla 161 | print(tesla.model) // отпечатва => Get value Tesla 162 | // отпечатва Tesla 163 | tesla.model = "Tesla X"// отпечатва => Set value Tesla X 164 | ``` 165 | 166 | В примера сме избрали да реализираме структура `Trace`, която само 167 | съхранява стойност от тип `String`. Тя отпечатва текст в конзолата, когато нейните методи са използвани. Прилагаме пропъртирапъра към `model` пропъртито на структурата `ElectricCar`. 168 | Имам точно две отпечатвания, понеже точно два пъти се изпозлва пропъртито. 169 | 170 | > Когато добавим атрибут към пропръти (което е пропъртирапър) тогава компилатора прави промени по кода за нас. Това променя типовете на пропъртитата. 171 | 172 | Ориентировъчно структурата започва да изглежда така: 173 | 174 | ```swift 175 | struct ElectricCar { 176 | //Important: A constructor is required! 177 | init(model: String, speed: Int) { 178 | self.speed = speed 179 | self.model = model 180 | } 181 | 182 | init(model: String) { 183 | self.speed = 0 184 | self.model = model 185 | } 186 | private var _model = Trace() 187 | var model: String { 188 | get { 189 | return _model.wrappedValue 190 | } 191 | set { 192 | _model.wrappedValue = newValue 193 | } 194 | } 195 | var speed:Int 196 | } 197 | ``` 198 | > Това налага следните две важни ограничения: 199 | 1. Трябва да имаме собствени конструктори, които да ползват стандартния тип на пропъртитата, понеже типът се променя при прилагане на атрибута. 200 | 1. Трябва да се инициализрат порпъритата без пропъртирапъри първо и после тези с пропъртирапъри. 201 | 202 | Ето и няколко проблеми реализации: 203 | (без конструктор) 204 | ```swift 205 | struct ElectricCar { 206 | @Trace var model: String 207 | var speed:Int 208 | } 209 | 210 | var tesla = ElectricCar(model:"Tesla", speed: 300) 211 | // не може да се компира 212 | // error: cannot convert value of type 'String' to expected argument type 'Trace' 213 | 214 | ``` 215 | (с грешна поредност на инициализирането на пропъртитата) 216 | ```swift 217 | struct ElectricCar { 218 | //Important: A constructor is required! 219 | init(model: String, speed: Int) { 220 | self.model = model // this is a setter function 221 | // self.speed = speed should be done before self.model = model 222 | self.speed = speed // this is a property 223 | 224 | } 225 | 226 | @Trace var model: String 227 | var speed:Int 228 | // не може да се компилира 229 | // error: 'self' used before all stored properties are initialized 230 | } 231 | 232 | var tesla = ElectricCar(model:"Tesla", speed: 300) 233 | ``` 234 | Когато искаме да поддържаме пропъртита, които имат начална стойност (стойност по подразбиране, трябва да добавим конструктор (`init(wrappedValue:)`) метод към пропъртирапъра. 235 | 236 | Ето как би излгеждало това в нашия случа: 237 | ```swift 238 | @propertyWrapper 239 | struct Trace { 240 | private var value: String = "" 241 | 242 | init(wrappedValue: String) { 243 | self.value = wrappedValue 244 | } 245 | 246 | var wrappedValue: String { 247 | get { 248 | print("=> Get value \(value)") 249 | return value 250 | } 251 | 252 | set { 253 | print("=> Set value \(newValue)") 254 | value = newValue 255 | } 256 | } 257 | } 258 | 259 | struct ElectricCar { 260 | //Important: A constructor is required! 261 | init(model: String, speed: Int) { 262 | self.speed = speed 263 | self.model = model 264 | } 265 | 266 | init(speed: Int) { 267 | self.speed = speed 268 | } 269 | 270 | @Trace var model = "Generic EV" // this calls init(wrappedValue: String) 271 | //it's converted to var model = Trace(wrappedValue: "Generic EV") 272 | var speed: Int 273 | } 274 | 275 | var ev = ElectricCar(speed: 300) 276 | print(ev.model) // отпечатва => Get value Generic EV 277 | // отпечатва Generic EV 278 | ``` 279 | 280 | Можем да създаваме пропъртирапъри, които да имат допълнителна конфигурация. За целта трябва да дефинираме допълнителни конструктори и да подадем съответните параметри. 281 | За целта ще направим нов пропъртирапър, който ограничава пропръти от тип `Int` и не може да надхвърля определена максимална стойност. 282 | 283 | ```swift 284 | @propertyWrapper 285 | struct MaxValue { 286 | private var value: Int = 0 287 | private var maxValue: Int = 100 288 | 289 | init(wrappedValue: Int) { 290 | self.value = min(maxValue, wrappedValue) 291 | } 292 | 293 | init(wrappedValue: Int, maximum: Int) { 294 | self.maxValue = maximum 295 | self.value = min(maxValue, wrappedValue) 296 | } 297 | 298 | var wrappedValue: Int { 299 | get { 300 | return value 301 | } 302 | 303 | set { 304 | value = min(maxValue, newValue) 305 | } 306 | } 307 | } 308 | ``` 309 | 310 | Добавяме възможност за конфигуриране, като реализираме допълнителен конструктор. Различните конструктори на пропъртирапъра се ползват по различно време. 311 | 312 | ```swift 313 | struct ElectricCar { 314 | //Important: A constructor is required! 315 | init(model: String, speed: Int) { 316 | self.speed = speed 317 | self.model = model 318 | } 319 | 320 | init(speed: Int) { 321 | self.speed = speed 322 | } 323 | 324 | @Trace var model = "Generic EV" // this calls init(wrappedValue: String) 325 | //it's converted to var model = Trace(wrappedValue: "Generic EV") 326 | @MaxValue var speed: Int = 120 327 | } 328 | ``` 329 | 330 | > Когато имаме стойност по подразбиране, позлваме стандартния конструктор `init(wrappedValue: Int)`. Компилаторът използва стойността по подразбиране и я предава на този конструктор. 331 | 332 | Ако искаме подадем и конкретна стойност и на останалите параметри, правим това като добавяме параметри към самата анотация. Възможни са следните варианти: 333 | 334 | 1. Стойности по подразбиране 335 | ```swift 336 | @MaxValue var speed: Int 337 | ``` 338 | ```swift 339 | @MaxValue var speed: Int = 120 340 | ``` 341 | 342 | 1. Предефиниране на максималната стойност 343 | ```swift 344 | @MaxValue(maximum: 200) var speed: Int = 120 345 | ``` 346 | > Компилаторът сам извиква съответния конструктор `init(wrappedValue: 120, maximum: 200)`. 347 | 1. Предефиниране на стойността по подразбиране и максималната стойност 348 | ```swift 349 | @MaxValue(wrappedValue: 120, maximum: 200) var speed: Int 350 | ``` 351 | 352 | Следният случай няма да се компилира: 353 | ```swift 354 | @MaxValue(maximum: 200) var speed: Int 355 | ``` 356 | понеже липсва стойност на `wrappedValue`. Грешката, която компилатора дава е `error: missing argument for parameter 'wrappedValue' in call`. 357 | 358 | > Възможно е да се дефинират конструктори с различни параметри, които да бъдат извикани експлицитно чрез подаване на съответния брой параметри в анотацията. 359 | Нека да добавим следния конструктор към `MaxValue` 360 | ```swift 361 | extension MaxValue { 362 | init(value: Int, max: Int) { 363 | self.maxValue = max 364 | self.value = min(maxValue, value) 365 | } 366 | } 367 | ``` 368 | После може да го използваме така: 369 | 370 | ```swift 371 | @MaxValue(value: 120, max: 200) var speed: Int 372 | ``` 373 | 374 | > Пропъртирапърите може да са шаблонни. 375 | 376 | Шаблоните позволява да отделяме съхраненеито на различни видове пропъртита в обща имплементация, която може да заема конкретна форма за рачлините типове пропъртита. 377 | 378 | Пропъртирапърът позволява да съхраняваме допълнителна информация в проекцията. Това е допълнителна характеристика (информация), която можем да добавим към всеки пропъртирапър. 379 | 380 | За целта трябва да дефинираме следното пропърти `var projectedValue: <тип>`. Името трябва да съвпада, като типът остава под наш контрол. 381 | 382 | Проекцията може да се достъпва със следния синтаксис: 383 | `$` последван от името на самото пропърти. 384 | 385 | В следния пример, ще надградим нашия тип за максимална стойност, като в проекцията 386 | ще показваме дали стойност по-голяма от максималната е била изпозлвана и каква е тя. 387 | 388 | ```swift 389 | @propertyWrapper 390 | struct MaxValue { 391 | private var value: Int = 0 392 | private var maxValue: Int = 100 393 | 394 | private(set) var projectedValue = (false, 0) 395 | 396 | init(wrappedValue: Int) { 397 | self.value = min(maxValue, wrappedValue) 398 | } 399 | 400 | init(wrappedValue: Int, maximum: Int) { 401 | self.maxValue = maximum 402 | self.value = min(maxValue, wrappedValue) 403 | } 404 | 405 | var wrappedValue: Int { 406 | get { 407 | return value 408 | } 409 | 410 | set { 411 | if newValue > maxValue { 412 | projectedValue = (true, newValue) 413 | } 414 | value = min(maxValue, newValue) 415 | } 416 | } 417 | } 418 | 419 | extension MaxValue { 420 | init(value: Int, max: Int) { 421 | self.maxValue = max 422 | if value > maxValue { 423 | projectedValue = (true, value) 424 | } 425 | self.value = min(maxValue, value) 426 | } 427 | } 428 | 429 | var ev = ElectricCar(speed: 300) 430 | print(ev.speed) // отпечатва 150 431 | // така достъпваме проекцията 432 | print(ev.$speed) // отпечатва (true, 300) 433 | ``` 434 | 435 | Научихме се как да дефинираме собствени пропъртирапъри. Кака да можем да ги ползваме със стойности по подразбиране. Можем да добавяме и допълнителни характеристики към тях, които можем да инициализираме в самата анотация. Също така, можем да изпозлваме шаблони и имаме възможност да представяме допълнителна информация, чрез механизмът на проекцията. 436 | 437 | Механизмът предоставен от езикът `Swift` се изпозлва активно при разработването на приложения със `SwiftUI` или със фреймуорка `Combine`, който е достъпен в iOS, macOS, tvOS, iPadOS и watchOS. 438 | 439 | Като последна ключова спирка ще разгледаме механизмът на `Result Builder`-ите и как да го използваме да създадем по-прост език, който изпозлва Swift като основа. 440 | 441 | ## Какво са `Result Builders` и как можем да ги ползваме за да изградим специфичен език (DSL) над Swift? 442 | 443 | > Doman Specific Language - език специфичен за съответния домейн на приложение 444 | 445 | Предоставяения механизъм на базата на result builder-ите позволява да изградим по-лесен начин за описване на сложни структури в Swift. Може да се изпозлва за изграждането на по-лесне език (за описване), който да се използва при реализирането на сложни компоненти на Swift. 446 | 447 | За да илюстрираме тази възможност трябва да разгледаме следния пример, в който ще описваме въпроси с възможни отговори. За целта, ще стъпим на следната йерархия. 448 | 449 | // TODO: FIG.1 450 | 451 | // TODO: code architecture 452 | 453 | // TODO: implementation 454 | 455 | // TODO: explanation 456 | 457 | // TODO: SwiftUI example 458 | 459 | 460 | --------------------------------------------------------------------------------