열거형 정의

// 열거형(Enumeration)을 정의하는 코드
enum Compass { 
    // `enum` 키워드로 열거형을 정의하며, 이 열거형의 이름은 `Compass`입니다.
    // 열거형 내에는 네 가지 방향을 나타내는 값들이 있습니다.
    
    // 열거형 값들 (각각의 경우는 특정 방향을 나타냄)
    case North // 북쪽을 나타내는 값
    case South // 남쪽을 나타내는 값
    case East  // 동쪽을 나타내는 값
    case West  // 서쪽을 나타내는 값
}

// `var x : Compass`는 `Compass`라는 열거형 타입을 가진 변수 `x`를 선언하는 코드입니다.
// 그러나 여기에 주석이 달린 대로 실제로 선언은 아래와 같이 되어 있습니다.
var x = Compass.West // `Compass` 열거형의 `West` 값을 `x`에 할당
// 위 코드에서는 `Compass`를 명시하지 않고 `West`만 사용해도 자동으로 `Compass` 타입임을 인식합니다.

// `print(Compass.North)`는 `Compass` 열거형의 `North` 값을 출력하는 코드입니다.
// 열거형의 각 값을 참조할 때는 `.`, 즉 점(.)을 사용하여 접근합니다.
print(Compass.North) // 출력 결과: North

// `var x = Compass.West`로 `x`라는 변수를 `Compass.West`로 초기화한 후,
// `type(of: x)`를 사용하여 변수 `x`의 타입을 확인할 수 있습니다.
print(type(of: x)) // 출력 결과: Compass

// `x = .East`는 `x` 변수에 `Compass` 열거형의 `East` 값을 할당하는 코드입니다.
// 이때 `Compass`를 생략할 수 있습니다. 왜냐하면 `x`가 이미 `Compass` 타입이기 때문입니다.
x = .East // `Compass`를 생략해도 `x`는 여전히 `Compass` 타입으로 인식됨

// `print(x)`는 `x` 변수에 할당된 값을 출력하는 코드입니다.
// 현재 `x`는 `Compass.East` 값을 가지고 있기 때문에, "East"가 출력됩니다.
print(x) // 출력 결과: East
 enum Compass { 
 //열거형은 enum 그리고 이름(자료형)을 씀 열거형은 처음에만 이름.을 쓰고 그 후에는 생략 가능 
case North,South,East,West //이렇게 한줄에 써도 됨
}
 //var x : Compass // Compass형 인스턴스 x
 print(Compass.North) // North /case를 접근할떄는 .으로 접근
var x = Compass.West
print(type(of:x)) // Compass
x = .East//Compass를 생략가능 위에서 var x = Compass.West이렇게 x에 compass를 알기 때문 
print(x) // East

 

 

열거형 멤버별 기능 정의

// 열거형(Enumeration) 정의
enum Compass {
    case North  // 북쪽을 나타내는 값
    case South  // 남쪽을 나타내는 값
    case East   // 동쪽을 나타내는 값
    case West   // 서쪽을 나타내는 값
}

// `direction`이라는 변수 선언. 타입은 `Compass` 열거형.
var direction: Compass

// `direction` 변수에 `Compass.South` 값을 할당
direction = .South

// `switch` 문을 사용하여 `direction` 값을 확인
switch direction { 
case .North:
    // 만약 `direction`이 `.North`이면 "북"을 출력
    print("북")
case .South:
    // 만약 `direction`이 `.South`이면 "남"을 출력
    print("남")
case .East:
    // 만약 `direction`이 `.East`이면 "동"을 출력
    print("동")
case .West:
    // 만약 `direction`이 `.West`이면 "서"를 출력
    print("서")
}
// 출력 결과: "남" (왜냐하면 direction이 .South로 설정되었기 때문)

 

 

열거형 멤버에는 메서드도 가능

주말 출력

// 열거형(Enumeration) 정의
enum Week {
    // 일주일의 각 요일을 나타내는 값들
    case Mon, Tue, Wed, Thur, Fri, Sat, Sun
    
    // 열거형에 메서드 추가
    func printWeek() {
        // self는 메서드가 호출된 인스턴스(여기서는 특정 요일)를 의미
        switch self {
        case .Mon, .Tue, .Wed, .Thur, .Fri:
            // 주중인 경우 (월요일부터 금요일까지)
            print("주중")
        case .Sat, .Sun:
            // 주말인 경우 (토요일과 일요일)
            print("주말")
        }
    }
}

// `Week.Sun` 열거형 값에 대해 `printWeek()` 메서드를 호출
Week.Sun.printWeek()  // 이 코드가 실행됨
//주말

주중 출력

// 열거형(Enumeration) 정의
enum Week {
    // 일주일의 각 요일을 나타내는 값들
    case Mon, Tue, Wed, Thur, Fri, Sat, Sun
    
