공부(Swift 기본 문법) - 3

47653 단어 swiftiOSiOS

프로퍼티

클래스, 구조체, 열거형 등에 관련된 값

저장 프로퍼티

struct Dog {
	var name: String
    let gender: String
}

var dog1 = Dog(name: "Coco", gender: "Male")
let dog2 = Dog(name: "Dodo", gender: "Female")

Dog 구조체에 선언된 name과 gender 멤버변수를 저장 프로퍼티라고 한다.
dog1의 gender 값은 변경할 수 없으며, dog2는 둘 다 변경할 수 없다.(let)

class Cat {
	var name: String
    let gender: String
    
    init(name: String, gender: String) {
    	self.name = name
        self.gender = gender
    }
}

let cat = Cat(name: "Coco", gender: "Male")
cat.name = "Dodo" // 가능
cat = Cat(name: "Dodo", gender: "Female") // gender가 var로 선언되었다고 하더라도 불가능

구조체는 value 타입이고 class는 참조 타입이기 때문에 cat이 let으로 선언되었다고 하더라도 name 값을 변경할 수 있다.
let이기 때문에 cat 자체를 재할당하는 것은 안 된다.

연산 프로퍼티

getter, setter를 이용해서 값을 연산하고 프로퍼티에 저장하는 역할

struct Stock {
	var averagePrice: Int
    var quantity: Int
    var purchasePrice: Int {
    	get {
        	return averagePrice * quantity
        }
        
        set(newPrice) {
        	averagePrice = newPrice / quantity
        }
    }
}

var stock = Stock(averagePrice: 2300, quantity: 3)
stock.purchasePrice // get 호출
stock.purchasePrice = 3000 // set 호출

setter 없이 getter 만 선언하면 읽기 전용 프로퍼티가 된다.
setter 를 매개변수 없이 사용하면 newValue 라는 이름으로 대신하여 사용할 수 있다.

프로퍼티 옵저버

프로퍼티 값의 변화를 관찰한다.
저장 프로퍼티에서 사용하거나 부모 클래스의 연산 프로퍼티를 오버라이딩할 경우 사용할 수 있다.
그냥 연산 프로퍼티에서는 사용할 수 없는데 그 이유는 getter를 통해 값의 변경을 알 수 있기 때문이다.

class Account {
	var credit: Int = 0 {
    	willSet {
        	print("잔액이 \(credit)원에서 \(newValue)원으로 변경될 예정입니다.")
        }
        
        didSet {
        	print("잔액이 \(oldValue)원에서 \(credit)원으로 변경되었습니다.")
        }
    }
}

var account = Account()
account.credit = 1000

타입 프로퍼티

인스턴스 생성 없이 객체 내 프로퍼티에 접근이 가능하게 한다.
static 키워드 사용

struct SomeStructure {
	static var storedTypeProperty = "Some value"
    static var computedTypeProperty: Int {
    	return 1
    }
}

SomeStructure.storedTypeProperty // Some Value
SomeStructure.computedTypeProperty // 1
SomeStructure.storedTypeProperty = "hello" // hello

클래스와 구조체의 차이

공통점

  • 값을 저장할 프로퍼티를 선언할 수 있다.
  • 메소드를 선언할 수 있다.
  • 내부 값에 .을 사용하여 접근할 수 있다.
  • 생성자를 사용하여 초기 상태를 설정할 수 있다.
  • Extension을 사용하여 기능을 확장할 수 있다.
  • Protocol을 채택하여 기능을 설정할 수 있다.

차이점

클래스

  • 참조 타입
  • 같은 클래스 인스턴스를 여러 개의 변수에 할당한 후, 값을 변경하면 모든 변수에 영향(메모리 복사)

구조체

  • 값 타입
  • 구조체를 새로운 변수에 할당할 때마다 새로운 구조체 할당
  • 같은 구조체를 여러 개의 변수에 할당한 후, 값을 변경시키더라도 다른 값에 영향을 주지 않음(값 자체 복사)
class SomeClass {
	var count: Int = 0
}

struct SomeStruct {
	var count: Int = 0
}

var class1 = SomeClass()
class2 = class1
class3 = class1

class2.count = 2

class1.count // 2
class2.count // 2
class3.count // 2

