구조체 내 메소드가 프로퍼티를 변경하는 경우, 메소드 앞에 반드시 mutating 키워드를 붙여 이 메소드가 프로퍼티 값을 수정하는 메소드임 을 표시하도록 강제한다. 메소드가 만약 프로토콜에서 선언된 메소드라면 반드시 프로토콜에 mutating 키워드가 추가되어 있어야 한다.
클래스와 같은 참조 타입은 mutating 키워드 없이 자유롭게 메소드 내의 프로퍼티를 수정할 수 있지만, 구조체나 열거형은 프로토콜의 메소드에 mutating 키워드가 추가되어 있지 않을 경우 프로퍼티 값을 변경할 수 없다. 억지로 키워드를 붙인다면 오류가 날 것이다.
보통 mutating 키워드가 붙지 않는 경우는 다음과 같은 경위다.
구조체나 열거형 등 값 타입의 객체에서 내부 프로퍼티의 값을 변경하기 원치 않을 때
주로 클래스를 대상으로 간주하고 작성된 프로토콜일 때
프로토콜의 초기화 메소드
일반적인 메소드와 비슷하게 초기화하는 것이 가능하다. 실행블록 없이 이름과 매개변수 등을 작성한다.
var res = httpCode.OK res = .NOT_MODIFY res.value // 304 res.getDescription()
httpCode.getName()
열거형의 활용
열거형은 추후 나오는 코코아터치 프레임워크에서 자주 쓰이는 객체이다. 메소드 호출 옵션이나 스타일을 설정할 때 열거형으로 미리 정의해 둔 멤버들을 속성값으로 사용하는 경우가 많다.
1 2 3 4 5
enumUIImagePickerControllerSourceType : Int { case photoLibrary case camera case savedPhotosAlbum }
위 소스는 이미지 피커 컨트롤러에서 이미지를 어디서 가져올 것인가를 결정하는 열거형 객체이다. 만약에 이것을 열거형이 아닌 다른 것으로 정의한다면 외부에서 수정이 가능할 것이다. 가변적인 성격을 띄는 것이 아니므로 열거형으로 해야 옳다.
이처럼 지정된 값만 허용되게 할 때는 열거형 객체를 이용하는 것이다. 적절한 경우에 열거형을 적극 활용하도록 하자.
익스텐션
익스텐션은 확장 구문이라고 번역된다. 클래스나 구조체, 열거형 등의 객체에 새로운 기능을 추가하여 확장하는 구문이다. 이미 존재하는 객체에 추가적인 요소를 가미하는 것이 특징이고, 거의 제약 없이 가능하여 꽤 강력한 것이라 볼 수 있다. (오브젝티브-C의 ‘카테고리’와 비슷하다고 한다.)
익스텐션은 다음과 같은 것을 구현할 수 있다.
연산 프로퍼티를 추가할 수 있다.
새 메소드 정의가 가능하다.
새로운 초기화 구문을 추가할 수 있다.
기존 객체를 수정하지 않고 프로토콜을 구현할 수 있다.
익스텐션 extension 키워드를 사용한다.
1 2 3
extension <확장할 기존 객체명> { // 추가 기능에 대한 구현 코드 작성 }
익스텐션과 연산 프로퍼티
프로퍼티를 기존 객체에 추가 가능하지만, 연산 프로퍼티로 제한이 된다. 저장 프로퍼티는 불가능하다. 대신 인스턴스 프로퍼티든 타입 프로퍼티든 연산 프로퍼티라면 모두 추가 가능하다.
1 2 3 4 5 6 7 8 9
extensionDouble { var km : Double { returnself*1_000.0 } var m : Double { returnself } var cm : Double { returnself/100.0 } var mm : Double { returnself/1_000.0 } var description : String { return"\(self)km는 \(self.km)m, \(self)cm는 \(self.cm)m, \(self)mm는 \self.mm)mm입니다." } }
위 예시는 Double 구조체 확장 구문이다. 숫자의 1_000.0의 언더바는 자릿수를 구분해주는 구분자이다. 숫자 값에는 영향이 없다.
익스텐션과 메소드
기존 객체에 새로운 인스턴스 메소드나 타입 메소드를 정의할 수 있다. 오버로딩 특성을 이용해서 새로운 메소드를 저으이할 수 있고, 매개변수명을 바꿔서 새로운 메소드를 작성할 수 있다. 그러나 기존 객체에서 사용된 같은 메소드를 익스텐션에서 재정의 하는 것은 허용되지 않는다.
인스턴스 메소드는 익스텐션에서도 mutating 키워드를 사용하여 인스턴스 자신을 수정하도록 허용할 수 있다. 구조체나 열거형에서 정의된 메소드가 자신의 인스턴스를 수정하거나 프로퍼티를 변경해야할 때 mutating 키워드를 사용하는데, 익스텐션이 구조체, 열거형을 확장의 대상으로 삼았을 때 사용한다.
구조체와 클래스는 하나의 큰 코드 블록이다. 안에 변수 또는 상수를 넣어 값을 정의하거나, 함수를 넣어서 기능을 정의할 수도 있다. 스위프트는 객체지향 언어이기 때문에 구조체와 클래스는 문법 상 중요한 요소 중 하나이다.
프로퍼티(Properties) : 구조체와 클래스 내부의 변수와 상수
메서드(Method) : 구조체와 클래스 내부에서 정의된 함수
멤버(Member) : 프로퍼티와 메소드를 합하여 구조체나 클래스의 멤버라고 한다.
구조체 vs 클래스
공통점
프로퍼티
메소드
서브스크립트 : 속성값에 접근할 수 있는 방법을 제공
초기화 블록 : 객체를 원하는 초기 상태로 설정
확장 : 객체에 함수적 기능을 추가하는 확장(extends) 구문 사용
프로토콜 : 특정 형식의 함수적 표준을 제공
클래스만 할 수 있는 기능
상속 : 클래스의 특성을 다른 클래스에게 물려줄 수 있다.
타입 캐스팅 : 실행 시 컴파일러가 클래스 인스턴스의 타입을 미리 파악하고 검사할 수 있다.
소멸화 구문 : 인스턴스가 소멸되기 직전에 처리해야 할 구문을 미리 등록할 수 있다.
참조에 의한 전달 : 클래스 인스턴스가 전달될 때에는 참조 형식으로 제공되며, 이 때 참조가 가능한 개수는 제약이 없다.
기본 개념
정의
구조체와 클래스의 이름은 대문자로 시작한다. 명명 규칙에 따르도록 하자.
1 2 3 4 5 6 7 8 9
// 구조체 struct 구조체 이름 { // 정의 내용 }
// 클래스 class 클래스 이름 { // 정의 내용 }
메소드와 프로퍼티
선언된 값에 따라 타입 선언이 가능하다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14
structResolution { var width =0 var height =0
funcdesc() -> String { return"Resolution 구조체" } }
classVideoMode { var interlaced =false var frameRate =0.0 var name : String? }
인스턴스
클래스와 구조체를 생성한 후 여러 개를 찍어내듯이 만들고 할당할 수 있다. 이 찍어낸 복사본과 같은 개념을 인스턴스라고 한다. 같은 건물의 설계도를 가지고 여러 개의 건물을 세우는 것과 유사한 방식이라고 보면 되겠다.
1 2 3
let g = guJoChae()
let c = makeClass()
. (dot)을 이용하여클래스와 구조체 내의 프로퍼티에 접근할 수 있다. 예를 들어 g.name, c.width와 같이 말이다.
초기화
구조체나 클래스 뒤에 빈괄호를 붙이면 기본적인 인스턴스가 만들어진다. 때로는 파라미터를 괄호 속에 넣어주기도 하는데, 이를 초기화(Initialize)하기 위해 반드시 필요한 값들입니다.
구조체나 클래스를 초기화할 때는 매개변수의 값 역시 초기화가 필요하다.
1
let initTest = testMethod(a: 1, b: 2)
구조체의 값 전달 방식: 복사에 의한 전달
구조체와 클래스의 차이는 값 전달 방식이다. 구조체는 인스턴스를 생성한 후 이를 변수나 상수에 할당하거나 함수의 인자값으로 전달할 때 값을 복사하여 전달하는 방식이다. 이를 값 타입(Value Type) , 또는 복사에 의한 전달 이라고 한다. 앞서 자료형은 모두 복사를 통해 값이 전달된다고 언급한 적이 있는데, 이는 자료형 자체가 실제로는 구조체로 구현되었기 때문이다.
구조체 인스턴스는 변수에 대입 시, 복사한 값으로 대입되는 것이다. 그러므로 기존 인스턴스와 대입된 인스턴스는 독립적이다. 한 쪽에서 변경이 되어도 다른 쪽에 영향을 주지 않는다.
클래스의 값 전달 방식 : 참조에 의한 전달
메모리 주소를 사용하여 객체 자체를 전달한다. 따로 주소값을 전달할 필요 없이, 객체 주소값만 넘긴다면, 자유롭게 사용이 가능하다. 클래스 초기화와 동시에 프로퍼티를 사용하는 것이 가능하다.
인스턴스 비교 연산자
1 2
// === : 동일 인스턴스인지 비교 // !== : 다른 인스턴스인지 비교
일반적으로 다음 조건에 하나 이상 해당한다면 구조체 사용을 권장한다.
서로 연관된 몇 개의 기본 데이터 타입들을 캡슐화하여 묶는 것이 목적일 때
캡슐화된 데이터에 상속이 필요하지 않을 때
캡슐화된 데이터를 전달하거나 할당하는 과정에서 참조 방식보다는 값이 복사되는 것이 합리적일 때
캡슐화된 원본 데이터를 보존해야 할 때
프로퍼티(Property)
클래스와 구조체를 구성하는 중요 요소이다. 프로퍼티는 값을 저장하기 위한 목적으로 클래스와 구조체 내에서 정의된 변수나 상수를 의미한다. 정확히는 값을 제공 한다.
프로퍼티에는 저장 프로퍼티와 연산 프로퍼티가 있다.
저장 프로퍼티
입력된 값을 저장, 저장된 값을 제공함
상수 및 변수를 사용하여 정의가 가능함
클래스와 구조체에서 사용이 가능하지만, 열거형에서는 사용할 수 없음
연산 프로퍼티
특정 연산을 통해 값을 만들어 제공하는 역할
변수만 사용해서 정의 가능함
클래스, 구조체, 열거형 모두에서 사용 가능함
저장 프로퍼티
클래스 내에서 선언된 변수나 상수를 부르는 이름이다. 변수는 속성 변수이고, 상수는 속성 상수라고 한다. 저장 프로퍼티는 일반적인 변수, 상수를 초기화하듯이 초기화 값을 지정할 수 있다. 단, 필수는 아니다.
초기값을 할당하지 않으면 유의해야 할 점들이 있다. 초기값이 할당되지 않은 저장 프로퍼티는 반드시 옵셔널 타입으로 선언해야 한다. 클래스의 프로퍼티에 값이 비어있다면 인스턴스를 생성할 때 무조건 nil로 초기화한다. 물론 처음부터 값 할당을 하면 옵셔널로 할 필요가 없다.
초기값을 주지 않으면서 옵셔널 타입으로 선언하지 않을 수 있는 방법도 존재한다. 초기화구문에서 프로퍼티의 값을 초기화하는 것이다. 클래스 프로퍼티는 인스턴스를 생성할 때 초기화하기 때문에 프로퍼티 초기값은 인스턴스를 생성하기 전까지만 할당하면 된다. 초기화 구문에서 프로퍼티 값 할당이 가능하다면 옵셔널로 선언하지 않아도 된다.
1 2 3 4
classUser { var name : String // 클래스 프로퍼티의 초기값 설정이 안되어서 Error }
1 2 3 4 5 6 7 8
/* 올바른 예시 */ classUser { var name : String
init() { self.name ="" } }
1 2 3 4 5
classUser { // 옵셔널의 예시 var name : String? // var name : String! 도 가능. }
1 2 3 4
classUser { // 아예 초기화하는 방법 var name : String="" }
저장 프로퍼티의 분류
var 키워드로 정의되느 변수형 저장 프로퍼티(멤버 변수)
let 키워드로 정의되는 상수형 저장 프로퍼티(멤버 상수)
1 2 3 4 5 6 7 8 9 10 11
// 구조체의 저장 프로퍼티
structguJoChae1 { var intValue1 : Int let intValue2 : Int }
structguJoChae2 { let intValue3 : Int var intValue4 : Int }
지연 저장 프로퍼티
저장 프로퍼티는 클래스 인스턴스가 생성될 때 함께 초기화되지만, lazy키워드가 있다면 예외가 된다. 키워드 이름처럼 초기화를 지연시키는 역할을 한다. 프로퍼티가 호출되는 순간에 초기화된다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14
classOnCreate { init() { print("OnCreate") } }
classLazyTest { var base =0 lazyvar late =OnCreate()
init() { print("Lazy Last") } }
클로저를 이용한 저장 프로퍼티 초기화
1 2 3 4 5
// 프로퍼티 참조 시에 최초 한 번만 실행한다. let/var 프로퍼티명: 타입 = { 정의 내용 return 리턴값 }()
연산 프로퍼티
저장 프로퍼티와는 다르게 다른 프로퍼티의 값을 연산 처리하여 간접적으로 값을 제공한다. 프로퍼티의 값을 참조하기 위해 구문이 get구문이다.
set 구문을 추가할 수 있다. 값을 할당하거나 변경할 때 실행된다. set 구문은 필요에 따라 생략이 가능하다.
1 2 3 4 5 6 7 8 9 10 11 12
class/struct/enum 객체명 { var 프로퍼티명 : 타입 { get { //...... return 리턴값 }
var thisYear : Int { get { let df =DateFormatter() df.dateFormat ="yyyy" returnInt(df.string(from: Date()))! } }
var age : Int { get { return (self.thisYear -self.birth) +1 } } }
let info =UserInfo(birth: 1990) print(info.age)
// 28
프로퍼티 옵저버
특정 프로퍼티를 관찰하다가 값 등이 변경되면 이에 반응한다. 직접 변경, 자동 변경 모두 반응하여 호출된다. 동일한 값이 재할당되어도 마찬가지이다.
willSet : 프로퍼티의 값이 변경되기 직전 호출되는 옵저버
didSet : 프퍼티의 값이 변경된 직후 호출되는 옵저버
1 2 3 4 5 6 7
// willSet 옵저버
var<프로퍼티명> : <타입> [ =<초기값> ] { willSet [ (<인자명>) ] { <프로퍼티 값이 변경되기 전에 실행할 내용> } }
1 2 3 4 5 6 7
// didSet
var<프로퍼티명> : <타입> [ =<초기값> ] { didSet [ (<인자명>) ] { <프로퍼티 값이 변경된 후에 실행할 내용> } }
타입 프로퍼티
인스턴스에 관련된 값이 아니라 클래스나 구조체, 또는 열거형과 같은 객체 자체에 관련된 값을 다루는 때도 있다. 이 때는 인스턴스를 생성하지 않고 클래스나 구조체 자체에 값을 저장하게 되며 이를 타입 프로퍼티라고 한다.
저장된 값은 모든 인스턴스가 공통적으로 사용할 수 있다. 타입 프로퍼티는 인스턴스가 아무리 많아도 모든 인스턴스가 하나의 값을 공용으로 사용한다.
static키워드가 핵심이다. 다른 언어에서와 같이 어느 곳에서나 접근이 가능하다.
1
staticlet/var 프로퍼티명 = 초기값
1 2 3 4 5 6 7 8
classlet/var 프로퍼티명 : 타입 { get { return 반환값 } set { // set 내용 } }
메소드
함수의 일종이다. 구조체, 클래스 등에서 함수가 선언되면 메소드라고 지칭한다. 허나 함수와는 차이가 있다. 함수는 독립적으로 기능을 구현하지만, 메소드는 객체에서 정의된 다른 메소드와 함께 함수적인 기능을 수행한다.
메소드는 인스턴스 메소드 와 타입 메소드 로 구분된다.
인스턴스 메소드 : 객체의 인스턴스를 생성해야 사용할 수 있는 메소드.
타입 메소드 : 객체의 인스턴스를 생성하지 않아도 사용 가능한 메소드.
인자값이 있는 메소드를 호출할 때에는 함수와 동일하다. 호출 시 인자값 앞에 인자 레이블을 붙여준다.
1 2
let temp = tempMethod() temp.isParameter(a : 5)
구조체나 열거형의 인스턴스 메소드 내부에서 프로퍼티의 값을 수정할 때는 반드시 앞에 mutating 키워드를 추가해야 한다. 이 키워드를 통해 내부 프로퍼티 값을 수정하는 메소드라는 것을 컴파일러에게 지시한다. 당연한 말이지만, 인스턴스가 상수라면 mutating 키워드는 필요가 없다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14
structPoint { var x =0.0, y =0.0 mutatingfuncmoveByX(xdeltaX: Double, ydeltaY: Double) { self.x += deltaX self.y += deltaY } }
var point =Point(x: 10.5, y: 12.0) point.moveByX(x: 3.0, y: 4.5)
print("New Point (\(point.x), \(point.y))) // 클래스 인스턴스
상속 (Inheritance)
클래스와 구조체가 구분되는 특성이다. 하나의 클래스가 다른 클래스에게 특성을 물려줄 수가 있다. 자식 클래스가 부모 클래스의 속성과 메소드를 물려받게 되고, 사용할 수 있다.
프로퍼티와 메소드를 물려준 클래스는 부모 클래스, 상위 클래스, 슈퍼 클래스, 기본 클래스
프로퍼티와 메소드를 물려준 클래스는 자식 클래스, 하위 클래스, 슈퍼 클래스, 파생 클래스
서브클래싱
기존 클래스를 기반으로 새로운 클래스를 작성하는 것을 의미한다.
1 2 3
class 클래스명 : 부모클래스 { // 추가 구현 내용 }
뒤에 나오는 코코아 터치 프레임워크의 클래스 정의 구문에서 콜론 : 다음에 여러 개의 클래스가 나열되는데, 가장 첫 번째는 상속, 나머지는 자바의 implements 키워드를 의미한다.
오버라이딩
재정의 라는 의미로, 부모 클래스에서 상속받은 내용을 덮어쓴다. 재정의한 내용은 서브클래싱한 하위 클래스에만 적용된다. 부모 클래스까지의 영향이 없다는 의미이다. 스위프트에서는 오버라이딩하는 메소드나 프로퍼티 앞에 override 키워드를 붙인다. 없다면 오류로 간주한다.
만약 오버라이딩 할 수 있는 메소드나 프로퍼티가 아닌 경우는 컴파일러가 override 키워드를 오류로 간주한다. 상위 클래스에서 선언된 것만 오버라이딩이 가능하다.
프로퍼티를 오버라이딩 할 때 허용되는 것과 허용되지 않는 사항이 존재한다.
허용되는 것
저장 프로퍼티를 get, set 구문이 모두 있는 연산 프로퍼티로 오버라이딩 하는 것
get, set 구문이 모두 제공되는 연산 프로퍼티를 get, set 구문이 모두 제공되는 연산 프로퍼티로 오버라이딩 하는 것.
get 구문만 제공되는 연산 프로퍼티를 get, set 구문이 모두 제공되는 연산 프로퍼티로 오버라이딩하는 것
get 구문만 제공되는 연산 프로퍼티를 get 구문만 제공되는 연산 프로퍼티로 오버라이딩 하는 것
허용되지 않는 것
저장 프로퍼티를 저장 프로퍼티로 오버라이딩하는 것
get, set 구문과 관계없이 연산 프로퍼티를 저장 프로퍼티로 오버라이딩하는 것
저장 프로퍼티를 get 구문만 제공되는 연산 프로퍼티(=readonly)로 오버라이딩하는 것
get, set 구문을 모두 제공하는 연산 프로퍼티를 get 구문만 제공되는 연산 프로퍼티로 오버라이딩하는 것
복잡하지만 단순하다. 상위 클래스의 기능을 하위 클래스가 확장하거나 변경은 할 수 있지만 제한하는 방식이 되어서는 안된다는 것이다.
classLongBoard : SkateBoard { var deck =0 var wheel =0
overridevar currentSpeed : Double { get { returnDouble(self.wheel *50) } set { // nothing } }
overridevar description : String { get { return"LongBoard : wheel\(self.wheel), so currentSpeed=\(self.currentSpeed)" } set { print("New Value is \(newValue)") } } }
오버로딩 : 같은 메소드이름이지만 매개변수 변화로 새로운 메소드를 만들어 적재할 수 있도록 한다.
구조체나 클래스는 항상 인스턴스를 생성해서 할당 후 사용해야 하는 데, 이를 초기화라 한다.
이 과정에서 저장 프로퍼티는 생성 과정에서 초기화되어야 한다. 그 후에 구조체나 클래스의 초기화가 가능하다.
Resolution(), Video(), Car() 등은 초기화 구문이다.
저장 프로퍼티를 초기화하는 구조체/클래스의 초기화다.
Board(id: 1, category: 3)
초기화를 할 때 주의할 점은 미리 정의된 형태로 해야 한다는 것이다.
init 메소드 (초기화)
1 2 3 4
init(매개변수 : 타입 ... ) { // 매개변수 초기화 // 인스턴스 생성 후 처리할 로직 }
일반적인 메소드와 비슷한 모양이지만 init 메소드만의 특징이 있다.
초기화 메소드의 이름은 init으로 통일한다.
매개변수의 개수, 이름, 타입은 임의로 정의할 수 있다.
매개변수의 이름과 개수, 타입이 서로 다른 여러 개의 초기화 메소드를 정의할 수 있다.
정의된 초기화 메소드는 직접 호출되지만, 대부분 인스턴스 생성 시 간접 호출된다.
여러 모양의 초기화 모양을 두고, 가까운 모양으로 호출된다.
초기화 구문의 오버라이딩
초기화 구문 역시 자식 클래스에서 오버라이딩이 가능하다. 동일하게 override 키워드를 붙여야 한다. 상위 클래스에서 선언된 적이 없는 초기화 형식이라면 붙이면 안 된다.
1 2 3 4 5 6 7 8 9
classBase {
}
classExBase : Base { overrideinit() { // some codes } }
메소드와는 달리 초기화 구문에서 오버라이딩은 더 이상 부모 클래스에서 정의한 초기화 구문이 실행되지 않는 오류를 일으킬 수 있다. 부모 클래스의 기본 초기화 구문에서 프로퍼티를 초기화했다면, 자식 클래스에서 기본 초기화 구문을 오버라이딩함으로써 부모클래스 프로퍼티의 초기화가 누락된다. 이는 곧 오류를 발생시키므로, 초기화 구문을 오버라이딩할 경우 부모 클래스에서 정의된 초기화 구문을 내부적으로 호출해야 하는데, 오버라이딩된 초기화 구문 내부에 super.init 구문을 작성한다.
일반적인 함수와는 다르게 함수명이 존재하지 않는다. 허나, 함수와 기능은 동일하게 수행이 가능하다. 파라미터를 받아 return 값을 뱉어낼 수 있다는 점에서 크게 다를 건 없지만, 모양이 다르다. 결국 클로저는 이름 없는 함수 라고 볼 수 있고, 함수는 이름 없는 클로저 라고 볼 수 있다.
클로저는 중괄호( { } )로 감싸져 있고, 파라미터를 괄호로 감싸서 정의한다. 함수와 동일하게 ->로 return type을 정의할 수 있다. in 키워드는 파라미터, return type과 실제 클로저 코드를 분리하고 있다.
앞서 굳이 ! 연산자를 사용하지 않고 해제하는 경우를 보았다.(컴파일러가 해결해 주는 경우였다.) 이와 비슷하지만, 묵시적 해제는 옵셔널 변수를 사용하는 모든 경우에 적용할 수 있고, 옵셔널 변수의 타입을 선언할 때 묵시적 해제를 미리 선언해 주어야 한다는 차이점이 있다.
1 2 3 4 5
// 묵시적 옵셔널 선언 var str : String! ="Swift" print(str)
// 결과 : Swift
타입 어노테이션 뒤에 연산자 하나로 결과가 달라진다.
묵시적 옵셔널을 사용하면 옵셔널 타입 변수의 연산을 처리할 수 있다.
1 2 3 4 5
var value01 : Int? =10 value01 +5// Error
var value02 : Int! =10 value02 +5// 15
일반적으로는 옵셔널 타입의 연산은 오류이지만, 묵시적 해제를 선언한 옵셔널 타입은 아니다. 묵시적 옵셔널 해제를 사용하는 경우는 ‘형식상 옵셔널로 정의해야 하지만, 실제로 사용할 때는 절대 nil 값이 대입될 가능성이 없을 변수에.’
묵시적 옵셔널은 클래스나 구조체에서다. 주로 멤버 변수를 정의할 때 선언과 초기화를 분리시켜야 하는 경우에 대항한다.(아직은 자세히 알 필요는 없다.)