옵셔널 체이닝(Optional Chaining)

자바스크립트에서 객체의 속성 접근을 더 안전하게 하는 방법

이 기능을 사용하면 객체의 속성이 존재하지 않을 때 발생하는 오류를 방지할 수 있음

// 옵셔널 문자열을 선언하고 "Hi"로 초기화합니다.
var x: String? = "Hi" // x는 옵셔널이므로 nil일 수 있습니다.

// x의 값을 출력합니다. x!는 강제로 언래핑하여 nil이 아닐 경우 값을 가져옵니다.
print(x, x!) // 출력: Optional("Hi") Hi (옵셔널 x의 값과 강제로 언래핑한 값)

// if-let 구문을 사용하여 x가 nil이 아닐 경우에만 a에 값을 할당합니다.
if let a = x {
    print(a) // 출력: Hi (x가 nil이 아닐 경우, a의 값을 출력합니다.)
}

// 강제로 언래핑하여 x의 문자 개수를 가져옵니다. x가 nil이면 런타임 오류가 발생합니다.
let b = x!.count // x가 nil이 아니므로, x의 문자 개수를 가져옵니다.
print(type(of: b), b) // 출력: Int 2 (b의 타입과 값을 출력합니다. Hi의 길이는 2)

// 옵셔널 체이닝을 사용하여 x의 문자 개수를 가져옵니다. x가 nil이면 nil이 반환됩니다.
let b1 = x?.count // x가 nil인 경우, b1은 nil이 됩니다.
print(type(of: b1), b1, b1!) // 출력: Optional<Int> Optional(2) 2 (b1의 타입과 값을 출력합니다. b1은 nil이 아니므로 강제로 언래핑 가능)

// nil 병합 연산자를 사용하여 x가 nil일 경우 빈 문자열을 할당합니다.
let c = x ?? "" // x가 nil이면 c는 빈 문자열이 됩니다.
print(c) // 출력: Hi (x가 nil이 아니므로 c는 x의 값을 그대로 사용)

?.해서 일반적임
근데 print할때는 !로 풀어줘야됨

 

var x : int //선언문
x=10//실행문
var x : int? //선언문
var x : int! //선언문
//공통점: 둘다 옵셔널 형임, 선언문에서 자료형 다음에 ?! 찍을 수 있음  
//!는 자동으로 언래핑함, 일반적으로는 ?를 사용함 

var x : int! = 10
실행문에 !는 변수 뒤에 씀 ex) x!

//실행문에서의 x?란
//풀긴풀되, 조심해서 푼다
//최종적인 값을 한번 더 풀어서 써야한다는 단점

//옵셔널 체인일때
.count같은

//!는 옵셔널을 푸는것
//?는 옵셔널을 풀긴하지만 조심스럽게 푸는 것

//--------------------------
// 정수형 변수 x 선언
var x: Int // 선언문

// x에 10을 할당
x = 10 // 실행문

// 옵셔널 정수형 변수 x 선언 (값이 nil일 수 있음)
var x: Int? // 선언문

// 암시적 언래핑 옵셔널 변수 x 선언 (nil이 아닐 것이라 가정)
var x: Int! // 선언문

// 공통점: 두 변수 모두 옵셔널 형식임
// 선언문에서 자료형 다음에 ? 또는 !를 사용할 수 있음

// 암시적 언래핑 옵셔널 변수 x에 10을 할당
var x: Int! = 10

// 실행문에서 !는 변수 뒤에 사용하여 값을 언래핑함
// 예시: x!

// 실행문에서 x?는 옵셔널 값을 풀긴 하지만 조심스럽게 다루어야 함
// 이 경우, 최종적인 값을 한번 더 풀어서 사용해야 함

// 옵셔널 체인 사용 예시
// 예: x?.count (x가 nil이 아닐 때만 count를 호출)

// !는 옵셔널을 강제로 언래핑함
// ?는 옵셔널을 안전하게 풀지만 nil인 경우 처리해야 함
class Person {
    var name: String
    var age: Int
    
    // 초기화 메서드
    init(name: String, age: Int) {
        self.name = name
        self.age = age
    }
}

// Person 객체 kim 생성
let kim: Person = Person(name: "Kim", age: 20)
print(kim.age) // kim의 나이 출력

// 옵셔널 Person 객체 han 생성
let han: Person? = Person(name: "Han", age: 25) // 옵셔널 Person

// han의 나이를 출력할 때 옵셔널을 강제로 언래핑해야 함
print(han!.age) // han이 nil이 아닐 경우, 나이를 출력 (강제 언래핑)
//옵셔널이기 때문에 !로 풀어줘야됨 안하면 error/ ?는 옵쳐널 체이닝 이경우 또 한번 아래에 풀어줘야됨
//print((han?.age)!) 또는 아래 if처럼 풀어줌


// 옵셔널 체이닝을 사용할 경우
// print(han?.age) // Optional(25), han이 nil일 경우 nil 반환

// 옵셔널 체이닝 후 강제 언래핑
print((han?.age)!) // han이 nil이 아닐 경우, 나이를 출력

// 옵셔널 바인딩을 사용하여 안전하게 언래핑
if let hanAge = han?.age {
    print(hanAge) // han이 nil이 아닐 경우, hanAge 출력
} else {
    print("nil") // han이 nil일 경우 "nil" 출력
}

6주차 9p X

 

// 옵셔널 체이닝: 점 앞에 ? 사용
class Company {
    var ceo: Person? // 회사의 CEO는 Person 타입이지만, nil일 수도 있음
}

class Person {
    var name: String // 사람의 이름을 저장하는 변수
    
    // 초기화 메서드
    init(name: String) {
        self.name = name // 주어진 이름으로 초기화
    }
}

// Company 객체 apple 생성
let apple = Company()

// apple의 CEO를 Person 객체로 설정
apple.ceo = Person(name: "Kim") // Kim이라는 이름의 CEO를 설정

// 다음은 오류가 발생함
// print(apple.ceo.name) // apple.ceo가 nil일 수도 있기 때문에 오류 발생

// 옵셔널 체이닝 없이 CEO의 이름을 안전하게 가져오는 방법
if let ceo = apple.ceo { // apple.ceo가 nil이 아닐 경우
    print(ceo.name) // CEO의 이름을 출력 (이 경우 "Kim")
}

// 강제 언래핑을 사용하여 CEO의 이름을 가져옴
print(apple.ceo!.name) // "Kim"을 출력 (apple.ceo가 nil이 아닐 것이라 가정)

// 옵셔널 체이닝을 사용하여 CEO의 이름을 가져옴
print(apple.ceo?.name) // "Optional("Kim")"을 출력 (옵셔널로 감싸져 있음)

// 옵셔널 체이닝을 사용하여 CEO의 이름을 안전하게 가져오는 방법
if let name = apple.ceo?.name { // apple.ceo가 nil이 아닐 경우
    print(name) // "Kim"을 출력
}

// 옵셔널 체이닝과 nil 병합 연산자를 사용
print(apple.ceo?.name ?? "CEO가 없습니다") // "Kim"을 출력 (apple.ceo가 nil일 경우 "CEO가 없습니다" 출력)

 

옵셔널 체이닝 장점

안전성: 옵셔널 체이닝을 사용하면, 객체가 nil인지 확인하면서 안전하게 값을 가져올 수 있습니다. 따라서 런타임 오류를 예방할 수 있습니다.

코드 간결성: 옵셔널 체이닝을 사용하면 코드가 더 간결해집니다. if let 구문 없이도 간단하게 값을 가져올 수 있습니다.

 

조건부 실행: 옵셔널 체이닝을 사용하면, 체인이 끊어지는 순간 nil을 반환하므로, 조건에 따라 다음 메서드나 프로퍼티를 안전하게 호출할 수 있습니다.

 

단점

옵셔널 값으로 반환: 옵셔널 체이닝을 사용하면 결과가 항상 옵셔널로 반환되기 때문에, 이를 사용하려면 다시 언래핑해야 합니다. 즉, if let 또는 !를 사용하여 값을 풀어줘야 합니다.

 

가독성 저하: 여러 개의 옵셔널 체이닝이 중첩될 경우, 코드의 가독성이 떨어질 수 있습니다. 이런 경우, 어떤 부분이 nil인지 추적하기 어려워질 수 있습니다.

 

강제 언래핑의 위험: !를 사용하여 강제로 언래핑할 경우, nil인 경우 런타임 오류가 발생할 수 있어 주의가 필요합니다.

 

?과 !이 실행문일 때, 선언문일 때 뭐가 다른지 표로 정리해줘

예외처리를 하고 싶을때 try?나 try!를 사용,

?를 쓰게 되면 리턴 값이 옵셔널로 나옴, 에러를 간단히 무시하거나 실패 여부만 확인하고 싶을때

!는 에러가 절대 발생하지 않을 것이라고 가정할떄, 만약 에러가 발생하면 프로그램이 크래시( 보통 사용안함)

 

 

throws 키워드

사용 방법: 함수의 매개변수나 반환값에 throws 키워드가 붙어 있으면, 해당 함수는 오류를 발생시킬 수 있다는 의미입니다.
특징: throws가 있는 함수는 반드시 try, try?, 또는 try!로 호출해야 하며, 예외 처리를 해주어야 합니다.

throws 키워드가 있으면 반드시 예외 처리해야됨

 

그냥 호출 안되고 앞에는 try를 써주고 앞 뒤에 do ~try ~catch를 써줌

예외가 발생할 수 있는 코드는 do 블록 안에 작성합니다.
try를 사용하여 함수를 호출하고, 오류가 발생하면 catch 블록에서 처리합니다.
이 구조를 통해 프로그램이 중단되지 않고 오류를 안전하게 처리할 수 있습니다.

 

*

오버로딩: 같은 이름의 함수가 여러 개 존재하며, 매개변수의 타입이나 개수로 구분됩니다.
중첩: 함수 안에 다른 함수를 정의하여 사용할 수 있으며, 중첩된 함수는 외부 함수의 범위 내에서만 호출 가능합니다.

 

 

Generic<>

소괄호 (): 함수 호출, 수학적 표현의 우선순위 지정.
대괄호 []: 배열, 리스트, 인덱스 접근.
중괄호 {}: 코드 블록 정의.
꺾쇠 괄호 <>: 제네릭 타입 정의, HTML 태그 등.

 

// 제네릭 함수를 정의합니다. T는 어떤 타입이든 될 수 있습니다.
func myPrint<T>(a: T, b: T) {
    // b와 a를 출력합니다. 매개변수의 순서를 바꿉니다.
    print(b, a)
}

// 정수 타입의 인수를 사용하여 함수 호출
myPrint(a: 1, b: 2) // 출력: 2 1

// 실수 타입의 인수를 사용하여 함수 호출
myPrint(a: 2.5, b: 3.5) // 출력: 3.5 2.5

// 문자열 타입의 인수를 사용하여 함수 호출
myPrint(a: "Hi", b: "Hello") // 출력: Hello Hi

 

 

빈배열 만들기 3가지 방법

// 방법 1: 타입과 함께 빈 배열을 선언합니다.
// 'x'는 Int 타입의 빈 배열입니다.
var x: [Int] = [] // 빈 배열 생성

// 방법 2: 타입을 명시하지 않고 배열을 생성합니다.
// 'y'는 Int 타입의 빈 배열입니다. Swift가 타입을 자동으로 추론합니다.
var y = [Int]() // 빈 배열 생성

// 방법 3: Array 제네릭 타입을 사용하여 빈 배열을 선언합니다.
// 'z'는 Int 타입의 빈 배열입니다. Array<Int>는 Int 타입의 요소를 가지는 배열을 의미합니다.
var z: Array<Int> = [] // 빈 배열 생성

 

 

컬렉션 타입(Collection Type)은 여러 값을 한 곳에 저장할 수 있는 데이터 구조를 의미

1. 배열 (Array)
정의: 순서가 있는 값들의 리스트입니다.
특징:
같은 타입의 요소를 저장합니다.
인덱스를 사용하여 요소에 접근합니다.
예시: [1, 2, 3, 4]
2. 집합 (Set)
정의: 중복되지 않는 값들의 모음입니다.
특징:
순서가 없으며, 각 요소는 유일합니다.
주로 중복 제거와 관련된 작업에 사용됩니다.
예시: Set([1, 2, 3])
3. 사전 (Dictionary)
정의: 키-값 쌍으로 이루어진 컬렉션입니다.
특징:
각 키는 유일하며, 이를 통해 값에 접근합니다.
키를 사용하여 데이터를 빠르게 검색할 수 있습니다.
예시: ["name": "Alice", "age": 30]

 

 

-Swift의 Array에서 자주 사용하는 프로퍼티와 메소드를간단정리

 

 

Array 프로퍼티 (속성)
count: 배열의 요소 개수.
예: let count = array.count
isEmpty: 배열이 비어 있는지 여부.
예: let isEmpty = array.isEmpty
first: 배열의 첫 번째 요소.
예: let firstElement = array.first


Array 메소드 (함수)
append(_:): 배열의 끝에 요소 추가.

예: array.append(newElement)
remove(at:): 특정 인덱스의 요소 제거.

예: array.remove(at: index)
contains(_:): 배열에 특정 요소가 있는지 확인.

예: let hasElement = array.contains(element)
sort(): 배열의 요소를 정렬.

예: array.sort()

 

// 타입 추론을 이용하여 빈 배열을 생성합니다.
// Swift가 자동으로 타입을 추론하여 Array<Int> 타입으로 설정됩니다.
let number = [1, 2, 3, 4] 

// 타입을 명시적으로 지정하여 Int 타입의 배열을 생성합니다.
let odd: [Int] = [1, 3, 5] 

// Array 제네릭 타입을 사용하여 Int 타입의 배열을 생성합니다.
let even: Array<Int> = [2, 4, 6] 

// number의 타입을 출력합니다. 결과는 Array<Int>입니다.
print(type(of: number)) // 출력: Array<Int>

// number 배열의 내용을 출력합니다.
print(number) // 출력: [1, 2, 3, 4]

// odd의 타입을 출력합니다. 결과는 Array<Int>입니다.
print(type(of: odd)) // 출력: Array<Int>

// odd 배열의 내용을 출력합니다.
print(odd) // 출력: [1, 3, 5]

// even의 타입을 출력합니다. 결과는 Array<Int>입니다.
print(type(of: even)) // 출력: Array<Int>

// even 배열의 내용을 출력합니다.
print(even) // 출력: [2, 4, 6]

// 문자열 배열을 생성합니다. 타입은 자동으로 추론됩니다.
let animal = ["dog", "cat", "cow"]

// animal의 타입을 출력합니다. 결과는 Array<String>입니다.
print(type(of: animal)) // 출력: Array<String>

// animal 배열의 내용을 출력합니다.
print(animal) // 출력: ["dog", "cat", "cow"]

 

 

빈 배열(empty array) 주의 사항

var number : [Int] = []
//빈 배열을 let으로 만들 수는 있지만 초기값에서 변경 불가이니 배열의 의미 없음
var odd = [Int]() //빈배열은 let이 아닌 var로 써야됨, 가장 많이 사용
var even : Array<Int> = Array()
print(number) //[]
//print(number[0]) //오류, 빈 배열을 값을 넣은 다음에 접근
number.append(100) //let으로 선언한 불변형 배열이라 추가 불가능, 반드시 append를 통해 방을 만들어야함, append는 하면할수록 방이 늘어남
//error: cannot use mutating member on immutable value: 'number' is a 'let' constant
print(number[0])
number.append(200)
print(number[0], number[1],number)

 

 

