1. 프로젝트 추가하기

 

2. 프로젝트 설정하기

iOS 번들 ID에는 Xcode의 프로젝트 이름과 일치하게 만든다.

App Store ID는 개발자 계정의 ID를 넣으면 된다.

 

'구성 파일 다운로드'에서 plist 파일을 받아 Xcode 프로젝트에 넣는다. 위치는 위 그림대로 넣는다.

 

시키는대로 CocoaPods Docs 페이지로 이동해서 CocoaPods를 설치하고 init을 진행한다.

sudo gem install cocoapods
pod init

 

터미널로 현재 프로젝트 파일이 있는 위치로 이동한 다음 vi 에디터로 Podfile을 열어

pod 'Firebase/Analytics'

를 마지막 end 앞에 넣어준다.

(지금부터 스샷은 프로젝트 명이 test가 아니고 smartFinance다. smartFinance가 이미 있어서 test로 찍었음...)

 

이제 Xcode를 닫고 터미널에 다음을 명령한다.

pod install

 

지금부터는 기존 Xcode 프로젝트 파일 말고 Xcode를 새로 생긴 하얀색 파일로 실행할거다. 클릭해서 Xcode 프로젝트를 열자.

 

시키는대로 추가하고 저장한다.

 

5번 단계에서 Firebase 서버가 앱 실행을 확인한다. 이 때 Xcode에서 Build를 Run 시켜서 연결이 확인되면 등록이 완료된다.

 

3. Main.storyboard에 로그인 폼 만들기

Labe, Text Field, Button을 만들고 변수, 함수로 할당한다. 비밀번호가 입력되는 Text Field는 우측 속성 창에서 'Secure Text Entry'를 체크해서 입력되는 문자가 보이지 않게 한다.

 

4. Firebase 인증 연결하기

 

 

Xcode 프로젝트를 다시 종료한 다음 진행한다. 시키는대로 터미널에서 프로젝트 폴더로 이동해 Podfile을 vi 에디터로 열고 Auth를 추가한 후 pod install을 명령한다. 그러면 추가로 필요한 모듈들을 가져와 설치한다. 그리고 Xcode 프로젝트를 재시작한다.(Xcode 종료 후 하얀색 프로젝트 파일로 열기)

이 부분은 위에서 처음 Firebase 프로젝트 생성할 때 한 부분으로 건너뛴다.

 

4.1 신규 사용자 가입

//
//  ViewController.swift
//  smartFinance
//
//  Created by     on 2020/06/01.
//  Copyright © 2020 dream. All rights reserved.
//

import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var m_id: UITextField!
    @IBOutlet weak var m_pwd: UITextField!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
    }

    @IBAction func tapLogin(_ sender: Any) {
    }
    @IBAction func tapSignUp(_ sender: Any) {
    }
//    m_id.text
//    m_pwd.text
    
    
}

tapSignUp에 넣을거다.

//
//  ViewController.swift
//  smartFinance
//
//  Created by     on 2020/06/01.
//  Copyright © 2020 dream. All rights reserved.
//

import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var m_id: UITextField!
    @IBOutlet weak var m_pwd: UITextField!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
    }

    @IBAction func tapLogin(_ sender: Any) {
    }
    @IBAction func tapSignUp(_ sender: Any) {
        Auth.auth().createUser(withEmail: email, password: password) { authResult, error in
          // ...
        }EmailViewController.swift
    }
//    m_id.text
//    m_pwd.text
    
    
}

 

'email'과 'password' 변수를 모르겠다고 빨간색 에러가 뜰거다. 우리 프로젝트에 맞게 바꿔준다. 그리고 구글 Firebase docs에는 나와있지 않지만 'EmailViewController.swift'를 누르고 들어가보면 ViewController 여기도 Fireabase를 추가해준다.

 

결과를 확인하기 위해 print를 추가하고 break point를 잡아준다.

//
//  ViewController.swift
//  smartFinance
//
//  Created by     on 2020/06/01.
//  Copyright © 2020 dream. All rights reserved.
//

import UIKit
import Firebase

class ViewController: UIViewController {

