Slide 1

Slide 1 text

Пишем скрипты на Swift Руслан Кавецкий

Slide 2

Slide 2 text

Однажды в… одном проекте 2

Slide 3

Slide 3 text

UI VIPER + Swift ‐ Data Objective-C 3

Slide 4

Slide 4 text

Мои ожидания struct PromoModel: Codable { let name: String let pictureUrl: URL? let amount: Int } 4

Slide 5

Slide 5 text

Реальность ! @interface PromoModel: NSObject @property (nonatomic, readonly, copy, nonnull) NSString * name; @property (nonatomic, readonly, strong, nullable) NSURL * pictureUrl; @property (nonatomic, readonly, assign) NSInteger amount; - (nonnull instancetype)initWithName:(nonnull NSString *)name pictureUrl:(nullable NSURL *)pictureUrl amount:(NSInteger)amount; @end static NSString *const kPromoModelName = @"PromoModel.name"; static NSString *const kPromoModelPictureUrl = @"PromoModel.pictureUrl"; static NSString *const kPromoModelAmount = @"PromoModel.amount"; @implementation PromoModel - (nonnull instancetype)initWithName:(nonnull NSString *)name pictureUrl:(nullable NSURL *)pictureUrl amount:(NSInteger)amount { self = [super init]; if (self) { _name = [name copy]; _pictureUrl = pictureUrl; _amount = amount; } return self; } - (nullable instancetype)initWithCoder:(NSCoder *)decoder { self = [super init]; if (self) { _name = [decoder decodeObjectForKey:kPromoModelName]; _pictureUrl = [decoder decodeObjectForKey:kPromoModelPictureUrl]; _amount = [decoder decodeIntegerForKey:kPromoModelAmount]; } return self; } - (void)encodeWithCoder:(NSCoder *)coder { [coder encodeObject:_name forKey:kPromoModelName]; [coder encodeObject:_pictureUrl forKey:kPromoModelPictureUrl]; [coder encodeInteger:_amount forKey:kPromoModelAmount]; } @end 5

Slide 6

Slide 6 text

[coder encodeObject:_name forKey:kPromoModelName]; [coder encodeObject:_pictureUrl forKey:kPromoModelPictureUrl]; [coder encodeInteger:_amount forKey:kPromoModelAmount]; 6

Slide 7

Slide 7 text

Идеальная задача, чтобы её не делать • Скучная • Повторяющаяся • Легко допустить ошибку 7

Slide 8

Slide 8 text

Скрипты 8

Slide 9

Slide 9 text

Что такое скрипт? • Нет графического интерфейса • Запускается из командной строки • Обычно один файл 9

Slide 10

Slide 10 text

На чём обычно пишут? • Bash • Ruby • Python • JavaScript • Kotlin Script ! 10

Slide 11

Slide 11 text

Почему Swift? 11

Slide 12

Slide 12 text

Вы уже его знаете ! • Знакомое API Foundation, URLSession, FileManager, etc • Привычные инструменты Xcode, Instruments • Меньшее переключение контекстов Тут Swift, там Swift 12

Slide 13

Slide 13 text

!"# Ваша команда знает Swift 13

Slide 14

Slide 14 text

!"# • Меньший бас фактор для скриптового кода • Проще поддерживать • Меньше зависимостей от других языков 14

Slide 15

Slide 15 text

А зачем это мне? ! • Новые классы и библиотеки • Любая архитектура и другие подходы • Не продакшен код • Можно писать в VIM или блокноте • Не надо верстать вьюшки ! 15

Slide 16

Slide 16 text

Технические плюсы • Быстрый ⚡ • Статическая типизация " • Современный синтакс # • Open Source, но поддерживается Apple 16

Slide 17

Slide 17 text

Я не знаю Swift ! • Отличный способ начать писать на нём • Можно учить язык без привязки к платформе и операционной системе • Первые шаги в сторону iOS разработки 17

Slide 18

Slide 18 text

Swift не всегда лучший вариант для любой задачи 18

Slide 19

Slide 19 text

Как начать? 19

Slide 20

Slide 20 text

Запускаем через REPL $ echo 'print("Hello, Script!")' > script.swift $ swift script.swift Hello, Script! 20

Slide 21

Slide 21 text

Добавляем немного магии #!/usr/bin/swift print("Hello, Script!") 21

Slide 22

Slide 22 text

Добавляем немного магии #!/usr/bin/swift print("Hello, Script!") Ещё чуть-чуть $ mv script.swift script $ chmod +x script $ ./script 22