    // 열거형에 메서드 추가
    func printWeek() {
        // self는 메서드가 호출된 인스턴스(여기서는 특정 요일)를 의미
        switch self {
        case .Mon, .Tue, .Wed, .Thur, .Fri:
            // 주중인 경우 (월요일부터 금요일까지)
            print("주중")
        case .Sat, .Sun:
            // 주말인 경우 (토요일과 일요일)
            print("주말")
        }
    }
}

// `Week.Tue` (화요일)을 주중으로 설정하여 `printWeek()` 메서드를 호출
Week.Tue.printWeek()  // 주중이 출력됩니다.

열거형의 rawValue

enum Color: Int { //원시값(rawValue) 지정/ 열거형들은 rowvalue를 지정할 수 있다
case red
case green = 2
case blue
}
print(Color.red) //red
print(Color.blue)//blue
print(Color.red.rawValue) //0
print(Color.blue.rawValue)//3

rowvalue를 string형으로 지정한 예시

// 열거형(Enumeration) 정의
// Week 열거형은 주의 요일을 나타내며, 각 요일은 문자열(String) 값과 연결됩니다.
enum Week: String {
    // 각 요일에 대해 문자열 값을 직접 할당
    case Monday = "월"    // 월요일은 "월"로 값이 할당됨
    case Tuesday = "화"   // 화요일은 "화"로 값이 할당됨
    case Wednesday = "수" // 수요일은 "수"로 값이 할당됨
    case Thursday = "목"  // 목요일은 "목"으로 값이 할당됨
    case Friday = "금"    // 금요일은 "금"으로 값이 할당됨

    // Saturday와 Sunday는 값을 명시하지 않으면, case 이름이 자동으로 값으로 할당됨
    case Saturday         // "Saturday"라는 문자열 값이 자동으로 할당됨
    case Sunday           // "Sunday"라는 문자열 값이 자동으로 할당됨
}

// `Week.Monday`는 열거형 값 `Monday`를 나타냄
// `print(Week.Monday)`는 열거형 자체의 이름을 출력
print(Week.Monday)  // 출력 결과: Monday

// `.rawValue`는 열거형 값에 연결된 실제 값을 반환
// `Week.Monday.rawValue`는 "월"이라는 값을 출력
print(Week.Monday.rawValue)  // 출력 결과: 월

// `Week.Sunday`는 열거형 값 `Sunday`를 나타냄
// 이 경우, raw value는 자동으로 "Sunday"라는 문자열로 할당됨
print(Week.Sunday)  // 출력 결과: Sunday

// `Week.Sunday.rawValue`는 `Sunday`에 해당하는 raw value인 "Sunday"를 출력
// 하지만 위에서 `rawValue`가 자동으로 "Sunday"로 설정되었기 때문에 "Sunday"가 출력됨
print(Week.Sunday.rawValue)  // 출력 결과: Sunday

연관 값(associated value)을 갖는 enum

enum Date {
case intDate(Int,Int,Int) //(int,Int,Int)형 연관값을 갖는 intDate
case stringDate(String) //String형 연관값을 값는 stringDate
}
var todayDate = Date.intDate(2025,4,30)
todayDate = Date.stringDate("2025년 5월 20일") //Date생략 가능/주석처리하면?
switch todayDate {
case .intDate(let year, let month, let day)://위 주석처리하면 caseprintprint("\(year)년 \(month)월 \(day)일")
case .stringDate(let date):
print(date)
}
// `age`라는 변수는 Optional 타입(Int?)로 선언되었습니다.
// Optional은 값이 있을 수도 있고, 없을 수도 있는 상태를 나타냅니다.
// 여기서는 `age`가 `30`이라는 값을 가지고 있습니다.
let age: Int? = 30 // Optional(30)

// Optional은 내부적으로 두 가지 상태인 `some`과 `none`으로 구분됩니다.
// `some`은 값이 있을 때, `none`은 값이 없을 때 (즉, `nil`일 때) 사용됩니다.

switch age {  // `age`가 Optional이므로, 이를 `switch` 문으로 처리합니다.
case .none:  // 값이 없는 경우, 즉 `age`가 `nil`일 경우
    print("나이 정보가 없습니다.")  // "나이 정보가 없습니다." 출력
case .some(let a) where a < 20:  // 값이 있을 경우, `a`로 해당 값을 받으며, 20살 미만인 경우
    print("\(a)살 미성년자입니다")  // 예: 18살이면 "18살 미성년자입니다" 출력
case .some(let a) where a < 71:  // 값이 있을 경우, `a`로 해당 값을 받으며, 71살 미만인 경우
    print("\(a)살 성인입니다")  // 예: 30살이면 "30살 성인입니다" 출력
default:  // `a`가 71 이상인 경우
    print("경로우대입니다")  // "경로우대입니다" 출력
}

(셤x(?))

구조체 Memberwise Initializer가 자동으로 만들어지는게 장점

 

