[Swift] 데이터 타입

eunseon·2023년 7월 10일
0

(핵심만 골라 배우는) SwiftUI 기반의 iOS 프로그래밍을 읽으며, 실습 및 이론을 적은 글입니다.

스위프트를 배우기 가장 좋은 방법은 스위프트 플레이그라운드 환경에서 이것저것 해보는 것 :)

스위프트 데이터 타입

컴퓨터는 단지 이진 연산을 하는 시스템이라는 것을 잊어버리곤 한다.
이진 시스템은 0과 1, 참/거짓, 설정됨/설정되지 않음으로 동작한다.
RAM에 올라온 모든 데이터는 디스크 드라이브에 저장되어 회로 보드와 버스를 통해 전달되는 1과0들의 나열에 지나지 않음
1과 0의 각각을 비트라고 부르며, 8개의 비트가 모이면 바이트라는 그룹이 된다.
흔히는 32비트 컴퓨터, 64비트 컴퓨터라고 말하는 것은 CPU 버스가 동시에 처리할 수 있는 비트 수에 대한 얘기다.
예를 들어, 64비트 CPU는 64비트 블록의 데이터를 처리할 수 있어 32비트 기반의 시스템보다 더 빠른 성능을 보여줄 수 있다.

사람과 컴퓨터 사이에 어떠한 중간 역할을 하는 게 필요. -> 스위프트 같은 프로그래밍 언어.
프로그래밍 언어들은 우리가 이해하는 구조와 명령을 컴퓨터에게 전달할 수 있게 하며, CPU에 의해 실행될 수 있도록 컴파일해준다.

컴파일이란?(Complie)
사람이 이해하는 언어를 컴퓨터가 이해할 수 있는 언어로 바꾸어 주는 과정.
참조 : https://medium.com/@js230023/%EC%BB%B4%ED%8C%8C%EC%9D%BC-compile-%EC%9D%B4%EB%9E%80-2d6120f2fb39

var myNumber = 10 

앞의 예제에서 우리는 myNumber라는 이름의 변수를 생성하고 10을 할당했다. 이 코드를 CPU가 사용하는 기계어로 컴파일하면 컴퓨터는 10이라는 숫자를 다음과 같은 2진수로 이해하게 된다.

1010 

정수형 데이터 타입

스위프트는 8비트, 16비트, 32비트, 64비트 정수를 지원하며, 각각의 데이터 타입은 Int8, Int16, Int32, Int64다.

또한, 각각에 대한 부호 없는 정수도 지원하며, 데이터 타입은 UInt8, UInt16, UInt32, UInt64이다.

일반적으로 특정 크기의 데이터 타입을 사용하기보다 Int 데이터 타입 사용 권장.
Int 데이터 타입은 코드가 실행되는 플랫몸에 맞는 정수 크기를 사용하게 될 것.

32비트 정수형 데이터 타입의 최댓값과 최솟값을 출력

print("Int32 Min = \(Int32.min)  Int32 Max =  \(Int32.max)")

실행하면

Int32 Min = -2147~~ Int Max = 2147~~

부동소수점 데이터 타입

스위프트의 부동소수점 데이터 타입은 소수점이 있는 값을 저장할 수 있는 데이터 타입.

1) Float
2) Double

불리언 데이터 타입

다른 언어들과 마찬가지로 스위프트도 참/거짓을 처리하는 목적의데이터 타입을 가지고 있다.
스위프트는 데이터 타입을 가지고 작업하기 위해 두 개의 불리언 상숫값인 true와 false를 제공한다.

문자 데이터 타입

스위프트의 문자 데이터 타입은 문자, 숫자, 문장 부호, 기호와 같은 하나의 문자를 저장하는데 사용된다.
스위프트에서 문자는 내부적으로 그래핌 클러스터(grapheme cluster)의 형태로 저장된다. 그래핌 클러스터는 눈에 보이는 하나의 문자를 표현하기 위해 결합된 둘 이상의 유니코드 스칼라로 구성된다.

문자열 데이터 타입

문자열 데이터 타입은 일발적으로 단어나 문장을 구성하는 일련의 문자들이다. 문자열 데이터 타입은 저장 메커니즘을 제공할 뿐만 아니라, 문자열 검색, 매칭, 연결, 그리고 수정 등의 다양한 문자열 편집 기능을 가지고 있다.

스위프트에서 문자열은 내부적으로 문자들의 집합으로 표시되며, 여기서 문자는 앞에서 설명한 것 처럼 하나 이상의 유니코드 스칼라 값이다.

