먹고 기도하고 코딩하라

Swift의 기본 문법 (1) - 변수 선언, 자료형 본문

앱/Swift

Swift의 기본 문법 (1) - 변수 선언, 자료형

2G Dev 2021. 11. 14. 21:55
728x90
728x90

재작년에 모바일앱 프로그래밍에서 swift를 배웠는데 복습 차 다시 공부 중이다.

올 연말까지는 앱을 잘하고 싶다. 그리고 잘할 거다!

 

 

(0)

  • swift 에는 main 함수가 없다.
  • 세미콜론은 선택! 필수가 아니다.
  • 큰따옴표를 이렇게 """ 3개를 쓰고 """ 3개를 써서 닫으면, 그 안에 여러 줄을 입력할 수 있다. 
  • 백슬래시를 이용해 문자를 여러 줄 쓸 수 있다.
let multipleLines = """
Do or do not. \
There is no try.
"""

 

 

(1) 변수 선언

swift의 변수 선언 키워드는 letvar 가 있다. let 이 상수, var 이 변수이다. 자바스크립트에서는 const 키워드가 상수이고 let 이 변수였는데 swift에서는 이게 다르다. 잘 알아둬야 할 듯. 선언할 때 타입을 명시하지 않는다면, 변수에 값이 처음 할당됐을 때 컴파일러가 타입 추론을 한다. 타입을 명시하고 싶은 경우 혹은 초기값이 타입에 대해 충분한 정보를 제공하지 못하는 경우(Double형에 int값을 넣는 경우라거나)에는 변수 이름 뒤에 콜론을 붙여 타입을 명시해준다.

import Swift

let age: Int = 10;  // 상수 선언 let
var name = "Docker";    // 변수 선언 var
let (x, y) = (1, 2)	// 튜플을 이용해 여러 값을 할당 가능

위의 예시처럼 "Docker"는 String이 확실하기 때문에 알아서 String 으로 타입 추론이 된다. 하지만 웬만하면 타입을 명시하는 것이 좋다.

어떤 타입의 값을 다른 타입으로 바꾸고자 한다면, 다른 타입의 인스턴스로 만들어야 한다. 예를 들면, Int 타입의 변수를 String으로 바꾸고 싶다면 그게 자동으로 바뀌지는 않으니 String()으로 감싸줘야 한다는 의미이다. Int + String 타입 간 덧셈 연산은 허용되지 않는다. 이런 경우라면 Int를 String으로 바꿔줘야 하는 것이다.

또한, 상수로 선언을 하더라도 값을 나중에 넣고 싶을 수도 있다. 이럴 때는 꼭 타입을 명시해야 한다.

let sum: Int;
let a: Int = 1;
let b: Int = 2;
sum = a + b;	//3

참고로 문장을 끝낼 때의 세미콜론은 선택이다. 하지만 나는 써주는 게 더 좋아서 습관처럼 붙여서 쓴다.

 

 

(2) 자료형

swift의 기본 자료형에는 Bool, Int, UInt, Float, Double, Character, String이 있다.

Bool은 오직 true, false만 받으며 0, 1처럼 다른 프로그래밍 언어에서 truthy, falsy하다고 여겨지는 값은 받지 않는다.

Int와 UInt는 호환이 안 된다. 둘 다 64비트 정수형 자료형으로, UInt는 Unsigned int 형이라 음수 값을 받지 않는다.

Float와 Double 역시 호환되지 않는다. 이 둘은 32비트 부동소수형 자료형이다.

Character는 한 글자인데, 유니코드를 사용하기 때문에 유니코드로 표현할 수 있는 모든 문자를 사용할 수 있다. 특이한 건, Character 역시 String처럼 큰따옴표로 묶어서 사용한다는 것이다. String 은 말할 것도 없이 당연지사 큰따옴표로 묶어야 한다.

String은 값 타입이다.

또한, 문자열이 비었는지 아닌지 확인하기 위해서는 isEmpty 프로퍼티를 사용하면 된다. 또한, 비어있지 않을 때 문자열에 몇 개의 문자가 들어있는지 알아볼 수도 있는데 이는 다른 프로그래밍 언어에서의 string length와도 같다.

// let str1 = 'This is not allowed.'
let str2 = "Accepted"
print(str2.isEmpty)	// false
print(str2.count)	// 8 (문자의 수를 나타냄)