    @IBOutlet weak var m_id: UITextField!
    @IBOutlet weak var m_pwd: UITextField!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
    }

    @IBAction func tapLogin(_ sender: Any) {
    }
    @IBAction func tapSignUp(_ sender: Any) {
//            m_id.text
//            m_pwd.text
        Auth.auth().createUser(withEmail: m_id.text!, password: m_pwd.text!) { authResult, error in
          // ...
            print(authResult)
        }
    }
}

 

이제 빌드를 한 다음 'abc@abc.com', 'test1234'를 넣고 SignUp을 누른다.

 

이제 Firebase로 돌아가 새로고침을 하면 사용자가 추가되어있다.

 

4.2 사용자 로그인

위에 사용자 가입 할때와 마찬가지로 로그인 function 버튼에 추가하고 변수 명을 우리 프로젝트에 맞게 바꿔준다.

//
//  ViewController.swift
//  smartFinance
//
//  Created by     on 2020/06/01.
//  Copyright © 2020 dream. All rights reserved.
//

import UIKit
import Firebase

class ViewController: UIViewController {

    @IBOutlet weak var m_id: UITextField!
    @IBOutlet weak var m_pwd: UITextField!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
    }

    @IBAction func tapLogin(_ sender: Any) {
        Auth.auth().signIn(withEmail: m_id.text!, password: m_pwd.text!) { [weak self] authResult, error in
          guard let strongSelf = self else { return }
          // ...
            print(strongSelf)
        }
    }
    @IBAction func tapSignUp(_ sender: Any) {
//            m_id.text
//            m_pwd.text
        Auth.auth().createUser(withEmail: m_id.text!, password: m_pwd.text!) { authResult, error in
          // ...
            print(authResult)
        }
    }
}

 

이제 위에서 가입할 때 사용한 'abc@abc.com'과 'test1234'를 넣고 로그인을 해본다. 현재 다른 기능을 구현하지 않아 로그인이 성공해도 다른 반응은 없겠지만 error가 nill(없음)이 나온다면 로그인이 성공한 것이다.

 

매번 아이디 비밀번호 입력하기 귀찮으니 테스트용으로 값을 아예 넣어두자.

 

동일한 아이디로 가입을 다시 눌러보면 에러가 발생한다.

 

 

 

 

AppDelegate.swift

//
//  AppDelegate.swift
//  smartFinance
//
//  Created by     on 2020/06/01.
//  Copyright © 2020 dream. All rights reserved.
//

import UIKit
import CoreData
import Firebase

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {



    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // Override point for customization after application launch.
        FirebaseApp.configure()
        return true
    }

    // MARK: UISceneSession Lifecycle

    func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
        // Called when a new scene session is being created.
        // Use this method to select a configuration to create the new scene with.
        return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
    }

    func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {
        // Called when the user discards a scene session.
        // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
        // Use this method to release any resources that were specific to the discarded scenes, as they will not return.
    }

    // MARK: - Core Data stack

    lazy var persistentContainer: NSPersistentContainer = {
        /*
         The persistent container for the application. This implementation
         creates and returns a container, having loaded the store for the
         application to it. This property is optional since there are legitimate
         error conditions that could cause the creation of the store to fail.
        */
        let container = NSPersistentContainer(name: "smartFinance")
        container.loadPersistentStores(completionHandler: { (storeDescription, error) in
            if let error = error as NSError? {
                // Replace this implementation with code to handle the error appropriately.
                // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
                 
                /*
                 Typical reasons for an error here include:
                 * The parent directory does not exist, cannot be created, or disallows writing.
                 * The persistent store is not accessible, due to permissions or data protection when the device is locked.
                 * The device is out of space.
                 * The store could not be migrated to the current model version.
                 Check the error message to determine what the actual problem was.
                 */
                fatalError("Unresolved error \(error), \(error.userInfo)")
            }
        })
        return container
    }()

    // MARK: - Core Data Saving support

    func saveContext () {
        let context = persistentContainer.viewContext
        if context.hasChanges {
            do {
                try context.save()
            } catch {
                // Replace this implementation with code to handle the error appropriately.
                // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
                let nserror = error as NSError
                fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
            }
        }
    }

}

 

SceneDelegate.swift

//
//  SceneDelegate.swift
//  smartFinance
//
//  Created by     on 2020/06/01.
//  Copyright © 2020 dream. All rights reserved.
//