가변형(mutable)

배열의 내용을 변경할 수 있는 형태

var animal = ["dog", "cat", "cow"]
animal.append("bird") // 배열에 "bird" 추가 가능
print(animal) // 출력: ["dog", "cat", "cow", "bird"]

 

불변형 (immutable)

초기화 후 변경 불가

let animal1 = ["dog", "cat", "cow"]
// animal1.append("bird") // 오류 발생: 변경할 수 없음
print(animal1) // 출력: ["dog", "cat", "cow"]

 

빈 배열 주의사항

var number: [Int] = [] // Int 타입의 빈 배열을 var로 선언

// number[0] = 1 // 오류 발생: 빈 배열이므로 직접 접근할 수 없음

// append() 메소드를 사용하여 배열에 값을 추가
number.append(1) // 1을 배열에 추가
print(number) // 출력: [1]

// 이제 배열에 값이 있으므로, 인덱스를 사용하여 값을 변경할 수 있음
number[0] = 10 // 첫 번째 요소를 10으로 변경
print(number) // 출력: [10]

빈 배열: 초기 상태에서 요소가 없으므로 직접 인덱스를 사용하여 값을 설정할 수 없습니다.
append(): 배열에 요소를 추가하는 메소드로, 이 메소드를 사용해야만 배열에 값을 넣을 수 있습니다.
인덱스 접근: 배열에 요소가 추가된 후에는 인덱스를 사용하여 값을 수정할 수 있습니다.

 

 

 

Array(repeating:count:)  특정값(repeating)으로 원하는 개수(count)만큼 초기화

// Int 타입의 배열을 5개의 0으로 초기화
var x = [0, 0, 0, 0, 0]
print(x) // 출력: [0, 0, 0, 0, 0]

// Array 생성자를 사용하여 0으로 초기화된 배열을 생성
var x1 = Array(repeating: 0, count: 5)
print(x1) // 출력: [0, 0, 0, 0, 0]

// [Int] 타입의 배열을 3개의 1로 초기화
var x2 = [Int](repeating: 1, count: 3)
print(x2) // 출력: [1, 1, 1]

// [String] 타입의 배열을 4개의 "A"로 초기화
var x3 = [String](repeating: "A", count: 4)
print(x3) // 출력: ["A", "A", "A", "A"]

 

 

배열 항목 가져오기: for~in문

// 문자열 배열을 생성하여 색상 목록을 초기화
let colors = ["red", "green", "blue"]
print(colors) // 출력: ["red", "green", "blue"]

// 배열의 각 요소를 반복(iterate)하여 출력하는 for 루프
for color in colors {
    print(color) // 각 색상을 출력
}
// 출력:
// red
// green
// blue

 

 

항목이 몇 개인지(count), 비어있는지(isEmpty) 알아내기

// Int 타입의 배열을 초기화
let num = [1, 2, 3, 4]

// 빈 Int 배열을 생성
var x = [Int]()

// num 배열이 비어있는지 확인하고 출력
print(num.isEmpty) // 배열이 비어있나? false (num 배열에는 요소가 있음)

// x 배열이 비어있는지 확인하고 출력
print(x.isEmpty) // 배열이 비어있나? true (x는 빈 배열)

// num 배열이 비어있으면 메시지를 출력
if num.isEmpty {
    print("비어 있습니다") // num 배열이 비어있다면 이 메시지가 출력됨
} else {
    // num 배열의 요소 개수를 출력
    print(num.count) // 배열 항목의 개수 (출력: 4)
}

 

 

first와 last 프로퍼티

// Int 타입의 배열을 초기화
let num = [1, 2, 3, 4]

// 빈 Int 배열을 생성
let num1 = [Int]()

// num 배열의 첫 번째 요소와 마지막 요소를 출력
// Optional로 감싸져서 출력됨
print(num.first, num.last) // 출력: Optional(1) Optional(4)

// num1 배열은 빈 배열이므로 첫 번째와 마지막 요소는 nil
print(num1.first, num1.last) // 출력: nil nil

// num 배열의 첫 번째와 마지막 요소를 안전하게 추출
if let f = num.first, let l = num.last {
    // f와 l이 각각 첫 번째와 마지막 요소를 가리킴
    print(f, l) // 출력: 1 4
}

// 배열의 first와 last는 둘 다 일반적으로 옵셔널 값으로 나옴
// 빈 배열이기 때문

 

첨자(subscript)로 항목 접근(셤)

// Int 타입의 배열을 초기화
var num = [1, 2, 3, 4]

// 배열의 첫 번째와 네 번째 요소를 출력
print(num[0], num[3]) // 출력: 1 4

// 배열의 첫 번째 요소를 안전하게 출력 (옵셔널 언래핑)
print(num.first!) // 출력: 1

// for 루프를 사용하여 배열의 모든 요소를 출력
for i in 0...num.count-1 {
    print(num[i]) // 출력: 1, 2, 3, 4 (각 요소를 한 줄씩 출력)
}

// 배열의 일부 범위를 출력 (1번과 2번 인덱스의 요소)
print(num[1...2]) // 출력: [2, 3]

// 배열의 0, 1, 2 인덱스 요소를 [10, 20, 30]으로 교체
num[0...2] = [10, 20, 30]

// 변경된 배열을 출력
print(num) // 출력: [10, 20, 30, 4]

 

 

 

Array: 추가 /제거

// let x = [1, 2, 3, 4] // 불변 배열은 초기값에서 변경 불가
// x.append(5) // 오류 발생: let으로 선언된 배열에 추가할 수 없음

// 가변 배열을 초기화
var num = [1, 2, 3]
print(num) // 출력: [1, 2, 3]

// 배열에 4를 추가
num.append(4)
print(num) // 출력: [1, 2, 3, 4]

// 여러 요소를 배열의 끝에 추가
num.append(contentsOf: [6, 7, 8])
print(num) // 출력: [1, 2, 3, 4, 6, 7, 8]

// 5를 4번 인덱스에 삽입
num.insert(5, at: 4)
print(num) // 출력: [1, 2, 3, 4, 5, 6, 7, 8]

// 3번 인덱스의 요소를 제거
num.remove(at: 3)
print(num) // 출력: [1, 2, 3, 5, 6, 7, 8]

// 마지막 요소를 제거
num.removeLast()
print(num) // 출력: [1, 2, 3, 5, 6, 7]

// 배열에서 2의 첫 번째 인덱스를 찾기
print(num.firstIndex(of: 2)) // 출력: Optional(1), 2는 1번 인덱스에 위치

// 2의 인덱스를 안전하게 가져와서 값을 20으로 변경
if let i = num.firstIndex(of: 2) {
    num[i] = 20 // num[1] = 20
}
print(num) // 출력: [1, 20, 3, 5, 6, 7]

// 배열을 자신과 더하여 중복된 요소를 만듦
num = num + num
print(num) // 출력: [1, 20, 3, 5, 6, 7, 1, 20, 3, 5, 6, 7]

// [8, 9]를 배열에 추가
num += [8, 9]
print(num) // 출력: [1, 20, 3, 5, 6, 7, 1, 20, 3, 5, 6, 7, 8, 9]

// 배열의 모든 요소를 제거
num.removeAll()
print(num) // 출력: []

let x: 주석 처리된 불변 배열. 변경할 수 없음을 설명합니다.
var num: 가변 배열을 초기화하여 요소를 추가하고 수정할 수 있습니다.
append(): 배열의 끝에 요소를 추가합니다.
append(contentsOf:): 여러 요소를 한 번에 추가합니다.
insert(): 특정 인덱스에 요소를 삽입합니다.
remove(at:): 특정 인덱스의 요소를 제거합니다.
removeLast(): 배열의 마지막 요소를 제거합니다.
firstIndex(of:): 특정 값의 첫 번째 인덱스를 찾습니다.
if let: 안전하게 옵셔널 값을 언래핑하여 요소를 수정합니다.
num + num: 배열을 자신과 더하여 중복된 요소를 만듭니다.
+=: 배열에 여러 요소를 추가합니다.
removeAll(): 배열의 모든 요소를 제거합니다.

 

 

Array는 구조체이므로 값 타입

// Int 타입의 배열을 초기화
var num = [1, 2, 3]

// num 배열의 복사본을 x에 할당. x는 num과 별개의 배열
var x = num 

// num 배열의 첫 번째 요소를 100으로 변경
num[0] = 100

// num 배열을 출력
print(num) // 출력: [100, 2, 3]

// x 배열을 출력
print(x) // 출력: [1, 2, 3] (x는 num의 복사본이므로 원래 값 유지)

 

 

배열(Array)에서 최댓값 최솟값 구하기

var num = [1,2,3,10,20]
print(num)
print(num.min())
print(num.max())
print(num.min()!)
print(num.max()!)

 

 

Array 요소의 정렬

var num = [1,5,3,2,4]
num.sort() //오름차순 정렬하여 원본 변경
print(num) //[1, 2, 3, 4, 5]
num[0...4] = [2,3,4,5,1]
num.sort(by:>) //내림차순 정렬하여 원본 변경
print(num) //[5, 4, 3, 2, 1]
num[0...4] = [2,3,4,5,1]
num.reverse() //반대로 정렬하여 원본 변경
print(num) //[1, 5, 4, 3, 2]
print(num.sorted()) //오름차순 정렬 결과를 리턴하고, 원본은 그대로, var x = num.sorted()
//[1, 2, 3, 4, 5]
print(num) //[1, 5, 4, 3, 2]
print(num.sorted(by:>)) //내림차순 정렬 결과를 리턴하고, 원본은 그대로
//[5, 4, 3, 2, 1]
print(num)//[1, 5, 4, 3, 2]

sort(): 배열을 직접 수정하여 정렬 (원본 변경).
sorted(): 새로운 배열을 반환하여 정렬 (원본 유지).
내림차순 정렬: sort(by: >) 또는 sorted(by: >)를 사용.

 

swift 접근제어 비교표

1. open

  • 최고 수준의 접근 제어입니다.
  • 모듈 외부에서도 해당 클래스나 메서드를 사용할 수 있고, 상속오버라이드가 가능합니다.
  • 예시: open class SomeClass {}

2. public

  • 모듈 외부에서 사용할 수 있지만, 상속이나 오버라이드는 할 수 없습니다.
  • 외부에서 접근할 수 있지만, 그 내부 동작을 변경할 수는 없어요.
  • 예시: public class SomeClass {}

3. internal (기본값)

  • 해당 모듈 내에서만 접근 가능합니다.
  • 다른 모듈에서는 접근할 수 없으며, 기본적으로 internal로 설정됩니다.
  • 예시: internal class SomeClass {} 또는 접근 제어를 생략한 경우

4. fileprivate

  • 해당 파일 내에서만 접근 가능합니다.
  • 다른 파일에서는 사용할 수 없고, 동일 파일 내에서만 제한적으로 사용됩니다.
  • 예시: fileprivate class SomeClass {}

5. private

  • 가장 제한적인 접근 제어입니다.
  • 같은 블록(즉, 같은 클래스나 구조체 내) 또는 extension에서만 접근할 수 있습니다.
  • 예시: private class SomeClass {}

6. package (Swift 6.0부터)

  • 같은 패키지 내에서만 접근할 수 있습니다.
  • 패키지 외부에서는 사용할 수 없습니다.
  •  
public class SomePublicClass {}        // 외부에서 사용 가능
internal class SomeInternalClass {}   // 같은 모듈 내에서만 사용 가능
fileprivate class SomeFilePrivateClass {} // 같은 파일 내에서만 사용 가능
private class SomePrivateClass {}     // 같은 클래스나 extension에서만 사용 가능

public var somePublicVariable = 0
internal let someInternalConstant = 0
fileprivate func someFilePrivateFunction() {}
private func somePrivateFunction() {}

Swift에서 기본 접근 제어는 **internal**입니다. 즉, 접근 제어를 명시적으로 지정하지 않으면 기본적으로 internal이 적용됩니다. 이 접근 수준은 해당 모듈 내에서만 사용할 수 있도록 제한됩니다.

internal 접근 제어의 특징:

  • 같은 모듈 내에서 모든 소스 파일에서 접근할 수 있습니다.
  • 다른 모듈(즉, 다른 앱이나 다른 프레임워크, 라이브러리)에서는 접근할 수 없습니다.

즉, internal 접근 수준은 프로젝트 내에서만 접근이 가능하고, 외부에서는 사용할 수 없다는 것입니다.

어디서 접근할 수 있나?

  • 같은 프로젝트같은 프레임워크 내의 다른 파일에서 접근이 가능합니다.
  • 다른 프레임워크나 앱에서는 접근할 수 없습니다.

'Swift' 카테고리의 다른 글

iOS프로그래밍 실무 5주차  (0) 2025.04.02
iOS실무 프로그래밍 4주차  (0) 2025.03.26
iOS프로그래밍 실무 3주차  (0) 2025.03.19
iOS 프로그래밍 실무 2주차  (0) 2025.03.17
iOS 프로그래밍 실무 1주차  (0) 2025.03.05

if~else문: 조건에 따라 둘 중 하나의 값 또는 변수를 선택할 때 사용

if(비교조건){
조건이 참일 떄 실행할 명령문(들)
}else{
조건이 거짓일 떄 실행할 명령문(들)
}

#기초코드 1
job.type <- 'A'
if(job.type=='B'){
  bonus <- 200 #직무 유형이 B일떄 실행
} else{ #else는 if문의 코드블록이 끝나는 부분에 있는 }와 같은 줄에 작성해야됨
  bonus <- 100#직무 유형이 B가 아닌 나머지 경우 실행
}
print(bonus)

 

for문

for(반복 변수 in 반복 범위){
반복할 명령문(들)
}

#기초코드3
for(i in 1:5){
  print('*')
}

for(i in 1:5){
  cat('*')#\n은 개행
}

 

 

while문: 어떤 조건이 만족하는 동안 코드블록을 수행하고, 해당 조건이 거짓일 경우 반복을 종료하는 명령문

while(비교 조건){
반복할 명령문(들)
}
#기초코드4
sum <- 0
i <- 
  while(i<=5){
    print(i)#i값이 계속1
  }

 

 

break: for문 밖으로 이동

#기초코드5
for(i in 1:5){
  if(i>=2)break #for문밖으로 이동
print(i)
}

 

 

next:특정 조건을 건너뛰고 반복

for(i in 1:10){
if(i%%2==0)next #짝수면 skip해서 for 문으로 이동  
print(i)
  }

 

apply()함수

- 반복 작업이 필요한 경우에는 반복문 적용

- 반복 작업의 대상이 매트릭스나 데이터프레임의 행, 또는 열인 경우는 for문이나 while대신 apply()함수 이용 가능

apply(데이터셋, 행/열방향 지정, 적용 함수)

mapply = matrix(1:6, nrow=2, ncol=3)
mapply
# 출력 결과:
#      [,1] [,2] [,3]
# [1,]    1    3    5
# [2,]    2    4    6

apply(mapply, 1, max) # row 방향으로 함수(최댓값) 적용
# 출력 결과:
# [1] 2 4 6

apply(mapply, 2, max) # col 방향으로 함수(최댓값) 적용
# 출력 결과:
# [1] 2 4 6

 

 

사용자 정의 함수 만들기

