객체와 클래스

냐옹·2024년 7월 11일
0

IOS

목록 보기
8/32

본 작성글은 swift 공식 문서를 참고하였습니다.

객체와 클래스

  • class뒤에 클래스의 이름을 사용하여 클래스를 생성한다.
  • 클래스에서 프로퍼티 선언은 클래스의 컨텍스트 context 안에 있다는 점을 제외하고는 상수 또는 변수를 선언하는 방법과 동일하다.
  • 마찬가지로 메서드함수선언도 동일한 방법으로 작성된다.
class Shape{
	var numberOfSides = 0

	func simpleDescription() -> String{
    	return "A shape with \(numberOfSides) sides"
    }
}
  • 클래스 이름 뒤에 소괄호를 넣어 클래스의 인스턴스를 생성한다.
    new 키워드 들어가지 않음
  • 인스턴스의 프로퍼티메서드에 접근하기 위해서 점구문을 사용한다.
var shape = Shape()
shape.numberOfSides = 7
var shapeDescription = shape.simpleDescription()

클래스 초기화자

  • 인스턴스가 생성될 때 클래스를 설정하기 위해서 초기화 문을 사용할 수 있다.

  • init을 사용하여 생성한다.

  • 모든 프로퍼티numberOfSides와 같이 선언 시나 name과 같이 초기화 구문에서 값을 할당해야 한다.

  • 객체가 할당해제되기 전에 어떠한 정리작업이 필요하여 초기화 해제 구문 deinitializer을 생성하려면 deinit을 사용한다.

  • 하위클래스 subclass는 클래스 이름 뒤에 콜론으로 구문하여 상위 클래스 superclass를 포함한다. 클래스가 모든 표준 루트 클래스를 하위클래스화 할 필요가 없으므로 필요에 따라서 상위클래스를 포함하거나 생략할 수 있다.

  • 상위 클래스의 구현을 재정의하는 하위클래스의 메서드는 override로 표시된다.

  • 실수로 override없이 메서드를 재정의하면 에러로 컴파일러에 의해서 감지된다.

  • 컴파일러는 실제로 상위 클래스의 어떤 메서드도 실제로 재정의 하지 않는 override가 있는 메서드를 감지한다.

class NamedShape{
	var numberOfSides : Int = 0
	var name : String
    
    init(name : String){
    	self.name = name
    }

	func simpleDescription() -> String{
    	return "A shape with \(numberOfSides) sides."
    }
}


class Square : NamedShape{
	var sideLength : Double
    
    init(sideLength : Double, name : String){
    	self.sideLength = sideLength
      	super.init(name : name)
      	numberOfSides = 4
    }
  
  	func area() -> Double{
    	return sideLength * sideLength
    }
  
  	override func simpleDescription() -> String{
    	return "A square with sides of length \(sideLength)."
    }
}

let test = Square(sideLength : 5.2, name : "my test square")
test.area()
test.simpleDescription()
  • 저장된 단순 프로퍼티외에도 프로퍼티는 gettersetter를 가질 수 있다.
class EquilateralTriangle : NamedShape{
	var sideLength : Double = 0.0
  
  	init(sideLength : Double, name : String){
    	self.sideLength = sideLength
      	super.init(name : name)
      	numberOfSides = 3
    }
  
  	var perimeter : Double {
    	get{
        	return 3.0 * sideLength
        }
      	set{
        	return sideLength = newValue / 3.0
        }
    }
  
  	override func simpleDescription() -> String {
    	return "An equilateral triangle with sides of length \(sideLength)."
    }
}


var triangle = EquilateralTriangle(sideLength : 3.1, name : "a triangle");

print(triangle.perimeter)
// 9.3