// struct Resolution { //구조체 정의 , 첫글자 대문자로 지정
// var width = 1024 //프로퍼티, 초기값 필요
// var height = 768
// }
// let myComputer = Resolution() //인스턴스 생성, 괄호 열고 닫으면 인스턴스가 됨
// print(myComputer.width) //프로퍼티 접근

 

// struct Resolution { //구조체 정의
// var width : Int //프로퍼티 초기값이 없어요!!
// var height : Int

// init(width: Int, height :Int){
//     self.width = width,
//     self.height = height
// }
// let myComputer = Resolution(width:1920,height:1080) //Memberwise Initializer /위에 init를 적어야 initializer을 호출 가능 
// print(myComputer.width) 
// // 과제 struct을 class로 변경하면 오류. init만들어 오류 제거하기


struct Resolution { //구조체 정의
var width : Int //프로퍼티 초기값이 없어요!!
var height : Int

} //init()메서드 없어요, 그런데!
let myComputer = Resolution(width:1920,height:1080) //Memberwise Initializer 구조체로 만들면 Memberwise Initializer 자동 생성 가능(init없어도 됨)
print(myComputer.width) 
// 과제 struct을 class로 변경하면 오류. init만들어 오류 제거하기

 

구조체는 init을 명시적으로 작성하지 않아도, 자동으로 "memberwise initializer"가 제공됨

 

 

클래스 내에 구조체

struct Resolution {//중첩된 class와 struct
var width = 1024
var height = 768
}
class VideoMode {
var resolution = Resolution()
var frameRate = 0.0
}
let myVideo = VideoMode()
print(myVideo.resolution.width) //024 최종적으로 접근하려면 점으로 접근

 

시험X /차이점이 나옴

 

 

차이점

class가 struct보다 더 갖는 특징에서 중요한 것은 상속이 가능 

swift에선 상속이 가능하기 때문에 부모와 자식간에는 형 변환 가능

+구조체는 값 타입 클래스는 참조타입

var x = 1
var y = x
print(x,y)//1 1
x = 2
print(x,y)//2 1
y = 3
print(x,y)//2 3
//값 타입은 복사할 때 새로운 데이터가 하나 더 생김

 

구조체struct일때 (value type)

//구조체는 별개임 둘 중에 하나만 바뀌면 그것만 바뀜

struct Human {
var age : Int = 1
}
var kim = Human()
var lee = kim //값 타입 한번 초기값을 복사해주고 lee와 kim은 별개임
print(kim.age, lee.age)//1 1
lee.age = 20
print(kim.age, lee.age)//1 20
kim.age = 30
print(kim.age, lee.age)//30 20

 

구조체를 calss로 바꿨을때

//값 복사가 아닌 주소 복사임

//구조체(struct)를 class로 바꿈
class Human {
var age : Int = 1
}
var kim = Human()
var lee = kim //참조reference 타입 //참조 타입은 복사할 때 주소를 복사해서
//한 데이터의 reference가 2개 생김
//주소를 복사해주기 때문에 하나가 바뀌면 동시에 바뀜
print(kim.age, lee.age)//1 1
lee.age = 20
print(kim.age, lee.age)//20 20
kim.age = 30
print(kim.age, lee.age)//30 30

 


struct Resolution {//구조체는 별개임 둘 중에 하나만 바뀌면 그것만 바뀜
var width = 0
var height = 0
}
class VideoMode {
var resolution = Resolution()
var frameRate = 0
var name: String?
}
var hd = Resolution(width: 1920, height: 1080)
//자동 Memberwise Initializer
var highDef = hd
//구조체는 값타입(value type)
print(hd.width, highDef.width)//1920 1920
hd.width = 1024
print(hd.width, highDef.width)//1024 1920

**간단한 자료들을 하나의 묶음으로 관리하고 싶을때는 구조체

덩치자 커지거나 기능이 많은 걸 만들때는 class사용함

 

 

타입이름 /대문자로 시작: Upper Camel case

클래스/구조체 안의 프로퍼치나 메서드 /소문자 시작: lower Camel Case

 

 

4주차 강의자료 p38

extension

:클래스를 건들이지 않고 기능을 추가할 때 사용

 extension Double { //double이라는 구조체의 기능을 확장하겠다
var squared : Double {//Double으로 나오게 해줘
return self * self 
}
}
 let myValue: Double = 3.0
 print(myValue.squared) //과제//9.0
 print(4.0.squared) //Double형 값에도 .으로 바로 사용 가능//16.0
 print(myValue.isZero) //instance property, 결과?//false

 

 

 

 

 

 

 

 

 

 

 

 

'Swift' 카테고리의 다른 글

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

Hooks

:함수 컴포넌트에서 React state와 생명주기 기능을 연동할 수 있게 해주는 함수

 

-리액트의 state와 생명주기 기능에 갈고리를 걸어 원하는 시점에 정해진 함수를 실행되도록 만드는 것

=>이때 실행되는 함수가 훅