또한, 문자열은 문자열 보간이라는 개념을 사용하여 변수, 상수, 표현식, 함수 호출을 조합하여 구성할 수도 있따.
예를 들어, 다음은 콘솔에 결과를 출력하기 전에 문자열 보간을 이용하여 다양한 문자열을 새롭게 생성하는 코드이다.

var userName = "John"
var inboxCount = 25 
let maxCount = 100 
var message = "\(userName) has \(inboxCount) messages. Message capacity remaining is \(maxCount - inboxCount) messages."
print(message)

이 코드를 실행하면 다음과 같은 메시지가 출력될 것 이다.

John has 25 messages. Message capacity remaining is 75 messages.

특수문자 / 이스케이프 시퀀스

1) \n - 개행
2) \r - 캐리지 리턴
3) \t - 탭
4) \ - 역슬래시
5) \" - 쌍따옴표 (문자열 선언부 내에서 쌍따옴표를 쓸 때 사용됨)
6) \' - 홑따옴표 (문자열 선언부 내에서 홑따옴표를 쓸 때 사용됨)
7) \u{nn} - 한 바이트 유니코드 스칼라. nn은 유니코드 문자를 표현하는 두 개의 16진수를 쓴다.
8) \u{nnnn} - 두 바이트 유니코드 스칼라. nnnn은 유니코드 문자를 표현하는 네 개의 16진수를 쓴다.
8) \u{nnnnnnnn} - 네 바이트 유니코드 스칼라. nnnnnnnn은 유니코드 문자를 표현하는 여덟 개의 16진수를 쓴다.

스위프트 변수와 상수 선언

변수 선언

var userCount = 10 

상수 선언

let userCount = 10 

애플은 코드의 효율성과 실행 성능의 향상을 위해 변수보다 상수 사용 권장.

스위프트 변수

변수는 애플리케이션이 사용하는 데이터를 저장하기 ㅜ이해 예약된 컴퓨터 메모리 내의 위치이다.
각각의 변수는 프로그래머에 의해 이름이 주어지고 값이 할당된다. 변수에 할당된 이름은 변수에 할당된 값을 스위트프 코드 내에서 접근하기 위해 사용.

스위프트 상수

상수(Constant)도 변수처럼 데이터 값을 저장하기 위해 메모리 내의 위치에 이름을 명명한다.
한 가지 큰 차이점은 한 번 할당되면 나중에 그 값을 바꾸지 못한다는 점이다.
상수는 애플리케이션 코드 내에서 반복적으로 사용되는 값이 있을 때, 특히 유용하다.

어떤 값을 상수에 처음 할당하고 그 값이 사용될 때마다 상수를 사용한다면, 코드의 가독성이 더 좋아질 것이다.

타입 선언과 타입 추론

스위프트는 데이터 타입이 안전한 프로그래밍 언어에 속함.

상수 또는 변수의 타입을 지정하는 방법

var userCount : Int = 10 

만약, 선언부에서 타입 선언이 없다면 스위프트 컴파일러는 타입 추론(type inference)이라는 기술을 사용하여 변수 또는 상수의 타입을 지정한다.
컴파일러가 타입 추론을 사용하게 되면 변수 또는 상수가 초기화되는 시점에 할당된 값의 타입이 무엇인지 판단하여 해당 타입으로 지정.
예를 들어, 다음의 변수와 상수 선언부를 보자

var signalStrength = 2.231
let companyName = "My Company"

앞의 코드를 컴파일하면 스위프트는 변수 signalStrength를 Double 타입 (모든 부동소수점 수에 대하여 스위프트는 Double이 디폴트)
상수 companyName은 String 타입으로 간주.

타입 선언 없이 상수 선언 시, 선언 시점에서 값 할당.

let bookTitle = "swiftUI Essentials"

하지만, 상수를 선언할 때에 타입 선언을 사용하면, 다음의 예제와 같이 코드 내에서 나중에 초기화에서 할당 가능

let bookTitle: String
.
.
if iosBookType {
	bookTitle = "SwiftUI Essentials"
} else {
	bookTitle = "Android Studio Development Essentials"
}

여기서, 기억해야할 점
상수에 값을 할당하는 것은 오직 한 번 뿐이라는 것이다. 값을 할당한 상수에 다시 값을 할당하려고 하면 구문 오류가 날 것이다.

튜플

튜플은 스위프트 프로그래밍에서 가장 단순하면서도 강력한 기능을 가진 것들 중 하나.
서로 다른 타입의 값들이 튜플에 저장될 수 있으며, 모두 동일한 타입의 값이어야 한다는 제약도 없다.
예를 들어, 튜플은 다음의 예제와 같이 정수, 부동소수점 수, 그리고 문자열을 가지도록 구성할 수 있다.