import UIKit

class SceneDelegate: UIResponder, UIWindowSceneDelegate {

    var window: UIWindow?


    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
        // If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
        // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
        guard let _ = (scene as? UIWindowScene) else { return }
    }

    func sceneDidDisconnect(_ scene: UIScene) {
        // Called as the scene is being released by the system.
        // This occurs shortly after the scene enters the background, or when its session is discarded.
        // Release any resources associated with this scene that can be re-created the next time the scene connects.
        // The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead).
    }

    func sceneDidBecomeActive(_ scene: UIScene) {
        // Called when the scene has moved from an inactive state to an active state.
        // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive.
    }

    func sceneWillResignActive(_ scene: UIScene) {
        // Called when the scene will move from an active state to an inactive state.
        // This may occur due to temporary interruptions (ex. an incoming phone call).
    }

    func sceneWillEnterForeground(_ scene: UIScene) {
        // Called as the scene transitions from the background to the foreground.
        // Use this method to undo the changes made on entering the background.
    }

    func sceneDidEnterBackground(_ scene: UIScene) {
        // Called as the scene transitions from the foreground to the background.
        // Use this method to save data, release shared resources, and store enough scene-specific state information
        // to restore the scene back to its current state.

        // Save changes in the application's managed object context when the application transitions to the background.
        (UIApplication.shared.delegate as? AppDelegate)?.saveContext()
    }


}

 

ViewController.swift

//
//  ViewController.swift
//  smartFinance
//
//  Created by     on 2020/06/01.
//  Copyright © 2020 dream. All rights reserved.
//

import UIKit
import Firebase

class ViewController: UIViewController {

    @IBOutlet weak var m_id: UITextField!
    @IBOutlet weak var m_pwd: UITextField!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
    }

    @IBAction func tapLogin(_ sender: Any) {
        Auth.auth().signIn(withEmail: m_id.text!, password: m_pwd.text!) { [weak self] authResult, error in
          guard let strongSelf = self else { return }
          // ...
            print(strongSelf)
        }
    }
    @IBAction func tapSignUp(_ sender: Any) {
//            m_id.text
//            m_pwd.text
        Auth.auth().createUser(withEmail: m_id.text!, password: m_pwd.text!) { authResult, error in
          // ...
            print(authResult)
        }
    }
}

 

오늘은 스프링으로 홈페이지 회원가입, 로그인/로그아웃을 배웠다.

음... 정보변경이 숙제인데 세션에서 아이디 비번을 어떻게 빼오지...? ㅠ,ㅠ 구글아 도와줘...

그리고 운영체제 시간에는 도커를 배웠다.

아... 도커도, VM도, 포트포워딩도 다 원래부터 쓰던거라 알고는 있는데 GUI로만 하다가 CLI로 하니까 생각보다 어렵다....

'개발자 > TIL' 카테고리의 다른 글

TIL 20.06.04  (0) 2020.06.04
TIL 20.06.03  (0) 2020.06.04
TIL 20.06.01  (0) 2020.06.02
TIL 20.05.30  (0) 2020.05.31
TIL 20.05.29  (0) 2020.05.30

greendreamtrre.tistory.com/434?category=891838 : 도커 자바 서버 구성하기 과제 정리

 

1. 도커 설치

sudo apt update

sudo apt install curl

curl -s https://get.docker.com | sudo sh

docker -v

또는

sudo apt update

sudo apt install docker.io

sudo ln -sf /usr/bin/docker.io /usr/local/bin/docker

2020/06/30 - [개발자/OS(Linux, Docker, VM...)] - Docker (도커) 자바 서버 구성하기

 

docker-compose로 설치할 경우

sudo apt update

sudo apt install curl

sudo curl -L "https://github.com/docker/compose/releases/download/1.26.0/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose

sudo chmod +x /usr/local/bin/docker-compose

docker-compose -v

 

 

2. 도커 그룹에 사용자 계정 추가하기

sudo usermod -aG docker $USER    ## 현재 접속 중인 사용자를 도커 그룹에 넣기

sudo su - $USER   ## 적용을 위해 현재 사용자 재로그인 하기