String 타입에 String을 결합하려면 그냥 + 연산자 써서 합쳐버리면 된다. 그런데 Character을 결합하려면 String에서 append를 해야 한다. Character 타입을 그냥 + 연산자로 결합하려면 cannot convert value of type 'Character' to expected argument type 'String' 하면서 문제가 생긴다. 

var str1 = "Do you have the time "
let str2 = "to listen to me whine"
str1 += str2
let questionMark: Character = "?"
str1.append(questionMark)
print(str1)	// Do you have the time to listen to me whine?

어떤 String에서 index 등을 쓰든지 해서 특정 인덱스의 문자에 접근할 수 있다. 그럼 문자열에서 해당 인덱스를 토대로 부분문자열을 추출할 수도 있다는 말이 된다. 이렇게 추출한 문자열은 Substring이 된다.

let greeting = "Hello World!"
// 문자열에서 ','의 위치를 돌려주거나 맨 마지막 인덱스 번호를 돌려주게 되어 있음
let index = greeting.index(of: ",") ?? greeting.endIndex
let beginning = greeting[..<index]	// SubString이 됨
print(type(of: beginning))	// Substring

이 부분문자열을 오래 사용하려면 String()으로 감싸서 String으로 만드는 게 더 좋다고 하는데, 이유는 Substring이 원본 문자열의 메모리를 참조해 사용하기 때문이다. 그렇기 때문에 전체 문자열이 필요하지 않는 상황이라도 원본 문자열을 유지해 메모리 사용에 비효율적이라 부분문자열만 따로 String으로 바꾸기를 권장한다고 한다.

문자열끼리 비교할 때는 그냥 ==나 != 쓰면 된다. 

문자열에서 접두사, 접미사가 있는지 확인하고 싶다면 str.hasPrefix(target), str.hasSuffix(target) 쓰면 된다.

 

또한 타입스크립트에서 보던 것과 비슷하게 Any라는 자료형 타입이 있는데.. 이건 Swift 의 모든 타입을 지칭하는 자료형이다.

Any에 들어가는 값은 계속 변경이 가능하다. 처음에는 Int를 넣었다가, String으로 바꾸고, Float로 바꾸는 게 다 가능하다는 의미이다.

그래서 Any 타입으로 선언된 변수를 쓰려면 (type(of:변수))로 어떤 타입인지 확인을 하고 쓰는 것이 좋다.

AnyObject라는 타입은 모든 클래스 타입을 지칭한다. Any가 swift의 모든 기본 자료형과 모든 클래스 인스턴스를 지칭할 수 있다면, AnyObject는 모든 클래스 인스턴스를 쥐고 있을 수 있다.

var anyVar: Any = 100;
anyVar = "computer";
anyVar = 3.141592;
print(anyVar);	//3.141592
print(type(of:anyVar));	//Double

class SomeObject { }
var anyObj: AnyObject = Some();
var someAnyO: Any = Some();	//OK
var someAnyObject: AnyObject = "C";
//error: value of type 'String' expected to be instance of class or class-constrained type

Any 타입이 클래스 타입도 지칭할 수 있는 반면, AnyObject는 swift의 기본 자료형 값은 들고 있을 수 없다. 바로 value of type 'Int' expected to be instance of class or class-constrained type 하면서 에러가 난다. 

 

또한 nil이라는 게 있는데 이건 다른 프로그래밍 언어에서 null로 설명되는 것과 같은 개념이다. 어떤 값이 없다는 것을 나타내는 의미이다. 이 nil 때문에 나중에 보면 ?를 썼다가 !를 썼다가 하는 식으로 nil safety 처리를 해줘야 하는데 이게 좀 복잡시럽다. 하지만 타입스크립트를 했다면 할 만할 것이다.

 

 

(3) 템플릿 리터럴

swift 에서 출력을 하려면 print 함수를 사용한다. 이 때, 변수의 값을 사용하고 싶다면 \() 안에 넣어서 쓸 수 있다. 표현식이 \() 안에 들어간다.

print("안녕하세요. 저는 \(age+15)살입니다.");
// 안녕하세요. 저는 25살입니다.

 

 

(4) 컬렉션 자료형

컬렉션 자료형에는 Array, Set, Dictionary가 있다. 다른 프로그래밍 언어에도 있는 배열, 셋, 딕셔너리이다. 딕셔너리는 객체와 비슷하게 보이기는 하지만 좀 더 정확히는 Map과 비슷한 것 같다. 객체는 클래스로~

 