-훅의 이름은 모두 use로 시작

 

useState( ) 기반 state 사용하기

• 함수 컴포넌트에서는 기본적으로 state를 제공하지 않기 때문에 state를 사용하려면 useState() 훅을 사용해야 함

• React 라이브러리를 로드할 때 useState도 함께 로드

 

import React, { useState } from "react";

 

사용법

const [변수명, set함수명] = useState(초기값);

useState()를 호출할 때는 state의 초기값을 파라미터로 전달

• 리턴 값 - 배열 1) state로 선언된 변수 2) state의 set 함수

• state 로 선언된 변수는 반드시 set 함수만을 사용해서 변경해야 함

• state 변수가 변경되면 이를 참조하는 UI는 자동으로 재렌더링 작업이 수행됨.

 

 

예시) 카운터 (useState( ) 훅 기반) • 변수 각각에 대해 set함수를 개별 정의하여 사용함.

import React, { useState } from "react";
function Counter(porps) {
const [count, setCount] = useState(0);
return (
<div>
<p>총 {count}번 클릭했습니다.</p>
<button onClick={ () => {
setCount(count+1);
 console.log(count);
}}>클릭</button>
</div>
);
}
export default Counter

**setCount는 바로 반영이 안됨, 컴포넌트가 재렌더링이 되어야 반영됨, 그렇기 때문에 화면 출력결과와 콘솔에서 출력한 count값이 다름 (즉시 반영이 아님, 렌더링된 시점에 state변수가 반영이 됨)

 

 

 

예시) 카운터 (useState( ) 훅 기반) – 함수 업데이트 방식 • 이전 상태의 값을 사용해야 할 경우 “함수형 업데이트“ 방식으로 처리

import React, { useState } from "react";
function Counter(porps) {
const [count, setCount] = useState(0);
return (
<div>
<p>총 {count}번 클릭했습니다.</p>
<button onClick={ () => {
setCount(prev => prev+1);
setCount(prev => prev+1);
 console.log(count);
}}>클릭</button>
</div>
);
}
export default Counter

재렌더링이 되기 전에 seycount함수 누적해서 수정이 됨

여러번 했을떄 횟수만큼 반영이 됨

기존의 최신값을 이루어지도록 사용?

 

 

useEffect() - Side effect를 수행하기 위한 훅

 

- 다른 컴포넌트에 영향을 미칠 수 있으며 렌더링 중에는 작업이 완료될 수 없음

- 즉, 렌더링이 끝난 이후에 실행되어야 하는 작업

 

클래스 컴포넌트에서 제공하는 생명주기 함수 componentDidMount(), componentDidUpdate(), ComponentWillMount()와 동일한 기능을 하나로 통합해서 제공

• useEffect() 훅만으로 위 생명주기 함수와 동일한 기능을 수행할 수 있음

 

useEffect(이펙트 함수, 의존성 배열);

 

의존성 배열 - – 이 이펙트가 의존하고 있는 배열. 배열안에 있는 변수 중 하나라도 값이 변경되면 이펙트 함수가 실행됨

이펙트 함수는 처음 컴포넌트가 렌더링 된 이후, 업데이트로 재렌더링 이후에 실행됨.

 

의존성 배열이 빈 값 – Effect 함수는 mount, unmount시에 단 한번씩 실행됨

useEffect(이펙트 함수, []);

 

의존성 배열 생략 – 컴포넌트가 업데이트 될 때마다 호출

useEffect(이펙트 함수);

 


 

import React, {useState, useEffect} from "react";

 

 

title메시지에 바로 반영됨

import React, {useState, useEffect} from "react";

function Counter(props){
   const [count, setCount] = useState(0);

   useEffect(() => {
    document.title = `${count}번 클릭`;//title메시지에 바로 반영됨
   }, [count]);

    return(
        <div>
            <p>{count}</p>
            <button onClick={()=>{
                setCount((prev) => prev +1);
               
            }}> 클릭
                
            </button>
          
        </div>
    );
}
export default Counter

 

 

title메시지에 바로 반영 안됨

import React, {useState, useEffect} from "react";

function Counter(props){
   const [count, setCount] = useState(0);

   useEffect(() => {
   // document.title = `${count}번 클릭`;
   }, [count]);

    return(
        <div>
            <p>{count}</p>
            <button onClick={()=>{
                setCount((prev) => prev +1);
                document.title = `${count}번 클릭`;//title메시지에 바로 반영 안됨
            }}> 클릭
                
            </button>
          
        </div>
    );
}
export default Counter

 

빈 배열이면 title에 반영 안됨

import React, {useState, useEffect} from "react";

function Counter(props){
   const [count, setCount] = useState(0);

   useEffect(() => {
    document.title = `${count}번 클릭`;
   //}, [count]);
}, []); //이렇게 빈 배열이면 title에 반영안됨
    return(
        <div>
            <p>{count}</p>
            <button onClick={()=>{
                setCount((prev) => prev +1);
               
            }}> 클릭
                
            </button>
          
        </div>
    );
}
export default Counter

 

 