triangle.perimeter = 9.9
print(triangle.sideLength)
// 3.30000000000000003
  • perimeter의 setter에서 새로운 값은 암시적으로 newValue라는 이름을 가진다. set이후에 소괄호에서 명시적으로 이름을 제공해줄 수 있다.

  • EquilateralTriangle 클래스의 초기화 구문은 세가지 단계가 있다.

  1. 하위 클래스가 선언한 프로퍼티의 값을 설정한다.
  2. 상위 클래스의 초기화 구문을 호출한다.
  3. 상위 클래스에 의해서 정의된 프로퍼티의 값을 변경한다. 메서드, getter 또는 setter를 사용하는 추가 설정 작업도 이 시점에서 수행할 수 있다.
  • 프로퍼티를 계산할 필요는 없지만, 새로운 값을 설정하기 전과 후에 실행되는 코드를 제공하려면, willSetdidSet을 사용하면 된다.
  • 이렇게 제공한 코드는 초기화 구문의 외부에서 값이 변경될 때마다 실행된다.
  • 예를 들어서 아래의 클래스는 삼각형의 측면의 길이가 항상 사각형의 측면의 길이와 같다는 것을 확인합니다.
class TriangleAndSquare{
	var triangle : EquilateralTriangle{
    	willSet {
        	square.sideLength = newValue.sideLength
        }
    }

	var square : Square {
    	willSet {
        	triangle.sideLength = newValue.sideLength
        }
    }

	init(size : Double, name : String){
    	square = Square(SideLength : size, name : name)
      	triangle = EquilateralTriangle(sideLength : size, name : name )
    }
}

var triangleAndSquare = TriangleAndSquare(size : 10, name : "another test shape")

print(triangleAndSquare.square.sideLength)
// 10.0
print(triangleAndSquare.triangle.sideLength)
// 10.0

triangleAndSquare.square = Square(sideLength : 50, name : "larger square")

print(triangleAndSquare.triangle.sideLength)
// 50.0
  • 옵셔널 값으로 동작할 때 메서드, 프로퍼티, 그리고 서브스크립트와 같은 동작 전에 ?를 작성할 수 있다. ? 전의 값이 nil이면 ?이후의 모든 것은 무시되고 전체 표현의 값은 nil이다.
  • 그렇지 않으면 옵셔널 값언래핑되고, ?후에 모든 동작은 언래핑된 값으로 동작한다.
  • 이 경우에 모드 전체 표현식의 값은 옵셔널 값이다.
let optionalSquare : Square? = Square(sideLength : 2.5, name : "optional Square");

let sideLength = optionalSquare?.sideLength

열거형과 구조체

  • 열거형을 생성하기 위해서 enum을 사용한다.
  • 클래스와 다른 명명된 타입과 같이 열거형은 메서드를 가질 수 있다.
  • 다른 언어의 열거형과 좀 다르다. 유용하게 사용할 수 있을 것 같은
enum Rank : Int{
	case ace = 1
  	case two, three, four, five, six, seven, eight, nine, ten
  	case jack, queen, king
  
  	func simpleDescription() -> String {
    	switch self{
          case .ace :
            return "ace"
          case .jack : 
            return "jack"
          case .queen :
            return "queen"
          case .king : 
            return "king"
          default : 
            return String(self.rawValue)
        }
    }
}

let ace = Rank.ace
let aceRawValue = ace.rawValue
enum Month {
	case mar, apr, may
	case jun, jul, aug
	case sep, oct, nov
	case dec, jan, feb

	func printMessage() {
		switch self {
			case .mar, .apr, .may:
				print("봄!")
			case .jun, .jul, .aug:
				print("여름!")
			...
		}
	}
}
  • 기본적으로 Swift는 0을 시작으로 매번 증가하는 원시값 raw Value을 할당하지만 명시적으로 특정 값으로 이 동작을 변경할 수 있다.

  • 위의 코드를 보면 Ace는 명시적으로 1의 값이 주어지고 나머지 원시 값은 순서대로 할당된다.

  • 열거형의 원시타입으로 문자열 또는 부동소수점도 사용할 수 있다.

  • 열거형 케이스원시값에 접근하기 위해서 rawValue 프로퍼티를 사용한다.

  • 원시값으로부터 열거형의 인스턴스를 생성하기 위해 init?(rawValue:)초기화 구문을 사용한다.

  • 원시값이 일치하는 열거형 케이스나 Rank에 일치하는 항목이 없으면 nil을 반환한다.

