본문 바로가기
iOS

스위프트 배우기 1부 - 스위프트 투어

by 붕어사랑 티스토리 2023. 3. 15.
반응형

https://docs.swift.org/swift-book/documentation/the-swift-programming-language/guidedtour

 

Documentation

 

docs.swift.org

1. 프로그래밍의 시작점

아래 코드는 완전한 하나의 프로그램이다. 스위프트는 글로벌 스코프에서 작성된 코드가 시작점이다. main함수같은게 필요 없다

print("Hello, world!")
// Prints "Hello, world!"

 

 

 

2. 변수 설정

스위프트에서는 letvar로 변수를 선언한다. let은 상수, var는 변수이다. 

스위프트에서는 변수는 사용되기전에 반드시 초기화 되어야 한다.

var myVariable = 42
myVariable = 50
let myConstant = 42

 

스위프트는 타입추론이 가능하다. 변수형을 명시적으로 지정해주려면 코틀린처럼 : 을 사용하여 지정해주자

let implicitInteger = 70
let implicitDouble = 70.0
let explicitDouble: Double = 70

 

스위프트는 암묵적인 형변환이없다. 형변환을 명시적으로 지정해주어야 한다

let label = "The width is "
let width = 94
let widthLabel = label + String(width)

 

변수의 문자열 변환은 아래처럼 백슬래쉬를 이용하여 쉽게 템플릿형태로 변환 가능하다

let apples = 3
let oranges = 5
let appleSummary = "I have \(apples) apples."
let fruitSummary = "I have \(apples + oranges) pieces of fruit."

 