let myTuple = {10, 432.433, "This is a String")

튜플에 저장된 값을 얻는 방법은 다양.

let myTuple = {10, 432.433, "This is a String")
let myString = myTuple.2
print(myString)

하나의 구문으로 튜플에 있는 모든 값을 추출하여 변수 또는 상수에 각각 할당

let (myInt, myDouble, myString) = myTuple

위와 같은 기술 사용으로, 튜플 내의 값들을 선택적 추출 가능
원하지 않은 값의 자리에, 밑줄 문자를 쓰면 해당 값 무시.

var (myInt, _, myString) = myTuple

튜플 생성할 시점에서 각각의 값을 변수에 할당할 수도 있다.

let myTuple = (count : 10, length: 432.433, message : "This is a String")

값들이 할당된 변수를 튜플에 저장하면 코드 내에서 저장된 값을 참조할 때, 변수에 사용할 수 있다.
예를 들어, myTuple 인스턴스에 있는 message 변수의 문자열 값을 출력하고자 한다면 다음과 같이 할 수 있다.

print (myTuple.message)

튜플이 가진 강력한 기능은 하나의 함수에서 여러 값을 반환할 수 있다는 점

스위프트 옵셔널 타입

대부분 다른 프로그래밍 언어에 없는 새로운 개념
옵셔널 타입의 목적은 변수 또는 상수에 값이 할당되지 않은 상황을 처리하기 위해 안전하고 일관된 접근 방식을 제공.

변수 선언할 때, 데이터 타입 다음에 '?' 문자를 두어 옵셔널이 되게 한다.
다음은 index 라는 이름의 Int 타입의 변수를 옵셔널로 선언하는 코드이다.

var index : Int?

이제 index 변수는 정수값이 할당되거나 아무런 값이 할당되지 않을 수 있다.
내부적으로 컴파일러와 런타임의 관점에서 볼 때, 어떤 값도 할당되지 않은 옵셔널은 실제로 nil의 값을 갖는다.

여기서, nil과 null의 차이는 무엇일까?
단순하게 보자면 다른 언어에서의 null이 Swift에서의 nill이라고 생각하면 된다.
자세히 보면, Objective-C의 잔재일 수도 있다.
아래, 링크를 확인해 알아보면 될듯! :)
참조 : https://skytitan.tistory.com/628

옵셔널은 할당된 값이 있는지 식별 테스트

var index : Int ?

if index != nil{ 
	// index 변수는 값이 할당되어있다. 
}else {
	//index 변수는 값이 할당되어 있지 않다.
}

만약, 옵셔널에 값이 할당되어있다면 해당 값이 옵셔널 내에서 '래핑되었다(wrapped)'고 말한다.
옵셔널 안에 래핑된 값을 사용할 때는 강제 언래핑(forced unwrapping)이라는 개념을 이용하게 된다. 간략하게 말해, 래핑된 값은 옵셔널 데이터 타입에서 옵셔널 이름 위에 느낌표(!)를 두어 추출하게 된다.

언래핑의 개념을 좀 더 자세히 살펴보기 위해 다음의 코드를 살펴보자

var index : Int?

index = 3

var treeArray = ["Oak", "Pine", "Yew", "Birch"]

if index != nil {
	print(treeArray[index!])
}else {
	print("index does not contain a value")
}

index 변수가 옵셔널 타입이기 때문에 변수명 뒤에 느낌표를 두어 값이 언래핑된다.

print(treeArray[index!])

반대로, 앞의 코드에서 느낌표를 빼서 index 변수가 언래핑되지 않는다면 컴파일러는 다음과 같은 에러를 낼 것이다.

Value of optional type 'Int?' must be unwrapped to a value a type 'Int'

강제 언래핑 대신, 옵셔널로 할당된 값은 옵셔널 바인딩을 이용하여 임시 변수나 상수에 할당할 수 있으며, 구문은 다음과 같다.

아래 코드가 옵셔널 바인딩.

if let constantname = optionalName {
}

if var variablename = optionalName {
}

앞의 코드는 두 가지 작업 수행.
1. 지정된 옵셔널이 값을 가지고 있는지 확인하는 작업
2. 옵셔널 변수가 값을 가지고 있는 경우에 선언된 상수 또는 변수에 그 값을 할당하고 코드 실행

var index: Int?

index = 3
 
var treeArray = ["Oak", "Pine", "Yew", "Birch"]

if let myValue = index {
	print(treeArray[myValue])
} else {
	print("index does not contain a value")
}