배열 생략 컴포넌트 업데이트 될때마다 title도 동일하게 변경됨

import React, {useState, useEffect} from "react";

function Counter(props){
   const [count, setCount] = useState(0);

   useEffect(() => {
    document.title = `${count}번 클릭`;
    });//배열 생략 컴포넌트 업데이트 될떄마다 title도 동일하게 변경됨

    return(
        <div>
            <p>{count}</p>
            <button onClick={()=>{
                setCount((prev) => prev +1);
               
            }}> 클릭
                
            </button>
          
        </div>
    );
}
export default Counter

실습. useState()와 useEffect()를 이용한 자동 증감 카운터

import React, { useState, useEffect } from "react";

function Counter(props) {
    // count: 현재 카운트 숫자 (초기값 0)
    // setCount: count를 변경하는 함수
    const [count, setCount] = useState(0);

    // isRunning: 카운터 작동 여부를 저장하는 상태 (초기값 false = 정지 상태)
    // setIsRunning: isRunning을 변경하는 함수
    const [isRunning, setIsRunning] = useState(false);

    // useEffect: 컴포넌트가 렌더링되거나 의존값이 바뀔 때 실행됨
    useEffect(() => {
        if (!isRunning) return; 
        // isRunning이 false면 아무 것도 하지 않음 → 카운터 멈춤

        // 1초(1000ms) 뒤에 count를 1 증가시킴
        const timer = setTimeout(() => {
            setCount(count + 1); 
            // 주의: count는 이전 상태값이므로, 배열에 count를 넣어줘야 최신 값 반영 가능
        }, 1000);

        // 클린업 함수: setTimeout이 쌓이지 않도록 정리
        return () => clearTimeout(timer);
    }, [count, isRunning]); 
    // 의존성 배열: count 또는 isRunning이 바뀔 때마다 useEffect 실행됨

    return (
        <div>
            <p>{count}</p> {/* 현재 count 값 화면에 표시 */}
            <button onClick={() => {
                setIsRunning(!isRunning);
                // 버튼 클릭 시 isRunning 값을 반전시킴 (START ↔ STOP)
            }}>
                {isRunning ? 'STOP' : 'START'}
                {/* 현재 상태에 따라 버튼 텍스트 변경 */}
            </button>
        </div>
    );
}

export default Counter;

 

 

useMemo란?

"비싼 계산 결과를 기억(Memoization)해서 불필요한 연산을 줄여주는 훅"

 
// 오래 걸리는 계산을 수행하는 함수
function compute(x, y) {
  // 이 부분은 계산량이 많은 작업이라고 가정
  // 예: 복잡한 수학 계산, 반복문, 대용량 데이터 처리 등
}

// React 컴포넌트
function MyComponent({ x, y }) {
  // 컴포넌트가 렌더링될 때마다 compute(x, y)가 실행됨
  // 즉, x나 y가 바뀌지 않아도 매번 동일한 계산을 다시 함
  // 계산량이 많은 작업일 경우 렌더링 속도 저하 발생 가능
  const value = compute(x, y);

  // 계산된 값을 화면에 출력함
  return <div>{value}</div>;
}

 동작 방식

  • x 또는 y가 바뀔 때만 computeExpensiveValue() 함수를 실행함.
  • 그 외에는 **이전에 계산해 둔 값(memoized value)**을 재사용함.
  • 즉, 불필요한 계산을 줄여서 성능 향상에 도움!

Memoization(메모이제이션)이란?

같은 입력 → 같은 출력이면,
결과값을 저장해서 재사용하는 기법

 왜 필요할까?

  • 반복해서 동일한 계산을 할 필요 없음
  • CPU 리소스 아낌
  • UI 렌더링 속도 향상

 예시

 
function computeExpensiveValue(a, b) { console.log(" 계산 중..."); // 시간이 오래 걸리는 작업이라고 가정 return a + b; }

이 함수가 매번 호출된다면 → 불필요한 낭비!
👉 useMemo()로 감싸면, 값이 변하지 않는 한 딱 한 번만 계산됨!

상황useMemo 필요 여부

단순한 값 계산 ❌ 필요 없음
반복적으로 실행되는 무거운 계산 ✅ 사용하는 것이 좋음
렌더링 성능 저하가 체감되는 경우 ✅ 추천

 

사용법

const memoizedValue = useMemo(
	() => {
		//연산량이 높은 작업을 수행하여 결과 반환
        return computeExpensiveValue(의존성변수1, 의존성 변수2);
        },
        [의존성 변수1, 의존성 변수2]
 );

• 의존성 배열의 요소 값이 업데이트될 때만 콜백 함수를 다시 호출

- momoization된 값을 업데이트 후 다시 memoization 해줌

• useMemo()로 전달된 함수는 렌더링이 일어나는 동안 실행됨

• 렌더링이 일어나는 동안 실행되서는 안되는 작업은 useMemo()에 사용하면 안됨

- useEffect()에서 실행돼야 할 이펙트 작업

 

 

 

실습)) usememo() 느리게 작동하는 숫자 제곱 함수