var struct1 = SomeStruct()
struct2 = struct1
struct3 = struct1

struct2.count = 2
struct3.count = 3

struct1.count // 0
struct2.count // 2
struct3.count // 3

상속

super class

class Vehicle {
	var currentSpeed = 0.0
    var description: String {
    	return "traveling at \(currentSpeed) miles per hour"
    }
    func makeNoise() {
    	print("Speaker on")
    }
}

sub class

class Bicycle: Vehicle {
	var hasBasket = false
}

var bicycle = Bicycle()
bicycle.currentSpeed // 0.0
bicycle.currentSpeed = 15.0
bicycle.currentSpeed // 15.0

오버라이딩

class Train: Vehicle {
	override func makeNoise() {
    	super.makeNoise()
    	print("Choo Choo")
    }
}

let train = Train()
train.makeNoise()
/*
	Speaker on
    Choo Choo
*/

프로퍼티 오버라이딩

class Car: Vehicle {
	var gear = 1
    override var description: String {
    	return super.description + " in gear \(gear)"
    }
}

let car = Car()
car.currentSpeed = 30.0
car.gear = 2
car.description
// traveling at 30.0 miles per hour in gear 2

오버라이딩을 통한 프로퍼티 옵저버 정의

class AutomaticCar: Car {
	override var currentSpeed: Double {
    	didSet {
        	gear = Int(currentSpeed / 10) + 1
        }
    }
}

let automatic = AutomaticCar()
automatic.currentSpeed = 35.0
print("AutomaticCar: \(automatic.description)")
// AutomaticCar: traveling at 35.0 miles per hour in gear 4

final : final 키워드를 프로퍼티나 함수에 사용하면 오버라이딩하여 재정의할 수 없다. 클래스에 사용하면 해당 클래스를 부모 클래스로 한 서브 클래스를 생성할 수 없다.

타입캐스팅

인스턴스의 타입을 확인하거나(is) 어떤 클래스의 인스턴스를 해당 클래스 계층 구조의 슈퍼 클래스나 서브 클래스로 취급하는(as) 방법

class MediaItem {
	var name: String
    
    init(name: String) {
    	self.name = name
    }
}

class Movie: MediaItem {
	var director: String
    
    init(name: String, director: String) {
    	self.director = director
        super.init(name: name)
    }
}

class Song: MediaItem {
	var artist: String
    
    init(name: String, artist: String) {
    	self.artist = artist
        super.init(name: name)
    }
}

let library = [
	Movie(name: "기생충", director: "봉준호")
    Song(name: "Butter", artist: "BTS")
	Movie(name: "올드보이", director: "박찬욱")
    Song(name: "Wonderwall", artist: "Oasis")
    Song(name: "Rain", artist: "이적")
] // library는 MediaItem 배열

var movieCount = 0
var songCount = 0

for item in library {
	if item is Movie {
    	movieCount += 1
    } else if item is Song {
    	songCount += 1
    }
}

print("Media library contains \(movieCount) movies and \(songCount) songs")
// Media library contains 2 movies and 3 songs

for item in library {
	if let movie = item as? Movie {
    	print("Movie: \(movie.name), dir. \(movie.director)")
    } else if let song = item as? Song {
    	print("Song: \(song.name), by \(song.artist)")
    }
}

다운캐스팅하는 경우
as?as! 사용
다운캐스팅이 실패할 수 있기 때문에 as? 를 사용하며, as! 를 사용하여 걍제적으로 unwrapping 했는데 잘못될 경우 에러가 발생하여 프로그램이 강제 종료될 위험이 존재
위의 예제에서는 조건문에 다운캐스팅을 사용하여 unwrapping 함.

assert와 guard

assert

특정 조건을 체크하고, 조건이 성립되지 않으면 메세지를 출력하게 할 수 있는 함수
디버깅 모드에서만 동작하고 주로 디버깅 중 조건의 검증을 위해 사용

var value = 0
assert(value == 0)

value = 2
assert(value == 0, "값이 0이 아닙니다.")

guard

조건을 검사하여 그 다음에 오는 코드를 실행할지 결정하는 것

func guardTest1(value: Int) {
	guard value == 0 else { return }
    print("안녕하세요")
}