Slide 23

Slide 23 text

$ swift script.swift ‑ $ ./script 23

Slide 24

Slide 24 text

Зависимости 24

Slide 25

Slide 25 text

CocoaPods? 25

Slide 26

Slide 26 text

"CocoaPods is a dependency manager for Swift and Objective-C Cocoa projects" — cocoapods.org 26

Slide 27

Slide 27 text

Carthage? 27

Slide 28

Slide 28 text

"The simplest way to add frameworks to your Cocoa application" — github.com/Carthage/Carthage 28

Slide 29

Slide 29 text

! 29

Slide 30

Slide 30 text

Динамические фреймворки Имеют расширение .framework Подключаются через флаг -F Для исходного кода $ swift -F Frameworks myScript.swift Можно запустить REPL с дополнительными фреймворками $ swift -F Frameworks 30

Slide 31

Slide 31 text

Динамические фреймворки Имеют расширение .framework Для скомпилированного скрипта добавляем путь к фреймворкам через утилиту install_name_tool $ install_name_tool \ -add_rpath "@executable_path/../Frameworks/" \ myScript ../Frameworks — относительный путь к папке с фреймворками myScript — исполняемый файл скрипта 31

Slide 32

Slide 32 text

Динамические фреймворки • Сложно подключать • Нужно распространять вместе с файлом скрипта ! 32

Slide 33

Slide 33 text

Статические библиотеки Имеют расширение .a • Код библиотеки копируется в исполняемый файл во время компиляции • Чтобы правильно собрать скрипт, понадобится Xcode 33

Slide 34

Slide 34 text

«Чёт сложные ваши скрипты» — Кто-то в зале 34

Slide 35

Slide 35 text

Swift Package Manager 35

Slide 36

Slide 36 text

Делаем ручками $ swift package init --type executable 36

Slide 37

Slide 37 text

// swift-tools-version:5.1 import PackageDescription let package = Package( name: "Script", dependencies: [ .package(url: "https://github.com/Alamofire/Alamofire.git", from: "4.0") ], targets: [ .target( name: "Script", dependencies: ["Alamofire"]) ] ) 37

Slide 38

Slide 38 text

Команды Обновляем зависимости $ swift package update Запускаем $ swift run Собираем $ swift build Генерируем Xcode проект swift package generate-xcodeproj 38

Slide 39

Slide 39 text

Xcode 11 • File → New → Project → macOS → Command Line Tool • File → Swift Packages → Add Package Dependency... 39

Slide 40

Slide 40 text

«А можно как-то ещё попроще?» — Тот же человек из зала 40

Slide 41

Slide 41 text

swift-sh github.com/mxcl/swift-sh 41

Slide 42

Slide 42 text

$ brew install mxcl/made/swift-sh 42

Slide 43

Slide 43 text

#!/usr/bin/swift sh import Foundation import Alamofire // @Alamofire ~> 4.0 request("ya.ru").response { print(response) } 43

Slide 44

Slide 44 text

$ chmod +x network.swift $ ./network.swift $ 44

Slide 45

Slide 45 text

$ chmod +x network.swift $ ./network.swift $ $ 45

Slide 46

Slide 46 text

$ chmod +x network.swift $ ./network.swift $ $ $ 46

Slide 47

Slide 47 text

RunLoop.main.run() 47

Slide 48

Slide 48 text

⏳ RunLoop.main.run(until: Date() + 3) 48

Slide 49

Slide 49 text

#!/usr/bin/swift sh import Foundation import Alamofire // @Alamofire ~> 4.0 request("ya.ru").response { print(response) exit(0) } RunLoop.main.run() 49

Slide 50

Slide 50 text

Типичные задачи 50

Slide 51

Slide 51 text

! Читаем аргументы 51

Slide 52

Slide 52 text

$ swift --version 52

Slide 53

Slide 53 text

• CommandLine CommandLine.arguments.contains("--version") • Commandant github.com/Carthage/Commandant • Commander github.com/kylef/Commander 53

Slide 54

Slide 54 text

! Ходим в сеть 54

Slide 55

Slide 55 text

• Alamofire, Moya • URLSession, Network framework • Data(contentsOf:) 55

Slide 56

Slide 56 text

let url = URL(string: "http://api.slack.com/messages/42") let jsonData = Data(contentsOf: url) let message = JSONDecoder().decode(Message.self, from: jsonData) 56

Slide 57

Slide 57 text

