구조체와 클래스🧑🏻‍💻

DEVJUN·2022년 5월 27일
0

Swift 문법

목록 보기
8/14

구조체(Struct) 🆚 클래스(Class)

  구조체와 클래스는 여러 가지 면에서 매우 유사하다.

  프로퍼티(멤버 변수), 메소드(클래스, 구조체 내부에 있는 함수), 서브 스크립트, 초기화 블록, 확장(extends 구문), 프로토콜은 공통적으로 존재한다.

  그러나 클래스의 기능범위가 구조체보다 더 크다. 구조체는 할 수 없지만 클래스는 할 수 있는 다음과 같은 기능들이 있다.

상속:   클래스의 특성을 다른 클래스에게 물려줄 수 있다.

타입 캐스팅:   실행 시 컴파일러가 클래스 인스턴스 타입을 미리 파악하고 검사할 수 있다.

소멸화 구문:   인스턴스가 소멸되기 직전에 처리해야 할 구문을 미리 등록해 놓을 수 있다.

참조에 의한 전달:  클래스 인스턴스가 전달될 때에는 참조 형식으로 제공되며, 이 때 참조가 가능한 개수는 제약이 없다.


1. 구조체와 클래스의 기본 개념

  1 - 1 정의 구문

	// 구조체
	struct Resolution {
    	var width = 0 // 구조체나 클래스 안에 있는 변수나 상수는 
        var height = 0 // 프로퍼티 혹은 속성이라고 함!  
        
        func desc() -> String { //구조체나 클래스 안에 있는 함수는 메소드라고 함!!
        	return "Resolution 구조체"
        }
    }
	
    // 클래스 
    class VideoMode {
    	var interlaced = fasle
        var frameRate = 0.0
        var name = String?
        
        func desc() -> String {
        	return "VideoMode 클래스"
        }
    }

  1 - 2 인스턴스

  위에서 정의한 구조체나 클래스를 그대로 사용해서 값을 저장하거나 메소드를 실행할 수는 없다. 단순히 객체의 정의일 뿐, 실제로 값을 저장하고 메소드를 호출하는 데에 필요한 메모리 공간을 할당받지 못했기 때문이다. 쉽게 말하면 붕어빵을 만듬에 있어 붕어빵의 틀일 뿐 실제 붕어빵이 아닌 것이다. 이 틀을 이용하여 찍어낸 붕어빵을 인스턴스(Instance)라고 한다. 인스턴스를 만들어 내는 방법은 다음과 같다.

	// Resolution 구조체에 대한 인스턴스를 생성하고 상수 insRes에 할당
	let insRes = Resolution()
    
    // VideoMode 클래스에 대한 인스턴스를 생성하고 상수 insVMode에 할당
    let insVMode = VideoMode()

  대표적인 객체지향 프로그래밍 언어인 자바와 비교했을 때 new 키워드만 제거한 형태이다.

  구조체나 클래스의 프로퍼티에 접근하기 위해서는 오직 인스턴스를 통해서만 접근할 수 있다. 프로퍼티에 접근할 때는 <인스턴스 이름>.<프로퍼티 이름> 점(.)을 이용하여 프로퍼티에 접근할 수 있다.

	let width =inRes.width
    
    //insRes 인스턴스의 width 값은 0
    print("insRes 인스턴스의 width 값은 \(width)")

  만약 Resolution.width와 같이 구조체 혹은 클래스 명으로 접근하는 경우 오류가 난다.

  만약 객체에 정의된 프로퍼티가 서브 프로퍼티를 가지고 있는 객체라면 다음과 같이 계속 점(.)을 통해 단계적으로 접근할 수 있다.

	class VideoMode {
    	var interlaced = fasle
        var frameRate = 0.0
        var name = String?
        
        var res = Resolution()
        
        (생략)
    }
    
    let vMode = VideoMode()
    
    // vMode 인스턴스의 width 값은 0
    print("vMode 인스턴스의 width 값은 \(vMode.res.width)") 
   

  추가된 프로퍼티 res는 VideoMode 클래스의 프로퍼티이면서 동시에 Resolution 구조체의 인스턴스이다. 따라서 res 프로퍼티 하위에 width 프로퍼티가 존재하여 접근할 수 있는 것이다.

  또한 점 구문을 이용하여 프로퍼티에 값을 대입할 수 도 있다.

	vMode.name = "Sample"
    vMode.res.width = 1280
    
    // Sample 인스턴스의 width 값은 1280
    print("\(vMode.name!) 인스턴스의 width 값은 \(vMode.res.width)")
    
    
    //프로퍼티의 하위 프로퍼티에 값을 할당할 때도 
    //점 구문을 연속으로 연결하여 값을 할당할 수 있다.(chain형태)
    //objective-C에서는 체인 형식을 지원하지 않는다.
    var res = vMode.res 
    res.width = 1280
    

  1 - 3 초기화

  스위프트에서 옵셔널 타입으로 선언되지 않은 모든 프로퍼티는 명시적으로 초기화해 주어야 한다. 초기화되지 않는 프로퍼티가 있을 경우 컴파일 오류가 발생한다.

  구조체는 모든 프로퍼티의 값을 인자값으로 입력받아 초기화하는 기본 초기화 구문을 자동으로 제공한다. 이 초기화 구문을 멤버와이즈 초기화 구문이라고 부르기도 한다.

	// width와 height를 매개변수로 하여 Resolution 인스턴스 생성
	let defaultRes = Resolution(width 1024, height 768)

  구조체에서는 멤버와이즈 초기화 구문을 제공하지만 클래스에서는 빈 괄호 형태의 기본 초기화 구문 밖에 없다.

  1 - 4 구조체와 클래스의 값 전달 방식

  구조체와 클래스의 결정적인 차이 중의 하나가 값을 전달하는 방식❗️이다. 구조체는 인스턴스를 생성한 후 이를 변수나 상수에 할당하거나 함수의 인자값으로 전달할 때 값을 복사하여 전달하는 방식을 사용한다.

	let hd = Resolultion(width: 1920, height: 1080)
    var cinema = hd
    
    
    cinema.width = 2048  
    // cinema 인스턴스의 width 값은 2048
    print("cinema 인스턴스의 width 값은 \(cinema.width)")
    
    // hd 인스턴스의 width 값은 1920
    print("hd 인스턴스의 width 값은\(hd.width)")

  Resolution 구조체로 초기화된 인스턴스를 만들고 이를 상수 hd에 할당하였고 cinema라는 변수를 만들어 cinema 변수에 hd 상수를 할당했다. hd를 cinema에 대입하는 시점에서 기존 인스턴스의 복사본이 하나 더 만들어진 다음 복사본이 cinema 변수에 대입된다.

  따라서 hd와 cinema는 같은 프로퍼티 값을 가지고 있지만 실제로는 별개의 인스턴스가 대입되어 있는 것이다. 따라서 위 코드와 같이 cinema.width = 2048로 프로퍼티 값을 변경해도 hd.width는 그대로 값이 1920인 것이다.


  구조체와 달리 클래스는 메모리 주소 참조에 의한 전달 방식을 사용한다. 참조는 복사가 이루어지지 않고, 인스턴스가 저장된 메모리 주소 정보가 전달된다.

  위와 유사한 개념으로 C언어의 포인터가 있다.

	let video = VideoMode()
    video.name = "Original Video Instance"
    
    // video 인스턴스의 name 값은 Original Video Instance
    print("video 인스턴스의 name 값은 \(video.name!).")
    
    
    let dvd = video
    dvd.name = "DVD Video Instance"
    
    // video 인스턴스의 name 값은 DVD Video Instance
    print("video 인스턴스의 name 값은 \(video.name!)")
    
    
    func changeName(v: VideoMode) {
    	v.name = "Function Video Instance"
    }
    
    changeName(v: video)
    
    // video 인스턴스의 name 값은 Function Video Instance
    print("video 인스턴스의 name 값은 \(video.name!)")
    

  앞에서 보았던 구조체와 달리 클래스는 메모리 주소를 사용하여 복사가 일어나지 않고 같은 메모리를 참조하기 때문에 값이 계속해서 바뀌는 것을 볼 수 있다.

  이처럼 클래스는 참조 타입이어서 한 곳에서 수정할 경우 다른 곳에서도 적용되는 특징과 함께, 하나의 클래스 인스턴스를 여러 변수나 상수, 또는 함수의 인자값에서 동시에 참조할 수 있다는 특성이 있다.

  반면 구조체는 값의 할당이 복사이므로 하나의 인스턴스는 오직 하나의 변수, 상수 만이 참조할 수 있다.

  이 때문에 클래스에서는 메모리 이슈 문제가 부각된다. 적절한 메모리 해제 시점을 계산해야 하기 때문이다. 클래스 인스턴스는 여러 곳에서 동시에 참조가 가능하므로 한 곳에서의 참조가 왼료되었다고 해도 마음대로 메모리에서 해제할 수 없다. 다른 곳에서 해당 인스턴스를 계속 참조하고 있을 가능성이 있기 때문인 것이다. 그런 경우를 주의하지 않고 인스턴스를 막 해제할 경우 오류가 발생할 수 있다.

  objective-C의 초기 버전에서는 이와 같은 문제들을 개발자가 직접 해 주어야 했지만 여러 문제가 생기다보니 스위프트에서 💥ARC(Auto Reference Counter)❗️라는 객체를 만들어 클래스 인스턴스를 참조하는 곳이 모두 몇 군데인지 자동으로 카운트해주어 메모리 관리를 할 수 있게 되었다.


  클래스 인스턴스에서 단순 값 비교는 불가능하고 메모리 공간을 참조하는 인스턴스인지 아닌지 비교할 수 있다. 이때 사용하는 연산자는 ===, !==이다.
	if(video === dvd) {
    	print("동일한 인스턴스 참조")
    } else {
    	print("다른 인스턴스")
    }
    // 동일한 인스턴스 참조
    
    let vs = VideoMode() //인스턴스 생성
    let ds = VideoMode() //새로운 인스턴스 생성
    
    if(vs === ds) {
    	print("동일한 인스턴스 참조")
    } else {
    	print("다른 인스턴스")
    }
    // 다른 인스턴스 
profile
🧑🏻‍💻iOS

0개의 댓글