여기서 중요한 점.
myValue라는 임시 상수에 할당되어 배열에 대한 인덱스로 사용.
왜냐하면 if 구문 안에서만 유효한 상수.
if 구문 실행이 끝나면 이 상수는 더이상 존재하지 않게 된다.
이러한 이유로 옵셔널 할당된 동일한 이름을 사용해도 충돌 발생 X

아래 예제도 유효한 코드

if let index = index {
	print(treeArray[index])
}else{

}

여러 개의 옵셔널 언래핑, 조건문 포함 가능

if let 상수명1 = 옵셔널 이름1, let 상수명2 = 옵셔널 이름2, let 상수명3 = ..., <조건식> {
}

예를 들어, 다음은 한 줄의 코드 내에서 두 개의 옵셔널은 언래핑하기 위해 옵셔널 바인딩 사용하는 코드

var pet1 : String?
var pet2 : String?

pet1 = "cat"
pet2 = "dog"

if let firstPet = pet1, let secondPet = pet2 {
	print(firstPet)
    print(secondPet)
} else {
 	print ("insufficient pets")
}

또한, 강제적으로 언래핑되도록 옵셔널 선언 가능.
강제적으로 옵셔널 선언하면, 강제 언래핑이나 옵셔널 바인딩을 하지 않아도 값에 접근 가능.

옵셔널을 선언할 때, ? 아닌 !를 사용하여 강제적 언래핑되도록 하는 것이다.

var index : Int! //이제 옵셔널은 강제적으로 언래핑된다. 

index = 3

var treeArray = ["Oak", "Pine", "Yew", "Birch"]

if index != nil {
	print (treeArray[index])
} else {
	print ("index does not contain a value")
}

좀 더 위 예제와는 달리 강제적으로 언래핑되도록 선언되어, 앞의 코드에서 배열의 인덱스로 사용될 때, 값을 언래핑할 필요가 없게 된다.

타입 캐스팅과 타입 검사

스위프트 코드를 작성할 때, 컴파일러가 어떤 값의 특정 타입을 식별하지 못하는 경우 발행.
이런 경우는 메서드나 함수가 반환하는 값이 불명확하거나 예상되지 않은 타입의 값일 경우 발생
이럴 때에는 as 키워드를 사용하여 여러분의 모드가 의도하는 값의 타입을 컴파일러가 알 수 있게 해야하낟.
이것을 타입 캐스팅(type casting, 형 변환)이라고 한다.

예를 들어, 다음은 Object(forKey:) 메서드가 반환하는 값을 String 타입으로 처리해야한다고 컴파일러에게 알려주는 코드다.

let myValue = record.object(forKey: "comment") as! String

실제로 타입 캐스팅에는 업캐스팅(upcasting)과 다운캐스팅(downcasting)이라는 두 가지 형태가 있다.

업캐스팅은 특정 클래스의 객체가 상위 클래스들 중의 하나로 변형되는 것
업캐스팅은 as 키워드를 사용하여 수행.
이러한 변환은 성공할 것이라고 컴파일러가 알려줄 수 있기 때문에 보장된 변환이라고도 한다.

let myButton: UIButton = UIButton()
let myControl : myButton as UIControl

반면, 다운캐스팅은 어떤 클래스에서 다른 클래스로 만드는 변환이 일어날 때 발생.
이런 변환이 안전하게 수행된다거나 유효하지 않은 변환 시도를 컴파일러가 잡아낼 것이라는 보장을 할 수 없다.

다운캐스팅으로 유효하지 않은 변환을 했는 때, 컴파일러가 발견하지 못한다면, 대부분의 경우, 런타임에서 에러 발생.

다운캐스팅은 보통 어떤 클래스에서 그 클래스의 하위 클래스로 변환.
다운캐스팅은 as! 키워드로 수행, 이를 강제 변환 (forced conversion)이라고 한다.

UIScrollView 객체를 UITextView 클래스로 변환하기 위해, 다운캐스팅 필요.
다음의 코드는 보장된 변환 또는 업캐스팅 방법을 사용하여 UIScrollView 객체를 UITextView로 다운캐스팅하려는 코드다.

let myScrollView : UIScrollView = UIScrollView()
let myTextView = myScrollView as UITextVIew 

앞의 코드는 다음과 같은 에러가 날 것이다.

'UIScrollView' is not convertible to 'UITextVIew'