guardTest1(value: 2) // 아무것도 출력되지 않음
guardTest1(value: 0) // 안녕하세요가 출력됨

// guard를 사용하여 옵셔널 바인딩
func guardTest2(value: Int?) {
	guard let value = value else { return }
    print(value)
}

guardTest2(value: 2) // 2가 출력됨
guardTest2(value: nil) // 아무것도 출력되지 않음

프로토콜

특정 역할을 하기 위한 메소드, 프로퍼티, 기타 요구 사항 등의 청사진

protocol FullyNames {
	var fullName: String { get set }
    func printFullName()
}

struct Person: FullyNames {
	var fullName: String
    
    func printFullName() {
    	print(fullName)
    }
}

protocol SomeProtocol {
	init()
}

class SomeClass: SomeProtocol {
	required init() {
    }
}

클래스에서 프로토콜이 요구하는 생성자를 정의하려면 required 식별자를 써줘야 한다.(구조체에서는 써주지 않아도 되고, final 키워드를 사용한 클래스인 경우에도 써주지 않아도 된다.)

익스텐션

기존의 클래스, 구조체, 열거형, 프로토콜에 새로운 기능을 추가하는 기능

익스텐션이 타입에 추가할 수 있는 기능

  • 연산 타입 프로퍼티 / 연산 인스턴스 프로퍼티
  • 타입 메소드 / 인스턴스 메소드
  • 이니셜라이저
  • 서브스크립트
  • 중첩 타입
  • 특정 프로토콜을 준수할 수 있도록 기능 추가

기존에 존재하는 기능을 오버라이딩할 수는 없다.

extension Int {
	var isEven: bool {
    	return self % 2 == 0
    }
    
    var isOdd: bool {
    	return self % 2 == 1
    }
}

var number = 3
number.isEven // false
number.isOdd // true

extension String {
	func convertToInt() -> Int? {
    	return Int(self)
    }
}

var string = "0"
string.convertToInt() // 0

열거형

연관성이 있는 값을 모아 놓은 것

enum CompassPoint1 {
	case north
    case south
    case east
    case west
}

var direction1 = CompassPoint1.east
direction1 = .west

switch direction1 {
	case .north:
    	print("north")
    case .south:
    	print("south")
    case .east:
    	print("east")
    case .west:
    	print("west")
}

// 원칙값 지정
enum CompassPoint2: String {
	case north = "북"
    case south = "남"
    case east = "동"
    case west = "서"
}

var direction2 = CompassPoint2.east
direction2 = .west

switch direction2 {
	case .north:
    	print(direction2.rawValue)
    case .south:
    	print(direction2.rawValue)
    case .east:
    	print(direction2.rawValue)
    case .west:
    	print(direction2.rawValue)
}

// 원칙값으로 열거형 반환
let direction3 = CompassPoint(rawValue: "남")

// 열거형은 연관값을 가질 수 있다.
enum PhoneError {
	case unknown
    case batteryLow(String) // String 타입 연관값을 갖는 열거형
}

let error = PhoneError.batteryLow("배터리가 곧 방전됩니다.")

// 연관값 추출
switch error {
	case .batteryLow(let message):
    	print(message)
    case .unknown:
    	print("알 수 없는 에러입니다.")
}

옵셔널 체이닝

옵셔널에 속해 있는 nil 일지도 모르는 프로퍼티, 메소드, 서브스크립션 등을 가져오거나 호출할 때 사용할 수 있는 일련의 과정
. 을 통해 프로퍼티나 메소드에 연속적으로 접근할 때 옵셔널이 하나라도 껴 있으면 옵셔널 체이닝이다.

struct Developer {
	let name: String
}

struct Company {
	let name: String
    var developer: Developer?
}

var developer = Developer(name: "Park")
var company = Company(name: "Gunter", developer: developer)
print(company.developer)
print(company.developer?.name) // Optional("Park")
// 옵셔널 바인딩으로 unwrapping할 수 있다.
print(company.developer!.name) // Park(강제적 해제)

출처

패스트캠퍼스, 초격차 패키지 : 30개 프로젝트로 배우는 iOS 앱 개발 with Swift

좋은 웹페이지 즐겨찾기