파이선처럼 쌍따음표 세개(""")를 이용하면 multipline 문자열을 만들 수 있다.

let quotation = """
I said "I have \(apples) apples."
And then I said "I have \(apples + oranges) pieces of fruit."
"""

 

 

swift의 자료형은 다음과 같다

 

  • Bool : true 와 false
  • Int, UInt : 정수형, 양의 정수
  • Float : 32bit 실수형
  • Double : 64bit 실수형
  • Character, String : 문자, 문자열. 특이한건 둘다 쌍따음표 (") 를 사용한다
  • Any : 모든타입을 지칭한다
  • AnyObject : 모든 클래스타입을 지칭한다
  • nil : 값이 missing임을 나타낸다

 

 

 

3. 배열과 딕셔너리

대괄호를 이용하여 배열과 디셔너리형을 만들 수 있다. 엘리먼트의 접근법은 여타 다른 언어들과 다를바 없다.

인덱싱과 키값을 이용해 접근한다.

var fruits = ["strawberries", "limes", "tangerines"]
fruits[1] = "grapes"

var occupations = [
    "Malcolm": "Captain",
    "Kaylee": "Mechanic",
 ]
occupations["Jayne"] = "Public Relations"

 

파이썬과 비슷하게 append를 이용하여 배열요소 추가 가능하다

fruits.append("blueberries")
print(fruits)
// Prints "["strawberries", "grapes", "tangerines", "blueberries"]"

 

빈배열과 빈딕셔너리를 만드려면 [] 과 [:] 을 이용하면 된다. 파이썬의 배열 얕은복사와 다르므로 주의

fruits = []
occupations = [:]

 

 

배열과 딕셔너리의 타입형은 아래처럼 지정이 가능하다

let emptyArray: [String] = []
let emptyDictionary: [String: Float] = [:]

 

 

 

 

4. 컨트롤 플로우

if, switch, for-in, while, repeat-while 문이 있다.

조건이나 루프변수의 괄호는 옵셔널이나, 조건문바디나 루프문 바디는 괄호가 필수이다.

 

if문의 조건문은 반드시 Boolean 형태이여야 한다.

let individualScores = [75, 43, 103, 87, 12]
var teamScore = 0
for score in individualScores {
    if score > 50 {
        teamScore += 3
    } else {
        teamScore += 1
    }
}
print(teamScore)
// Prints "11"

 

 

 

?을 이용하면 optional value를 만들 수 있다. 코틀린의 nullable과 유사하다

if문에다가 let과 optional value를 조합할 수 있다. 아래 코드는 optionalName이 값이 있을 경우 조건문 바디를 수행한다.

var optionalString: String? = "Hello"
print(optionalString == nil)
// Prints "false"

var optionalName: String? = "John Appleseed"
var greeting = "Hello!"
if let name = optionalName {
    greeting = "Hello, \(name)"
}

위 코드에서 optionalName이 만약 nil 일경우 조건문이 스킵된다. nil이 아닐경우 조건문이 수행되며, let 다음에 생긴 상수는 조건문 바디 안에서 사용이 가능하다.

 

 

코틀린과 같이 ?? 연산자가 있다. nil이 아니면 좌측값을, nil이면 우측값을 사용한다

앞서배운 let과 if문의 조합에서 = 을 생략해버리면 변수값을 바로 가져다가 사용한다.

let nickname: String? = nil
let fullName: String = "John Appleseed"
let informalGreeting = "Hi \(nickname ?? fullName)"


if let nickname {
    print("Hey, \(nickname)")
}
// Doesn't print anything, because nickname is nil.

 

 

 

switch문은 다음과 같이 사용 가능하다. 다른 언어와 차이점이라면 명시적으로 break를 선언해 주지 않아도 된다.

let vegetable = "red pepper"
switch vegetable {
case "celery":
    print("Add some raisins and make ants on a log.")
case "cucumber", "watercress":
    print("That would make a good tea sandwich.")
case let x where x.hasSuffix("pepper"):
    print("Is it a spicy \(x)?")
default:
    print("Everything tastes good in soup.")
}
// Prints "Is it a spicy red pepper?"

 

 

 

for-in 루프로 배열을 순회 할 수 있다. 딕셔너리의 경우 순서가 정해지지 않았기 때문에 순서가 제멋대로 돌게된다.

 

let interestingNumbers = [
    "Prime": [2, 3, 5, 7, 11, 13],
    "Fibonacci": [1, 1, 2, 3, 5, 8],
    "Square": [1, 4, 9, 16, 25],
]
var largest = 0
for (_, numbers) in interestingNumbers {
    for number in numbers {
        if number > largest {
            largest = number
        }
    }
}
print(largest)
// Prints "25"

 

숫자의 범위를 지정해 for문을 돌고 싶으면 아래처럼 하면 된다

var total = 0
for i in 0..<4 {
    total += i
}
print(total)
// Prints "6"

...< : 점 세개를 붙이면 끝자리 수자를 포함한다. 즉

  • ..<숫자 : 숫자를 포함안함
  • ...<숫자 : 숫자를 포함함 <=과 같음

 

 

 

 

while문은 간단하다. repeat while문은 do-while과 같다

var n = 2
while n < 100 {
    n *= 2
}
print(n)
// Prints "128"

var m = 2
repeat {
    m *= 2
} while m < 100
print(m)
// Prints "128"

 

 

5. 함수와 클로저

 

func를 이용하면 함수를 만들 수 있다.(fun도 아니고 왜 func람...) ->를 이용하여 리턴타입 지정이 가능하다

 

함수를 호출할때는 인풋인자의 레이블을 명시해주어야 한다.

func greet(person: String, day: String) -> String {
    return "Hello \(person), today is \(day)."
}
greet(person: "Bob", day: "Tuesday")

 

인풋인자의 이름을 커스텀하게 바꾸고 싶을경우, 인풋인자 앞에 다가 다른 이름을 붙이면 바꿀 수 있다.

아래의 경우 커스텀 레이블로 언더바(_)를 줄 경우 함수를 호출할 때 레이블 없이 호출할 수 있다

func greet(_ person: String, on day: String) -> String {
    return "Hello \(person), today is \(day)."
}
greet("John", on: "Wednesday")

 

 

 

함수값을 리턴할 때 튜플을 이용하면 여러개의 값을 리턴할 수 있다. 튜플의 값은 이름이나 숫자로 참조가능하다.

func calculateStatistics(scores: [Int]) -> (min: Int, max: Int, sum: Int) {
    var min = scores[0]
    var max = scores[0]
    var sum = 0

    for score in scores {
        if score > max {
            max = score
        } else if score < min {
            min = score
        }
        sum += score
    }

    return (min, max, sum)
}
let statistics = calculateStatistics(scores: [5, 3, 100, 3, 9])
print(statistics.sum)
// Prints "120"
print(statistics.2)
// Prints "120"

 

 

 

스위프트에서 함수는 일급객체이다. 즉 함수는 값으로 취급되어 함수의 인풋으로 사용될 수도 있고, 리턴값으로 사용 될 수도 있다

func makeIncrementer() -> ((Int) -> Int) {
    func addOne(number: Int) -> Int {
        return 1 + number
    }
    return addOne
}
var increment = makeIncrementer()
increment(7)
func hasAnyMatches(list: [Int], condition: (Int) -> Bool) -> Bool {
    for item in list {
        if condition(item) {
            return true
        }
    }
    return false
}
func lessThanTen(number: Int) -> Bool {
    return number < 10
}
var numbers = [20, 19, 7, 12]
hasAnyMatches(list: numbers, condition: lessThanTen)

 

 

 

스위프트에서 함수는 사실 특별한 케이스의 클로저라 한다. 즉 함수의 코드블럭이 나중에 call될 수 있다. 클로저는 클로저가 생선된 장소에서 접근가능한 변수들에 접근가능하다. 이는 클로저가 다른 영역에 가도 마찬가지이다.

 

위 얘기를 정리하면 함수 is 클로저가 된다.

 

 

스위프트에서는 이름이 없는 클로저를 중괄호 {} 를 이용하여 만들 수 있다.

이때 in을 이용하여 함수의 인풋타입과 리턴타입 그리고 함수바디를 구분해준다

numbers.map({ (number: Int) -> Int in
    let result = 3 * number
    return result
})

 

만약 클로저의 타입이 이미 알려진경우, 아래와 같이 클로저를 간소화 시킬 수 있다

let mappedNumbers = numbers.map({ number in 3 * number })
print(mappedNumbers)

 

스위프트에서는 파라미터를 이름이 아닌 숫자로 접근이 가능하다. 이를 이용하면 훨 씬 더 간결한 클로저 작성이 가능하다.

 

함수의 마지막 파라미터로 클로져라면 괄호 다음에 바로 함수바디를 작성 가능하다.

만약 함수의 파라미터가 하나이고 그 하나가 함수 argument라면, 괄호를 생략 가능하고 바로 함수를 작성하면 된다.

let sortedNumbers = numbers.sorted { $0 > $1 }
print(sortedNumbers)

 

 

오브젝트와 클래스

자바와 c++의 클래스 작성법과 비슷하다. 변수와 함수 선언은 중괄호 안에다 하면 된다.

class Shape {
    var numberOfSides = 0
    func simpleDescription() -> String {
        return "A shape with \(numberOfSides) sides."
    }
}

 

인스턴스 생성은 new 키워드 없이 그냥 클래스 이름을 이용한다. 점을 이용하여 클래스 프로퍼티에 접근가능하다

var shape = Shape()
shape.numberOfSides = 7
var shapeDescription = shape.simpleDescription()

 

 

생성자 생성은 init 키워드를 이용한다. destructor는 deinit을 정의해주면 된다. 파이썬처럼 self를 이용하여 클래스 자신을 가리킬 수 있다.

class NamedShape {
    var numberOfSides: Int = 0
    var name: String

    init(name: String) {
       self.name = name
    }

    func simpleDescription() -> String {
       return "A shape with \(numberOfSides) sides."
    }
}

 

 

클래스의 상속은 콜론 : 을 이용하여 상속한다. super키워드를 이용하여 부모 클래스의 생성자를 호출 할 수 있다.

class Square: NamedShape {
    var sideLength: Double

    init(sideLength: Double, name: String) {
        self.sideLength = sideLength
        super.init(name: name)
        numberOfSides = 4
    }

    func area() -> Double {
        return sideLength * sideLength
    }

    override func simpleDescription() -> String {
        return "A square with sides of length \(sideLength)."
    }
}
let test = Square(sideLength: 5.2, name: "my test square")
test.area()
test.simpleDescription()

 

 

 

변수를 선언할 때 getter와 setter를 커스텀 해 줄 수 있다. 개인적으로 코드 가독성을 떨어뜨려 호불호가 갈리는 기능이라고 생각.

 

setter를 보면 newValue라는 것이 있다. 이는 setter로 들어오는 값이며 암묵적으로 자동 생성된다.

만약에 이 newValue가 싫고, 명시적으로 인풋으로 들어오는 값의 이름을 정하고 싶으면 set뒤에 괄호를 이용하여 파라미터를 지정해주면 된다.

class EquilateralTriangle: NamedShape {
    var sideLength: Double = 0.0

    init(sideLength: Double, name: String) {
        self.sideLength = sideLength
        super.init(name: name)
        numberOfSides = 3
    }

    var perimeter: Double {
        get {
             return 3.0 * sideLength
        }
        set {
            sideLength = newValue / 3.0
        }
    }

    override func simpleDescription() -> String {
        return "An equilateral triangle with sides of length \(sideLength)."
    }
}
var triangle = EquilateralTriangle(sideLength: 3.1, name: "a triangle")
print(triangle.perimeter)
// Prints "9.3"
triangle.perimeter = 9.9
print(triangle.sideLength)
// Prints "3.3000000000000003"

 

 

만약에 값을 세팅할때 세팅하는과정 앞뒤로 무언가를 해주고 싶다면 willSet과 didSet을 이용하면 된다.

class TriangleAndSquare {
    var triangle: EquilateralTriangle {
        willSet {
            square.sideLength = newValue.sideLength
        }
    }
    var square: Square {
        willSet {
            triangle.sideLength = newValue.sideLength
        }
    }
    init(size: Double, name: String) {
        square = Square(sideLength: size, name: name)
        triangle = EquilateralTriangle(sideLength: size, name: name)
    }
}
var triangleAndSquare = TriangleAndSquare(size: 10, name: "another test shape")
print(triangleAndSquare.square.sideLength)
// Prints "10.0"
print(triangleAndSquare.triangle.sideLength)
// Prints "10.0"
triangleAndSquare.square = Square(sideLength: 50, name: "larger square")
print(triangleAndSquare.triangle.sideLength)
// Prints "50.0"

 

클래스에서도 optional value ? 를 사용할 수 있다. 만약 ? 앞에 있는 밸류의 값이 nil이면, ? 뒤에있는 오퍼레이션들은 싸그리 무시된다.

let optionalSquare: Square? = Square(sideLength: 2.5, name: "optional square")
let sideLength = optionalSquare?.sideLength

 

 

 

Enum과 Structure

다른언어와 비슷하게 enum을 사용한다. enum뒤에다가 자료형을 지정해 줄 수 있다. 단순히 Int뿐만 아닌 실수형도 가능하다.

case의 시작점을 따라서 값이 1씩 증가하게 된다.

 

Enum은 자료와 관련된 메소드를 추가할 수 있다.

 

enum의 rawValue라는 프로퍼티를 통하여 기존값에 접근할 수 있다.

enum Rank: Int {
    case ace = 1
    case two, three, four, five, six, seven, eight, nine, ten
    case jack, queen, king

    func simpleDescription() -> String {
        switch self {
        case .ace:
            return "ace"
        case .jack:
            return "jack"
        case .queen:
            return "queen"
        case .king:
            return "king"
        default:
            return String(self.rawValue)
        }
    }
}
let ace = Rank.ace
let aceRawValue = ace.rawValue

스위프트에서는 기본적으로 raw Value의 시작값을 0으로 하나 명시적으로 값을 지정 가능하다. 그리고 1 씩 증가시기킨다.

 

아래처럼 init?(rawValue:) 를 이용하여 enum의 인스턴스를 생성할 수 있다. 만약에 rawValue에 매칭되는 값이 없다면 nil이 리턴된다.

if let convertedRank = Rank(rawValue: 3) {
    let threeDescription = convertedRank.simpleDescription()
}

 

반응형

댓글