일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- 소울칼리버6
- 명심보감
- 코틀린
- jetpack compose
- 공자명언
- Coroutine
- 파이썬
- 1인개발자
- 넷플릭스
- 명언모음
- 좋은글필사하기
- Flutter
- 이모지
- ASMR
- kotlin
- androidx
- Android
- 공부집중
- FSM
- 이모지메모
- Linux
- Firebase
- bash
- Freesound
- 벤자민플랭클린
- 오픈소스
- Streaming
- DART
- recyclerview
- 장자명언
- Today
- Total
Vintage appMaker의 Tech Blog
[iOS] swift, swiftUI 빠르게 시작하기 본문
개발자이지만 swift도 모르고 swiftUI도 모를 경우
빠른 시간에 학습하여 프로젝트에 투입되는 것을 목적으로 만든 프로젝트.
swift에서 반드시 알아야 할 핵심 예제와 생존에 필요한 swiftUI의 예제로 하나의 앱을 만들기
github repository
https://github.com/VintageAppMaker/UltraQuickStartSwift
Quick Start Swift
흔한 생존형 개발자(=만들며 배우는 개발자)가 iOS를 모르는 상태에서 바로 프로젝트를 투입되어야 할 경우, 단시간에 서바이벌 swift & swiftUI를 학습하고자함이 목적.
- 최소한의 필수 swift 문법
- 현업에서 사용하는 필수 코딩스타일 위주정리
1. 메인함수
(1)메인함수 정의없이 바로 코드가 실행된다. (2) import 문은 외부라이브러리에나 사용한다. 즉, 같은 프로젝트의 파일에 대해서 사용할 필요가 없다. (3) iOS에서는 @UIApplicationMain로 메인함수를 정의가능하다.
// 종결자(;)없이 사용가능함
print ("메인함수없이 코드순서대로 실행됩니다")
2. 변수선언
읽고쓰기가 가능한 변수와 읽기만 가능한 변수를 구분하여 선언 . 변수명은 유니코드까지 지원(emoji로 된 변수명도 가능하다)
- var : 읽기/쓰기 가능한 변수
- let : 읽기만 가능한 변수(상수개념)
- {} 과정을 통한 초기값 지정 가능함
let fixed_value = 1000
// fixed_value = 1 // error
var editable_value = "read / write"
editable_value = "changed"
var 🐈 = "cat"
print (🐈)
// {} 과정을 통한 초기값 지정
var sum : Int {
30 / 2
}
print ("\(sum)")
3. 타입정의
명시적 선언하는 방법과 선언과 동시에 값 대입하며 타입이 정해지는 방법이 있다.
- 타입 명시적 정의 : var(let) 변수명 : 타입
- 타입추론(대입 후, 타입정의) : var(let) 변수명 = 값
var stringValue = ""
// type(of : 변수)는 타입채크 함수임
print (type(of : stringValue ) )
// 아래는 에러임
stringValue = 100
4. 기본타입
최근 함수형 프로그래밍 언어는 타입이 유연화되는 추세이다.
- 숫자형: Int, Uint, Double, Float
- boolean 형 : Bool
- 문자형 : String, Charater(char형)
- 모든 데이터형 : Any
- 널(nil)을 허용하는 형: Optional
5. 문자열처리
문자열 처리는 kotlin과 매우 유사하다.
- 문자열내의 "\(변수명 )" : 문자열내에서 변수값을 대치
- """ """ : 줄넘김이 있는 긴 문자열을 변수에 대입
var stringValue = """
1
2
3
4
5
"""
print (stringValue)
let name = "arucard"
print ("player name is \(name)")
6. 숫자 <--> 문자형 변환
숫자형을 문자형으로 문자형을 숫자형으로 변환
- String() : 문자열로 변환
- Int(): 숫자형으로 변환
var sNum = "100"
// Int(변수)는 결과값은 optional이다.
// optional은 코틀린의 nullable(?)과 같다.
// 그러므로 강제적으로 !(코틀린은 !!)를 지정하면 Int로 사용가능하다
var num = Int(sNum)! + 100
print ( String (num) )
7. optional, nil
함수형 언어에서는 기본적으로 null(nil)을 허용하지 않는다. 그러나 허용해야 할 경우, swift에서는 optional이라는 형태로 사용한다.
- ? 로 선언하는 방법은 변수 : 타입? 형태로 nil을 허용하겠다는 선언. print 함수사용시 nil 출력
- ! 로 선언하는 방법은 변수 : 타입! 형태로 nil을 대입할 수는 있으나, 읽을 시 에러발생
- 형변환 함수의 결과는 optional형
- optional로 선언된 변수는 바로 사용이 불가능
- 변수명 뒤에 !를 붙여 사용가능함(코틀린의 !!와 유사 -> 에러발생후 종료)
- optional binding: if let 지역변수 = optional변수{ }else{} 형태로 값이 nil이면 else 구문의 값을 지역변수에 저장한다. 지역변수는 if{} else{} 구문 안에서만 사용가능함. !처럼 nil일 경우, 에러발생하며 종료하는 것이 싫다면 귀찮더라도 옵셔널 바인딩을 구현해야 한다.
- optional은 개발자에게 null 처리에 민감하게 반응하라고 강요하는 기능임(코틀린에 비해 귀찮을 정도로 까다로운 방식을 요구함)
var name : String?
if let optName = name{
print(optName)
} else {
print("nil 입니다")
}
8. 배열(리스트)
모든 원소는 같은 형이어야 한다(경쟁언어인 코틀린은 다양한 형을 지원함).
- 좌측변수가 형정의 안되어있을 경우, 값이 없다면 [데이터형] ()로 대입
- 좌측변수가 형정의 되어있을 경우, 값이 없다면 []로 대입
- 배열에 값이 있다면 [값, 값, ...] 로 초기화 가능
- 위의 2경우를 Array<데이터형>(), Array() 으로 치환가능
- 숫자 범위는 Array(시작...끝)을 사용
- 함수에 배열을 파라메터로 넘길 수는 있으나, 이때 append같은 메소드로 배열을 수정할 수는 없다.
var arr = [Int]()
var arr2 : [Int] = []
var arr3 = Array<Int>()
var arr4 : [Int] = Array()
var arr5 = [1, 2, 3, 4, 5]
var arr6 = Array(1...5)
arr6.append(2)
var sum = arr5[1] + arr5[3]
print (arr5)
print (arr6)
9. 사전, 튜플
사전은 키 : 값 형태의 데이터형. 배열과 비슷하나 [키:값]으로 사용함. 튜플은 배열과 유사하나 각 원소의 데이터형이 다를 수 있다. 주로 함수의 리턴값에 다양한 형태의 값을 넘길 떄 사용한다.
- 좌측변수가 형정의 안되어있을 경우, 값이 없다면 [키:값] ()로 대입
- 좌측변수가 형정의 되어있을 경우, 값이 없다면 [:]로 대입
- 초기값이 있다면 [키:값, 키:값, ...] 로 초기화 가능
- 위의 2경우를 Dictionary< 키:값 >(), Dictionary() 으로 치환가능
- 튜플은 ()안에 다양한 원소를 나열함
- 원소에 이름을 넣어 튜플변수명.원소이름으로 액세스 가능
- 이름이 없다면 튜플변수명.인덱스번호로 액세스 가능
- 튜플의 원소를 선택해서 좌측 (변수, 변수...) 에 대입가능함
var d : [String : String] = [:]
var d2 = [String : Int]()
var d3 : Dictionary = [Int:String]()
var d4 : Dictionary<Int, String> = Dictionary<Int, String>()
var dic2 = [1:"one", 2:"two", 3:"three"]
print (dic2)
print (dic2[1]!)
var t : (Int, Bool, String) = (12, true, "AAA")
print (t)
print (t.0)
print (t.1)
print (t.2)
var (age, _ , message) = t
print("\(age) \(message)")
var t2 = (name : "name", age: 30)
print (t2.name)
print (t2.age)
10. 조건문(if, switch) - 기본
swift의 조건문은 상당히 특이하고 많은 기능을 제공한다. 일반적으로 조건영역인 ()를 삭제할 수 있다. 복잡한 조건문은 나중에 다루기로 한다.
- if 이후 조건영역인 ()를 삭제가능
- &&의 경우 ,로 대치할 수 있다
- switch의 경우, case에 범위가 가능하다
- 범위비교 연산자는
- ...
- ..<
- ,
var num = 3
// 흔히 접했던 조건문
if(num == 3){
print ("\(num)")
}
// swift 스타일 () 삭제
if num == 3 {
print ("\(num)")
}
var name = "snake"
// && 대신 , 를 사용함
if num == 3, name == "snake"{
print ("\(num):\(name)")
}
switch num {
case 2:
print (name)
break
// break 반드시 할필요없음
case 3, 10 :
print ("\(num) is in 3 or 10" )
case 10 ..< 20:
break
default:
print("default")
}
11. 반복문(for, while)
대부분의 언어와 유사한 구조이다. 그러나 조건영역인 ()를 삭제할 수 있다.
- 조건영역인 ()를 삭제가능
- for 변수 in 배열 또는 범위연산자
- 범위연산자는
- ...
- ..<
- for문에서 in으로 변수를 할당하고 싶지않다면 _를 사용한다
- while은 대부분의 언어와 비슷하다
- break, continue등은 파라메터로 레이블을 지정하여 이동가능하다
- 레이블은 [이름:]로 정의한다
// 리스트
let team = ["paladin", "mage", "berserver" ]
for player in team {
print("\(player) is my team.")
}
// 사전
let enemy = ["slime" : 100, "bat" : 150]
for (name, life) in enemy {
print("name is \(name) life is \(life)")
}
// 범위
for i in (0 ..< 10){
print (i)
}
// 범위 -2 변수할당 (X)
for _ in (0 ... 2){
print ("하이")
}
// 범위 - 3 레이블 이동
loopexit:
for i in (0 ... 3){
if i == 2{
print ("break loopexit")
break loopexit
}
print ("하이")
}
12. 함수
func로 함수를 정의한다. 다른 언어에 비해 많은 차이점을 가지고 있다.
- 함수정의는 func 함수명 -> 리턴값 { }
- 리턴값이 없으면 Void로 표기하거나 리턴값을 표기하지 않음
- 기본적으로 파라메터의 이름을 반드시 써줘야 한다
- 파라메터이름을 표기하고 싶지않다면, _를 표기하고 빈 칸 후, 변수를 정의한다
- 가변 파라메터의 정의는 [데이터형...] 이다
- 함수형 언어이기에 함수안에 함수정의가 가능하다
- {}()는 클로져를 정의와 함께 실행한다. 주로 초기값 지정할 때 사용함
- 파라메터 이름 앞에 레이블을 지정할 수 있다. 그러면 파라메터 이름대신 레이블을 사용해야 한다
- 함수이름이 같고, 파라메터 이름이나 레이블이 다르면 다른 함수이다
// function 1. 파라메터 이름과 함께
func test(name : String ) -> String {
return name
}
print ( test (name : "cat") )
// function 2. 이름없이 파라메터 넘기기
func test2(_ name : String ) -> String {
return name
}
print ( test2 ( "cat" ) )
// function 3. 가변파라메터
func test3(_ names : String... ){
for i in names{
print(i)
}
}
test3 ( "cat", "dog", "human", "bird" )
// function 4. 함수 안의 함수
func test4(num : Int ){
func add (n : Int ) -> Int{
return n + 100
}
let rst = add (n : num)
print ( "\(rst)" )
}
test4(num : 100)
// 파라메터 변수명 앞에 레이블이 다르면
// 같은 이름에 다른 함수를 사용가능하다
func test (op1 a : Int, op2 b : Int) -> Int{
return a + b
}
func test (o1 a : Int, o2 b : Int) -> Int{
return a - b
}
let rst = test (op1 : 10 , op2 : 10)
print("\(rst)")
let rst2 = test (o1 : 10 , o2 : 10)
print("\(rst2)")
13. 클로져(closure)
클로져를 직감적으로 설명하자면, 이름없는 함수덩어리를 정의하는 것이다. 이를 "실행가능한 코드덩어리"라는 표현하기도 한다. 그러다보니 함수로 정의하기에는 코드가 짧고 함수의 파라메터로 함수를 넘겨야 할 떄 사용한다.
- 클로져의 구조는 { (매개변수 목록) -> 반환타입 in 실행 코드 }
- 함수형 변수 타입정의는 (파라메터타입) -> 리턴되는타입
- 클로져 코드를 사용할 경우, 파라메터 이름을 표기할 수 없다
- 클로져가 파라메터로 넘겨지고, 그 함수의 결과값도 클로져일 경우. 만약 넘겨진 파라메터 클로져가 그 안에 실행된다면 "Escaping Closure: 전역변수 룰이 적용" 이다. 그 때에는 반드시 @escaping (데이터형)->데이터형으로 선언되어야 한다
- 함수 안으로 넘겨진 클로져가 함수 밖에 저장되고 관리된다면, Escaping Closure로 선언해야 한다.
// 함수형 변수에 클로져를 대입
let fn : (String) -> Void = { ( msg : String ) in print (msg) }
fn("AAAA")
// 함수의 리턴값으로 클로져를 리턴
func makeCounter(nStep : Int) -> ( ()->Int ){
var sum = 0
return{
sum = sum + nStep
return sum
}
}
let c0 = makeCounter(nStep : 2)
for _ in 0..<4{
print ( c0() )
}
// 함수의 파라메터로 클로져를 받고
// 리턴되는 클로져(함수밖에서 관리)에서
// 파라메터 클로져를 실행할 경우
// @escaping (데이터형)->데이터형 으로 반드시
// 선언해주어야 한다.
func makeCounterByEscaping(nStep : Int, fn : @escaping (Int)->Int ) -> ( ()->Int ){
var sum = 0
return{
sum = fn(sum)
return sum
}
}
let c1 = makeCounterByEscaping(nStep: 5, fn: { (sum : Int) in return sum + 5 })
for _ in 0..<4{
print ( c1() )
}
let num : Int = {
let left = 3
return left * 3
}()
print ("\(num)")
14. 특수한 데이터형과 캐스팅
swift에서는 다음과 같은 특수한 데이터형과 변환(채크) 연산자를 사용한다.
- Any형은 어떤 값을 대입해도 문제가 없다
- Optional 형은 데이터형?으로 선언한다
- ?? 연산자는 Optional 변수가 nil 일 경우, 대체할 기본값을 지정한다
- 객체의 형변환은 as를 사용한다. 다운캐스팅이면 as?를 사용해야 한다.
- 튜플타입은 복잡한 데이터 모임이 한 개의 데이터처럼 사용된다. (데이터형,...)로 선언하고 (값, 값, ...)으로 대입한다.
// any형
var ay : Any = 3
ay = "300"
ay = true
// optional형
var opt : Int?
opt = nil
print (type(of : opt))
var n = opt ?? 0
print ("\(n)")
// any 형을 자식형으로 변환(다운캐스팅)
let ay2 = 111 as Any
let intVal = ay2 as? Int
let stringVal = ay2 as? String
// 상속받은 자식형을 any형으로 변환
var ay3 : Any = ""
ay3 = intVal as Any
ay3 = stringVal as Any
// type check
if(ay3 is Any){
print ("any")
}
// tuple
var tpl: (Int, String)
t = (100, "math")
15. 클래스
swift에서는 객체지향 기본적인 기능을 지원한다. 프로토콜 언어를 지향하기 때문에 abstract와 interface는 지원하지 않는다.
- 일반 : class 클래스명 {}
- 생성자: init()
- 상속 : class 클래스명 : 상속받을클래스명{}
- 오버라이드 : override 지시어를 사용
- compute 속성: var 속성 : 유형 { get{} set{} }
// 기본클래스
class A{
var name : String?
func test (){
print ("test")
}
}
A().test()
// 생성자
class B{
var name : String
func test (){
print ("\(name)")
}
init(name : String ){
self.name = name
}
}
B( name : "Linkin Park").test()
// 상속
class C: B{
}
C( name : "korn").test()
// 오버라이드
class D: B{
override func test (){
print ("overrided")
}
}
D(name : "initial D").test()
// compute 속성
class E: B{
var _age : Int = 0
var age : Int {
get {
return _age
}
// (파라메터)를 사용하지 않으면
// newValue로 넘겨짐
set(n){
if n > 50 {
_age = 49
} else {
_age = n
}
}
}
}
var e = E(name : "compute properies")
e.age = 60
print ("\(e.age)")
16. 프로토콜(protocol)
swift는 프로토콜 지향언어이다. 프로토콜에 대한 복잡한 개념들이 난무하지만, 일반 개발자 입장에서는 "편리하게 변한 자바의 interface"라고 생각하면 쉽게 활용할 수 있다. struct는 클래스와 유사하게 함수를 정의하고 프로토콜을 상속받을 수 있다.
- 일반 : protocol 이름{}
- 인터페이스처럼 비어있는 값과 형을 정의
- 상속 : 클래스나 struct에서 protocol을 상속받을 수 있다. 이때는 interface 처럼 선언된 변수나 함수를 반드시 구현해야 한다
- 상속을 하지 않고 extension 스트럭쳐이름{ 프로토콜 구현 } 을 통해 프로토콜의 내용을 미리 구현해놓을 수 있다. java의 adapter class와 사용목적이 유사하다. 이미 정의된 클래스(또는 스트럭쳐)에 추가로 함수를 정의할 때 사용한다.
// protocol, structure, extension
// 복잡한 개념으로 protocol을 접근하지 말고
// java의 Interface가 더 유연해진 기능
// 으로 생각하면 사용하기 편하다.
protocol Animal{
var name : String {get set}
func cry ()
}
// struct는 class처럼
// 프로토콜 상속과 함께 함수도 가질 수 있다.
// protocol을 상속 시, 변수와 함수도 구현해야 한다.
struct Dog : Animal {
var name : String
func cry(){
print ("Kahhh~ krrr")
}
}
var animal : Animal = Dog(name : "puppy")
animal.cry()
// 다중프로토콜도 상속가능
// 클래스도 상속과 다중 프로토콜 상속을
// 동시에 사용할 수 있다.
class Human{
}
protocol Worker{
func working()
}
class Alba : Human, Animal, Worker{
var name : String = " "
func cry(){
print ("more money~!")
}
func working(){
print ("@!#@!!!~ ")
}
}
var ab = Alba()
ab.cry()
ab.working()
// 최근 코딩스타일은
// 상속받지 않고 extension으로
// 기본값을 구현하는 방법을 사용함
protocol math{
func add ( a : Int, b : Int) -> Int
func sub ( a : Int, b : Int) -> Int
}
extension math{
func add ( a : Int, b : Int) -> Int{
return a + b
}
func sub ( a : Int, b : Int) -> Int{
return a - b
}
}
// add, sub을 구현 안했음.
struct newMath : math{
}
var mt = newMath()
var rst = mt.add (a : 1 , b : 2)
var rst2 = mt.add (a : 1 , b : 2)
print ("rst -> \(rst) rst2 -> \(rst2)" )
17. 열거형(enum)
swift의 열거형은 다른 언어에 유연한 편이다. 그러므로 수 많은 기능을 가지고 있다.
- 선언 : enum 이름 : 형 { case 이름, 이름, ... }
- 형을 생략할 수도 있다. case 값 값 형태를 case 이름 [리턴] 형태로 사용하기도 한다.
- 이름에 값을 대입(=)할 수 있다
- enum 외부에서 rawValue로 값을 읽을 수 있다
- struct처럼 함수(메소드)를 정의할 수 있다.
- 연관값을 가질 수 있다. enum의 이름을 함수처럼 이용할 수 있다
- 연관값을 정의할 경우, enum 정의 시에는 (데이터형, ..) 형식으로 선언한다
- 연관값을 대입 및 비교할 경우, (let 변수명, ...) 형식으로 사용한다
// 형을 지정하는 경우
enum Rank : Int {
case one, two, three = 1000
}
print (Rank.one)
print (Rank.one.rawValue)
print (Rank.three)
print (Rank.three.rawValue)
// 함수를 정의하는 경우
enum Food{
case bread
case apple
func showDesc () -> String{
switch self{
case .bread: return "빵"
case .apple: return "사과"
}
}
}
print (Food.bread)
var fd : Food = .bread
print (fd.showDesc() )
// 연관값을 가지는 경우
enum Status{
case run
case suspend(Int, String)
}
var st : Status = .run
var st2 : Status = .suspend(100, "힘들어서 잠시 멈춥니다")
if case .suspend(let time, let message) = st2{
print ("time is \(time) message is \(message)")
}
18. 복잡한 if 문 (조건리스트)
swift만의 독특한 조건리스트를 정리한다. 일종의 패턴을 비교하는 문법이다.
- 조건리스트는 하나 이상의 [조건패턴]이 , 로 연결된 것을 말한다
- 일반조건식과 조건리스트를 , 로 연결하여 사용할 수 있다
- (옵셔널) 바인딩 패턴, case 조건 패턴이 대표적이다
// 1. 옵셔널 바인딩
var name : String?
name = "snoop dog"
// nil이 아니면
if let rapper = name {
print(rapper)
}
// 2. if case 패턴매칭
let geo = (0, 234.0000)
if case (_, 234.0000) = geo{
print("geo *, 234.0000")
}
// 3. 타입채크
var n: Any = 1
if case is Int = n {
print("Integer")
}
19. 예외처리
다른 언어와 비교해 사용법과 문법이 익숙한 구조가 아니다. 상당히 번거롭다.
// 일반적인 구조
do {
try throwing 함수
} catch <#pattern#> {
<#statements#>
}
// 한줄구조 리턴값이 옵셔널(리턴값이 있을 경우, 옵셔널 처리를 해주어야 한다)
try ?
// 한줄구조 에러발생시 종료
try !
- Error를 상속받아 에러형을 정의해야 한다.
- 에러발생이 예상되는 부분을 guard let 변수 = 값 [, 조건] else { 예외발생시 코드 }로 처리한다
- 에러를 발생하는 함수는 throws로 정의한다
- do try .. catch 문에서 try는 한 줄로 throws로 정의된 함수를 사용한다
var book = [ 1 : "one", 2 : "two" ]
//1. Error 프로토콜을 재정의한다
enum MyError : Error{
case unknown
}
//2. 함수를 throws로 정의한다.
//에러발생하는 code를 guard let 변수 = 값 [, 조건] else { 예외발생시 코드}
func errFunc() throws -> String{
guard let word = book[0] else {
throw MyError.unknown
}
return word
}
//3.try catch로 처리
do {
try errFunc()
} catch {
print("\(error)")
}
//4. try?로 처리
func errFunc2() throws -> String{
guard let word = book[1] else {
throw MyError.unknown
}
return word
}
if let word = try? errFunc2(){
print (word)
}
20. 프로퍼티와 프로퍼티래퍼
struct/class 레벨에서 프로퍼티를 적용할 수 있다. 그리고 어노테이션 형태로 struct/class 레벨에서 범용적인 프로퍼티를 적용할 수 있다. 이를 프로퍼티 래퍼라고 한다.
- 실제저장되는 변수를 따로 지정한다.
- var 프로퍼티명 : 데이터형 {get{} set(){}}
- 프로퍼티래퍼는 swift 5.X이상부터 지원
- @propertyWrapper로 정의
- 이미 지정된 wrappedValue의 이름으로 프로퍼티 정의
struct MyWantAge{
var _age : Int = 0
var age : Int {
get {return _age}
set (n) {
if n >= 50 {
_age = 49
} else {
_age = n
}
}
}
}
var me = MyWantAge()
me.age = 50
print (me.age)
@propertyWrapper
struct WomanMannerAge{
// 실제저장되는 곳
private(set) var _age : Int = 0
// 반드시 아래의 이름으로 프로퍼티 생성
var wrappedValue : Int {
get {return _age}
set (n) {
if n >= 30 {
_age = n - 10
} else {
_age = n
}
}
}
init (wrappedValue initAge: Int){
self.wrappedValue = initAge
}
}
// 프로퍼티래퍼는 class/struct에서만 사용가능함
struct OldLady{
@WomanMannerAge var age = 0
}
var woman1 = OldLady( age: 50 )
print (woman1.age)