만약 해당 계정에 sudo 권한이 없다면
addgroup 계정명 sudo
으로 sudo 그룹에 넣어준다.

현재 접속 중인 사용자가 아닌 다른 계정을 도커 그룹에 넣어주고 싶다면

sudo usermod -aG docker [유저이름]

을 하면 된다.

 

3. 도커 이미지 명령어

docker images : 로컬에 보유한 이미지 목록 보기
docker search [이미지이름] : 이미지 검색하기
docker pull [이미지이름][:태그] : 도커허브에서 이미지 내려받기
docker rmi [이미지이름] : 이미지 삭제하기 (해당 이미지를 사용하는 컨테이너가 있다면 삭제가 되지 않는다.)
docker rmi -f [이미지이름] : 이미지 강제 삭제하기 (해당 이미지를 사용하는 컨테이너도 함께 삭제한다.)

 

4. 도커 컨테이너 명령어

Create
docker create [OPTIONS] IMAGE [COMMAND] [ARG...]

Start <-> Stop
docker start [OPTIONS] CONTAINER [CONTAINER...]

Attach
docker attach [OPTIONS] CONTAINER

 

Run : docker run = docker create + docker start + docker attach

docker run [OPTIONS] IMAGE[:TAG|@DIGEST] [COMMAND] [ARG...]

@DIGEST는 특정 이미지의 고유 해시값으로
docker run ubuntu:14.04 이렇게 태그 대신 고유의 해시값을 이용할 수 있다.
docker run ubuntu@sha256:9cacb71397b640eca97488cf08582ae4e4068513101088e9f96c9814bfda95e0
이런식으로 사용할 수 있다.

옵션  
-d detached mode : 백그라운드 모드로 실행
-p [서버 포트]:[컨테이너 포트] port forwarding
-v=[서버 디렉토리]:[컨테이너 디렉토리] volume mount : (docker volume create [볼륨이름] 으로 생성한 경우 [서버 디렉토리]에 [볼륨이름]만 넣으면 된다. 물론, 내가 원하는 서버 내 디렉토리로 설정하는 것도 가능하다.)
-e environment : 환경변수
-env -e 옵션처럼 line by line이 아니라 여러 환경변수 일괄 적용
-it -i : interactive(사용자 입출력), -t : tty -> 컨테이너에서 터미널을 사용하기 위해 넣는 옵션
--name 컨테이너 이름 설정
--link="" 컨테이너 연결 : Add link to another container (<name or id>:alias or <name or id>)
--rm 컨테이너 종료시 자동 삭제

참고 : docs.docker.com/engine/reference/run/

 

docker run [OPTIONS] IMAGE[:TAG|@DIGEST] [COMMAND] [ARG...]
ex ) docker run -d -it --name greendreamtree -p 8080:80 ubuntu:latest /bin/bash

 

docker volume create [볼륨이름] : /var/lib/docker/volumes 디렉토리 안에 [볼륨이름]으로 디렉토리를 생성한다. (이렇게 생성하지 않고 내가 원하는 디렉토리 경로를 마운트 시켜도 된다.)

(참고 CentOS 7에서는 SELinux 때문에 도커가 도커 기본 볼륨 디렉토리에 접근이 불가능하다는 것 같다.)

 

 

docker ps : start 상태의 도커 컨테이너 보기
docker ps -a : 모든 상태의 도커 컨테이너 보기

docker top [컨테이너이름] : 실행중인 [컨테이너이름] 도커의 프로세스 보기

docker exec : 이미 실행중인 컨테이너에 명령을 실행
docker exec [OPTIONS] CONTAINER COMMAND [ARG...]

docker exec -d greendreamtree touch /tmp/execWorks
: 이미 실행중인 greendreamtree라는 이름을 가진 컨테이너에 touch 명령을 수행한다. /tmp/execWorks에 빈 파일 생성하는...

이미 실행중인 도커에 bash 쉘로 접속하기

$ docker exec -it <container> /bin/bash

# Use this if bash is part of your PATH

$ docker exec -it <container> bash

 

 

도커 컨테이너 내부 쉘에서 나가기

1 ) 컨테이너 종료시키지 않고 나오기
Ctrl + P, Q     이후 다시 컨테이너로 들어가려면 docekr attach [컨테이너이름]