⚠ Data(contentsOf:) синхронная операция Вызов RunLoop.main.run() не нужен 57

Slide 58

Slide 58 text

! Ходим в файловую систему 58

Slide 59

Slide 59 text

• FileManager • Files github.com/JohnSundell/Files • PathKit github.com/kylef/PathKit • FileKit github.com/nvzqz/FileKit 59

Slide 60

Slide 60 text

! Ходим в базу данных 60

Slide 61

Slide 61 text

• PostgresQL github.com/IBM-Swift/Swift-Kuery-PostgreSQL github.com/PerfectlySoft/Perfect-PostgreSQL • SQLite github.com/stephencelis/SQLite.swift • MongoDB github.com/mongodb/mongo-swift-driver 61

Slide 62

Slide 62 text

! Пишем в консоль 62

Slide 63

Slide 63 text

print 63

Slide 64

Slide 64 text

! 64

Slide 65

Slide 65 text

Пишем в стандартный error stream • fputs • print • TextOutputStream • FileHandle 65

Slide 66

Slide 66 text

fputs fputs("Hello, error", stderr) stderr — глобальная переменная, указывающая на стандартный поток ошибок 66

Slide 67

Slide 67 text

print + fputs struct StderrOutputStream: TextOutputStream { mutating func write(_ string: String) { fputs(string, stderr) } } var standardError = StderrOutputStream() print("Error!", to: &standardError) 67

Slide 68

Slide 68 text

FileHandle let errorFileHandle = FileHandle.standardError let error = "Something went wrong" errorFileHandle.write(error.data(using: .utf8)) 68

Slide 69

Slide 69 text

! Пишем цветом 69

Slide 70

Slide 70 text

github.com/onevcat/Rainbow github.com/paulot/Colors github.com/mxcl/Chalk github.com/jdhealy/PrettyColors 70

Slide 71

Slide 71 text

Взаимодействуем с остальным миром 71

Slide 72

Slide 72 text

! Shellout github.com/JohnSundell/ShellOut 72

Slide 73

Slide 73 text

try shellOut(to: "mkdir", arguments: ["~/Projects/Scripts"]) try shellOut(to: "mkdir Scripts", at: "~/Projects") try shellOut( to: [ "mkdir ~/Projects/Scripts", "cd ~/Projects/Scripts", "touch script.swift"]) 73

Slide 74

Slide 74 text

Готовые команды ! try shellOut(to: .gitCommit(message: "A scripted commit!")) try shellOut(to: .readFile(at: "Podfile")) try shellOut(to: .buildSwiftPackage()) try shellOut(to: .runFastlane(usingLane: "appstore")) try shellOut(to: .updateCocoaPods()) 74

Slide 75

Slide 75 text

Что со всем этим можно сделать 75

Slide 76

Slide 76 text

$ swift-objc-gen \ models.txt \ -output Sources/Models 76

Slide 77

Slide 77 text

$ swift-objc-gen \ models.txt \ -output Sources/Models • Читаю аргументы • Читаю файл models.txt • Генерирую Objective-C код • Записываю код в файлы в папке Sources/Models 77

Slide 78

Slide 78 text

! models.txt PromoModel name:String:nonnull pictureUrl:NSURL*:nullable amount:Int UserModel name:String:nonnull id:Int addresses:Array:nonnull email:Email:nullable 78

Slide 79

Slide 79 text

Sources/ Models/ PromoModel.h PromoModel.m UserModel.h UserModel.m 79

Slide 80

Slide 80 text

"You can replace make or rake files with code written in Swift!" — yonaskolb/Beak 80

Slide 81

Slide 81 text

// beak: kylef/PathKit @ 1.0.0 import PathKit import Foundation /// Releases the product /// - Parameters: /// - version: the version to release public func release(version: String) { // implementation here print("version \(version) released!") } /// Installs the product public func install() { // implementation here } 81

Slide 82

Slide 82 text

$ beak list release: Releases the product install: Installs the product $ beak run release --version 1.2.0 version 1.2.0 released! 82

Slide 83

Slide 83 text

John Sundell Plot DSL язык для генерации HTML, XML и RSS Splash Инструмент подсветки синтаксиса Swift TestDrive Возможность попробовать любой фреймворк в одну строчку кода Ink Парсер Markdown файлов 83

Slide 84

Slide 84 text

Пишите скрипты на Swift • Swift — крутой современный язык • Вы и ваша команда его знаете • Начать очень просто • Библиотек много и становится всё больше Руслан Кавецкий @pycuk 84