import React, { useState, useMemo } from 'react';

function BigSquare() {
  // 📌 number: 입력 숫자 (제곱 계산의 대상)
  // 📌 setNumber: number 값을 변경하는 함수
  const [number, setNumber] = useState(0);

  // 📌 count: 단순히 +1씩 증가하는 카운터 값
  // 📌 setCount: count 값을 변경하는 함수
  const [count, setCount] = useState(0);

  // 🐢 느리게 계산되는 함수 (일부러 느리게 만들었음)
  const slowSquare = (num) => {
    console.log('계산 중...'); // 매번 계산될 때마다 콘솔 출력
    let result = 0;

    // 이 반복문은 실제로 제곱 계산이 느리게 되도록 만든 예제용
    for (let i = 0; i < 2500000000; i++) {
      result = num * num;
    }

    return result;
  };

  // 🚀 useMemo: number 값이 바뀔 때만 slowSquare(number) 호출
  // 그렇지 않으면 이전에 계산된 제곱 값을 재사용함 (성능 최적화!)
  const squared = useMemo(() => slowSquare(number), [number]);

  return (
    <div>
      <h2>느린 제곱 계산기</h2>

      {/* 사용자 입력 필드: 입력한 숫자가 number에 저장됨 */}
      <input
        type="number"
        value={number}
        onChange={(e) => setNumber(Number(e.target.value))}
      />

      {/* 계산된 제곱 결과 출력 */}
      <p>제곱 결과: {squared}</p>

      {/* count 값 출력 */}
      <p>{count}</p>

      {/* 클릭할 때마다 count 값을 1씩 증가시킴 */}
      <button onClick={() => setCount(count + 1)}>+1</button>
    </div>
  );
}

export default BigSquare;

 

*setCount 함수 사용시 useMemo 덕분에 number 값이 바뀌지 않으면 계산 작업이 생략됨. 즉 불필요한 재렌더링 작업이 발생하지 않음.

 

 

useCallback()

: 함수를 메모이제이션 하기 위한 훅

-첫번째 인자로 넘어온 함수를 의존성 배열 내의 값이 변경될 때까지 저장하여 재사용

 

const memoizedCallback = useCallback(
()=>{
doSomething(의존성 변수1, 의존성 변수2);
},
[의존성 변수1, 의존성 변수2]
);

  useCallback() 훅을 사용하지 않고 컴포넌트 내에 함수를 정의할 경우, 렌더링이 일어날 때마다 함수가 새로 정의됨

• useCallback() 훅을 사용하면 특정 변수의 값이 변한 경우에만 함수를 다시 정의

 

실습. useCallback( )

import React, { useState, useEffect, useCallback } from "react";

// FetchComponent: 검색어에 따라 데이터를 가져오는 예제 컴포넌트
export default function FetchComponent() {
  // 📌 keyword: 사용자가 입력한 검색어 상태
  const [keyword, setKeyword] = useState("");

  // 📌 data: 검색 결과를 저장하는 상태
  const [data, setData] = useState("");

  // 📌 fetchData 함수: keyword를 기반으로 가짜 검색 결과를 가져옴
  // useCallback 사용 이유 → 의존성 배열([keyword])에 따라 함수 재생성 방지
  const fetchData = useCallback(() => {
    console.log("데이터 불러오는 중...");

    // 1초 후에 검색 결과를 보여주는 타이머 (API 대체용)
    setTimeout(() => {
      setData(`"${keyword}"에 대한 검색 결과`);
    }, 1000);
  }, [keyword]); // keyword가 바뀔 때만 fetchData 함수가 새로 생성됨

  // 📌 useEffect: keyword가 변경될 때마다 자동으로 fetchData 실행
  useEffect(() => {
    console.log("useEffect()");
    if (keyword) fetchData(); // 검색어가 비어있지 않으면 fetch 실행
  }, [fetchData]); // fetchData가 바뀔 때 실행되므로, keyword 변화 감지 가능

  return (
    <div>
      <h2>검색 예제 (단일 컴포넌트)</h2>

      {/* 사용자 입력: keyword 상태 업데이트 */}
      <input
        type="text"
        placeholder="검색어 입력"
        value={keyword}
        onChange={(e) => setKeyword(e.target.value)}
      />

      {/* 결과 출력 */}
      <p>결과: {data}</p>

      {/* 버튼 클릭 시 수동으로 fetchData 호출 가능 */}
      <button onClick={fetchData}>수동 검색</button>
    </div>
  );
}

 

*차이점

usememo는 데이터임 데이터가 저장되는, usecallback은 함수 함수로 호출

 

 

useRef( )