2 ) 컨테이너 종료시키고 나오기
Ctrl + D
터미널에 'exit'입력

 

컨테이너, 이미지 관리 명령어

docker container logs -t [컨테이너이름] : 백그라운드로 실행중인 컨테이너 로그 보기

docer container rename [기존컨테이너이름] [새컨테이너이름] : 컨테이너 이름 변경

 

docker container rm -v [컨테이너이름] : 컨테이너에 할당한 볼륨 삭제

docker rmi가 이미지 삭제이기 때문에 
docker container rm [컨테이너이름] : 컨테이너 삭제
docker container rm -f [컨테이너이름] : 실행중인 컨테이너 강제 삭제
docker container prune : stop 상태의 도커 컨테이너를 모두 삭제

 

위에 rmi 명령어를 더 많이 쓸 것 같다.
docker image rm
[이미지이름]
: 이미지 삭제
docker image rm -f [이미지이름] : 이미지 강제 삭제
docker image prune  : unused 상태의 도커 이미지를 모두 삭제
docker image prune -a : 버전이 다른 동일 이미지 여러개가 있을 경우 가장 마지막 버전을 남기고 모두 삭제

 

image docker rmi [이미지이름]
= docker image rm [이미지이름]
이미지 삭제
docker rmi f [이미지이름]
= docker image rm -f [이미지이름]
이미지 강제 삭제
docker image prune unused 상태의 이미지 모두 삭제
docker image prune -a latest version만 남기고 모두 삭제
docker image prune -f ! 와 같다. 묻지만 않고 기능은 동일.
container docker rm [컨테이너이름]
=docker container rm [컨테이너이름]
컨테이너 삭제
docker rm -f [컨테이너이름]
=docker container rm -f [컨테이너이름]
컨테이너 강제 삭제
docker rm -l [컨테이너이름]
=docker container rm -l [컨테이너이름]
컨테이너 링크 삭제
docker rm -v [컨테이너이름]
=docker container rm -v [컨테이너이름]
컨테이너 볼륨 삭제
docker container prune stop 상태의 컨테이너 모두 삭제
docker container prune -f ! 와 같다. 묻지만 않고 기능은 동일.

마찬가지로, 'docker volume prune'을 하게 되면 도커 컨테이너에서 사용하지 않는 모든 볼륨을 삭제하고, 'docker network prune'을 하게 되면 사용하지 않는 모든 네트워크를 삭제한다. 마법의 명령어 prune

 

5. 도커허브

docker commit [컨테이너이름] [이미지이름][:태그] : 컨테이너 -> 이미지 (태그는 버전을 붙이면 된다)

docker tag [SOURCE_IMAGE][:TAG] [TARGET_IMAGE][:TAG] : 이미지에 태그 달기 (깃의 브랜치 정도로 볼 수 있다)
                       (TARGET_IMAGE 이름과 [:TAG] 버전명은 원본과 아무런 연관이 필요 없다. 식별하기 좋게만 만들면 된다.)

cf.

더보기
docker images

REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
myubuntu            1.0.0               64a7aa9f814b        About an hour ago   1.13GB
test                2.0.0               64a7aa9f814b        About an hour ago   1.13GB
ubuntu              20.04               74435f89ab78        2 weeks ago         73.9MB

## ubuntu:20.04는 도커 허브에서 pull 해서 받은 최초의 이미지다. (docker pull ubuntu:20.04)
## ubuntu:20.04를 ub라는 컨테이너로 만들어 여러 앱을 설치했고, (docker run -d -it --name ub ubuntu:20.04 /bin/bash)
## ub라는 컨테이너를 myubuntu:1.0.0 이라는 이미지로 commit을 해서 생성했다. (docker commit ub myubuntu:1.0.0)
## myubuntu:1.0.0 이라는 이미지를 test:2.0.0 이라는 이미지로 태그를 달아줬다. (docker tag myubuntu:1.0.0 test:2.0.0)
## IMAGE ID를 보면 myubuntu:1.0.0은 이미지를 새로 생성해서 IMAGE ID가 부여되었다는 것을 알 수 있다.
## test:2.0.0은 태그를 달아준 것이기 때문에 myubuntu:1.0.0과 IMAGE ID가 같다는 것을 알 수 있다.
## 이런식으로 태그를 여러개 따면 이미지를 여러개 만들지 않고도 브랜치 따듯 관리를 할 수 있다.

 

