1. The Architecture of Swift (1)

Seoyoung Lee·2022년 6월 23일
0
post-thumbnail
post-custom-banner

스위프트 및 iOS에 대해 더 깊이 있게 이해하고 싶어 iOS 15 Programming Fundamentals with Swift 라는 책을 읽기 시작했다. 이번 여름방학 동안 꼭 완독해야지!

Ground of Being

스위프트는 컴파일 언어이다. 즉, 실행 시간 전에 코드가 빌드되어야 한다. 스위프트 컴파일러는 매우 엄격하다. 이는 스위프트의 가장 큰 장점 중 하나로, 실행하기 전에 코드가 올바른지 확인할 수 있게 해준다.

Everything Is an Object?

스위프트에서 모든 것은 객체이다. 이는 다른 현대의 객체 지향 언어와도 유사하다.

이 말의 의미를 이해하기 위해 먼저 객체가 무엇인지 알아보자. 객체는 메시지를 보낼 수 있는 대상이다. 스위프트에서는 메시지를 보내기 위해 dot-notation 을 사용한다. (ex. fido.bark() )

모든 것이 객체라는 것은 원시적인 언어 개체들도 메시지를 보낼 수 있다는 뜻이다.

let sum = 1 + 2

놀랍게도 위에 있는 1. 을 이용해 메시지를 보낼 수 있다.

let s = 1.description

1 + 2 는 실제 동작하는 방식을 숨기고 편리하게 표시한 것이다. 1 은 객체이고, + 는 메시지이다. 다만 특별한 문법(operator syntax)을 사용한 메시지일 뿐이다. 스위프트에서 모든 명사는 객체이고, 모든 동사는 메시지이다.

어떤 것이 객체인지 판단하는 좋은 기준 중 하나는 수정 가능한지 여부이다. 스위프트에서 object 타입은 확장이 가능하다. 다시 말해 그 타입에 내가 직접 메시지를 정의할 수 있다.

extension Int {
	func sayHello() {
		print("Hello, I'm \(self)")
	}
}
1.sayHello()

스위프트에서 1 은 객체이다. 그러나 오브젝티브 C와 같은 특정 언어에서는 객체가 아닌 기본 자료형이다. 스위프트에서는 스칼라(scalar) 타입은 존재하지 않는다. 모든 타입이 객체이다. 이것이 “모든 것이 객체이다"의 진정한 의미이다.

Three Flavors of Object Type

스위프트에서 object는 세 가지로, class, struct, enum이 있다. (스위프트 5.5에서는 actor 라는 타입이 새로 추가되었다.)

Objective-C는 struct와 enum이 object가 아니다. 그러나 스위프트에서 struct은 Objective-C에서보다 훨씬 중요한 역할을 한다. 스위프트와 Objective-C가 struct, enum을 바라보는 관점 차이는 Cocoa를 얘기할 때 중요하게 다뤄진다.

Variables

변수는 오브젝트의 이름 이다. 기술적으로는 오브젝트를 지칭하는, 오브젝트 레퍼런스라고도 한다. 변수가 참조한는 오브젝트는 그 변수의 값(value)이다.

모든 변수는 선언이 되어야 한다. let 이나 var 키워드를 이용하여 선언하고, 주로 초기화와 함께 선언한다.

let 으로 선언된 변수는 상수로, 한 번 할당된 값은 바뀌지 않는다. 초기값이 절대 변하지 않을 것이라는 걸 알고 있다면 let 을 사용하는 것이 좋다.

변수는 타입을 가지고 있다. 이 타입은 변수가 선언될 때 확정되고 절대 바뀌지 않는다.

변수는 자신만의 생애를 가진다. 변수가 존재하는 한 그 값은 계속 존재한다. 따라서 변수는 어떤 것에 이름을 지어주는 방법이 될 뿐만 아니라 어떤 값을 보존하는 방법이 되기도 한다.

💡 주로 타입 이름(예: String, Int 등)은 대문자, 변수 이름은 소문자로 시작한다.

Functions

executable code는 반드시 함수의 내부에 있어야 한다. 함수 내에 작성된 명령들은 누군가가 함수를 호출해야 실행된다.

go()

위는 go 라는 함수를 호출하는 명령이다. 그러나 이 명령 자체도 executable code로, 혼자서 존재할 수 없다.

func doGo() {
	go()
}

go 함수를 위해 doGo 라는 함수를 선언했다. 하지만 doGo 함수를 호출하는 명령 역시 executable code이다.

모든 executable code가 함수 내부에 존재해야 한다면 누가 모든 함수들을 실행하라고 말해줄 수 있을까? → 맨 처음 이런 역할을 해주는 무언가가 있어야 한다.

다행히 현실에서는 이런 문제를 신경쓰지 않아도 된다. iOS 디바이스 또는 시뮬레이터에서 실행되는 앱은 특정 함수들을 호출하기 원하는(?) 런타임에 의해 실행된다. 따라서 우리는 런타임이 호출할 것임을 알고 있는 특별한 함수들을 작성해서 개발을 시작하면 된다.

💡 스위프트에는 main.swift 라는 특별한 파일이 있다. 이 파일은 예외적으로 함수 밖(상위 레벨)에 executable code가 존재해도 된다. main.swift 를 이용해 앱을 개발해도 되지만 일반적으로는 그렇게 하지 않아도 된다.