• useRef() – Reference를 사용하기 위한 훅

• 리액트 레퍼런스 – 특정 컴포넌트에 접근할 수 있는 객체

• useRef()는 레퍼런스 객체를 반환 •

레퍼런스 객체의 .current 속성 – 현재 참조하고 있는 엘리먼트

 

const refContainer = useRef(초기값);

 

• 초기값으로 초기화된 레퍼런스 반환

• 초기값이 null이면 .current 값이 null인 객체 반환 - { current: null }

 

실습. useRef( ) • 예시) input 태그의 포커싱 되도록 수행

import React, { useState, useRef } from "react";

// 🔹 TextInputWithFocusButton 컴포넌트
// 사용자가 버튼을 누르면 input에 자동으로 포커스를 주고, 값을 채워줌
export default function TextInputWithFocusButton(props) {
  // 📌 useRef 훅: DOM 요소(input)를 직접 참조할 수 있게 해줌
  // inputElem.current로 해당 input DOM에 접근 가능
  const inputElem = useRef(null);

  // 🔹 버튼 클릭 시 실행되는 함수
  const onButtonClick = () => {
    // 📌 focus(): 해당 input 박스로 커서를 자동으로 이동시킴
    inputElem.current.focus();

    // 📌 value 설정: input에 텍스트 값을 직접 넣음
    inputElem.current.value = "test1234";
  };

  return (
    <div>
      {/* 📌 ref 속성을 통해 input 요소를 inputElem에 연결 */}
      <input ref={inputElem} />

      {/* 🔘 버튼 클릭 시 onButtonClick 함수 실행 */}
      <button onClick={onButtonClick}>
        Focus the input
      </button>
    </div>
  );
}

 

* 버튼 클릭시 input 태그가 포커싱 된다.

개념설명

useRef() DOM 요소나 값을 기억할 수 있는 React 훅
ref={inputElem} 해당 input 요소를 inputElem.current로 직접 제어 가능하게 연결
focus() input에 커서(포커스)를 자동으로 이동시키는 메서드
value 직접 설정 input 값을 프로그래밍적으로 변경 가능

 

훅(Hook)의 규칙 정리

 규칙 1. 훅은 무조건 컴포넌트의 최상위 레벨에서만 호출해야 함

  • 반복문, 조건문, 중첩된 함수 내부에서 호출 ❌
  • 컴포넌트가 렌더링될 때마다 항상 같은 순서로 훅이 실행되어야 하기 때문

 규칙 2. 훅은 리액트 함수 컴포넌트 또는 커스텀 훅에서만 호출 가능

  • 일반 자바스크립트 함수에서 훅 사용 ❌
  • 훅은 오직 함수형 컴포넌트 또는 커스텀 훅 내부에서만 사용 가능

 커스텀 훅(Custom Hook) 개념 정리

  • 리액트에서 기본 제공 훅 외에 직접 만든 훅 함수
  • 여러 컴포넌트에서 반복되는 로직을 재사용하기 위해 사용
  • 이름은 반드시 use로 시작
  • 내부에서 useState, useEffect 등 다른 훅 사용 가능
  • 공통 로직을 분리하여 코드 가독성과 유지보수성 향상

커스텀 훅(Custom Hook) 정리

 커스텀 훅이란?

  • 중복된 로직을 함수로 분리한 것
  • 일반 함수처럼 파라미터와 리턴 값을 자유롭게 정할 수 있음
  • 하지만 기본 훅과 동일한 규칙을 따라야 함

 커스텀 훅의 규칙

  1. 이름은 반드시 use로 시작해야 함
  2. 컴포넌트의 최상위에서만 호출해야 함
  3. React 함수형 컴포넌트 또는 다른 훅 안에서만 사용 가능

 이름을 use로 시작하지 않으면?

  • React가 훅인지 인식 못 해서 규칙 위반 검사를 못함
  • 예기치 않은 오류 발생 가능

상태(state) 공유 여부

  • 여러 컴포넌트에서 같은 커스텀 훅을 사용해도
    상태는 각각 따로 관리됨 (독립적임)
  • 즉, 호출된 만큼 각각 개별 상태 생성

'React' 카테고리의 다른 글

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

 1. 웹이란?

  • 웹(Web): 하이퍼텍스트 문서들이 인터넷으로 서로 연결된 구조
  • WWW: 1989년 CERN에서 시작, 팀 버너스 리가 제안
  • 기본 구성:
    • HTTP: 통신 규약
    • HTML: 문서 형식
    • URL/URI: 웹 주소 체계

 2. HTTP의 개념

✔ 요청 메시지 (Request)

  • 클라이언트가 서버에 "요청"을 보냄
  • 구성: 메소드, URI, HTTP 버전, 헤더, 바디(POST일 경우)

✔ 응답 메시지 (Response)

  • 서버가 클라이언트에 "응답"을 줌
  • 구성: 버전, 상태 코드, 헤더, 바디(결과 데이터)