아래는 도커허브를 사용하기 위해 로그인이 필요한 명령어다

docker login <-> docker logout

docker push [이미지이름][:태그] : 이미지를 도커허브에 업로드 

단, 도커허브에 이미지를 push 할 때는 이미지 이름에 규칙이 따른다.

[도커허브ID]/[이미지이름][:TAG]
 형식으로 이미지를 만들어야한다.

이것은 컨테이너에서 이미지로 commit할 때 이렇게 만들어도 되고, 이미지에 새 태그를 달 때 만들어도 된다. 다만 이미지를 만든 다음 도커허브에 올릴 이미지를 하나 더 태그하는 방식으로 브랜치처럼 관리하는 것이 권장된다.
( 주의 : [이미지이름][:TAG] 으로 만들고 push할 때 앞에 '[도커허브ID]/'를 붙여도 될 것 같지만 '[도커허브ID]/[이미지이름][:TAG]' 전체를 하나의 문자열로 인식해 해당하는 이미지가 없다고 에러가 발생한다.)

 

 

docker save : 하나 또는 그 이상의 이미지를 tar archive로 저장한다.

docs.docker.com/engine/reference/commandline/save/

 

docker load : tar archive로부터 하나의 이미지를 불러온다.

docs.docker.com/engine/reference/commandline/load/

 

docker cp : 호스트와 도커 컨테이너간 파일/폴더를 복사한다. (맥에서 Parallels에 설치된 VM 내에 파일/폴더를 보내고 받고 하는 것을 생각하면 된다.)

docs.docker.com/engine/reference/commandline/cp/

 

 

오늘은 파이썬 시간에 concat과 merge를 배웠고, 스위프트 시간에는 firebase를 배웠다.

근데... firebase 너는 대체 무엇이냐... aws도 이런거려나??

'개발자 > TIL' 카테고리의 다른 글

TIL 20.06.03  (0) 2020.06.04
TIL 20.06.02  (0) 2020.06.02
TIL 20.05.30  (0) 2020.05.31
TIL 20.05.29  (0) 2020.05.30
TIL 20.05.28  (0) 2020.05.29

이건 뭐 매일 당일꺼를 쓰는게 아니라 하루씩 지나서 전날꺼를 쓰네...

뭔가 자바를 계속 보긴 했는데... 별로 진도가 안 나갔다.

쓰레드 예제는 저장이 제대로 안 됐는지 없다 ㅠㅠ 학교 가서 따로 복사해둔 파일 있는지 찾아봐야겠다.

'개발자 > TIL' 카테고리의 다른 글

TIL 20.06.02  (0) 2020.06.02
TIL 20.06.01  (0) 2020.06.02
TIL 20.05.29  (0) 2020.05.30
TIL 20.05.28  (0) 2020.05.29
TIL 20.05.27  (0) 2020.05.27

1. ExecClass

public class ExecClass {
	public static void main(String[] args) {
		// 1.
		class GameManager{		// 다른 곳에서는 사용 못 하고 오직 이 메소드 내에서만 사용할 수 있는 클래스. 클래스 파일을 하나 더 만들고 빼도 된다.
			public boolean doGame(Horse[] horse) {
				boolean isEnd = true;	// Horse의 isEnd와 다른 isEnd다. 이 위치에 선언하고 각 말의 객체의 isEnd와 비교해야 모든 말이 완주할 수 있고 return을 돌려보낼 수 있다.
				for (int i = 0; i < horse.length; i++) {	// Horse 객체를 0번부터 n번까지 달리게 만드는 반복문
					horse[i].runHorse();
					isEnd = isEnd && horse[i].isEnd; // doGame에서 true로 놓은 게임
				}
				return isEnd;
			}
		}
		// Horse 객체 생성
		Horse[] horse = {new Horse("흑마"), new Horse("적토마")};
		
		// GameManager 메모리에 할당
		GameManager gameManager = new GameManager();
		
		// 턴을 증가시키는 반복문
		for (int i = 0; i < 99999; i++) {
			boolean isEnd = gameManager.doGame(horse);
			System.out.println("---------------------------------");
			if (isEnd) {
				break;
			}
		}
	}
}

 