The Structure of a Swift File

스위프트 프로그램은 하나 이상의 파일로 구성될 수 있다. 스위프트에서 파일은 의미있는 단위로, 다음 네 가지만 스위프트 파일의 상위 레벨에 올 수 있다.

  • 모듈 import 구문
    • 모듈은 파일보다 높은 레벨의 단위이다. 모듈은 여러 개의 파일로 구성될 수 있고, 파일들은 서로를 볼 수 있다. 우리가 만들 앱의 파일들은 하나의 모듈에 속하고 서로를 볼 수 있다. 그러나 모듈은 import 구문 없이는 다른 모듈을 볼 수 없다.
  • 변수 선언
    • 파일의 상위 레벨에서 선언된 변수는 전역 변수이다.
  • 함수 선언
    • 파일의 상위 레벨에서 선언된 함수는 전역 함수이다.
  • Object 타입 선언
    • class, struct, enum 선언

중괄호({}) 안에도 변수, 함수, Object 선언들이 포함될 수 있다.

executable code는 파일의 상위 레벨에 올 수 없고, 함수 내부에서만 존재할 수 있다. 또한 클래스 선언문의 중괄호 안에도 포함될 수 없다. 대신 클래스 선언문 안에 포함된 함수 선언문 안에는 들어갈 수 있다.

class Manny {
	let name = "manny"
	// executable code는 여기 올 수 없다.
	func sayName() {
		print(name) // executable code
	}
}

Scope and Lifetime

스위프트 프로그램에서 모든 것들은 scope 를 갖는다. 이는 다른 것들에게 인식될 수 있는 능력을 의미한다. 각 코드들이 중첩되고 중첩되면서 계층 구조를 이루게 된다. 코드들은 자기가 가진 레벨과 자신을 포함하는 상위 레벨의 것들을 볼 수 있다.

레벨들은 다음과 같다.

  • 모듈은 스코프다.
  • 파일은 스코프다.
  • 중괄호는 스코프다.

스코프는 정보를 공유하는 아주 중요한 방법이다. 코드들은 또한 각자의 수명을 가지는데, 이는 그들이 갖는 스코프와 동일하다. 코드들은 자신을 둘러싼 스코프가 살아 있는 동안 존재한다. 예를 들어, 전역 변수는 파일이 존재하는 동안, 즉 프로그램이 실행되는 동안 살아 있게 된다. 그러나 클래스 안에서 선언된 변수는 클래스 인스턴스가 존재하는 동안만 살아 있을 수 있다.

깊은 레벨에서 선언될수록 더 짧은 수명을 가진다.

Object Members

오브젝트 타입(class, struct, enum)의 최상위 레벨에서 선언된 것들은 특별한 이름을 가진다.

  • 프로퍼티 : 오브젝트의 상위 레벨에서 선언된 변수
  • 메소드 : 오브젝트의 상위 레벨에서 선언된 함수

프로퍼티, 메소드뿐만 아니라 오브젝트의 상위 레벨에서 선언된 모든 오브젝트들은 오브젝트의 멤버 라고 부른다. 멤버들은 오브젝트에 전송하려는 메시지 를 정의하기 때문에 중요한 의미를 갖는다.

Namespaces

네임스페이스 외부에서는 region의 이름을 먼저 불러야 그 안에 있는 것들의 이름에 접근할 수 있다. 이는 다른 곳에서 같은 이름이 문제없이 사용될 수 있게 해준다. 네임스페이스와 스코프는 서로 관련이 있다.

Modules

최상위 네임스페이스는 모듈 이다. 우리가 만드는 앱은 모듈이고, 따라서 네임스페이스다. 이 네임스페이스의 이름은 앱의 이름과 같다.

내 앱의 이름이 MyApp 이라면, 파일의 최상위에 선언한 Manny 라는 클래스의 진짜 이름은 MyApp.Manny 이다. 하지만 우리가 작성한 코드들은 이미 같은 네임스페이스 안에 존재하고 Manny 라는 이름을 직접 볼 수 있기 때문에 MyApp. 을 포함하지 않는다.

모듈을 임포트하면 그 모듈의 최상위에서 선언된 것들도 내 코드 안에서 볼 수 있다. 따라서 임포트한 모듈의 네임스페이스를 명시적으로 사용하지 않아도 된다.

스위프트도 Swift 라는 모듈 내에 정의되어 있다. 그러나 Swift 모듈은 항상 암시적으로 임포트되기 때문에 import Swift 문을 작성하지 않아도 된다. (물론 포함해도 되긴 함!)

print 와 같은 함수는 Swift 모듈의 최상위에 선언되어 있다. 따라서 네임스페이스를 명시하지 않고 사용할 수 있다.

그러나 임포트한 모듈에 포함된 이름과 같은 이름의 변수나 함수를 선언하면 임포트된 변수나 함수의 네임스페이스 명시를 생략할 수 없다.

예를 들어, 내 코드 안에서 print 라는 함수를 선언했다면 기존 print문은 반드시 Swift.print 라고 네임스페이스를 명시해주어야 한다. 그래서 임포트된 이름과 같은 이름을 사용하는 것은 피하는 것이 좋다!

profile
나의 내일은 파래 🐳
post-custom-banner

0개의 댓글