-R은 사용자들도 자신만의 함수를 만들어 사용할 수 있는 기능 제공

함수명<-function(매개변수 목록){
실행할 명령문(들)
return(함수의 실행 결과)
}
mymax <- function(x, y) { # x와 y를 입력으로 받는 mymax라는 함수를 정의
  num.max <- x            # num.max에 x 값을 초기화
  if (y > x) {           # y가 x보다 큰지 확인
    num.max <- y         # y가 더 크면 num.max를 y로 변경
  }
  return(num.max)        # num.max 값을 반환
}

mymax(2, 3)               # mymax 함수를 호출하여 2와 3을 인자로 전달
# 출력 결과:
# [1] 3                   # 3이 반환됨

 

조건에 맞는 데이터의 위치 찾기

-데이터 분석을 하다보면 자신이 원하는 데이터가 벡터나 매트릭스, 데이터 프레임 안에서 어디에 위치하고 있는지를 알기 원하는 때가 있음,

-예를 들어 50명의 학생 성적이 저장된 벡터가 있는데 가장 성적이 좋은 학생은 몇번쨰에 있는지 알고 싶은 경우

이런경우 편리하게 사용할 수 있는 함수: which(), which.max(), which.min()함수

 

score <- c(71, 88, 84)  # 성적 벡터 생성

which(score == 88)      # 성적이 88인 학생은 몇 번째에 있나
# 출력 결과:
# [1] 2                   # 88은 2번째 학생

which(score >= 70)      # 성적이 70 이상인 학생은 몇 번째에 있나
# 출력 결과:
# [1] 1 2 3               # 70 이상인 학생은 1, 2, 3번째

which.max(score)        # 최고 점수는 몇 번째에 있나
# 출력 결과:
# [1] 2                   # 최고 점수는 88로 2번째 학생

which.min(score)        # 최저 점수는 몇 번째에 있나
# 출력 결과:
# [1] 1                   # 최저 점수는 71로 1번째 학생

 

 

종합문제

#문제 1: 성별에 따른 문자열 할당
gender.type <- 'M'                # 성별을 'M'으로 설정
if (gender.type == 'F') {         # 성별이 'F'인지 확인
  g <- '여성'                     # 여성일 경우 g에 '여성' 할당
} else {                          # 그렇지 않으면
  g <- '남성'                     # g에 '남성' 할당
}
print(g)                          # g 출력
# 출력 결과:
# [1] "남성"

#문제 2: ifelse를 이용한 성별 할당
gender.type <- 'M'                # 성별을 'M'으로 설정
g <- ifelse(gender.type == 'F', '여성', '남성')  # 성별에 따라 g에 값 할당
print(g)                          # g 출력
# 출력 결과:
# [1] "남성"

#문제 3: 별 찍기 패턴 (for문)
for(i in 1:5) {                    # 1부터 5까지 반복
  for(j in 1:i) {                  # i에 따라 j를 1부터 i까지 반복
    cat('*')                       # '*' 출력
  }
  cat('\n')                       # 줄바꿈
}
# 출력 결과:
# *
# **
# ***
# ****
# *****

#문제 4: 별 찍기 패턴 (while문)
i <- 1                             # i를 1로 초기화
while(i <= 5) {                   # i가 5 이하일 때까지 반복
  j <- 1                           # j를 1로 초기화
  while (j <= i) {                # j가 i 이하일 때까지 반복
    cat('*')                      # '*' 출력
    j <- j + 1                    # j 증가
  }
  cat('\n')                       # 줄바꿈
  i <- i + 1                      # i 증가
}
# 출력 결과:
# *
# **
# ***
# ****
# *****

#문제 5: 반복문에서 break와 next 사용
for(i in 1:5){                    # 1부터 5까지 반복
  if(i %% 4 == 0) break           # i가 4의 배수일 때 반복 종료
  print(i)                       # i 출력
}
# 출력 결과:
# [1] 1
# [1] 2
# [1] 3

for(i in 1:10){                   # 1부터 10까지 반복
  if(i %% 3 != 0) next           # i가 3의 배수가 아닐 경우 다음 반복으로
  print(i)                       # i 출력
}
# 출력 결과:
# [1] 3
# [1] 6
# [1] 9

#문제 6: 데이터프레임의 열 합계 계산
msleep = data.frame(study = c(1, 2, 3), sleeping = c(7, 8, 9))  # 데이터프레임 생성
msleep
# 출력 결과:
#   study sleeping
# 1     1       7
# 2     2       8
# 3     3       9

apply(msleep, 2, sum)            # 각 열의 합 계산
# 출력 결과:
# study sleeping 
#     6       24

#문제 7: 함수 정의와 호출
namef <- function(name1) {        # name1을 인자로 받는 함수 정의
  for(i in 1:5) {                 # 1부터 5까지 반복
    print(name1)                  # name1 출력
  }
}
namef('kdh')                      # 'kdh'를 인자로 함수 호출
# 출력 결과:
# [1] "kdh"
# [1] "kdh"
# [1] "kdh"
# [1] "kdh"
# [1] "kdh"

#문제 8: 벡터의 특정 값 위치 찾기
value1 <- c(100, 200, 300)        # 값 벡터 생성
which(value1 == 300)              # 300의 위치 찾기
# 출력 결과:
# [1] 3
which.max(value1)                 # 최대값의 위치 찾기
# 출력 결과:
# [1] 3
which.min(value1)                 # 최소값의 위치 찾기
# 출력 결과:
# [1] 1

#문제 9: 구구단 9단 출력
for(i in 1:9){                    # 1부터 9까지 반복
  cat('9*', i, '=', 9 * i, '\n')   # 구구단 9단 출력
}
# 출력 결과:
# 9* 1 = 9
# 9* 2 = 18
# 9* 3 = 27
# 9* 4 = 36
# 9* 5 = 45
# 9* 6 = 54
# 9* 7 = 63
# 9* 8 = 72
# 9* 9 = 81

#문제 10: 구구단 8단 출력
i <- 1                             # i를 1로 초기화
while(i < 10) {                   # i가 10 미만일 때 반복
  cat('8*', i, '=', 8 * i, '\n')   # 구구단 8단 출력
  i <- i + 1                      # i 증가
}
# 출력 결과:
# 8* 1 = 8
# 8* 2 = 16
# 8* 3 = 24
# 8* 4 = 32
# 8* 5 = 40
# 8* 6 = 48
# 8* 7 = 56
# 8* 8 = 64
# 8* 9 = 72

#문제 11: 1부터 100까지 반복하며 3의 배수 출력
for(i in 1:100){                  # 1부터 100까지 반복
  result <- ifelse(i %% 3 == 0, '*', i)  # i가 3의 배수면 '*'로, 아니면 i로 설정
  cat(result, '')                  # 결과 출력
}
# 출력 결과:
# 1 2 * 4 5 * 7 8 * 10 11 * 13 14 * ... (3의 배수는 '*'로 출력)

'R' 카테고리의 다른 글

벡터&매트리스&데이터프레임  (0) 2025.03.27
R의 기본연산/ 변수/ 벡터의 이해  (0) 2025.03.08

스푸핑(Spoofing) 

🔹 스푸핑이란?

  • 원래 뜻: 다른 사람을 흉내 내거나 속이는 행위
  • 공격자가 자신을 송·수신자인 것처럼 속여 정보를 가로채는 공격 기법

🔹 스니핑(Sniffing) vs. 스푸핑(Spoofing)

구분                                                             스니핑                                                   스푸핑

 

공격 방식 수동적 공격 (엿듣기) 능동적 공격 (속이기)
메시지 전달 여부 정상적으로 전달됨 메시지 전달이 방해됨
주요 특징 데이터를 몰래 수집 신원을 위장하여 속임

🔹 스푸핑의 종류

  1. 2계층(ARP 스푸핑)
    • 공격자가 같은 스위치 내에서 MAC 주소를 조작해 공격
  2. 3계층~7계층(고급 스푸핑 기법)
    • 내부·외부 네트워크에서도 공격 가능

🔹 ARP(Address Resolution Protocol)의 개념

  • 역할: IP 주소를 MAC 주소(네트워크 카드 고유 번호)로 변환하는 프로토콜
  • 특징: 네트워크에서 장치 간 통신을 위해 반드시 필요
  • 공격 방법: 공격자가 대상의 MAC 주소를 가로채서 잘못된 정보로 속임

🔹 ARP 동작 방식

 같은 네트워크(로컬)에서의 동작

  1. 단말 A → 단말 B의 MAC 주소를 모름, ARP 요청(Who has IPB?)
  2. 스위치 → 네트워크 전체에 요청을 브로드캐스트
  3. 단말 B → "내가 IPB야!" 응답 전송
  4. 단말 A → B의 MAC 주소를 ARP 테이블에 저장

 외부 네트워크(인터넷)에서의 동작

  • 단말 A가 단말 C와 통신하려면, 게이트웨이(Gateway)의 MAC 주소를 이용하여 ARP 응답을 받음

ARP 스푸핑(ARP Spoofing)

🔹 공격 원리

  • 공격자가 가짜 ARP 응답을 계속 보내서, 다른 컴퓨터의 ARP 테이블을 조작
  • 피해자가 잘못된 MAC 주소를 저장 → 데이터가 공격자에게 전달됨

🔹 공격 과정

  1. 공격자가 가짜 ARP 응답 전송 → 피해자의 ARP 테이블에 잘못된 MAC 주소 저장
  2. 피해자가 B에게 보내는 메시지공격자로 전달됨
  3. 공격자는 데이터를 가로채서 확인하거나 수정 후 다시 원래 수신자에게 전송
    • 즉, 공격자는 중간에서 엿듣기(스니핑) & 변조 가능

🔹 현상 및 탐지 방법
이상한 ARP 응답이 반복적으로 발생 (Wireshark 등 패킷 분석 툴로 확인)
ARP 테이블에 중복된 MAC 주소가 존재 (arp -a 명령어 사용)
네트워크 속도 저하 (공격자를 거쳐 메시지가 전송되기 때문)

🔹 방지 방법
정적인 ARP 테이블 관리 (변경 불가능한 MAC-IP 매핑 설정)
보안 프로그램 사용 (xarp, arpwatch 등으로 ARP 테이블 감시)
PC 및 서버 보안 강화 (보안 업데이트, 방화벽 설정)


IP 스푸핑(IP Spoofing)

🔹 공격 원리

  • 공격자가 자신의 IP 주소를 속여 신뢰할 수 있는 시스템인 척하는 공격
  • IP 기반 인증 시스템이 있을 경우 쉽게 속을 수 있음

🔹 공격 과정

  1. 공격자가 신뢰받는 서버(A)를 먼저 공격 → 무력화(DoS 공격 등)
  2. A의 IP 주소(IPA)로 변조한 패킷을 B 서버로 전송
  3. B는 신뢰하는 A의 IP라고 착각하고 공격자의 요청을 허용
    • 결과: 공격자는 인증 없이 B 서버에 접근 가능

🔹 방지 방법
IP 기반 인증 시스템을 사용하지 않음 (더 안전한 인증 방식 적용)
트러스트 관계를 맺은 서버 보안 강화 (보안 업데이트, 취약점 점검)
패킷 필터링(Packet Filtering)

    • 게이트웨이에서 출발지가 내부 IP인 외부 패킷을 차단

ICMP & DNS 스푸핑

 

ICMP (Internet Control Message Protocol)

ICMP는 네트워크 문제를 감지하고 해결하는 프로토콜

🔹 ICMP의 역할
네트워크 흐름 통제

      • 게이트웨이가 2개 이상인 네트워크에서 트래픽을 효율적으로 분배
      • 특정 게이트웨이가 과부하일 경우, ICMP 리다이렉트 메시지로 다른 게이트웨이로 유도

네트워크 문제 감지

      • 핑(Ping) 명령어 사용 → 네트워크 연결 상태 확인

ICMP 스푸핑(ICMP Spoofing)

공격자가 ICMP 메시지를 조작하여 자신을 게이트웨이처럼 속이는 공격

🔹 공격 과정

      1. 공격자가 ICMP 리다이렉트 메시지를 조작하여 A가 자신을 거치도록 만듦
      2. A가 C에게 보낼 메시지가 공격자에게 전달됨
      3. 공격자는 메시지를 가로채고 다시 원래 게이트웨이로 전송 (사용자는 공격 사실을 인지하기 어려움)

🔹 ICMP 스푸핑의 위험성
✔ 네트워크 내부뿐만 아니라 게이트웨이 바깥에서도 공격 가능
✔ 공격자가 데이터를 가로채고 변조할 수 있음

🔹 방지 방법
방화벽에서 ICMP 차단 (최근 게이트웨이들은 기본적으로 적용)
네트워크 보안 강화 (보안 장비 & 침입 탐지 시스템 활용)

 

DNS (Domain Name System)

DNS는 웹사이트의 도메인(URL)을 IP 주소로 변환하는 시스템

      • 예: www.google.com → 142.250.190.46

DNS 스푸핑(DNS Spoofing)

공격자가 가짜 IP 주소를 사용하게 만들어 사용자를 속이는 공격

🔹 공격 방식

      1. 사용자가 www.bank.com에 접속하려 함
      2. 공격자가 DNS 응답을 조작하여 가짜 은행 사이트로 접속하도록 유도
      3. 사용자가 가짜 사이트에 로그인하면, 개인정보 & 금융 정보 탈취

🔹 DNS 캐시 포이즈닝(DNS Cache Poisoning) 공격
DNS 서버의 캐시를 변조하여 잘못된 IP 주소를 저장
✔ 사용자가 요청할 때마다 가짜 사이트로 연결

🔹 공격 기법
랜덤 ID 기반 공격: 공격자가 무작위 ID 값을 대량 생성하여 DNS 서버를 속임
생일 공격(Birthday Attack): ID 충돌 확률을 높여 DNS 변조 성공률 증가

🔹 방지 방법
DNS 서버 소프트웨어 최신 버전 유지 (예: BIND 최신 업데이트)
DNSSEC(Domain Name System Security Extensions) 사용 (DNS 응답 암호화)
DNS 서버 설정 변경 (외부에서 요청하는 DNS 질의 제한)

네트워크 보안 공격 개요

1. ICMP (인터넷 제어 메시지 프로토콜)와 보안 위협

ICMP의 역할

ICMP는 네트워크의 원활한 흐름을 돕고, 문제 발생 시 알리는 역할을 함.

  • 네트워크 흐름을 조절하여 최적의 경로로 패킷을 전달 (ICMP 리다이렉트 메시지)
  • 네트워크 부하를 조절하여 특정 게이트웨이로 트래픽을 분산

ICMP 스푸핑(위장 공격)

  • 공격자가 ICMP 리다이렉트 메시지를 조작하여 사용자가 공격자의 네트워크를 통해 데이터를 전송하도록 유도
  • 이를 통해 스니핑(도청) 후 원래 목적지로 데이터를 전달하여 공격을 숨김
  • 최신 네트워크 장비는 ICMP 스푸핑 방지를 위해 방화벽 등으로 차단

2. DNS(도메인 네임 시스템) 스푸핑

DNS 역할

도메인(URL)을 입력하면 해당하는 IP 주소를 찾아주는 서비스

DNS 스푸핑 (DNS 변조 공격)

  • 공격자가 가짜 IP 주소를 제공하여 사용자가 악성 웹사이트에 접속하게 만듦
  • 예: 가짜 은행 사이트를 만들어 금융 정보를 탈취