2. Horse

import java.util.Random;

public class Horse {
	String name;
	int distance;
	boolean isEnd = false;
	static int rank = 0;
	
	Horse(String name) {
		this.name = name;
	}
	public void runHorse() {
		if (!this.isEnd) {
			this.distance = this.distance + (int)Math.floor(Math.random() * 100);	// 매 턴마다 0~100 사이의 정수의를 달린 거리로 랜덤 누적.
			System.out.println(this.name + " : " + this.distance);
			this.checkEnd();	// 달린 누적 거리가 1000 이상이면 해당 객체의 isEnd를 True로 바꿔주는 메소드를 매번 체크 (게임에서 isLive 체크와 동일)
		}
	}
	public void checkEnd() {
		if (distance >= 1000) {
			isEnd = true;
			Horse.rank++;
			System.out.println(this.name + " is goal" + " rank" + Horse.rank);
		}
	}
	
	public void run() {
		Random r = new Random();
		for (int i = 0; i < 99999; i++) {
			this.runHorse();
			if (this.isEnd) {
				break;
			}
			int sleepTime = r.nextInt(10) * 1000 + 300;
			try {
				Thread.sleep(sleepTime);
			} catch (InterruptedException e) {}
		}
	}
}

 

결과

흑마 : 87
적토마 : 53
---------------------------------
흑마 : 163
적토마 : 121
---------------------------------
흑마 : 252
적토마 : 172
---------------------------------
흑마 : 336
적토마 : 240
---------------------------------
흑마 : 401
적토마 : 261
---------------------------------
흑마 : 434
적토마 : 328
---------------------------------
흑마 : 529
적토마 : 343
---------------------------------
흑마 : 594
적토마 : 360
---------------------------------
흑마 : 679
적토마 : 436
---------------------------------
흑마 : 740
적토마 : 468
---------------------------------
흑마 : 781
적토마 : 503
---------------------------------
흑마 : 801
적토마 : 530
---------------------------------
흑마 : 835
적토마 : 559
---------------------------------
흑마 : 864
적토마 : 629
---------------------------------
흑마 : 915
적토마 : 636
---------------------------------
흑마 : 973
적토마 : 716
---------------------------------
흑마 : 1015
흑마 is goal rank1
적토마 : 783
---------------------------------
적토마 : 851
---------------------------------
적토마 : 926
---------------------------------
적토마 : 980
---------------------------------
적토마 : 1042
적토마 is goal rank2
---------------------------------

 

또 다른 결과

흑마 : 68
적토마 : 62
---------------------------------
흑마 : 89
적토마 : 66
---------------------------------
흑마 : 139
적토마 : 129
---------------------------------
흑마 : 189
적토마 : 195
---------------------------------
흑마 : 274
적토마 : 216
---------------------------------
흑마 : 373
적토마 : 294
---------------------------------
흑마 : 454
적토마 : 391
---------------------------------
흑마 : 509
적토마 : 489
---------------------------------
흑마 : 530
적토마 : 523
---------------------------------
흑마 : 587
적토마 : 565
---------------------------------
흑마 : 599
적토마 : 650
---------------------------------
흑마 : 607
적토마 : 678
---------------------------------
흑마 : 706
적토마 : 736
---------------------------------
흑마 : 802
적토마 : 805
---------------------------------
흑마 : 813
적토마 : 883
---------------------------------
흑마 : 841
적토마 : 954
---------------------------------
흑마 : 931
적토마 : 1008
적토마 is goal rank1
---------------------------------
흑마 : 1010
흑마 is goal rank2
---------------------------------
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

public class ExecMain {
	public static void main(String[] args) throws IOException  {
		InputStream in = null;
		OutputStream out = null;
		try {
			in = new FileInputStream(".//Hello Polytech.rtf");
			out = new FileOutputStream(".//Hello Polytech2.rtf");
			System.out.println("실행1");
			while(true) {
				int data = in.read();
				System.out.println("실행2");
				if (data == -1) {
					System.out.println("실행3");
					break;
				}
				out.write(data);
				System.out.println("실행4");
			}
		} catch (Exception e) {
			System.out.println("실행5");
		} finally {	// try ~ catch 구문 뒤에 붙어 '작동을 보장해야 하는 로직'을 넣는다.
			if (in != null) {
				in.close();
			}
			if (out != null ) {
				out.close();
			}
			System.out.println("실행6");
		}
	}
}