컴파일러는 UIScrollView 인스턴스를 UITextView 클래스 인스턴스로 안전하게 변환할 수 없음을 알려준다. 이것은 이렇게 하는 것이 틀렸다는 것을 의미하는 게 아니라 컴파일러가 이 변환의 안정성을 보장할 수 없다고 말하는 것이다.
as! 키워드를 통해 강제적으로 다운캐스팅될 수도 있다.

let myTextView = myScrollView as! UITextVIew 

위 코드는 에러 없이 컴파일 O

다운캐스팅을 하는 더 안전한 방법은 as?를 사용한 옵셔널 바인딩 사용.
만약 변환이 성공적으로 수행된다면, 지정한 타입의 옵셔널 값이 반환될 것이며, 변환에 오류가 발생한다면 옵셔널 값은 nil 될 것이다.

if let myTextView = myScrollView as? UITextView {
	print ("Type cast to UITextView succeeded")
} else {
	print("Type cast to UITextVIew failed")
}

is 키워드를 사용하여 타입 검사 할 수도 있다. 예를 들어, 다음은 해당 객체가 MyClass라는 이름의 클래스의 인스턴스인지 검사하는 코드

if myobject is MYClass {
	// myobejct는 MyClass의 인스턴스다.
}

스위프트 표현식 구문

하나의 연산자, 두 개의 피연산자, 그리고 할당자로 구성된다. 다음은 가장 기본적인 표현식의 예

var myResult = 1 + 2

범위 연산자

스위프트는 값의 범위를 선언할 수 있도록 하는 몇 가지 연산자가 포함되어 있다. 이후에 보게 되겠지만, 이들 연산자는 프로그램에서 반복 작업을 할 때, 매우 중요하다.

다음은 닫힌 범위 연산자에 대한 구문이다.

x ... y

이 연산자는 x부터 시작하여 y로 끝나는 숫자의 범위를 나타내며 x와 y 모두는 이 범위에 포함된다. 예를 들어, 5...8은 5,6,7,8을 지칭

단방향 범위 연산자
범위 앞의 시작 또는 끝에 도달할 때까지, 지정된 범위 방향으로 최대한 확장할 수 있는 범위 지정

x ...
... y
예를 들어, 이전 장에서 스위프트의 문자열은 사실 각각의 문자 집합이라는 설명을 했다.
문자열의 길이와는 상관없이 문자열의 세 번째 문자부터 시작해어 마지막 문자까지의 문자들을 지정하는 범위는 다음과 같이 선언될 수 있다.

2 ...

삼항 연산자

스위프트는 코드 내에서의 판단을 간단히 하기 위해 삼항 연산자를 지원.

조건부 ? 참 (true)인 경우의 표현식 : 거짓(false)인 경우의 표현식

삼항 연산자의 동작 방식은 true 또는 false를 반환하는 표현식을 '조건문'위치에 두는 것이다. 만약, 그 결과가 true이면 '참인 경우의 표현식'이 수행되며, 반대로 결과가 false이면 '거짓인 경우의 표현식'이 수행된다. 자, 실제로 동작하는 것을 살펴보자

let x = 10 
let y = 20

print("Largest number is \(x>y ? x : y)")

앞의 예제 코드가 x가 y보다 더 큰지 평가하게 될 것이다.
이것은 명백하게 false의 결과가 되기 때문에 사용자에게 y를 출력하게 될 것 이다.

Largest number is 20 

NOT 비트 연산

00000011 NOT

11111100

따라서 다음의 스위프트 코드는 -4의 값이 된다.

let y = 3
let z = ~y
print("Result is (z)")

AND 비트 연산

엠퍼샌드 문자로 표현되며, 두 개의 숫자를 비트 단위로 비교된다.

10101011 AND

00000011

00000011

let x = 171
let y = 3
let z = x & y

print ("Result is (z)")
=> 3

OR, XOR ..

복합 비트 연산자

x &= y -> x와 y의 AND 비트 연산을 하고 그 결과를 X에 할당
x |=y -> x와 y의 OR 비트 연산을 하고 그 결과를 X에 할당
x ^=y -> x와 y의 XOR 비트 연산
x <<= n -> x를 n번 왼쪽 시프트 비트 연산을 하고, 그 결과를 x에 할당
x >>= n -> x를 n번 오른쪽 시프트 비트 연산을 하고, 그 결과를 x에 할당

where 구문 사용

where 구문은 case 구문에 부가적인 조건을 추가하기 위해 사용된다.
예를 들어

let temperature = 54

switch (temperature) {
	case 0 ... 49 where temperature %2 == 0 :
    	print("Cold and even")
    

위를 보면 where 로 온도 뿐만 아니라 짝수/홀수인지도 검사하고 있다.

0개의 댓글