DNS 스푸핑 기법

  1. 스니핑을 이용한 공격
    • 공격자가 사용자의 DNS 질의를 가로채서 변조된 응답을 보냄
    • UDP 방식의 특성(먼저 도착한 응답을 선택)을 이용
  2. DNS 캐시 포이즈닝
    • DNS 서버의 캐시에 잘못된 IP 정보를 저장하여 다수의 사용자가 악성 사이트로 접속하게 만듦
    • 생일 공격(Birthday Attack)을 활용하여 무작위 응답 ID를 대량 전송해 성공 확률 증가

DNS 스푸핑 방어 방법

  • DNS 서버 소프트웨어 최신 버전 유지 (예: BIND 업데이트)
  • DNSSEC(보안 확장 기능) 사용하여 응답을 암호화
  • 외부 DNS 질의에 대해 순환 질의(Recursive Query) 차단

3. 서비스 거부 공격 (DoS & DDoS)

DoS(서비스 거부) 공격이란?

  • 특정 서버나 네트워크에 과부하를 유발하여 정상적인 서비스를 방해하는 공격
  • DDoS(분산 서비스 거부) 공격은 여러 대의 좀비 PC를 이용해 다수의 경로에서 공격 수행

DoS & DDoS 공격 유형

  1. TCP SYN 플러딩
    • SYN 패킷을 과도하게 보내 연결 요청을 대기 상태로 유지하여 서버의 자원을 소진
    • 해결책: 대기 큐 크기 증가, 접속 대기 시간 단축, 침입 방지 시스템(IPS) 적용
  2. ICMP 플러딩 (스머프 공격)
    • ICMP echo 요청을 대량으로 전송하여 네트워크 대역폭을 고갈
    • 해결책: 브로드캐스트 차단, ICMP 트래픽 제한
  3. IP 플러딩
    • 랜드(LAND) 공격: 출발지와 목적지 IP를 동일하게 설정하여 네트워크를 무한 루프에 빠뜨림
    • 티어드랍(Teardrop) 공격: IP 패킷의 조각을 변조하여 재조합 시 오류 발생
  4. HTTP GET 플러딩
    • 다수의 HTTP 요청을 보내 웹 서버를 마비시키는 공격
    • 변종 공격:
      • HTTP CC 공격: 캐시 기능을 무력화하여 부하 증가
      • 동적 HTTP 요청 공격: 계속 URL을 변경해 방어 우회

서비스 거부 공격 방어 방법

  • 네트워크 트래픽 모니터링 및 이상 탐지 시스템 적용
  • 임계치 기반 차단 (특정 IP에서 일정량 이상의 요청이 들어오면 차단)
  • 최신 보안 패치 적용 (운영체제 및 서버 소프트웨어 업데이트)

'정보보안' 카테고리의 다른 글

정보보안 3주차  (0) 2025.03.27
정보보안 2주차  (0) 2025.03.21
정보보안 개요  (0) 2025.03.19

 

테이블 뷰는 두 개의 프로토콜을 가질 수 있으며, 프로토콜은 여러 개 채택할 수 있음

부모 프로토콜: 프로토콜은 하나의 부모를 가질 수 있으며, 부모가 없을 수도 있음

프로토콜 정의:

특정 클래스와 관련 없는 프로퍼티 및 메서드 선언의 집합.
함수(메서드)의 정의는 포함되지 않음.
기능이나 속성에 대한 설계도 역할.
프로토콜 채택: 클래스, 구조체, 열거형에서 프로토콜을 채택하여 해당 메서드를 구현해야 함

UITableViewDataSource프로토콜 필수 메소드: numberOfRowsInSection(row개수), cellForRowAt


UITableViewDataSource프로토콜 : 선택적 메서드

-numberOfSections(in:)
테이블 뷰에 섹션 수를 지정


-tableView(_:titleForHeaderInSection:)
각 섹션의 헤더에 표시될 텍스트를 지정

 

-tableView(_:titleForFooterInSection:)

각 섹션의 푸터에 표시될 텍스트를 지정


-tableView(_:canEditRowAt:)
셀을 삭제하거나 추가할 수 있는지 여부를 결정


-tableView(_:canMoveRowAt:)
셀의 순서를 변경할 수 있는지 여부를 결정

 


테이블 뷰에 사용하는 두번째 프로토콜은

TableView의 Delegate: UITableViewDelegate프로토콜

테이블 뷰 컨트롤러에는 테이블 뷰와 테이블 뷰 셀이 이미 들어가 있음

table view를 화면에 꽉차게 하는 법: add new constraints를 모두 0으로 맞추고, constrain to margins는 체크 해제

 

 

***필수 메소드를 구현하지 않았다는 뜻***
fix누른 후 수정된 코드

섹션은 1

행(row)5


import UIKit // UIKit 프레임워크를 임포트, UI 관련 요소가 포함되어 있음

class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
    
    // 테이블 뷰의 섹션 수를 반환하는 메서드
    func numberOfSections(in tableView: UITableView) -> Int {
        return 3 // 섹션 수를 3으로 설정
    }
    
    // 각 섹션에 있는 행(row)의 수를 반환하는 메서드
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 10 // 각 섹션에 10개의 행을 설정
    }
    
    // 각 행에 대한 셀을 구성하는 메서드
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = UITableViewCell.init(style: .default, reuseIdentifier: "myCell") // 기본 스타일의 셀 생성
        cell.textLabel?.text = indexPath.description // 셀의 텍스트를 섹션 번호와 행 번호로 설정
        return cell // 구성한 셀을 반환
    }
    
    // IBOutlet으로 연결된 테이블 뷰
    @IBOutlet weak var table: UITableView!
    
    // 뷰가 로드될 때 호출되는 메서드
    override func viewDidLoad() {
        super.viewDidLoad() // 부모 클래스의 viewDidLoad 호출
        table.delegate = self // 테이블 뷰의 delegate를 현재 클래스(ViewController)로 설정
        table.dataSource = self // 테이블 뷰의 dataSource를 현재 클래스(ViewController)로 설정
    }
}

indexPath.description //앞에는 섹션번호(0부터 시작), 뒤에는 row


import UIKit // UIKit 프레임워크를 임포트, UI 관련 요소가 포함되어 있음

class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
    
    // 테이블 뷰의 섹션 수를 반환하는 메서드
    func numberOfSections(in tableView: UITableView) -> Int {
        return 3 // 섹션 수를 3으로 설정
    }
    
    // 각 섹션에 있는 행(row)의 수를 반환하는 메서드
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 10 // 각 섹션에 10개의 행을 설정
    }
    
    // 각 행에 대한 셀을 구성하는 메서드
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = UITableViewCell.init(style: .subtitle, reuseIdentifier: "myCell") // 서브타입 스타일의 셀 생성
        cell.textLabel?.text = "\(indexPath.row)" // 셀의 주요 텍스트를 현재 행 번호로 설정
        cell.detailTextLabel?.text = indexPath.description // 셀의 세부 텍스트를 섹션과 행 정보를 설정
        cell.imageView?.image = UIImage(named: "smile.png") // 셀의 이미지 뷰에 "smile.png" 이미지를 설정
        return cell // 구성한 셀을 반환
    }
    
    // IBOutlet으로 연결된 테이블 뷰
    @IBOutlet weak var table: UITableView!
    
    // 뷰가 로드될 때 호출되는 메서드
    override func viewDidLoad() {
        super.viewDidLoad() // 부모 클래스의 viewDidLoad 호출
        table.delegate = self // 테이블 뷰의 delegate를 현재 클래스(ViewController)로 설정
        table.dataSource = self // 테이블 뷰의 dataSource를 현재 클래스(ViewController)로 설정
    }
}

 

 


import UIKit // UIKit 프레임워크를 임포트, UI 관련 요소가 포함되어 있음

// 이미지, 음식 이름, 가격을 저장하는 배열
var image = ["1.png", "2.png", "3.png", "4.png", "5.png"] // 음식 이미지 파일 이름 배열
var foodName = ["한식", "간식", "학식", "중식", "집밥"] // 음식 이름 배열
var price = ["1000", "2000", "3000", "4000", "5000"] // 음식 가격 배열

class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
    
    // 테이블 뷰의 섹션 수를 반환하는 메서드
    func numberOfSections(in tableView: UITableView) -> Int {
        return 6 // 섹션 수를 6으로 설정
    }
    
    // 각 섹션에 있는 행(row)의 수를 반환하는 메서드
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 5 // 각 섹션에 5개의 행을 설정
    }
    
    // 각 행에 대한 셀을 구성하는 메서드
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = UITableViewCell.init(style: .subtitle, reuseIdentifier: "myCell") // 서브타입 스타일의 셀 생성
        cell.textLabel?.text = foodName[indexPath.row] // 셀의 주요 텍스트를 음식 이름으로 설정
        cell.detailTextLabel?.text = price[indexPath.row] // 셀의 세부 텍스트를 가격으로 설정
        cell.imageView?.image = UIImage(named: image[indexPath.row]) // 셀의 이미지 뷰에 해당 음식 이미지 설정
        return cell // 구성한 셀을 반환
    }
    
    // IBOutlet으로 연결된 테이블 뷰
    @IBOutlet weak var table: UITableView!
    
    // 뷰가 로드될 때 호출되는 메서드
    override func viewDidLoad() {
        super.viewDidLoad() // 부모 클래스의 viewDidLoad 호출
        table.delegate = self // 테이블 뷰의 delegate를 현재 클래스(ViewController)로 설정
        table.dataSource = self // 테이블 뷰의 dataSource를 현재 클래스(ViewController)로 설정
    }
}

 

 


constraints확인

 

 


import UIKit // UIKit 프레임워크를 임포트, UI 관련 요소가 포함되어 있음

// 이미지, 음식 이름, 가격을 저장하는 배열
var image = ["1.png", "2.png", "3.png", "4.png", "5.png"] // 음식 이미지 파일 이름 배열
var foodName = ["1한식", "2간식", "3학식", "4중식", "5집밥"] // 음식 이름 배열
var price = ["1000", "2000", "3000", "4000", "5000"] // 음식 가격 배열

class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
    
    // 테이블 뷰의 섹션 수를 반환하는 메서드
    func numberOfSections(in tableView: UITableView) -> Int {
        return 6 // 섹션 수를 6으로 설정
    }
    
    // 각 섹션에 있는 행(row)의 수를 반환하는 메서드
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 5 // 각 섹션에 5개의 행을 설정
    }
    
    // 각 행에 대한 셀을 구성하는 메서드
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        // 재사용 가능한 셀을 큐에서 가져오고, MyTableViewCell 타입으로 캐스팅
        let cell = tableView.dequeueReusableCell(withIdentifier: "myCell", for: indexPath) as! MyTableViewCell
        
        // 셀의 라벨에 음식 이름을 설정
        cell.myLabel.text = foodName[indexPath.row]
        
        // 현재 행의 인덱스를 출력 (디버깅용)
        print(indexPath.description)
        
        return cell // 구성한 셀을 반환
    }
    
    // IBOutlet으로 연결된 테이블 뷰
    @IBOutlet weak var table: UITableView!
    
    // 뷰가 로드될 때 호출되는 메서드
    override func viewDidLoad() {
        super.viewDidLoad() // 부모 클래스의 viewDidLoad 호출
        table.delegate = self // 테이블 뷰의 delegate를 현재 클래스(ViewController)로 설정
        table.dataSource = self // 테이블 뷰의 dataSource를 현재 클래스(ViewController)로 설정
    }
}

cellForRowAt메소드: 각각에 대한 셀 정보를 만드는 일을 함

 




import UIKit//ui붙은거 전부 uikit안에 있음
var image = ["1.png", "2.png", "3.png", "4.png", "5.png"]
var foodName = ["1한식","2간식","3학식","4중식","5집밥"]
var price = ["1000","2000","3000","4000","5000"]
class ViewController: UIViewController ,UITableViewDelegate,UITableViewDataSource{
    
    func numberOfSections(in tableView: UITableView) -> Int {
        return 6
    }
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 5
    }
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        print(indexPath.description)
    }//테이블 뷰에서 셀을 눌렀을때 자동으로 호출되는 메소드
    
  func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
//        let cell = UITableViewCell.init(style:.subtitle, reuseIdentifier: "myCell")
//        cell.textLabel?.text=foodName[indexPath.row]
//        cell.detailTextLabel?.text = price[indexPath.row]
//        cell.imageView?.image = UIImage(named: image[indexPath.row])//indexPath.row라고 해야됨
      let cell=tableView.dequeueReusableCell(withIdentifier: "myCell", for: indexPath) as! MyTableViewCell
      cell.myLabel.text = foodName[indexPath.row]
      print(indexPath.description)
        return cell
    }
    //부모(UIViewController), 프로토콜(UITableViewDelegate)


    @IBOutlet weak var table: UITableView!
    override func viewDidLoad() {
        super.viewDidLoad()
        table.delegate = self
        table.dataSource = self
    }


}

didSelectRowAt:

테이블 뷰에서 셀을 눌렀을때 자동으로 호출되는 메소드

 

//
//  ViewController.swift
//  Table
//
//  Created by 소프트웨어컴퓨터 on 2025/04/02.
//

import UIKit//ui붙은거 전부 uikit안에 있음
var image = ["1.png", "2.png", "3.png", "4.png", "5.png"]
var foodName = ["1한식","2간식","3학식","4중식","5집밥"]
var price = ["1000","2000","3000","4000","5000"]
class ViewController: UIViewController ,UITableViewDelegate,UITableViewDataSource{
    
    func numberOfSections(in tableView: UITableView) -> Int {
        return 6
    }
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 5
    }
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        print(indexPath.description)
    }//테이블 뷰에서 셀을 눌렀을때 자동으로 호출되는 메소드
    
  func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
//        let cell = UITableViewCell.init(style:.subtitle, reuseIdentifier: "myCell")
//        cell.textLabel?.text=foodName[indexPath.row]
//        cell.detailTextLabel?.text = price[indexPath.row]
//        cell.imageView?.image = UIImage(named: image[indexPath.row])//indexPath.row라고 해야됨
      let cell=tableView.dequeueReusableCell(withIdentifier: "myCell", for: indexPath) as! MyTableViewCell
      cell.myLabel.text = foodName[indexPath.row]
      cell.myImage.image = UIImage(named: image[indexPath.row])
      print(indexPath.description)
        return cell
    }
    //부모(UIViewController), 프로토콜(UITableViewDelegate)


    @IBOutlet weak var table: UITableView!
    override func viewDidLoad() {
        super.viewDidLoad()
        table.delegate = self
        table.dataSource = self
    }


}

'Swift' 카테고리의 다른 글

iOS프로그래밍 실무 6주차  (0) 2025.04.09
iOS실무 프로그래밍 4주차  (0) 2025.03.26
iOS프로그래밍 실무 3주차  (0) 2025.03.19
iOS 프로그래밍 실무 2주차  (0) 2025.03.17
iOS 프로그래밍 실무 1주차  (0) 2025.03.05

컴포넌트 기반 구조 (Component-Based)

컴포넌트 : 독립적인 기능을 수행하는 작은 기능 단위 모듈

• 리액트에서는 모든 페이지가 컴포넌트로 구성

• 하나의 컴포넌트는 또 다른 여러개의 컴포넌트 조합으로 구성될 수 있음.