(4)-(1) 배열

먼저 배열부터 살펴보자. 기본적으로 var 로 선언한 Array는 길이가 늘어날 수 있다. 자바에서처럼 몇 개 공간을 쓰겠다고 정하지 않아도 된다. 단, let 으로 선언한 Array는 변경 불가의 immutable 배열이 된다. 이는 다른 컬렉션 자료형인 셋과 딕셔너리에도 똑같이 적용된다. 자바스크립트와 달리 아예 배열 조작 메소드를 사용할 수 없다.

var nums: Array<Int> = [Int]();	// Array<Int>와 [Int]는 같은 표현
nums.append(1);
nums.append(2);
print(nums);	//[1, 2]
print(nums.contains(1));	//true
nums.insert(0, at: 0);	// [0, 1, 2]. 맨 앞에 원소를 넣으려면 insert를 사용하되 at을 0으로 지정하면 됨
// nums.insert(contentsOf: 0, at: 0);	// 위의 문장과 같음

print(nums[1])	// 1
print(nums[nums.startIndex])	// nums[0]과도 같음. 0
print(nums[nums.index(after: nums.startIndex)])	// 1
print(nums[nums.index(before: nums.endIndex)])	// 2
// nums[nums.endIndx]를 하면 인덱스 범위를 벗어나기 때문에 오류가 남

nums.remove(at: 0);	//0번 인덱스 원소 삭제 (즉, 0 삭제하고 그 값을 반환)
nums.removeLast();	//맨 뒤의 원소 삭제 (즉, 2 삭제하고 그 값을 반환)
nums += [3, 5, 7]	// [1, 3, 5, 7]
nums.removeSubrange(2..<4)	// 2, 3번 범위까지 삭제. 이제 [1, 3]만 남음

nums.removeAll();	//배열의 원소 전부 삭제. 이 때 빈 배열을 반환

print(nums.count);	//0
print(nums.isEmpty);	// true

let iArray: [Int] = [1, 2, 3];	// let으로 선언하면 변경 불가한 배열이 됨
var emptyArray: [Double] = [];
// Array 생성자의 repeating을 사용하면 초기값을 줄 수 있음)
var zeroArray: [Int] = Array(repeating: 0, count: 5);	// [0, 0, 0, 0, 0]

// 배열의 값과 인덱스가 필요할 때는 enumerated() 메소드 사용
for (idx, value) in iArray.enumerated() {
  print("\(idx) : \(value)")
}

기본적으로 원소를 삽입, 삭제하는 것은 appendremove로 할 수 있다. append는 주어진 배열의 맨 뒤에 원소를 삽입하는 메소드이다. 배열의 맨 앞에 원소를 넣고 싶다면 insert 를 사용할 수 있다. 이 때, at 옵션을 0으로 지정해 줘야 원하는 값이 맨 앞에 들어간다.

remove 는 좀 많은데, 기본적으로 remove를 하면 at을 설정해서 인덱스 번호를 토대로 원소를 삭제할 수 있다. (at: 0)으로 하면 0번 인덱스를 지우는 것이므로, 맨 앞의 원소를 삭제하는 것과 마찬가지다. 맨 뒤의 원소를 삭제하려면 removeLast를 사용하면 되고, 배열의 모든 원소를 삭제하려면 removeAll을 사용하면 된다.

배열에 몇 개의 원소가 있는지 확인하려면 count를 확인하면 된다.

 

(4)-(2) 딕셔너리

딕셔너리는 키: 값 쌍으로 이뤄진 사전 자료형이다. 다른 프로그래밍 언어의 Map과 비슷한 자료형이라고 생각하면 되겠다. key와 value의 타입을 고정해서 선언한다.

var emptyDict: [Int: String] = [:];	// 빈 딕셔너리
var initDict: [String: String] = ["cake": "c", "dice": "d", "egg": "e"];	// 초기화된 딕셔너리
print(initDict.count)	// 3
print(initDict.isEmpty)	// false

아래의 경우에는 key, value 모두 String 타입으로 선언된 딕셔너리이다.

var strDict: [String: String] = Dictionary<String, String>();
strDict["apple"] = "a";
strDict["bear"] = "b";