✔ 특징

  • 비연결지향: 요청/응답 후 연결 종료
  • 무상태(Stateless): 상태 기억 안 함
    → 이를 보완하기 위해 쿠키세션 사용

 쿠키(Cookie) vs 세션(Session)

구분쿠키세션
저장 위치 클라이언트(브라우저) 서버
용도 로그인 유지, 사용자 설정 등 로그인 인증, 장바구니 등
보안 상대적으로 낮음 상대적으로 높음
유지 기간 브라우저 종료 시 또는 설정한 만료일까지 서버가 설정한 시간 동안

쿠키 예시

  • "다시 보지 않기" 팝업 체크
  • 쇼핑몰에서 최근 본 상품 저장

세션 예시

  • 로그인 상태 유지
  • 장바구니 정보 유지

 3. URI와 HTML

  • URI: 인터넷 자원의 고유 주소
  • GET 방식: 주소에 정보 포함 (보안 낮음)
  • POST 방식: 본문에 정보 포함 (보안 좋음)
  • 퍼센트 인코딩: 특수문자를 안전하게 변환 (%20 등)

 4. 웹 취약점 정보 수집

  • 도구: Acunetix, OpenVAS, 스패로우 등
  • 웹사이트의 보안 취약점을 자동으로 점검

 5. 주요 웹 취약점 & 대책

취약점설명보안 대책
SQL 인젝션 DB 명령어 조작 입력값 검증, 바인딩
경로 조작 서버 파일 접근 우회 특수문자 제거
파일 업로드 악성 파일 업로드 확장자 제한, 실행 금지
XSS 악성 스크립트 삽입 입력값 필터링
인증 없음 로그인 없이 기능 사용 인증 확인
인가 없음 권한 없이 기능 사용 권한 체크
암호화 미흡 평문 전송, 약한 암호 HTTPS 사용, 강한 알고리즘
오류 처리 부족 내부 정보 노출 에러 메시지 제한

 웹 서버 보안 관리

 계정 관리

  • 웹 서버 실행 계정은 최소 권한 부여
    (예: apache, IIS_NOBODY)

파일 관리

  • 디렉터리 검색 금지
  • 불필요 파일 제거 (매뉴얼, 예제 등)

 서비스 관리

  • 업로드/다운로드 크기 제한
  • WebDAV 비활성화

 로그 관리

  • 로그 파일 주기적 생성 및 저장
  • Apache/IIS 모두 설정 가능

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

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

Git 과 GitHub

-Git: 내 컴퓨터 안에서 버전 관리

-GitHub: Git저장소를 인터넷에 업로드하고 공유하는 공간(클라우드)

 

1. vscode 또는 터미널에서

폴더 생성부터 Git초기화

# 1. 프로젝트 폴더 만들기
mkdir blog-project

# 2. 폴더로 이동
cd blog-project

# 3. Node.js 초기화
npm init -y

 

.gitignore

:.env 같은 민감한 정보나 불필요한 파일을 Git에 올리지 않도록 하기 위해 사용

 

windows에서 .gitignore만드는 방법

-좌측 탐색기에서 폴더 열고/ .gitignore파일 만들기

# 환경 변수 파일
.env

# node_modules는 너무 큼
node_modules/

# DB 설정 파일 
config.js

 

커밋하는 방법

git commit -m "프로젝트 초기 설정"

 

커밋 확인

git log --oneline

 

 

2. GitHub 웹사이트에서 저장소 생성

-github -> your profile -> Repositories -> New

-저장소 이름 기입후 public / private설정

-code 주소 복사

git remote add origin https://github.com/내아이디/blog-project.git//내 Git 저장소와 GitHub 저장소를 연결
git branch -M main//현재 브랜치를 main으로 설정
git push -u origin main//GitHub에 코드 업로드 (처음은 -u 옵션 필수)

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

옵셔널 체이닝(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프로그래밍 실무 7주차  (0) 2025.04.16
iOS프로그래밍 실무 5주차  (0) 2025.04.02
iOS실무 프로그래밍 4주차  (0) 2025.03.26
iOS프로그래밍 실무 3주차  (0) 2025.03.19
iOS 프로그래밍 실무 2주차  (0) 2025.03.17

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에서 일정량 이상의 요청이 들어오면 차단)
  • 최신 보안 패치 적용 (운영체제 및 서버 소프트웨어 업데이트)

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

웹 보안  (0) 2025.04.15
정보보안 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프로그래밍 실무 7주차  (0) 2025.04.16
iOS프로그래밍 실무 6주차  (0) 2025.04.09
iOS실무 프로그래밍 4주차  (0) 2025.03.26
iOS프로그래밍 실무 3주차  (0) 2025.03.19
iOS 프로그래밍 실무 2주차  (0) 2025.03.17

컴포넌트 기반 구조 (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' 카테고리의 다른 글

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

+ Recent posts