• 레고 블록을 조립하는 것처럼 컴포넌트를 조합해서 사용

 

함수와 리액트 컴포넌트

• 리액트 컴포넌트는 개념적으로 자바스크립트의 함수와 비슷

• 다만 리액트 컴포넌트는 속성(Props)을 입력 받아 이를 이용해 리액트 엘리먼트를 생성 하여 리턴해 준다는 것이 차이점

 

리액트 컴포넌트

리액트 컴포넌트는 객체지향 프로그래밍(OOP)의 클래스와 인스턴스 개념과 유사

  • 클래스 → 붕어빵 틀 (설계도)
  • 인스턴스(객체) → 실제 만들어진 붕어빵

*리액트에서 컴포넌트는 "붕어빵 틀" 같은 역할을 하고, 이를 사용해 만든 **화면 요소들이 "붕어빵"

 

Props

Props(Properties)는 리액트 컴포넌트의 속성을 뜻함
즉, 컴포넌트에 전달할 **데이터(값)**를 의미

컴포넌트에 전달되는 데이터를 담은 자바스크립트 객체
즉, 하나의 컴포넌트에 다른 props(속재료)를 넣어 다양한 결과를 만들 수 있음

 

*붕어빵 비유

  • 붕어빵 틀(컴포넌트)은 같지만,
    속 재료(Props)에 따라 팥붕, 크림붕, 치즈붕처럼 다양한 붕어빵(엘리먼트)이 나올 수 있음

Props의 특징

읽기 전용(Read-Only)

  • Props는 컴포넌트가 받기만 하고, 직접 수정할 수 없음
  • 붕어빵을 찍는 도중에 속재료를 바꿀 수 없는 것과 같음.

Props 값을 변경하려면?

  • 새로운 Props를 전달해야 함
  • 같은 Props가 들어오면 항상 같은 결과(엘리먼트)가 나와야 함.

 

Props 전달 방식 정리

JSX 문법을 사용하면 키-값 형태로 Props를 전달할 수 있고,
내부적으로는 자바스크립트 객체 형태로 변환

또한, Props의 값으로 컴포넌트도 전달 가능
(즉, 컴포넌트 안에 다른 컴포넌트를 넣을 수도 있음.)

 

 

1. JSX 기반 Props 전달 예시

function App() {
  return (
    <Profile
      name="소품" // name이라는 props에 "소품" 전달
      introduction="안녕하세요, 소품입니다." // introduction props 전달
      viewCount={1500} // 숫자 값도 전달 가능 (문자열 X, 중괄호 사용!)
    />
  );
}

위 코드는 내부적으로 아래처럼 변환됨!

const props = {
  name: "소품",
  introduction: "안녕하세요, 소품입니다.",
  viewCount: 1500
};

 

2. Props 값으로 컴포넌트 전달하기

function App() {
  return (
    <Layout
      width={2560} // width 속성 (숫자 값)
      height={1440} // height 속성 (숫자 값)
      header={
        <Header title="소품의 블로그입니다" /> // header props에 Header 컴포넌트 전달
      }
      footer={<Footer />} // footer props에 Footer 컴포넌트 전달
    />
  );
}

컴포넌트 안에 또 다른 컴포넌트를 props로 넘길 수도 있음

 

 


3. JSX 미사용 - React.createElement() 함수 기반

 

React.createElement() 기본 문법

React.createElement(
  type,         // 요소 타입 (예: 'div', 'span', 컴포넌트 등)
  props,        // 속성 객체 (예: { className: "my-class", id: "my-id" })
  ...children   // 자식 요소 (예: 문자열, 다른 React 요소 등)
);
const profileElement = React.createElement(Profile, {
  name: "소품",
  introduction: "안녕하세요, 소품입니다.",
  viewCount: 1500
});

React.createElement()를 사용하면 JSX 없이도 컴포넌트를 만들 수 있음
하지만 코드가 복잡해지기 때문에 JSX를 주로 사용

 

 

실습(댓글 컴포넌트)
Comment.jsx

import React from "react"; // 리액트 라이브러리 불러오기
import imgUserIcon from "../assets/user_icon.png"; // 사용자 아이콘 이미지 불러오기

// 🟢 함수 컴포넌트(Function Component)
// Comment 컴포넌트는 name(작성자), comment(댓글 내용)을 props로 받아 화면에 표시함
function Comment(props) {
    // 🟢 스타일 객체 정의
    const styles = {
        wrapper: { // 전체 컨테이너 스타일 (댓글 박스)
            margin: 8,
            padding: 8,
            display: "flex", // 가로 정렬
            flexDirection: "row", // 요소들을 가로로 배치
            border: "1px solid grey", // 회색 테두리
            borderRadius: 16, // 테두리를 둥글게
        },
        imageContainer: {}, // 프로필 이미지 컨테이너 (추후 스타일 추가 가능)
        image: { // 프로필 이미지 스타일
            width: 50, // 가로 크기
            height: 50, // 세로 크기
            borderRadius: 25, // 원형 모양
        },
        contentContainer: { // 텍스트 컨테이너 스타일
            marginLeft: 10, // 왼쪽 여백 추가
            display: "flex",
            flexDirection: "column", // 세로 정렬
            justifyContent: "center", // 중앙 정렬
        },
        nameText: { // 이름 스타일
            color: "black",
            fontSize: 16,
            fontWeight: "bold",
        },
        commentText: { // 댓글 내용 스타일
            color: "black",
            fontSize: 16,
        },
    };

    // 🟢 컴포넌트 렌더링 (Rendering)
     return (
        <div style={styles.wrapper}>
            <div style={styles.imageContainer}>
                <img src={imgUserIcon} alt="user icon" style={styles.image} />
            </div>
            <div style={styles.contentContainer}>
                <span style={styles.nameText}>{props.name}</span>
                <span style={styles.commentText}>{props.comment}</span>
            </div>
        </div>
    );
}

export default Comment;

 

CommentList.jsx(여러개의 댓글 보여주는 리스트)

import React from "react"; // 리액트 라이브러리 불러오기
import Comment from "./Comment"; // Comment 컴포넌트 불러오기

// 🟢 함수 컴포넌트(Function Component)
// 여러 개의 Comment 컴포넌트를 조합(컴포넌트 합성)하여 CommentList를 생성
function CommentList(props) {
    return (
        <div> {/* 전체 댓글 리스트 컨테이너 */}
            {/* 🟢 컴포넌트 합성 (Composition) */}
            {/* 여러 개의 Comment 컴포넌트를 조합하여 리스트를 만듦 */}
            <Comment name="홍길동" comment="안녕하세요. 댓글 남깁니다~" /> {/* 첫 번째 댓글 */}
            <Comment name="유재석" comment="리액트 재미있어요~!" /> {/* 두 번째 댓글 */}
            <Comment name="강민경" comment="저도 리액트 배워보고 싶어요!!" /> {/* 세 번째 댓글 */}
        </div>
    );
}

// 🟢 CommentList 컴포넌트 내보내기 (다른 파일에서 사용 가능)
export default CommentList;

 

 

index.js

// React 라이브러리를 불러옴 (React 컴포넌트를 만들기 위해 필요)
import React from 'react';

// ReactDOM을 불러옴 (React 요소를 실제 DOM에 렌더링하는 역할)
import ReactDOM from 'react-dom/client';

// CommentList 컴포넌트를 불러옴 (댓글 목록을 표시하는 컴포넌트)
import CommentList from './components/CommentList';

// HTML 문서에서 id가 'root'인 요소를 찾아 React의 렌더링 루트(root)로 지정
const root = ReactDOM.createRoot(document.getElementById('root'));

// React 컴포넌트를 화면에 렌더링하는 함수
root.render(
  <React.StrictMode> {/* StrictMode는 잠재적인 문제를 감지하고 경고를 표시하는 개발 모드 */}
    <CommentList /> {/* CommentList 컴포넌트를 화면에 렌더링 */}
  </React.StrictMode>
);

*root.render()란?

root.render()는 React 컴포넌트를 실제 화면에 그리는 역할

 

 

실습2(컴포넌트기반스터디룸현황UI 제작)

• 총5개의스터디룸현황표시

• 이미지, 이름, 상태 텍스트 출력

 

 

Room.jsx(방 정보 컴포넌트)

// React 라이브러리를 불러옴 (React 컴포넌트를 만들기 위해 필요)
import React from "react";

// 방 아이콘 이미지 파일을 불러옴
import imgUserIcon from "../assets/room_icon.png";

// Room(방) 정보를 나타내는 컴포넌트
function Room(props) {
    // 스타일을 정의하는 객체
    const styles = {
        wrapper: {
            margin: 20, // 외부 여백
            padding: 8, // 내부 여백
            width: 300, // 가로 크기
            height: 400, // 세로 크기
            display: "flex", // flexbox 사용
            flexDirection: "column", // 세로 방향 정렬
            alignItems: "center", // 가로 중앙 정렬
            justifyContent: "center", // 세로 중앙 정렬
            border: "1px solid grey", // 테두리 추가
            borderRadius: 16, // 모서리를 둥글게
        },
        imageContainer: {
            display: "flex",
            flexDirection: "column",
            alignItems: "center",
        },
        image: {
            width: 100, // 이미지 크기 (가로)
            height: 100, // 이미지 크기 (세로)
            borderRadius: 50, // 둥근 이미지 처리
        },
        contentContainer: {
            marginTop: 15,
            display: "flex",
            flexDirection: "column",
            alignItems: "center",
            justifyContent: "center",
        },
        nameText: {
            marginTop: 30,
            color: "black", // 글자색 검정
            fontSize: 20, // 글자 크기
            fontWeight: "1000", // 글자 두께
            textAlign: "center", // 글자 중앙 정렬
        },
        commentText: {
            marginTop: 30,
            color: "black",
            fontSize: 16,
            fontWeight: "bold",
            textAlign: "center",
        },
    };

    return (
        <div style={styles.wrapper}> {/* 방 정보를 감싸는 컨테이너 */}
            <div style={styles.imageContainer}> {/* 이미지 컨테이너 */}
                <img src={imgUserIcon} alt="room icon" style={styles.image} />
            </div>
            <div style={styles.contentContainer}> {/* 텍스트 정보 컨테이너 */}
                <span style={styles.nameText}>{props.name}</span> {/* 방 이름 */}
                <span style={styles.commentText}>{props.comment}</span> {/* 사용 가능 여부 */}
            </div>
        </div>
    );
}

// Room 컴포넌트를 다른 파일에서 사용 가능하도록 내보내기
export default Room;

 

RoomList.jsx(방 목록을 보여주는 컴포넌트)

// React 라이브러리를 불러옴
import React from "react";

// Room 컴포넌트를 불러옴 (각 방 정보를 표시)
import Room from "./Room";

// RoomList 컴포넌트 정의 (여러 개의 Room을 한 번에 렌더링)
function RoomList(props) {
    return (
        <div> {/* 전체 컨테이너 */}
            <div style={{ display: "flex", flexDirection: "row", flexWrap: "wrap" }}> 
                {/* flexbox를 사용하여 가로 방향으로 나열하고, 줄바꿈 가능하도록 설정 */}
                
                <Room name="[1F] Cube A" comment="사용가능" /> 
                {/* Room 컴포넌트를 사용하여 Cube A 정보를 전달 */}
                
                <Room name="[1F] Cube B" comment="사용가능" /> 
                
                <Room name="[1F] Cube C" comment="사용불가능" /> 
                
                <Room name="[1F] Cube D" comment="사용가능" /> 
                
                <Room name="[1F] Cube E" comment="사용가능" /> 
                
            </div>
        </div>
    );
}

// RoomList 컴포넌트를 다른 파일에서 사용 가능하도록 내보내기
export default RoomList;

 

index.js

// React 라이브러리를 불러옴 (React 컴포넌트를 만들기 위해 필요)
import React from 'react';

// ReactDOM을 불러옴 (React 요소를 실제 DOM에 렌더링하는 역할)
import ReactDOM from 'react-dom/client';

// RoomList 컴포넌트를 불러옴 (방 목록을 표시하는 컴포넌트)
import RoomList from './components/RoomList';

// HTML 문서에서 id가 'root'인 요소를 찾아 React의 렌더링 루트(root)로 지정
const root = ReactDOM.createRoot(document.getElementById('root'));

// React 컴포넌트를 화면에 렌더링하는 함수
root.render(
  <React.StrictMode> {/* StrictMode는 잠재적인 문제를 감지하고 경고를 표시하는 개발 모드 */}
    <RoomList /> {/* RoomList 컴포넌트를 화면에 렌더링 */}
  </React.StrictMode>
);

'React' 카테고리의 다른 글

리액트JSX  (0) 2025.03.25
소프트웨어 설계 3주차  (1) 2025.03.18
소프트웨어 설계 2주차  (1) 2025.03.18

서버 및 시스템 보안 개요

1. 서버 시장 및 보안 개요

  • 대형 서버 시장: Unix 기반, 클라우드 환경에서는 Linux 사용
  • 시스템 보안 주요 요소
    • 계정관리: ID/PW, OTP, 생체 인증 등
    • 세션 관리: 일정 시간 후 세션 종료
    • 접근 제어: 네트워크 내 보호
    • 권한 관리: 사용자별 적절한 권한 부여
    • 로그 관리: 시스템 활동 기록
    • 취약점 관리: 보안 결함 관리 
      • 2. 계정 관리
        • 인증 방법 (4가지)
          1. 알고 있는 것 → ID/PW
          2. 가지고 있는 것 → OTP, 출입카드
          3. 자신의 모습 → 지문, 홍채 인식
          4. 위치하는 곳 → 콜백 인증
        • 리눅스 계정 관리
          • 관리자(root) 계정
            • 시스템 전체 관리 가능 → 엄격한 보안 필요
            • 원격 접속 금지, 복잡한 비밀번호 사용
          • 일반 사용자 계정
            • 제한된 권한, 그룹을 통해 권한 관리
        • 원격 접속(root 계정 보안)
          • 기본적으로 root 원격 로그인 금지
          • SSH 서버 설정 방법
sudo apt update  
sudo apt install openssh-server  
systemctl status ssh

SSH 보안 설정 파일: /etc/ssh/sshd_config

  • 기본 포트(22) 변경
  • PermitRootLogin no 설정

2. 비밀번호 및 계정 파일 관리

  • 비밀번호 파일 보호 (shadow 파일 사용)
    • /etc/passwd → 계정 정보 저장
    • /etc/shadow → 암호화된 비밀번호 저장 (root만 접근 가능)
  • 비밀번호 관리 명령어
adduser [계정명]   # 사용자 추가  
passwd [계정명]   # 비밀번호 변경  
deluser [계정명]   # 사용자 삭제 (root 권한 필요)
  • 그룹 관리 명령어
addgroup [그룹명]  # 그룹 추가  
delgroup [그룹명]  # 그룹 삭제  
groups            # 현재 그룹 확인

3. 계정 전환 및 대리 실행

  • 사용자 전환 (su 명령어)
    • su [계정명] → 계정 전환 (디렉토리 유지)
    • su - [계정명] → 홈 디렉토리 변경 (새 로그인처럼)
  • 대리 실행 (sudo 명령어)
    • 특정 사용자 권한으로 명령 실행
    • 예시:
sudo -u user 명령어
  • sudoers 파일 수정 필요: /etc/sudoers