딕셔너리에는 순서가 없고, 이미 value가 있는 key에 다른 value를 대입하면 value를 덮어쓰게 된다. 만약 key-value 쌍을 없애고 싶다면 다음 2가지 방법 중 하나를 쓸 수 있다.

strDict.removeValue(forKey: "apple");
strDict["apple"] = nil;

nil을 대입하면 key에 해당하는 값을 지우는 효과를 볼 수 있다.

배열과 딕셔너리 모두 대괄호 []로 만들 수 있고, 원소는 인덱스나 키로 접근이 가능하다.

 

(4)-(3) 셋

셋은 집합 개념과 같다. 배열과 비슷한데 중복이 없다. 그러므로 중복 데이터를 허용하지 않는 컬렉션을 만들어야 할 때 셋을 사용하면 좋다.

다음과 같이 만들 수 있다.

var intSet: Set<Int> = Set<Int>();	// Int형 셋 만들기. 오직 이 방법으로만 생성 가능
intSet.insert(1);
intSet.insert(2);
intSet.insert(1);
intSet.insert(1);
intSet.insert(3);

print(intSet.contains(4));	// false
print(intSet);	// {1, 2, 3}

intSet.remove(2);	// 2를 삭제함
print(intSet.count);	// 2
intSet.remove(2);	// nil. 없는 걸 삭제하려고 하면 nil

intSet.removeAll();
print(intSet.isEmpty)	// true

let fibSet: Set = [1, 2, 3, 5, 8, 13];
print(fibSet.contains(4));	// false

배열에서는 기본적으로 append를 사용하지만 셋에서는 insert를 사용한다. contains와 remove는 똑같이 쓰면 된다.

그런데 셋은 배열처럼 순서가 있는 컬렉션이 아니다. 때문에 인덱스 번호를 토대로 원소를 지우거나 값을 바꾸는 것은 허용되지 않는다. 그래서 removeLast 이런 건 못 쓴다.

상수 셋으로 바로 선언하려면 배열처럼 선언하면 된다.

한편 set은 교집합, 합집합, 차집합 만들기가 가능하다. 새로운 집합을 만들어서 실험해보자.

 

let primeSet: Set<Int> = [2, 3, 5, 7, 11, 13, 17, 19];
var unionSet: Set<Int> = primeSet.union([1, 2, 3, 5, 8, 13]);
print(unionSet.sorted());	// [1, 2, 3, 5, 7, 8, 11, 13, 17, 19]

var interSet: Set<Int> = primeSet.intersection([1, 2, 3, 5, 8, 13]);
print(interSet.sorted());	// [2, 3, 5, 13]

var subtractSet: Set<Int> = primeSet.subtracting([1, 2, 3, 5, 8, 13]);
print(subtractSet.sorted());	// [7, 11, 17, 19]

var symmetricSet: Set = primeSet.subtracting([1, 2, 3, 5, 8, 13]);
print(symmetricSet.sorted());	// [1, 7, 8, 11, 17, 19]. A + B - (A x B)

interSet.isSubset(of: primeSet)	// true. primeSet은 interSet의 모든 원소를 포함함
interSet.isSuperset(of: primeSet)	// false. interSet은 primeSet의 모든 원소를 포함하지 않음
interSet.isDisjoint(with: subtractSet)	// true. interSet과 subtractSet은 공통값이 없음

이름에서 알 수 있다시피 union은 합집합, intersection은 교집합, subtracting은 차집합을 만드는 메소드이다. 이 메소드들은 단독으로는 쓸 수 없고 반드시 앞에 어떤 집합이 있어야 한다. 지금은 primeSet을 기준으로 만들었지만, 그냥 즉석에서 Set<Int>([1, 2, 3, 4]) 이런 식으로 써도 되긴 한다. sorted()를 해준 이유는 셋에는 순서가 없기 때문에 오름차순으로 결과를 볼 수 있게 하기 위함이다.

union과 intersection은 어떤 집합이 기준이 되든지 딱히 상관은 없는데 subtracting의 경우에는 차집합이다. A-B를 하는지 B-A를 하는지에 따라 결과가 달라지기 때문에 순서를 잘 봐줘야 한다.

 

다음에는 함수랑 클래스를 정리하며 내 안의 swift를 일깨워보기로

 

 

References

 

728x90
반응형
0 Comments
댓글쓰기 폼