이렇게 하고 실행하면 어떤 순서로 실행 되는지를 확인할 수 있다.

 

 

Hello Polytech라는 파일에

H

라는 문자가 하나 있는 경우

결과 :

실행1
실행2
실행4
실행2
실행3
실행6

동일 디렉토리에 "H"이라는 문자 하나를 저장한 Hello Polytech2.txt 생성

 

 

Hello Polytech라는 파일에 

He

라는 문자 2개가 있는 경우

결과 :

실행1
실행2
실행4
실행2
실행4
실행2
실행3
실행6

동일 디렉토리에 "H"이라는 문자 하나를 저장한 Hello Polytech2.txt 생성

 

while문만 보자.

"H" 문자 하나만 저장된 경우
2 > 4 > 2 > 3

"He" 문자 2개가 저장된 경우
2 > 4 > 2 > 4 > 2 > 3

 

"Hello Polytech" 공백 포함 문자 14개가 저장된 경우
2 > 4 > 2 > 4 > 2 > 4 > 2 > 4 > 2 > 4 > 2 > 4 > 2 > 4 > 2 > 4 > 2 > 4 > 2 > 4 > 2 > 4 > 2 > 4 > 2 > 4 > 2 > 4 > 2 > 3

2 > 4 가 14번 반복된 후 2 > 3 을 타고 종료된다.

 

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

public class ExecMain {
	public static void main(String[] args) throws IOException  {
		InputStream in = null;
		OutputStream out = null;
		try {
			in = new FileInputStream(".//Hello Polytech.txt");
			out = new FileOutputStream(".//Hello Polytech2.txt");
			System.out.println("실행1");
			while(true) {
				int data = in.read();
				System.out.println("실행2 : " + data);
				if (data == -1) {
					System.out.println("실행3");
					break;
				}
				out.write(data);
				System.out.println("실행4");
			}
		} catch (Exception e) {
			System.out.println("실행5");
		} finally {	// try ~ catch 구문 뒤에 붙어 '작동을 보장해야 하는 로직'을 넣는다.
			if (in != null) {
				in.close();
			}
			if (out != null ) {
				out.close();
			}
			System.out.println("실행6");
		}
	}
}


결과 :

실행1
실행2 : 72 -> H
실행4
실행2 : 101 -> e
실행4
실행2 : 108 -> l
실행4
실행2 : 108 -> l
실행4
실행2 : 111 -> o
실행4
실행2 : 32 -> " "
실행4
실행2 : 80 -> P
실행4
실행2 : 111 -> o
실행4
실행2 : 108 -> l
실행4
실행2 : 121 -> y
실행4
실행2 : 116 -> t
실행4
실행2 : 101 -> e
실행4
실행2 : 99 -> c
실행4
실행2 : 104 -> h
실행4
실행2 : -1
실행3
실행6

data를 찍어보면 저렇게 문자별로 매칭이 된다. 문자를 읽기 못 할 경우 -1 False로 반환한다. 따라서 문자열 3개만 반환하겠다고 0 1 2 생각해서 2를 넣거나 하면 안 된다. '108'을 넣어 'l'이 나오면 멈추게 하는 것은 가능하지만... 그냥 -1 False일 경우 멈추는 형태의 공식으로만 이용하는 것이 좋다.

 

cf.

리치 텍스트 문자 같은 서식이 있는 문서는 텍스트 편집기로 열었을 때는 문자가 보이지 않지만 실제로는 서식을 정의하는 텍스트가 포함되어 있기 때문에 문자 1개만 들어있는 파일이라도 해당 서식을 정의하는 텍스트 문자열까지 인식해서 복사하기 때문에 많은 실행을 하게 된다.(html에서 실제로는 font-size : 12px 이런게 있어도 웹 페이지에는 보이지 않는 것과 마찬가지)

+ Recent posts