4. 패스워드 정책 및 보안

  • 좋은 패스워드 정책
    • 8자 이상, 숫자/대소문자/특수문자 포함
    • 60~90일마다 변경
    • 5회 이상 로그인 실패 시 계정 잠금
  • 6. 기타 계정 관리
    • 데이터베이스 계정 관리
      • MS-SQL 관리자 계정: sa
      • Oracle 관리자 계정: sys, system
      • system 계정은 DB 생성 불가
    • 응용 프로그램 계정 관리
      • 보안이 취약한 프로그램(TFTP) 대신 SFTP 사용
    • 네트워크 장비 계정 관리
      • 기본적으로 패스워드만 있으면 접근 가능
      • 사용자 모드 vs 관리자 모드 (enable 명령어 필요)
  • 예시)
# 사용자 추가
sudo adduser aa  # 'aa'라는 새 사용자 생성

# 비밀번호 변경
sudo passwd aa  # 'aa' 사용자의 비밀번호 변경

# 사용자 삭제 (홈 디렉토리는 유지)
sudo deluser aa  # 'aa' 사용자 삭제

# 사용자 삭제 (홈 디렉토리까지 삭제)
sudo deluser --remove-home aa  # 'aa' 사용자 및 홈 디렉토리 삭제

# 그룹 추가
sudo addgroup developers  # 'developers' 그룹 생성

# 그룹 삭제
sudo delgroup developers  # 'developers' 그룹 삭제

# 현재 사용자의 그룹 확인
groups  # 현재 로그인한 사용자가 속한 그룹 확인

# 특정 사용자의 그룹 확인
groups aa  # 'aa' 사용자가 속한 그룹 확인

# 사용자 계정 전환 (현재 디렉토리 유지)
su aa  # 'aa' 계정으로 전환

# 사용자 계정 전환 (홈 디렉토리로 이동)
su - aa  # 'aa' 계정으로 전환하며 홈 디렉토리로 이동

1. 세션 관리

 클라이언트와 서버 간의 연결 상태 유지

-세션이 필요한 이유

    • HTTP는 stateless(상태 저장 X), connectionless(1회성 연결) → 세션으로 해결
    • 일정 시간이 지나면 자동 종료하여 비인가자가 세션을 탈취하지 못하도록 보호

2. 패치 관리

 보안 취약점 해결 및 시스템 안정성 유지

-패치가 필요한 이유

      • 운영체제도 소프트웨어 → 새로운 버그 & 취약점 발생
      • 보안 공격(예: 해킹) 방지
      • (예시) Ubuntu LTS의 보안 업데이트로 19개 취약점 해결 (2023년)

3. 로그 관리

시스템 활동 기록 & 보안 점검

-로그의 역할

      • 시스템의 성능, 오류, 보안 정보 기록
      • 서버 관리자는 정기적으로 로그 분석
      • 해킹 시도 파악 및 대응

-로그 저장 규칙 (syslog.conf 예시)

*.emerg *      # 긴급(emergency) 로그는 모든 곳에 기록  
*.alert /var/adm/syslog.log  # 심각한(alert) 오류는 특정 파일에 저장  
authpriv.none /var/log/messages  # 보안 관련 로그는 남기지 않음  
authpriv.* /var/log/messages  # 보안 관련 로그를 저장

 

-syslog의 로그 수준

  • 모든 로그를 남기는 것은 성능과 부하 측면에서 현실적으로 불가능
  • 어느 이상의 심각한 수준에 대해 선별적으로 로그를 남기는  것을 권장

 

-syslog로그 파일의 종류 및 경로

대부분 로그 파일의 경로가 미리 약속되어 있음

 

-로그 관리 주의사항

  • 로그 파일 용량 정기 점검
  • 보관 주기 설정 (오래된 로그 삭제 or 백업)
  • 보안 침입 시도 감지 & 분석

4. 서비스 관리

 불필요한 서비스 차단 & 보안 강화

-이메일 서비스 (sendmail) 보안

  • 보안 공격의 주요 대상 (예: 버퍼 오버플로 공격)
  • 가능하면 비활성화
systemctl stop sendmail  # 서비스 정지  
systemctl disable sendmail  # 부팅 시 자동 실행 비활성화

 

  • 대체 서비스 추천: Postfix (SMTP) + Dovecot (IMAP/POP3) + 스팸 필터
  • 반드시 sendmail을 사용해야 하면 최신 버전 유지 (KISA 참고)

5. 안전한 소프트웨어 개발 (시큐어 코딩)

 소스코드에서 보안 취약점 제거

-대표적인 시큐어 코딩 가이드

  • CERT (국제 표준)
  • 행정안전부 소프트웨어 보안 개발 가이드 (boho.or.kr 참고)

6. 주요 보안 공격 & 대응 방법

① 메모리 버퍼 오버플로 공격

-개념

  • 허용된 크기보다 더 큰 데이터를 입력하여 메모리를 침범
  • 악성 코드 실행 가능

-취약한 C 코드 예제

#include <stdio.h>
#include <string.h>
int main(int argc, char* argv[]) {
    char buffer[10];  // 크기가 10인 버퍼
    strcpy(buffer, argv[1]);  // 버퍼 크기 체크 X (보안 취약)
    printf("%s\n", buffer);
}

 

-대응 방법
입력값 크기 검증 (strlen 사용)

if (argv[1] != NULL && strlen(argv[1]) < sizeof(buffer)) {
    strcpy(buffer, argv[1]);
}

-보안 라이브러리 사용 (strncpy 등)
-정적 분석 도구 활용 (valgrind, cppcheck 등)

 

② 포맷 스트링 공격

-개념

  • printf() 등에서 포맷 문자열(%x, %n 등)을 악용하여 메모리 조작

-취약한 C 코드 예제

#include <stdio.h>
int main(int argc, char* argv[]) {
    printf(argv[1]);  // 보안 취약 (사용자가 직접 입력값을 전달)
}

 

-대응 방법

printf("%s", argv[1]);  // 안전한 코드

-%n 등 위험한 포맷 스트링 사용 제한
- 정적 분석 도구 활용하여 취약점 검사

 

③ 경쟁 조건 (Race Condition) 공격

-개념

  • 여러 프로세스가 동시에 같은 파일/자원에 접근하여 보안 문제 발생
  • 예) 하나의 파일을 두 개의 프로세스가 동시에 수정하려고 할 때

-대응 방법
동기화 기법 사용 (Mutex, Lock)
파일 접근 시 순서 강제 설정

 

 

실습

vi로 test.c 생성 후 컴파일 방법

vi test.c //vi [편집 하려는 file의 경로]

i //현재 커서가 있는 곳에 글자 추가

#include<stdio.h>
int main(int argc, char* gray[])
{
	int a = 100;
    printf("%x \n", a);
    printf("%d \n", a);
    
    return 0;
}

//esc키  
:wq // 현재 편집하는 파일을 저장하고 vi에디터 종료
gcc test.c -o test
./test

//출력결과
//60
//100

'정보보안' 카테고리의 다른 글

정보보안 5주차  (0) 2025.04.03
정보보안 2주차  (0) 2025.03.21
정보보안 개요  (0) 2025.03.19

벡터에 적용 가능한 함수

# 코드 3
d <- c(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)  # 벡터 d를 생성
sum(d)  # d의 모든 요소를 더함
# [1] 55

sum(2 * d)  # d의 모든 요소를 2배로 늘린 후 더함
# [1] 110

length(d)  # d의 요소 개수를 반환
# [1] 10

mean(d[1:5])  # d의 첫 5개 요소의 평균을 계산
# [1] 3

max(d)  # d의 최대값을 반환
# [1] 10

min(d)  # d의 최소값을 반환
# [1] 1

sort(d)  # d를 오름차순으로 정렬
# [1]  1  2  3  4  5  6  7  8  9 10

sort(d)  # 동일한 결과를 다시 출력
# [1]  1  2  3  4  5  6  7  8  9 10

sort(d, decreasing = FALSE)  # d를 오름차순으로 정렬 (기본값)
# [1]  1  2  3  4  5  6  7  8  9 10

sort(d, decreasing = TRUE)  # d를 내림차순으로 정렬
# [1] 10  9  8  7  6  5  4  3  2  1

v1 <- median(d)  # d의 중앙값을 계산
v1  # 중앙값 출력
# [1] 5.5

v2 <- sum(d) / length(d)  # d의 평균을 계산 (합계 / 요소 개수)
v2  # 평균 출력
# [1] 5.5

벡터에 논리연산자 적용

# 코드 4
d <- c(1, 2, 3, 4, 5, 6, 7, 8, 9)  # 벡터 d를 생성

d >= 5  # d의 각 요소가 5 이상인지 확인
# [1] FALSE FALSE FALSE FALSE  TRUE  TRUE  TRUE  TRUE  TRUE

d[d > 5]  # d에서 5보다 큰 요소만 선택
# [1] 6 7 8 9

sum(d > 5)  # d에서 5보다 큰 요소의 개수를 계산
# [1] 4

sum(d[d > 5])  # d에서 5보다 큰 요소의 합을 계산
# [1] 30

d == 5  # d의 각 요소가 5와 같은지 확인
# [1] FALSE FALSE FALSE FALSE  TRUE FALSE FALSE FALSE FALSE

condi <- d > 5 & d < 8  # d의 요소가 5보다 크고 8보다 작은 조건
d[condi]  # 조건을 만족하는 d의 요소 선택
# [1] 6 7

리스트(List)

-다양한 데이터 유형의 요소 저장(동일값 저장, 여러 자료형을 혼합, 중복 가능)

-서로 다른 자료형의 값들을 1차원 배열에 저장하고 다룰 수 있도록 해주는 수단

- $,  [[]] 를 사용

ex)필통 안에 연필, 지우개, 색연필이 다 들어 있는 것처럼, 리스트는 다양한 데이터를 한 곳에 모아 놓은 것

# 코드 6
# 리스트 생성
my_list <- list(name = "john", age = 25, scores = c(88, 92, 95))  # 이름, 나이, 점수를 포함한 리스트 생성

# 리스트의 요소에 접근
print(my_list$name)  # name 요소 출력
# [1] "john"

print(my_list$age)  # age 요소 출력
# [1] 25

print(my_list$scores)  # scores 요소 출력
# [1] 88 92 95

팩터(factors)

-하나의 데이터형(동일값 없음, 중복X(단일 품목))

-벡터의 일존으로서 값의 종류가 정해져 있는 범주형 자료의 저장에 사용

-범주형 자료 예시: 성별, 혈액형, 선호 계절 등

ex)필통 안의 물건들을 "연필", "지우개", "색연필"로 분류한 것처럼, 팩터는 데이터를 특정 그룹으로 나누어 정리한 것

 

*R 데이터 구조 비교

: 리스트: 다양한 타입의 상자

팩터: 미리 정해진 카테고리를 가진 상자

# 코드 8
# 팩터 생성
colors <- factor(c("red", "blue", "green", "red", "blue"))  # 색상 벡터를 팩터로 변환

# 팩터 출력
print(colors)  # 생성된 팩터 출력
# [1] red   blue  green red   blue 
# Levels: blue green red  (팩터의 레벨이 출력됨)

# 팩터의 레벨 확인
levels(colors)  # 팩터의 레벨을 확인
# [1] "blue"  "green" "red"  

# 팩터의 각 레벨에 대한 빈도수 확인
table(colors)  # 각 레벨에 대한 빈도수를 계산
# colors
#  blue green   red 
#     2     1     2

매트릭스

-1차원 데이터: 몸무게 데이터와 같은 단일 주제의 데이터-> 벡터

-2차원 데이터: 키, 나이,와 같은 여러 주제의 데이터-> 매트릭스, 데이터프레임

                   -데이터 테이블의 모든 셀의 값들이 동일한 자료형->매트릭스

                   -자료형이 다른 값들로 구성->데이터프레임

 

 

 

 

-기존 매트릭스에 벡터를 추가해, 새로운 매트릭스 만들기

 

-cbind(벡터1, 벡터2)->벡터1과 벡터2를 열방향으로 결합

-rbind(벡터1, 벡터2)->벡터1과 벡터2를 행방향으로 결합

# 코드 11
x <- c(1:4)  # 벡터 x 생성
y <- c(5, 8)  # 벡터 y 생성
z <- matrix(1:20, nrow = 4, ncol = 5)  # 매트릭스 z 생성

m1 <- cbind(x, y)  # x와 y를 열 방향으로 결합하여 매트릭스 생성
m1  # 매트릭스 m1의 내용을 출력
#     [,1] [,2]
# [1,]    1    5
# [2,]    2    8
# [3,]    3   NA
# [4,]    4   NA

m2 <- rbind(x, y)  # x와 y를 행 방향으로 결합하여 매트릭스 생성
m2  # 매트릭스 m2의 내용을 출력
#     [,1] [,2] [,3] [,4]
# [1,]    1    2    3    4
# [2,]    5   NA   NA   NA

m4 <- cbind(z, x)  # 매트릭스 z와 벡터 x를 열 방향으로 결합
m4  # 매트릭스 m4의 내용을 출력
#      [,1] [,2] [,3] [,4] [,5] [,6]
# [1,]    1    5   9   13   17    1
# [2,]    2    6  10   14   18    2
# [3,]    3    7  11   15   19    3
# [4,]    4    8  12   16   20    4

# 코드 12
z <- matrix(1:20, nrow = 4, ncol = 5)  # 매트릭스 z 생성
z  # 매트릭스 z의 내용 출력
#      [,1] [,2] [,3] [,4] [,5]
# [1,]    1    5    9   13   17
# [2,]    2    6   10   14   18
# [3,]    3    7   11   15   19
# [4,]    4    8   12   16   20

# 특정 값 접근
z[2, 3]  # 2행 3열에 있는 값
# [1] 10

z[1, 4]  # 1행 4열에 있는 값
# [1] 13

z[2, ]   # 2행에 있는 모든 값
# [1]  2  6 10 14 18

z[, 4]   # 4열에 있는 모든 값
# [1] 13 14 15 16

# 코드 13
z[2, 1:3]  # 2행의 값 중 1~3열에 있는 값
# [1]  2  6 10

z[1, c(1, 2, 4)]  # 1행의 값 중 1, 2, 4열에 있는 값
# [1]  1  5 13

z[1:2, ]  # 1, 2행에 있는 모든 값
#      [,1] [,2] [,3] [,4] [,5]
# [1,]    1    5    9   13   17
# [2,]    2    6   10   14   18

z[, c(1, 4)]  # 1, 4열에 있는 모든 값
#      [,1] [,2]
# [1,]    1   13
# [2,]    2   14
# [3,]    3   15
# [4,]    4   16

# 코드 14
score <- matrix(c(90, 85, 69, 78, 85, 96, 49, 95, 90, 80, 70, 60), nrow = 4, ncol = 3)
rownames(score) <- c('John', 'Tom', 'Mark', 'Jane')  # 행 이름 설정
colnames(score) <- c('English', 'Math', 'Science')  # 열 이름 설정
score  # score 매트릭스 출력
#        English Math Science
# John       90   85      69
# Tom        78   85      96
# Mark       49   95      90
# Jane       80   70      60

# 코드 15
score['John', 'Math']  # John의 수학 성적
# [1] 85

score['Tom', c('Math', 'Science')]  # Tom의 수학, 과학 성적
# Math Science 
#   85      96

score['Mark', ]  # Mark의 모든 과목 성적
# English Math Science 
#      49   95      90