if let convertedRank = Rank(rawValue : 3){
	let threeDescription = convertedRank.simpleDescription()
}
  • 열거형의 케이스 값은 원시값을 작성하는 다른 방법이 아니라 실제 값이다. 실제로 의미없는 원시값의 케이스의 경우에는 제공할 필요가 없다.
enum Suit{
	case spaces, hearts, diamonds, clubs
  
  	func simpleDescription() -> String{
    	switch self{
          case .spades:
            return "spades"
          case .hearts:
            return "hearts"
          case .diamonds:
            return "diamonds"
          case .clubs:
            	return "clubs"
        }
    }
}

let hearts = Suit.hearts
let heartsDescription = hearts.simpleDescription()
  • 위에서 열거형의 hearts케이스를 참조하는 2가지 방법을 살펴보자.
    1. hearts상수에 값을 할당할 때 명시적으로 타입을 지정하지 않았으므로, 열거형 케이스 Suit.hearts 전체이름으로 참조된다.
    1. 스위치에서 self의 값은 이미 카드(suit)로 알고 있기 때문에 열거형 케이스는 짧은 형식인 .hearts로 참조된다. 값의 타입을 이미 알고 있다면 언제나 짧은 형식을 사용할 수 있다.
  • 값의 타입을 알고 있다면 언제나 짧은 형식을 사용가능하다.
  • 열거형이 원시값을 갖는 경우 선언의 부분으로 결정된다. 이것은 특정 열거형 케이스의 모든 인스턴스는 항상 같은 원시값을 갖는다는 의미이다.
  • 열거형케이스의 또다른 선택은 케이스와 연관된 값 associated value을 가지는 것이다. 이러한 값은 인스턴스를 생성할 때 결정되고, 열거형 케이스의 각 인스턴스에 대해 다를 수 있다.
  • 연관된 값 associated value은 열거형 케이스 인스턴스의 저장된 프로퍼티처럼 동작한다고 생각할 수 있다. 예를 들어 서버에서 일출과 일몰 시간에 대해 요청한다고 가정해보자.
  • 서버는 요청된 정보에 대한 응답을 하거나 무엇이 잘못되었는지에 대한 설명을 응답한다.
enum ServerResponse{
	case result(String, String)
  	case failure(String)
    
    let success = ServerResponse.result("6:00 am", "8.09 pm")
    let failure = ServerResponse.failure("Out of cheese")
    
    switch success{
      case let .result(sunrise, sunset):
      	print("Sunrise is at \(sunrise) and sunset is at \(sunsat).")
      case let .failure(message):
      	print("Failure ... \(message)")
    }
}

// "Sunrise is at 6:00 am and sunset is at 8:09 pm."
  • 스위치 케이스에 대한 값이 일치하는 부분으로 ServerResponse 값에서 일출과 일몰 시간이 어떻게 추출되는지 확인
  • 구조체를 생성하기 위해 struct를 사용한다. 구조체는 메서드와 초기화 구문을 포함하여 클래스와 동일한 동작을 많이 지원한다.
  • 구조체클래스의 가장 큰 차이점은 구조체는 코드에서 전달될 때 항상 복사되지만 클래스는 참조로 전달된다.
struct Card{
	var rank : Rank
    var suit : Suit
    
    func simpleDescription() -> {
    	return "The \(rank.simpleDescription()) of \(suit.simpleDescription())
    }
  
  	let threeOfSpades = Card(rank : .three, suit : .spades)
    let threeOfSpadesDesctiption = threeOfSpades.simpleDescription()
}

0개의 댓글