Apple에서 차세대 언어로 Swift를 발표했습니다. 개인적으로는 Obj-C에 비해 꽤 정감이 가는 문법이라 요즘 한창 Swift로 외도 중입니다. 그래서 공부도 할겸 간단히 정리해보았습니다. 주요 기능을 정리하긴 했지만 전부를 정리한 것은 아니니 이 점에 주의해주세요.
기본
- 스트롱 타입(Strong type) 언어입니다.
변수를 선언하거나 사용할 때, 또는 함수의 인수를 선언하거나 반환값을 선언할 때 자료형을 꼭 정해줘야 한다는 뜻입니다. 원칙적으로는 그런데 타입 추론(type inference) 기능도 있어서 변수 선언할 때 초기값과 함께 선언하면 대충 알아서 판단하기도 합니다. - 유니코드 기반입니다.
자바스크립트처럼 유니코드 기반이라 변수 이름으로 유니코드 문자를 사용할 수 있습니다(이모지도 가능). 문자열의 길이를 셀 때도 영문과 한글이 모두 1 길이입니다. - 대소문자 구분합니다.
null
이 아닌nil
을 사용합니다.
// 한 줄 주석은 이렇게 씁니다.
/* 여러 줄 주석은
이렇게 씁니다.
*/
/* 여러 줄 주석은
/* 이렇게 중첩도 가능한 것이 */
특이한 점입니다.
*/
var num = 1 // 타입 추론에 의해 정수형으로 선언됩니다. 선언은 var 키워드를 사용합니다.
var 둘 = 2 // 유니코드 변수명 가능합니다. 한 줄에 한 문장만 쓰면 세미콜론 안 써도 됩니다.
var 셋 = 3; var 넷 = 4 // 하지만 두 문장 이상을 쓰려면 문장 끝에 세미콜론 붙여야 합니다.
var (five, six) = (5, 6) // 이렇게 하면 five와 six 변수에 각각 5, 6이 저장됩니다.
var 일곱 // 변수 이름만 선언하면 에러가 발생합니다.
var eight:Int = 8 // 타입과 함께 선언할 때는 "var 변수이름:타입"으로 선언합니다.
eight = 8.2 // 타입이 맞지 않아 에러가 발생합니다.
eight = Int(8.2) // 형 변환하면 잘 됩니다.
eight = nil // 에러가 발생합니다. 변수가 nil을 가지려면 아래처럼 선언할 때 ?를 추가해야 합니다.
var nine:Int? = nil // 타입 뒤에 ?을 붙이면 nil을 할당할 수 있습니다.
var ten:Int? // 이래도 nil이 자동으로 할당됩니다.
var million:Int = 1_000_000 // 숫자 사이에 _를 쓸 수 있습니다.
var twomillion:Int = 2__0_0_0_00_ // 사실 제일 앞만 아니면 숫자 어디에 써도 됩니다.
let pi = 3.14 // 상수는 let 키워드로 선언합니다.
pi = 3.1415 // 값을 바꾸려 하면 에러가 발생합니다.
// 그 외에는 변수와 같습니다.
let str = "문자열"
let 명시적문자열:String = "String 타입과 함께 선언합니다."
var 여덟:String = "eight은 " + String(eight) + "입니다." // 형변환이 필요합니다.
var 백만:String = "million은 \(million)입니다." // \(...) 문법이 더 편리합니다.
var 백만여덟 = "이 값은 \(million + eight)입니다." // 수식이나 함수 실행도 가능합니다.
배열과 딕셔너리
// 배열
var shoppingList = ["catfish", "water"] // [..]로 선언합니다.
shoppingList[1] = "생수 한 병" // 수정
shoppingList[2] = "마늘" // 에러. 이런 식으로는 추가 못합니다.
shoppingList[0] = 5 // 에러. 한 배열에는 같은 타입만 저장할 수 있습니다.
shoppingList = [] // 빈 배열로 만듭니다.
shoppingList.append("대파") // .append()로 추가합니다.
shoppingList += "배추" // += 연산자로도 추가합니다.
shoppingList += ["생강", "무"] // 배열도 합칠 수 있습니다.
/* shoppingList는 ["대파", "배추", "생강", "무"] */
shoppingList[2..4] = ["사이다", "당근"] // 3, 4번째 원소를 "사이다","당근"으로 교체.
var emptyList = [] // 빈 배열
emptyList.append("아이템") /* 타입을 정하지 않은 빈 배열이라 .append 메소드가 없습니다. 에러가 발생합니다. */
var stringList:String[] = [] // 빈 문자열 배열
stringList.append("나랏말싸미") // 이건 잘 동작합니다.
// 딕셔너리
var 제조사 = [
"TV" : "LG",
"노트북" : "Apple", // 마지막 쉼표는 없어도 됩니다.
]
제조사["마우스"] = "로지텍" // 이런 식으로 추가 할 수 있습니다.
let 빈_문자열_배열 = String[]()
// 또는
let 빈_문자열_배열:String[] = []
let 빈_딕셔너리 = Dictionary<String, Float>()
제어문
자바스크립트를 비롯한 C 계열의 많은 언어와 달리 제어문의 조건절에는 괄호를 쓰지 않습니다.
// if
var optionalName:String? = "John"
var greeting = "Hello!"
if optionalName {
greeting = "Hello, \(optionalName)"
} else {
greeting = "Who are you?"
}
// 또는
if let name = optionalName {
greeting = "Hello, \(name)"
} else {
greeting = "Who are you?"
}
// for .. in
let fruits = ["귤", "사과", "수박"]
for fruit in fruits {
println(fruit)
}
let prices = ["귤":300, "사과":1000, "수박":12_000]
for (fruit, price) in prices {
println(fruit+"의 가격은 \(price)입니다.")
}
for character in "Hello" {
println(character) // H, e, l, l, o가 각각 출력
}
// for..조건..증감
var index:Int = 0
for index = 0; index < 3; index++ {
println("index = \(index)")
}
// 위 코드는 for...in으로 다음과 같이도 표현할 수 있습니다.
for index in 0..3 {
println("index = \(index)")
}
// while
var i = 1
while i < 1000 {
i *= 2
}
// do while
var i = 1
do {
println("hello \(i++)")
} while i < 3
// switch
/* case 문 끝날 때 break 안 씁니다. 쓰면 오히려 에러가 발생합니다.
C, JS처럼 다음 case문으로 넘어가고 싶다면 fallthrough를 사용합니다.
*/
let 채소 = "청양고추"
switch 채소 {
case "상추":
let 채소설명 = "먹으면 졸리다."
case "오이", "양상추": // 여러값
let 채소설명 = "샐러드 만들 때 좋다."
case let x where x.hasSuffix("고추"): // where를 사용해 조건도 가능
let 채소설명 = "\(x)는 매울까?"
default: // 만족하는 case가 없을 때 기본값
let 채소설명 = "채소는 몸에 좋다."
}
// 결과 : 채소설명 == "청양고추는 매울까?"
// switch - 범위
let count = 3_000_000
var 별의숫자:String = ""
switch count {
case 0:
별의숫자 = "없다"
case 1...3: // 1부터 3까지
별의숫자 = "아주 조금 있다"
case 4...9: // 4부터 9까지
별의숫자 = "약간 있다"
case 10...99:
별의숫자 = "수십개 있다"
case 100...999:
별의숫자 = "수백개 있다"
case 1000...9999:
별의숫자 = "수천개 있다"
default:
별의숫자 = "어마어마하게 많다"
}
println("하늘에 별이 \(별의숫자).")
// switch - 튜플
let somePoint = (1, 1)
switch somePoint {
case (0, 0):
println("(0, 0)은 원점입니다.")
case (_, 0): // 두번째 값만 일치할 때
println("(\(somePoint.0), 0)은 X축 위에 있습니다.")
case (0, _): // 첫번째 값만 일치할 때
println("(0, \(somePoint.1))은 Y축 위에 있습니다.")
case (-2...2, -2...2): // 범위
println("(\(somePoint.0), \(somePoint.1))은 상자 안에 있습니다.")
default:
println("(\(somePoint.0), \(somePoint.1))은 상자 바깥에 있습니다.")
}
함수
함수는 객체로서 전달할 수도 있고 중첩도 가능합니다.
// 문자열을 반환하는 함수
func greet(name: String, day: String) -> String {
return "Hello \(name), today is \(day)."
}
greet("Bob", "Tuesday")
// 인수 기본값 설정
func greet(name: String, day: String = "Monday") -> String {
return "Hello \(name), today is \(day)."
}
greet("Bob")
// 튜플로 여러 값을 한꺼번에 반환하는 함수
func getGasPrices() -> (Double, Double, Double) {
return (3.59, 3.69, 3.79)
}
let (p1, p2, p3) = getGasPrices()
// 참조 인수 - inout 키워드를 사용합니다.
func swapInt(inout a: Int, inout b: Int) {
let tempA = a
a = b
b = tempA
}
var (someInt, anotherInt) = (3, 107)
swapInt(&someInt, &anotherInt)
// 여러 개의 숫자를 인수로 받는 함수. 인수가 배열로 전달됩니다.
func setup(numbers: Int...) {
// 실행 코드 자리
}
setup(5, 16, 38) // 전달한 숫자는 배열로서 전달됩니다.
// 함수 중첩
func printWelcomeMessage() -> String {
var y = "Hello,"
func add() {
y += " world" // 부모 함수의 값을 사용하고 있다는 점에도 주목
}
add()
return y
}
printWelcomeMessage() // Hello, world
// 함수를 반환하기
func makeIncrementer() -> (Int -> Int) {
func addOne(number: Int) -> Int {
return 1 + number
}
return addOne
}
var increment = makeIncrementer()
increment(7)
// 함수를 인수로 받기
func myMax( 갑:Int, 을:Int, 앞이크다:(Int, Int) -> Bool ) -> Int {
if 앞이크다(갑, 을) {
return 갑
} else {
return 을
}
}
// 함수를 변수에 저장
func 더하기(a:Int, b:Int) -> Int {
return a + b
}
var addFunc:(Int, Int) -> Int = 더하기
클로져
자바스크립트 개발자들에게는 익숙하지만 컴파일 언어 사용자들에게는 아직도 다소 생소할 수 있는 개념입니다.
// 클로저문법 : { (인수선언) -> 반환타입 in 함수몸체 }
// `->` 앞은 인수, 뒤는 반환값 타입
// `in` 앞은 클로저 헤더(인수 선언, 반환값 정의), 뒤는 클로저 본문(함수 몸체)
var numbers = [1, 2, 3, 4, 5]
numbers.map({
(number: Int) -> Int in
let result = 3 * number
return result
})
// 가능한 경우 선언시 타입을 생략할 수 있습니다.
numbers = [1, 2, 6]
numbers = numbers.map({ number in 3 * number })
println(numbers) // [3, 6, 18]
// 굳이 인수 이름이 필요없는 경우에는 인수 선언도 생략할 수 있습니다.
// 전달된 인수는 첫 번째 인수부터 $0, $1, $2...와 같이 접근할 수 있습니다.
numbers = [2, 5, 1]
numbers.map { 3 * $0 } // [6, 15, 3]
enum
enum 방향 {
case 동
case 서
case 남
case 북
}
// 또는
enum 방향 {
case 동, 서, 남, 북
}
// 할당 및 사용 - 값 앞의 점(.)에 주의
let 어디로 = 방향.동
switch 어디로 {
case .동:
println("해뜨는 동쪽")
case .서:
println("해지는 서해바다")
case .남:
println("따뜻한 남쪽나라")
case .북:
println("북쪽은 추울텐데")
}
// 타입과 값의 명시적 선언
enum ASCIIControlCharacter: Character {
case Tab = "\t"
case LineFeed = "\n"
case CarriageReturn = "\r"
}
클래스와 구조체
사실 겉보기에는 클래스와 구조체는 다른 점이 거의 없습니다. 둘 다 생성자도 추가할 수 있고, 메소드도 추가할 수 있으며 상속도 되고 (뒤에 나오는) protocol
을 적용하는 것도 가능합니다. 가장 큰 차이점은 클래스는 참조 타입(Reference Type)이고 구조체는 하나의 자료형이라는 것입니다. 공식 문서에 따르면 이러한 특성 때문에, 할당 또는 복사할 때 구조체는 passed by value 방식으로 아예 다른 인스턴스로 복제되지만 클래스는 passed by reference 방식으로 참조만 복제된다고 합니다. 그 외의 차이점으로는 구조체는 다른 구조체를 상속할 수 없다는 점, ... 등이 있습니다.
클래스와 구조체의 인스턴스를 만들 때는 구조체이름() 또는 클래스이름()처럼 마치 함수를 실행하듯 사용합니다. 인스턴스를 만드는 별도의 키워드가 없습니다.
// 구조체
struct Resolution {
var width = 0
var height = 0
}
let res = Resolution()
// 또는
let hd = Resolution(width: 1920, height: 1080) // 초기화
// 클래스
class VideoMode {
var resolution = Resolution()
var interlaced = false
var frameRate = 0.0
var name: String?
}
let someVideoMode = VideoMode()
// 생성자 - init() 사용.
struct 섭씨 {
var 온도:Double = 0.0
init(화씨온도:Double) {
온도 = (화씨온도 - 32.0) / 1.8
}
init(켈빈온도:Double) {
온도 = 켈빈온도 - 273.15
}
}
let 물의끓는점 = 섭씨(화씨온도:212.0)
// 물의끓는점.온도 = 100.0
class 도형 {
init() {
}
func 면적() -> Int {
return 0
}
}
// 상속
class 사각형: 도형 {
var width = 0
var height = 0
init(width:Int, height:Int) {
super.init()
self.width = width
self.height = height
}
override func 면적() -> Int {
return width * height
}
}
// 프로퍼티 getter, setter
class 정사각형: 사각형 {
var 한변의길이:Int {
get {
return width
}
set(길이) {
width = 길이
height = 길이
}
}
}
// 프로퍼티 변경 감시 - willSet(변경전), didSet(변경후)
class 자동차 {
var 이동거리:Int = 0 {
willSet(거리) {
println("\(거리)만큼 움직일 겁니다.")
}
didSet {
if 이동거리 < oldValue {
println("\(oldValue - 이동거리)만큼 뒤로 이동")
} else {
println("\(이동거리 - oldValue)만큼 앞으로 이동")
}
}
}
}
// 타입 프로퍼티(Type Property) - 정적 프로퍼티
struct SomeStructure {
static var storedTypeProperty = "Some value."
static func someMethod() {
// ...
}
}
enum SomeEnumeration {
static var storedTypeProperty = "Some value."
}
class SomeClass {
class var storedTypeProperty = "Some value."
class func someMethod() {
// ...
}
}
// @lazy 프로퍼티 속성 - 사용할 때 초기화
class XmlExporter {
func saveToFile(filename:String) {
// 메소드 내용
}
}
class XMLData {
@lazy var exporter = XmlExporter()
var data: String
// 그 외 기능들
}
let xml = XMLData()
// 이 시점에서는 xml.exporter가 초기화되지 않음.
xml.exporter.saveToFile() // 여기서 초기화
기타
프로토콜(Protocol)
C++의 interface
와 비슷합니다. enum
, struct
, class
가 프로토콜을 상속합니다. 변수나 함수의 타입까지만 정의할 뿐 몸체를 정의하지는 않습니다.
protocol 예제프로토콜 {
var 읽기쓰기가능변수 : Int { get set }
var 읽기전용변수 : Int { get }
func 어떤함수() -> Double
}
class 클래스: 부모클래스, 예제프로토콜, 프로토콜은여러개도가능 {
// 클래스 내용
}
struct 구조체: 예제프로토콜, 또다른프로토콜 {
// 구조체 내용
}
Extension
이미 정의된 클래스, 구조체, enum을 확장할 수 있는 손쉬운 방법입니다.
extension Int {
func 번_반복(클로저:() -> ()) {
for _ in 0..self { // _는 변수 인덱스가 필요없을 때 사용
클로저()
}
}
}
2.번_반복({
println("안녕하세요!")
})
연산자 정의
기존의 연산자를 재정의 할 수도 있고, / = - + * % < > ! & | ^ . ~
문자를 조합해 커스텀 연산자를 만들 수도 있습니다.
/* @infix는 좌항, 우항이 있는 이항 연산자. 예) a + 3
@assignment는 할당 연산자. 예) a += 3
@prefix는 값 앞에 붙는 연산자. 예) ++a
@postfix는 값 뒤에 붙는 연산자. 예) a++
*/
@infix func + (a: Int, b: Int) -> Int {
return a - b
}
var x = 5 + 4 // x == 1
// Vector2D 타입에 대해 더하기 연산자 정의
struct Vector2D {
var x = 0.0, y = 0.0
}
@infix func + (left: Vector2D, right: Vector2D) -> Vector2D {
return Vector2D(x: left.x + right.x, y: left.y + right.y)
}
// 커스텀 연산자
operator postfix ^-^ {}
@postfix @assignment func ^-^ (inout num:Int) -> Int {
num += 2
return num
}
var 숫자 = 1
println("숫자의 값은 \(숫자^-^)")
Generic
C++ 또는 Java 같은 스트롱 타입 언어에서 코드에 유연함을 더할 수 있는 방법의 하나입니다. 간단히 말하면 타입의 템플릿을 만들어두고 여러 형태로 사용하겠다는 건데, 경험해보지 않은 분들에게는 다소 헷갈릴 수도 있습니다.
func 서로바꿈<T>(inout a: T, inout b: T) {
let temporaryA = a
a = b
b = temporaryA
}
let num1 = 3.5, num2 = 5.8
서로바꿈<Double>(num1, num2)
struct Stack<T> {
var elements = T[]()
mutating func push(element: T) { // mutating은 하단에서 설명합니다.
elements.append(element)
}
mutating func pop() -> T {
return elements.removeLast()
}
}
class 계산<T> {
func 더하기(a:T, b:T) -> T {
return a + b
}
func 곱하기(a:T, b:T) -> T {
return a * b
}
}
var calc = 계산<Int>()
calc.더하기(3, 5)
Type Alias
C계열의 typedef 와 비슷한 기능을 합니다.
typealias 실수 = Double
let num: 실수 = 30.2
typealias 포인트 = (Int, Int)
var point = (10, 20)
typealias 정수더하기함수: (Int, Int) -> Int
Mutating
struct
또는 enum
타입의 메소드는 기본적으로 인스턴스에서 프로퍼티의 값을 바꿀 수 없습니다. 하지만 메소드 앞에 mutating
키워드를 추가하면 프로퍼티의 값을 수정할 수 있습니다.
struct Point {
var x = 0.0, y = 0.0
func moveBy(x deltaX:Double, y deltaY:Double) {
x += deltaX
y += deltaY
}
}
var pt = Point(x:1.0, y:1.0)
pt.moveBy(x:3.0, y:5.0) // 값을 바꿀 수 없으므로 에러
struct Point {
var x = 0.0, y = 0.0
mutating func moveBy(x deltaX:Double, y deltaY:Double) {
x += deltaX
y += deltaY
}
}
var pt = Point(x:1.0, y:1.0)
pt.moveBy(x:3.0, y:5.0) // 정상 동작
// pt.x == 4.0, pt.y == y:6.0
참고자료
- Apple 공식 자료 - The Swift Programming Language
- Swift cheat sheet
[adsense]
우왕ㅋ 잘봤습니다. 은근 배우기가 쉽네요
[…] (금) swift 재빨리 훑어보기 : https://taegon.kim/archives/4695 정리가 아주 […]