score[, 'English']  # 모든 학생의 영어 성적
# [1] 90 78 49 80

rownames(score)  # score의 행 이름
# [1] "John" "Tom"  "Mark" "Jane"

colnames(score)  # score의 열 이름
# [1] "English" "Math"    "Science"

colnames(score)[2]  # score의 열 이름 중 두 번째 값
# [1] "Math"

데이터프레임

-숫자형 벡터, 문자형 벡터 등 서로 다른 형태의 데이터를 2차원 데이터 테이블 형태로 묶을 수 있는 자료구조

-외관상으로는 매트릭스와 차이가 없지만 매트릭스에 저장되는 모든 값들이 동일한 자료형(ex 모두 숫자)인과는 달리 

데이터프레임에는 서로다른 자료형의 값들이 함께 저장(ex 성별, 나이 등)

 

 

-자료형이 같을 수도 있고 다를 수도 있는 여러 개의 벡터를 세로 방향으로 묶어 놓은 개념

data.frame(벡터1, 벡터2)

# 코드 16
city <- c("Seoul", "Tokyo", "Washington")  # 문자로 이루어진 벡터
rank <- c(1, 3, 2)  # 숫자로 이루어진 벡터
city.info <- data.frame(city, rank)  # 데이터프레임 생성
city.info  # city.info의 내용 출력
#          city rank
# 1      Seoul    1
# 2      Tokyo    3
# 3 Washington    2

 

iris데이터셋

-R에서 제공하는 실습용 데이터셋 중의 하나로 데이터프레임으로 되어 있음

-150그루의 붓꽃에 대한 4개의 분야의 측정데이터와 품목 정보를 결합하여 만든 데이터셋(통계학과 머신러닝에서 널리 사용되는 유명한 실습용 데이터셋)

# 코드 17
iris[, c(1, 2)]  # 1, 2열의 모든 데이터
#   Sepal.Length Sepal.Width
# 1           5.1          3.5
# 2           4.9          3.0
# 3           4.7          3.2
# 4           4.6          3.1
# 5           5.0          3.6

iris[, c(1, 3, 5)]  # 1, 3, 5열의 모든 데이터
#   Sepal.Length Petal.Length Species
# 1           5.1          1.4  setosa
# 2           4.9          1.4  setosa
# 3           4.7          1.3  setosa
# 4           4.6          1.5  setosa
# 5           5.0          1.4  setosa

iris[, c("Sepal.Length", "Species")]  # 1, 5열의 모든 데이터
#   Sepal.Length Species
# 1           5.1  setosa
# 2           4.9  setosa
# 3           4.7  setosa
# 4           4.6  setosa
# 5           5.0  setosa

iris[1:5, ]  # 1~5행의 모든 데이터
#   Sepal.Length Sepal.Width Petal.Length Petal.Width Species
# 1           5.1          3.5          1.4         0.2  setosa
# 2           4.9          3.0          1.4         0.2  setosa
# 3           4.7          3.2          1.3         0.2  setosa
# 4           4.6          3.1          1.5         0.2  setosa
# 5           5.0          3.6          1.4         0.2  setosa

iris[1:5, c(1, 3)]  # 1~5행의 데이터 중 1, 3열의 데이터
#   Sepal.Length Petal.Length
# 1           5.1          1.4
# 2           4.9          1.4
# 3           4.7          1.3
# 4           4.6          1.5
# 5           5.0          1.4

'R' 카테고리의 다른 글

조건문, 반복문, 함수  (0) 2025.04.05
R의 기본연산/ 변수/ 벡터의 이해  (0) 2025.03.08

ViewController.swift

//
//  ViewController.swift
//  aa
//
//  Created by 소프트웨어컴퓨터 on 2025/03/26.
//

import UIKit // UIKit 프레임워크를 임포트하여 UI 요소를 사용할 수 있도록 함

// ViewController 클래스를 정의, UIViewController를 상속받음
class ViewController: UIViewController {

    // 뷰가 메모리에 로드될 때 호출되는 메서드
    override func viewDidLoad() {
        super.viewDidLoad() // 부모 클래스의 viewDidLoad() 메서드를 호출하여 기본 초기화 수행
        
        // 뷰가 로드된 후 추가적인 설정을 할 수 있는 곳
        // 여기에서 UI 요소를 초기화하거나 데이터를 설정할 수 있음
    }
}

 

 

AppDelegate.swift

//
//  AppDelegate.swift
//  aa
//
//  Created by 소프트웨어컴퓨터 on 2025/03/26.
//

import UIKit // UIKit 프레임워크를 임포트하여 UI 관련 기능을 사용 가능하게 함

@main // 이 클래스가 프로그램의 진입점임을 나타냄
class AppDelegate: UIResponder, UIApplicationDelegate { // AppDelegate 클래스 정의, UIResponder 및 UIApplicationDelegate 프로토콜을 준수

    // 애플리케이션이 시작될 때 호출되는 메서드
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // 애플리케이션이 실행된 후 사용자 정의를 위한 오버라이드 포인트
        return true // 애플리케이션이 성공적으로 시작되었음을 나타냄
    }

    // MARK: UISceneSession Lifecycle // UISceneSession 생명주기 관련 메서드 구분을 위한 주석

    // 새로운 장면 세션이 생성될 때 호출되는 메서드
    func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
        // 새로운 장면을 생성하기 위한 설정을 선택하는 메서드
        return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) // 기본 설정을 사용하여 새로운 장면 구성 반환
    }

    // 사용자가 장면 세션을 폐기할 때 호출되는 메서드
    func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {
        // 애플리케이션이 실행 중이지 않을 때 세션이 폐기되면, 이 메서드가 호출됨
        // 폐기된 장면에 특정한 자원을 해제하는 데 사용
    }
}
 func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
  • _의 사용: _를 사용하면 외부 이름 생략 가능

 

  • configurationForConnecting connectingSceneSession: 

      -configurationForConnecting은 메서드를 호출할 때 사용되는 argument로, 메서드의 목적을 명확히 설명

      -connectingSceneSession은 메서드 정의에서 사용되는 parameter로, 메서드 내부에서 해당 값을 참조하는 데 사용

 

  • options는 application(_:configurationForConnecting:options:) 메서드의 매개변수 중 하나로, 새로운 장면 세션을 연결할 때 사용할 수 있는 추가적인 설정이나 정보를 포함하는 객체

 

  •  Set<UISceneSession>: 제네릭 (집합)

 

 

 

  • 함수에서 정의부의 값을 매개변수, 호출시 값을 아규먼트라고 부름

        함수 내부에서 사용: parameter

        argument함수 에서 호출할때

 

  • 함수 명: application(_:configurationForConnecting:options:)

1. 아규먼트 레이블 (Argument Label)

 

  • _ application:
    외부에서 이 매개변수를 호출할 때 사용할 아규먼트 레이블입니다. _가 붙어있어 이 레이블이 생략된다는 것을 의미합니다. 즉, 호출 시에는 application이라는 이름을 사용하지 않고, 단순히 매개변수의 타입만 사용합니다.
  • configurationForConnecting:
    이 매개변수의 외부 이름으로, 메서드를 호출할 때 사용됩니다. 이 이름은 메서드의 목적을 설명합니다.
  • options:
    이 매개변수의 외부 이름으로, 메서드 호출 시 사용됩니다. 여기서도 호출 시 options라는 이름이 사용됩니다.

 

2. 아규먼트 네임 (Argument Name)

 

  • application:
    메서드 본문에서 사용할 내부 이름입니다. UIApplication 타입의 객체를 참조하기 위해 사용됩니다.

 

  • connectingSceneSession:
    메서드 본문 내에서 사용할 내부 이름입니다. UISceneSession 타입의 객체를 참조합니다.

 

  • options:
    메서드 본문 내에서 사용할 내부 이름입니다. UIScene.ConnectionOptions 타입의 객체를 참조합니다.

 

3. 자료형 (Type)

 

  • UIApplication:
    application 매개변수의 타입입니다. 현재 실행 중인 애플리케이션의 인스턴스를 나타냅니다.

 

  • UISceneSession:
    connectingSceneSession 매개변수의 타입입니다. 새로운 장면 세션을 나타내며, 사용자가 새 장면을 여는 과정에서 생성되는 객체입니다.

 

  • UIScene.ConnectionOptions:
    options 매개변수의 타입입니다. 장면 세션을 연결할 때 사용할 수 있는 추가적인 설정이나 정보를 포함하는 객체입니다.

 

4. 반환 타입 (Return Type)

 

  • -> UISceneConfiguration:
    이 메서드가 반환하는 타입입니다. UISceneConfiguration 객체를 반환하여 새로운 장면의 설정을 정의합니다.

import UIKit

//함수 정의
func sayHello() -> Void{//리턴값이 없는 경우는 ->void는 생략 가능
    print("Hello")
}
sayHello()//호출

 

 

 

import UIKit

//함수 정의
func sayHello() -> Void{//리턴값이 없는 경우는 ->void는 생략 가능
    print("Hello")
}
sayHello()//호출

//int add(int x, int y) { //C, C++
//    return(x+y);
//}
//add(10,20);

// commend+/주석처리

func add(x:Int, y:Int) -> Int {
    return x+y
}
add(x: 10,y: 20)

 

func add(x:Int, y:Int) -> Int {
    return x+y
}
print(add(x: 10,y: 20))//30
var x = 10
print(type(of: x))//Int
print(type(of: add))// (Int, Int) -> Int

함수의 타입 (자료형,자료형,…) -> 리턴형 (Int, Int) -> Int 리턴형이 Void형이면 ()

 

 

func add(xx x:Int, yy y:Int) -> Int {//첫번째는 호출(argument label), 두번째는 함수 정의 (parameter name)
    return x+y
}
print(add(xx: 10,yy: 20))//30


func add(x:Int, y:Int) -> Int {//하나만 있을땐, 내부 외부 전부 같은거
    return x+y
}
print(add(x: 10,y: 20))//30

//#1
func add(xx x:Int, yy y:Int) -> Int {//첫번째는 호출(argument label), 두번째는 함수 정의 (parameter name)
    print(#function)//add(xx:yy:)
    return x+y
}
print(add(xx: 10,yy: 20))//30

//#2
func add(x:Int, y:Int) -> Int {//하나만 있을땐, 내부 외부 전부 같은거
    print(#function)//add(x:y:)
    return x+y
}
print(add(x: 10,y: 20))//30

//#3
func add(_ x:Int, _ y:Int) -> Int {//
    print(#function)//add(_:_:)
    return x+y
}
print(add(1,2))//3

//#4
func add(_ x:Int, with y:Int) -> Int {//
    return x+y
}
print(add(5,with:3))//8

함수의 이름은 다 다름

:의 개수가 매개변수의 개수다, 콤마 없음

 

class Man {
    var age : Int = 0
    //클래스 안에 있는 변수를 프로퍼티라고 하는데 값이 있어야됨, =0 이런식으로 (값이 없으면 에러남)
    //옵셔널 방법도 있음 var age : Int?
}
var x : Int
var han : Man = Man()
han.age = 10
print(han.age)//10

 

메서드 정의

class Man{
    var age : Int = 1
    var weight : Double = 3.5
    //init(){}
    func display(){ //인스턴스 메서드
        print("나이=\(age), 몸무게=\(weight)")
    }
}

var han : Man = Man()
han.display()

 

class Man { // 'Man'이라는 클래스 정의
    var age: Int // 'age'라는 저장 속성 정의 (정수형)
    var weight: Double // 'weight'라는 저장 속성 정의 (실수형)
    
    // 'display' 메서드 정의 - Man 객체의 나이와 몸무게를 출력
    func display() {
        print("나이=\(age), 몸무게=\(weight)") // 나이와 몸무게를 출력
    }
    
    // Designated initializer - Man 객체를 초기화하는 메서드
    init(age: Int, weight: Double) {
        self.age = age // 매개변수 'age'를 저장 속성 'age'에 할당
        self.weight = weight // 매개변수 'weight'를 저장 속성 'weight'에 할당
    } // 초기화 메서드는 객체 생성 시 자동으로 호출됨
}

// 'Man' 클래스의 인스턴스를 생성하고 'han'이라는 변수에 할당
var han: Man = Man(age: 20, weight: 35.5)

// 'han' 인스턴스의 'display' 메서드를 호출하여 나이와 몸무게를 출력
han.display()

designated initializer는 클래스의 주요 초기화 메서드로, 클래스의 모든 속성을 초기화하는 책임을 가진 초기화자

 

class Man{
    var age : Int
    var weight : Double
    func display(){
        print("나이=\(age), 몸무게=\(weight)")
    }
    init(age: Int, weight : Double){
        self.age = age
        self.weight = weight
    } //designated initializer//init메소드는 자동 호출
}
class Student : Man{
    
}
var kim : Student = Student(age: 25, weight: 55.5)
//print(kim.age)//25
kim.display()//나이=25, 몸무게=55.5
var han : Man = Man(age: 20, weight: 35.5)
han.display()//나이=20, 몸무게=35.5

 

class Man{
    var age : Int
    var weight : Double
    func display(){
        print("나이=\(age), 몸무게=\(weight)")
    }
    init(age: Int, weight : Double){
        self.age = age
        self.weight = weight
    }
}
class Student : Man {
    var name : String
    func displayS() {
        print("이름=\(name), 나이=\(age), 몸무게=\(weight)")
    }
    init(age: Int, weight : Double, name : String){
        self.name = name
        super.init(age:age, weight:weight) //이 줄을 안쓰면?
    }//error:'super.init' isn't called on all paths before returning from initializer
} //자식 클래스에서 designated initializer를 만들면 부모 init()상속 안됨
var lee : Student = Student(age:20,weight:65.2,name:"홍길동")
lee.displayS()
lee.display()

 

 self.name = name
        super.init(age:age, weight:weight) //이 줄을 안쓰면?

위 두줄의 순서를 바꾸면 안되는 이유: self.name = name을 super.init(...) 호출 이전에 수행하면, 초기화되지 않은 self를 사용하는 것이 되어 에러가 발생합니다. 따라서 반드시 부모 클래스의 초기화를 먼저 수행한 후, 자식 클래스의 속성을 초기화해야 함

 

class Man { // 'Man'이라는 클래스 정의
    var age: Int // 'age'라는 저장 속성 정의 (정수형)
    var weight: Double // 'weight'라는 저장 속성 정의 (실수형)
    
    // 'display' 메서드 정의 - Man 객체의 나이와 몸무게를 출력
    func display() {
        print("나이=\(age), 몸무게=\(weight)") // 나이와 몸무게를 출력
    }
    
    // Designated initializer - Man 객체를 초기화하는 메서드
    init(age: Int, weight: Double) {
        self.age = age // 매개변수 'age'를 저장 속성 'age'에 할당
        self.weight = weight // 매개변수 'weight'를 저장 속성 'weight'에 할당
    }
}

class Student: Man { // 'Student'라는 클래스 정의, 'Man' 클래스를 상속받음
    var name: String // 'name'이라는 저장 속성 정의 (문자열형)
    
    // display 메서드를 오버라이드 - Student 객체의 이름, 나이, 몸무게를 출력
    override func display() {
        // 오버라이드의 목적: 부모 클래스의 'display' 메서드를 재정의하여
        // Student 클래스에 맞는 출력 형식으로 변경
        print("이름=\(name), 나이=\(age), 몸무게=\(weight)") // 이름, 나이, 몸무게를 출력
    }
    
    // Designated initializer - Student 객체를 초기화하는 메서드
    init(age: Int, weight: Double, name: String) {
        self.name = name // 매개변수 'name'을 저장 속성 'name'에 할당
        super.init(age: age, weight: weight) // 부모 클래스의 designated initializer 호출
        // 위 줄이 없으면 에러 발생: 'super.init'이 모든 경로에서 호출되지 않음
    }
}

// 'Student' 클래스의 인스턴스를 생성하고 'lee'라는 변수에 할당
var lee: Student = Student(age: 20, weight: 65.2, name: "홍길동")

// 'lee' 인스턴스의 'display' 메서드를 호출하여 이름, 나이, 몸무게를 출력
lee.display()

override func display() { ... } 
// 오버라이드의 목적: 부모 클래스의 'display' 메서드를 재정의하여
// Student 클래스에 맞는 출력 형식으로 변경

 


델리게이트(Delegate) 디자인 패턴은 한 객체가 다른 객체에게 특정 작업을 맡기는 방법

 

프로토콜(protocol) :특정 클래스와 관련없는 함수(메서드)들의 선언 집합

 

Swift에서 프로토콜의 속성(property)은 접근 제어를 명시해야됨

즉, 속성이 읽기 전용인지(get), 쓰기 가능인지(get set) 명시해야됨
따라서, Runnable 프로토콜의 x 속성에 대해 { get } 또는 { get set }을 추가해야 함

 

메시지 "Type 'Man' does not conform to protocol 'Runnable'"는 Man 클래스가 Runnable 프로토콜의 요구사항을 충족하지 않음을 나타냄

Runnable 프로토콜은 x라는 읽기/쓰기 속성과 run()이라는 메서드를 요구하고 있음

 

protocol Runnable { // 'Runnable'이라는 프로토콜 정의
    var x: Int { get set } // 'x'라는 읽기/쓰기 가능한 속성 요구
    func run() // 'run()'이라는 메서드 요구
}

class Man: Runnable { // 'Man' 클래스가 'Runnable' 프로토콜을 준수 ,adopt
    var x: Int = 1 // 'x' 속성을 구현, 기본값 1로 초기화,준수, conform
    
    func run() {print("Runnnn~~~~")}// 'run()' 메서드를 구현, 메서드가 호출되면 출력, 준수,conform
   
}

var kim: Man = Man() // 'Man' 클래스의 인스턴스 생성
kim.run() // 'kim'의 'run()' 메서드 호출, "Runnnn~~~~" 출력

프로토콜은 여러개가 올 수 있음

 

 

상속과 프로토콜을 사용한 간단한 예제

// 'Workable'이라는 프로토콜 정의
protocol Workable {
    func work() // 일하기 메서드 요구
}

// 기본 클래스 'Employee' 정의
class Employee: Workable {
    var name: String // 직원의 이름
    var salary: Double // 직원의 급여
    
    // 초기화 메서드
    init(name: String, salary: Double) {
        self.name = name // 이름 초기화
        self.salary = salary // 급여 초기화
    }
    
    // 프로토콜의 'work()' 메서드 구현
    func work() {
        print("\(name) is working.") // 직원이 일하고 있음을 출력
    }
}

// 'Manager' 클래스가 'Employee' 클래스를 상속
class Manager: Employee {
    // 사장만의 고유 메서드
    func manage() {
        print("\(name) is managing the team.") // 사장이 팀을 관리하고 있음을 출력
    }
}

// 'PartTimeWorker' 클래스가 'Employee' 클래스를 상속
class PartTimeWorker: Employee {
    // 아르바이트생만의 고유 메서드
    func serve() {
        print("\(name) is serving customers.") // 아르바이트생이 고객을 서비스하고 있음을 출력
    }
}

// 사용 예: 'Manager' 인스턴스 생성
let boss = Manager(name: "사장님", salary: 5000)
boss.work() // "사장님 is working." 출력
boss.manage() // "사장님 is managing the team." 출력

// 사용 예: 'PartTimeWorker' 인스턴스 생성
let worker = PartTimeWorker(name: "아르바이트생", salary: 2000)
worker.work() // "아르바이트생 is working." 출력
worker.serve() // "아르바이트생 is serving customers." 출력

 

'Swift' 카테고리의 다른 글

iOS프로그래밍 실무 6주차  (0) 2025.04.09
iOS프로그래밍 실무 5주차  (0) 2025.04.02
iOS프로그래밍 실무 3주차  (0) 2025.03.19
iOS 프로그래밍 실무 2주차  (0) 2025.03.17
iOS 프로그래밍 실무 1주차  (0) 2025.03.05

JSX

• A syntax extension to Javascript (자바스크립트 확장 문법)

• Javascript + XML / HTML

const element = <h1>Hello, wordl!</h1>;

 

 

역할

• JSX는 내부적으로 XML/HTML 코드를 자바스크립트로 변환함.

• createElement 함수 : JSX 코드를 자바스크립트 코드로 변환하는 역할

// JSX를 사용한 코드
const element = (
<h1 className=""greeting">
Hello, world!
</h1>;
)



// JSX를 사용하지 않은 코드
const element = React.createElement(
'h1',
{ className: 'greeting' },
'Hello, world!'
)

 

 

 

• React.createElement()의 결과로 자바스크립트 객체(엘리먼트)가 생성됨

React.createElement(
type,
[props],
[...children]
)

 

① type - 엘리먼트의 유형 ,<div> <span>같은 HTML 태그나 다른 리액트 컴포넌트

② props – 엘리먼트의 속성. class, style, src 및 onclick 등의 태그 속성

③ children - 현재 엘리먼트가 포함하는 자식 엘리먼트

 

 

JSX 사용 여부에 따른 코드 비교 

// JSX 사용함
<div>Hello, {name}</div>
// JSX 사용 안함
React.createElement('div', null, `Hello, ${name}`);

 

장점

-코드 간결해짐

-가독성 향상

-보안성 향상: 입력창에 문자나 숫자 같은 소스코드를 입력해, 코드가 실행되도록 만드는 injection Attack해킹 방법을 방어 가능해짐( ex) ID 입력 창에 자바스크립트 코드 )

 

 

ReactDOM은 렌더링하기 전에 임베딩(삽입)된 값을 모두 문자열로 변환

const title = response.pontentiallyMaliciousInput;

//이 코드는 안전
const element = <h1>{title}</h1>;

 

• 위 예시 코드는 JSX 코드에서 중괄호를 사용해서 title 변수를 삽입하며, 이는 보안 위험이 발생할 수 있는 코드의 삽입 가능성이 있음

. • XSS(Cross-site-scripting) 공격을 방어할 수 있음

 

 

 

중괄호를 이용한 변수 참조

const name = "hh";
const element = <h1>Hello, {name}</h1>;
ReactDOM.render (
element,
document.getElementById('root’)
);

 

 

중괄호를 이용한 함수 호출

function formatterName(user) {
return user.firstName + ' ' + user.lastName;
}
const user = {
firstName: 'Hyunwoo',
lastName: 'Nam'
}
const element = (
<h1>
Hello, { formatterName(user) }
</h1>
);
ReactDOM.render (
element, document.getElementById('root')
);

 

태그의 속성에 값을 넣는 방법

 

• 큰따옴표 사이에 문자열을 넣는 방식

const element = <div tabIndex="0"></div>;

 

 

• 중괄호 사이에 자바스크립트 코드를 넣는 방식

const element = <img src={user.avatarUrl}></img>;

 

 

자식(children)을 정의하는 방법

• 평소 HTML 코드와 같이 상위 태그가 하위 태그를 둘러싸도록 작성하면 됨.

const element = (
<div>
<h1>안녕하세요.</h1>
<h2>반갑습니다.</h2>
</div>
);

 

 

 

실습

-저번에 만들어둔 파일))

 

-src안에 components폴더 생성

-components폴더 안에 Book.jsx, Library.jsx파일 생성

 

Book.jsx

import React from "react"; //리액트 라이브러리 가져오기, ui를 구축하는데 사용용

function Book(props){//Book 이라는 함수 + 컴포넌트 정의(props는 부모 컴포넌트에서 전달된데이터 포함)
    return(//jsx를 반환하기 시작, jsx는 js와 html결합한 문법법
        <div>
        <h1>{`이 책의 이름은 ${props.name}입니다.`}</h1>
        <h2>{`이 책은 총 ${props.numOfPage}페이지로 이루어져 있습니다.`}</h2>
        </div>
    );
}
export default Book;//이 컴포넌트를 다른 파일에서 사용할 수 있도록 내보냄냄

 

 

 

Library.jsx

import React from "react";//react 라이브러리 가져옴
import Book from "./Book";//Book컴포넌트를 현재 파일로 가져옴

function Library(props) {
    return(
        <div>
            <Book name="처음 만난 파이썬" numOfPage={300} />
            <Book name="처음 만난 AWS" numOfPage = {400} />
            <Book name ="처음 만난 리액트" numOfPage={500} />

        </div>
    );
}

export default Library;

 

 

index.js수정

import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';

import Library from './components/Library' //Library컴포넌트 불러오기기
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <Library /> //이 부분도 불러올 컴포넌트인 Library로 변경경
  </React.StrictMode>
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

 

 

컴포넌트 구조

 

 

 

  • 엘리먼트(Element)란?
    • "요소" 또는 "성분"을 의미하며, 리액트 앱을 구성하는 가장 작은 단위.
    • 기존 웹에서 사용하던 DOM 엘리먼트 개념과 유사.
  • 리액트 엘리먼트의 역할
    • 화면에 보이는 UI를 기술하는 자바스크립트 객체.
    • 실제로 웹 브라우저에서 렌더링될 DOM 엘리먼트를 생성하는 기반이 됨.
  • 엘리먼트와 DOM의 관계
    • 리액트 엘리먼트: 화면에 어떤 UI가 나타날지 정의하는 객체.
    • DOM 엘리먼트: 리액트 엘리먼트의 정보를 바탕으로 실제 브라우저에서 렌더링되는 요소.
  • 왜 ‘엘리먼트’라고 부를까?
    • 리액트 초기에는 이를 **Descriptor(설명자)**라고 불렀음.
    • 하지만 결국 DOM 엘리먼트를 나타내므로, 개념적 통일성을 위해 **엘리먼트(Element)**라는 용어를 사용하게 됨.
l const element = <h1>Hello, world!</h1>;

• 리액트의 createElement( ) 함수에 의해 리액트 엘리먼트가 생성됨

 

 

 

엘리먼트의 형태

• 리액트 엘리먼트는 자바스크립트 객체 형태로 존재

• 컴포넌트 유형, 속성, 자식(children)에 대한 정보를 포함하는 자바스크립트 객체

 

 

리액트 엘리먼트

{
	type: 'button',
    props: {
    	className: 'bg-green',
        children: {
        	type:'b',
            props: {
            	children: 'Hello, element!'
                }
            }
        }
}

 

 

DOM엘리먼트

<button class = 'bg-green'>
	<b>
    	Hello, element!
    <b>
 </button>

 

 

 

createElement( ) 함수

• 함수의 실행 결과로 자바스크립트 객체(엘리먼트)가 생성됨

React.createElement(
type,
[props],
[...children]
)

① type - 엘리먼트의 유형 ,<div> <span>같은 HTML 태그나 다른 리액트 컴포넌트

② props – 엘리먼트의 속성. class, style, src 및 onclick 등의 태그 속성

③ children - 현재 엘리먼트가 포함하는 자식 엘리먼트

 

 

 

• createElement( ) 함수의 동작 과정

• Buttton 컴포넌트를 포함하는 ConfirmDialog 컴포넌트 구성

function Button(props){
	return(
    	<button className = { `bg-${props.color}`}>
        	<b>
            	{props.children}
            </b>
        </button>
     )
 }
 
 
 function ConfirmDialog(props) {
 	return(
    	<div>
        	<p>내용을 확인하셨으면 확인 버튼을 눌러주세요.</p>
            <Button color = 'green'>확인</button>
        </div>
        )
 }

 

 

createElement( ) 함수의 동작 과정 – ConfirmDialog 컴포넌트

• 현 단계에서 Button 컴포넌트는 아직 HTML 태그가 아니기 때문에 DOM 렌더링이 안됨.

 

{
	type: 'div'
    props:{
    	children: [
        {
        	type: 'p',
            props: {
            	children: '내용을 확인하였으면 확인버튼을 눌러주세요.'
                }
             },
             {
             	type: Button,
                props: {
                	color: 'green',
                    children: '확인'
                    }
                }
           }
       ]
   }
}

 

 

 

• createElement( ) 함수의 동작 과정 – ConfirmDialog 컴포넌트 • 최종적으로 DOM에 렌더링되는 엘리먼트의 구성

{
	type: 'div'
    props:{
    	children: [
        {
        	type: 'p',
            props: {
            	children: '내용을 확인하였으면 확인버튼을 눌러주세요.'
                }
             },
             {
             	type: Button,
                props: {
               		className: 'bg-green',
                    children: {
                    	type: 'b',
                        props: {
                        	children:'확인'
                         }
                    }
                }
           }
       ]
   }
}

 

 

 

• Root DOM node

• root라는 id를 가진 div 태그에 모든 리액트 엘리먼트들이 렌더링되며, 리액트 DOM에 의해 관리됨.

• 리액트만으로 만들어진 앱은 단 하나의 Root DOM Node를 가짐

 

Root DOM node 엘리먼트 렌더링

const element = <h1>안녕, 리액트!<h1>;
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(element);

 

• createRoot로 설정된 Root DOM Node에 render 함수를 사용하여 화면을 렌더링 함.

• 엘리먼트가 렌더링되는 과정은 Virtual DOM에서 실제 DOM으로 이동하는 과정

• 렌더링된 엘리먼트 업데이트

• 엘리먼트는 한번 생성되면 바꿀 수 없기 때문에 다시 생성해야 함

• 즉 기존 엘리먼트를 변경하는 것이 아니라 새로운 엘리먼트를 생성해서 바꿔치기 방식으로 새로운 화면으로 업데이트 할 수 있음.

 

 

 

 

 

실습: 시계 만들기

 

-1초마다 실행: setinterval()함수 사용

-시간 정보 추출

: new Date().toLocaleTimeString() 내장함수 사용

 

 

components폴더 안에 Clock.jsx파일 생성후 작성

import React from "react" //react라이브러리 가져옴

function Clock(props) {//Clock이라는 이름의 함수형 컴포넌트 정의
    return (
        <div>
            <h1>
                현재시간: {new Date().toLocaleDateString() }

            </h1>
        </div>
    )
}

export default Clock;//이 컴포넌트를 다른 파일에서 사용할 수 있도록 내보냄

 

 

index.js

import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import Clock from './components/Clock'

//DOM의 'root'요소를 선택하여 react가 렌더링 할 루트 생성
const root = ReactDOM.createRoot(document.getElementById('root'));
//1초 간격으로 Clock 컴포넌트 렌더링
setInterval(() => {
  root.render(
    <React.StrictMode>
      <Clock />
      </React.StrictMode>
  );
}, 1000);//1초 후에 컴포넌트 갱신


// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

 

 

 

 

'React' 카테고리의 다른 글

컴포넌트와 Props  (0) 2025.04.02
소프트웨어 설계 3주차  (1) 2025.03.18
소프트웨어 설계 2주차  (1) 2025.03.18

+ Recent posts