Reactive Programming

Block Chain

  • Block Chain 시작 1차 기본 개념 이해 및 정리 / 2019-12-08 / 진행 / < >

ReactJS

ReactNative

Electron

Realm

APR ( Apache Portable Runtime ) 구축하기 

Restful Web Services with React JS

Apache - Kafka

Apache Spark 

Apache Hadoop

Javascript ( ECMAScript 6 )

ReasonML

Progressive Web Apps

GraphQL

NodeJS

Machine Learning

Rabbit MQ

Java

Spring IO

Rust - https://rinthel.github.io/rust-lang-book-ko/

C인메모리 DB 만들어보기 

C++

C#

Web Assembly

Go

 


Docker

쿠버네티스 

프로메테우스

Scala

Kotlin

TensorFlow

Firebase

Jenkins

Xamarine

Cordova


Unity

Linux 

AWS 


 

  • 객체지향 프로그래밍 

    • 객체지향 설계 개발하기 

  • 소켓 프로그래밍

  • 자바로 테일 프로그래밍 

  • 큐 프로그래밍하기 

  • 비동기 I/O 개발하기

  • JVM 원리 

  • 네트워크

  • 보안 프로그래밍

  • 파서 개발하기 

  • 웹 소켓 프록래밍

  • XML 데이터 다루기 

  • 분산 트렌젝션

  • 암호화/복호화 

  • 직렬화 

  • 파일 입출력 처리 

  • 멀티 스레드

  • 제네릭 

  • Netty를 활용한 WebSocket 프로세스 

  • UDP를 이용한 채팅 프로그램

  • Pubsub Flow


 

화면 구성하기 


 본격적으로 App을 만들기 위해서 구성한 화면이다. Timer App을 아래와 같이 만들어봤다. 

 

Timer 상위 카테고리 
Timer 하위 카테고리 

위의 화면을 구성하기 위해서 React Native에서 기본적으로 배워야할 Layout 구성 방식을 보면, 

 

1. Layout with FlexBox 

 

  Flex는  내가 지정할 View를 지정가능한 공간에 대해서 얼마나 "채울수" 있는지 지정할 수 있다. 즉 비율로 상대적 크기를 설정할 수 있다. 

 

  Flex 값은 각각의 영역을 구분하는데 효과적으로 사용할 수 있는데,

 

  예를 들어 

 

    <View style={{flex : 1}} >

         <View style={{flex : 2}}></View>

         <View style={{flex : 3}}></View>

    </View>

 

   와 같은 레이아웃으로 구성되어 있다고 할 때, 

 

  가장 외부를 감싸고 있는 View의 높이를 기준으로 내부의 View는 각각 2/5 , 3/5의 높이를 가지게 된다. 

 

import React, { Component} from 'react';
import {
  View,
} from 'react-native';



class App extends Component {
  render(){
    return (
      <View style={{flex : 1, backgroundColor:"white"}} >
  
           <View style={{flex : 2, backgroundColor:"red"}}></View>
  
           <View style={{flex : 3, backgroundColor:'blue'}}></View>
  
      </View>
    );
  }
};

export default App;

위의 코드를 실제로 실행해보면 아래와 같이 영역이 구분되는 것을 볼 수 있다.      

2/5 , 3/5

여기에서 부모의 View를 크기 내에서 자식들의 View의 영역을 비율로 지정하는 것인데, 실제로 부모의 View의 크기를 조정하면 아래와 같이 조정할 수 있다. 

import React, { Component} from 'react';
import { View, } from 'react-native';

class App extends Component {
  render(){
    return (
      <View style={{ height:400, backgroundColor:"white" }} >
           <View style={{flex : 2, backgroundColor:"red"}}></View>
           <View style={{flex : 3, backgroundColor:'blue'}}></View>
      </View>
    );
  }
};

export default App;

강제로 View의 높이를 400으로 지정했을 때 내부의 View의 크기가 "상대적"으로 각각의 비율(2/5 , 3/5)에 따라 조정되었다. 

 

2. Flex Direction

 

 flexDirection은 각각의 하위 아이템들의 정렬 방향을 지정할 수 있다. 

 

   flexDirection : 'row' 로 지정되면, 행을 기준으로 정렬된다. 

 

<View style={{ flex :1, flexDirection : 'row', backgroundColor:"white" }} >
   <View style={{flex : 2, height : 60, backgroundColor:"red"}}></View>
   <View style={{flex : 3, height : 60, backgroundColor:'blue'}}></View>
 </View>

   

flexDirection : 'row'

    flexDirection : 'row-reverse' 로 지정되면 내부 아이템의 정렬이 반대로 적용된다. 

 

<View style={{ flex :1, flexDirection : 'row-reverse', backgroundColor:"white" }} >
           <View style={{flex : 2, height : 60, backgroundColor:"red"}}></View>
           <View style={{flex : 3, height : 60, backgroundColor:'blue'}}></View>
      </View>

row-direction : 'row-reverse'

     flexDirection : 'column' 으로 지정되면 내부 아이템이 세로로 위치된다.

 

class App extends Component {
  render(){
    return (
      <View style={{ flex :1, flexDirection : "column", backgroundColor:"white", marginTop : 100, backgroundColor : '#00ffff' }} >
           <View style={{height : 60, width : 60, backgroundColor:"red"}}></View>
           <View style={{height : 60, width : 60, backgroundColor:'blue'}}></View>
      </View>
    );
  }
};

flexDirection : 'column'

     flexDirection : 'column-reverse' 으로 지정되면 아이템의 정렬 순서 및 시작위치가 반대로 위치한다.

class App extends Component {
  render(){
    return (
      <View style={{ flex :1, flexDirection : "column-reverse", backgroundColor:"white", marginTop : 100, marginBottom :100, backgroundColor : '#00ffff' }} >
           <View style={{height : 60, width : 60, backgroundColor:"red"}}></View>
           <View style={{height : 60, width : 60, backgroundColor:'blue'}}></View>
      </View>
    );
  }
};

flexDirection : 'column-reverse'

3. Layout Direction

 

  Justity Content - 내부 아이템을 감싸는 Container를 기준으로 어떻게 정렬할 것인지를 지정할 수 있음 

import React, { Component} from 'react';
import { View, } from 'react-native';

class App extends Component {
  render(){
    return (
      <View style={{ flex :1, flexDirection : "column-reverse", justifyContent : 'space-around' , backgroundColor:"white", marginTop : 100, marginBottom :100, backgroundColor : '#00ffff' }} >
           <View style={{height : 60, width : 60, backgroundColor:"red"}}></View>
           <View style={{height : 60, width : 60, backgroundColor:'blue'}}></View>
           <View style={{height : 60, width : 60, backgroundColor:'yellow'}}></View>
           <View style={{height : 60, width : 60, backgroundColor:'green'}}></View>
           <View style={{height : 60, width : 60, backgroundColor:'purple'}}></View>
      </View>
    );
  }
};

export default App;

     

  Align Items - 내부 아이템을 감싸는 Continer의 가로축을 기준으로 어떻게 정렬할 것인지를 지정할 수 있음 

import React, { Component} from 'react';
import { View, } from 'react-native';

class App extends Component {
  render(){
    return (
      <View style={{ flex :1, flexDirection : "column-reverse", alignItems : 'center', backgroundColor:"white", marginTop : 100, marginBottom :100, backgroundColor : '#00ffff' }} >
           <View style={{height : 60, width : 60, alignSelf : 'flex-start', backgroundColor:"red"}}></View>
           <View style={{height : 60, width : 60, backgroundColor:'blue'}}></View>
           <View style={{height : 60, width : 60, backgroundColor:'yellow'}}></View>
           <View style={{height : 60, width : 60, backgroundColor:'green'}}></View>
           <View style={{height : 60, width : 60, backgroundColor:'purple'}}></View>
      </View>
    );
  }
};

export default App;

  Align Self  - 내부 아이템 각각의 정렬 어떻게 정렬할 지를 지정할 수 있고 만약 부모 Container에서 Align Item이 선언되어 있다면 해당 속성은 AlignSelf에 의해 무시된다. 

import React, { Component} from 'react';
import { View, } from 'react-native';

class App extends Component {
  render(){
    return (
      <View style={{ flex :1, flexDirection : "column-reverse", alignItems : 'center', backgroundColor:"white", marginTop : 100, marginBottom :100, backgroundColor : '#00ffff' }} >
           <View style={{height : 60, width : 60, alignSelf : 'flex-start', backgroundColor:"red"}}></View>
           <View style={{height : 60, width : 60, backgroundColor:'blue'}}></View>
           <View style={{height : 60, width : 60, backgroundColor:'yellow'}}></View>
           <View style={{height : 60, width : 60, backgroundColor:'green'}}></View>
           <View style={{height : 60, width : 60, backgroundColor:'purple'}}></View>
      </View>
    );
  }
};

export default App;

  Align Content - 횡축을 기준으로 분포를 정의하는데, 이는 flexWrap을 사용하는 다중 라인(줄)으로 감쌀때만 적용된다. 

import React, { Component} from 'react';
import { View, } from 'react-native';

class App extends Component {
  render(){
    return (
      <View style={{ flex :1, flexDirection : "column-reverse", flexWrap : 'wrap', alignContent : 'center', backgroundColor:"white", marginTop : 100, marginBottom :100, backgroundColor : '#00ffff' }} >
           <View style={{height : 60, width : 60, backgroundColor:"red"}}></View>
           <View style={{height : 60, width : 60, backgroundColor:'blue'}}></View>
           <View style={{height : 60, width : 60, backgroundColor:'yellow'}}></View>
           <View style={{height : 60, width : 60, backgroundColor:'green'}}></View>
           <View style={{height : 60, width : 60, backgroundColor:'purple'}}></View>
      </View>
    );
  }
};

export default App;

 Flex Wrap - 내부의 아이템의 Container의 사이즈를 벗어나는 것을 제어하고자 할 때 사용할 수 있다. alignContent는 아이템에 Container 영역을 넘어서서 다음칸으로 채워질 때 어떤 정렬로 채워지게 할 것인가를 정할 수 있다. 

 

 그외에도 

 

 flexGrow, flexShrink, flexBasis 등의 옵션이 존재한다. 

 

 

 

'따라해보기' 카테고리의 다른 글

0001 React Native App 개발기 6-1  (0) 2019.12.12
0001 React Native App 개발기 5  (0) 2019.12.08
0001 React Native App 개발기 3  (1) 2019.11.17
0001 React Native App 개발기 2  (0) 2019.11.15
0001 React Native App 개발기 1  (0) 2019.11.15

 

Realm을 활용하기 위한 Realm Helloworld!


1. 프로젝트 생성

 

npx를 이용한 react-native 프로젝트 생성

 

2. npm을 이용한 Realm 인스톨 

npm install 진행

3. ios 폴더로 들어가 pod install 진행

pod install 진행

 하지만, 위의 작업을 진행하자마자 아래와 같은 메세지가 발생했다.

 

흠.. 왜일까, 

  이와 관련된 누락사항에 대해서 하나씩 찾아보기로 했다.

 

 1. 우선 React Native 의 버전이다.

 

   만약 React Native의 버전이 v0.60보다 낮다면 react-native link realm을 처리해줘야한다. 그래서 package.json 파일을 확인했다.

 

package.json의 react-native 버전 확인

 React Native 버전의 문제가 아닌건 확실해보인다. 

 

 2. Node Modules을 삭제하고 다시한번 npm install을 진행했다. 

 

node modules 삭제 후 npm을 통한 재설치 진행

아~! 위와 같이 진행하고 난 뒤에 다시 pod install을 진행했더니 너무 잘 진행이되는 것이었다!

정상적인 pod install 결과 

위와 같이 설치 하고 난 이후, ios 폴더 내의 xcworkspace 파일을 실행했다. 

XCode 내의 RealmJS를 확인할 수 있었다. 

RealmJS 설치 후 XCode 확인

본격적으로 RealmJS를 활용하기 위한 로딩 화면이다. 

React Native를 이용한 IOS의 기본 화면

 

'알아보기' 카테고리의 다른 글

0004 Reactive Programming 2  (0) 2019.12.05
0004 Reactive Programming 1  (0) 2019.12.04
0002 Realm 활용하기 3  (0) 2019.11.22
0003 자바를 이용한 병렬 프로그래밍 1  (0) 2019.11.20
0002 Realm 활용하기 1  (0) 2019.11.20

2019년 11월 22일 기준 

 

Realm을 활용할 수 있는 가이드는 아래의 링크에 상세하게 잘나와있기 때문에 실제 개발 예제에 따라 어떤 방식으로 사용할 수 있는지 알아보려 한다. 

 

Realm Javascript 한글 공식 가이드  

 


요구사항 

  1. Lines 라는 그룹에 사용자를 화면이 로딩 될 때 추가한다. 

  2. 버튼을 이용해 추가한 사용자를 가져온다. 

  3. 중복 사용자는 저장되면 안된다. 

 

 위의 요구사항를 위해서 아래와 같이 코드를 작성해보았다. 

import React, { Component} from 'react';
import { View, Text, Button, TextInput } from 'react-native';
const Realm = require('realm');

class DBSample extends Component {
    constructor(props){
      super(props);

      this.realm = {};

      this.state = {
        formData : {
          userId : "",
          userPwd : "",
          position : ""
        }
      }

      this.createLinesSchema();

      
    }

    createLinesSchema = () => {
      const LineSchema = {
        name : 'LineUser',
        properties : {
          userId : 'string',
          userPwd : 'string',
          position : 'string?'
        }
      }

      this.realm = new Realm({schema : [ LineSchema ]});

      this.realm.write( () => {
        const LineUser = this.realm.create('LineUser', {
          userId : "admin",
          userPwd : "admin@1234"
        });

        LineUser.position = "Manager";
      });
    }
    
    searchUserData = () => {
      const cars = this.realm.objects("LineUser").filtered("userId == 'admin'");

      this.setState({ formData : cars[0]});
    }

    handleInputData = ( value, field ) => {

      let formData = this.state.formData;

      formData[field] = value;

      this.setState({ formData : formData});
    }

    render() {
      return (
        
          Home Screen
           this.props.navigation.navigate('Home')}
          />
          
           { this.handleInputData(value, "userid") } } >
           { this.handleInputData(value, "userpwd") } } >
           { this.handleInputData(value, "position") } } >
        
      );
    }
  }

export default DBSample;

 

위에서 사용된 Realm의 기본적인 함수들에 대해서 알아보면,

 

Lines 24 :

   - Realm에 등록할 Model을 설정한다. 

     우리가 데이터를 저장할 Table이라고 생각해도 되며 

   Lines 25의 name은 Realm으로 부터 Model을 꺼내 데이터를 저장, 삭제, 수정할 수 있는 Key되는 명칭이다. 

   Lines 26 부터는 Model에서 사용할 Property를 지정하는데, Table의 Field라고 생각하면 되며, Field에 지정하는 Not Null, Nullable, Default Value, Primary Key , Type 등을 지정할 수 있다. 다만 데이터 타입으로 올 수 있는 것이 좀더 확장된다고 생각해도 좋을 것 같다. 

 

Lines 34 :

   - Realm 연결하기 

    > Realm의 새로운 객체를 생성 후 반환하다. 만약 Realm 이 아예 생성되어 있지않다면 새로 생성할 것이며, 이미 존재한다면 존재하는 Realm 객체를 반환할 것이다. 

 

Lines 36 :

   - 쓰기 트랜잭션을 사용하기 위해서 callback 메서드를 제공하는데, 동기적으로 호출 된다. 

 

Lines 37 :

   -  정의한 데이터 타입과 속성에 맞는 새로운 Realm 객체를 생성한다.

 

Lines 42 : 

   - Realm 오브젝트에 대해서 업데이트를 처리한다. 

 

Lines 47 :

    - Realm 안에 등록되어 있는 type의 모든 오브젝트를 반환한다. 

    - realm.objects로 부터 시작해 데이터를 조회 하고 정렬하는 등의 다양한 작업을 지원할 수 있으며, 메서드 체인으로 호출이 가능합니다. 

 

위에서 사용된 함수들은 

 

    new Realm(config)

    write(callback)

    create(type, properties, update) -> Realm.Object

    objects(type) -> Realm.Results 

    filtered(query, arg) -> Realm.Results

 

이었다. 

 

   위의 함수들외에도 Realm에서 제공하는 Realm API Docs 에서 자세히 확인할 수 있다. 

 

'알아보기' 카테고리의 다른 글

0004 Reactive Programming 2  (0) 2019.12.05
0004 Reactive Programming 1  (0) 2019.12.04
0002 Realm 활용하기 2  (0) 2019.11.22
0003 자바를 이용한 병렬 프로그래밍 1  (0) 2019.11.20
0002 Realm 활용하기 1  (0) 2019.11.20

자바 병렬 프로그래밍이라는 책으로 병렬 프로그래밍을 위한 기본 사항들에 대해서 알아보았다. 

 

 

싱글 스레드 ( Single Thread ) : 순차적으로 작업을 진행하는 것 

 

멀티 스레드 ( Multi Thread ) : 여러 작업을 동시에 진행할 수 있는 것 

 


기본사항에 대해서 알아보자 [더보기] 클릭

더보기

스레드 와 프로세스의 차이 

 아래의 이미지를 보면 프로세스는 211개 , 스레드는 2675개가 존재한다. 


프로세스와 스레드 

 프로세스는 컴퓨터에서 실행되고 있는 컴퓨터 프로그램으로 운영 체제로 부터 시스템 자원을 할당 받는 단위이다. 

 

 스레드는 프로세스 내에서 실행되는 여러 흐름의 단위를 이야기하고, 프로세스가 할당받은 자원을 실행하는 단위이다.

 

 

Context Switching


우리가 사용하는 프로그램들은 각기 프로세스를 가지고 있다. 한 프로세스가 CPU를 사용중일 때, 다른 프로세스가 CPU를 사용하기 위해, 이전의 프로세스 상태를 보관하고 새로운 프로세스 상태를 CPU에 적재하는 것을 Context Switching 이라고 하는데, 프로세스 단위의 Context Switching은 Data, Heap, Stack의 모든 것이 그대로 추가 된다. Process 간에는 데이터 공유가 불가능하다. 

 

Thread Context Switching 


Thread 간에도 지금 실행되고 있는 Thread에서 다른 Thread가 실행될 수 잇게 스케줄러가 현재 실행 중인 스레드를 잠시 멈추고, 이전 스레드의 Context를 저장하고 작업을 진행할 스레드의 Context를 저장하고 작업을 진행할 Thread의 Context를 읽어오는 작업을 한다. 프로세스와는 달리 Data와 Heap은 그대로 사용되어 공유하게 되고, Stack 만 추가되어 새로운 class 호출이 용이하다. 

 

- 자바 병렬 프로그래밍 책 중, 

 

   잘 설계된 병렬 프로그램은 스레드를 사용해서 궁극적으로 성능을 향상시킬 수 있다. 하지만 스레드를 사용하면 실행 중에 어느정도 부하가 생기는 것도 사실이다.  실행 중인 컨텍스트를 저장하고 다시 읽어 들여야 하며, 메모리를 읽고 쓰는 데 있어 지역성이 손실되고, 스레드를 실행하기도 버거운 CPU 시간을 스케쥴링하는데 소모해야한다. 또 스레드가 데이터를 공유할 때는 동기화 수단도 사용해야한다. 이런 동기화는 컴파일러 최적화를 방해하고, 메모리 캐시를 지우거나 무효화 하기도 한다.

  이를 우리는 멀티스레드 상에서 동작시에 발생할 수 있는 성능 위험이라고 이야기할 수 있다. 

 


멀티스레드와 관련된 사항 


  • 경쟁 조건 

경쟁조건은 상대적인 시점이나 또는 JVM이 여러 스레드를 교차해서 실행하는 상황에 따라 계산의 정확성이 달라질 때 나타난다. 다시 말하자면 타이밍이 딱 맞았을 때만 정답을 얻는 경우를 말한다.

 

경쟁 조건 vs 데이터 경쟁

 

 데이터 경쟁은 공유된 final이 아닌 필드에 대한 접근을 동기화로 보호하지 않았을 때 발생한다. 스레드가 다음에 다른 스레드가 읽을 수 있는 변수에 값을 쓰거나 다른 스레드가 마지막에 수정했을 수 있는 변수를 읽을 때 두 스레드 모두 동기화하지 않으면 데이터 경쟁이 생길 위험이 있다. 데이터 경쟁이 있는 코드는 자바 메모리 모델 하에선 유용한 정의된 의미가 없다. 모든 경쟁조건이 데이터 경쟁인 것 아니고, 모든 데이터 경쟁이 경쟁 조건인 것도 아니다. 하지만 경쟁조건이든 데이터 경쟁이든 병렬 프로그램을 예측할 수 없이 실패하게 만든다.

 

  • 가시성 ( 메모리 가시성 ) 

 메모리 상의 공유된 변수를 여러 스레드에서 서로 사용할 수 있게 하려먼 반드시 동기화 기능을 구현해야 한다. 공유 자원(변수)에 대해서는 반드시 적절한 동기화 작업이 필요하다.

 

 동기화 기능을 지정하지 않으면 컴파일러나 프로세서, JVM(자바 가상 머신) 등이 프로그램 코드가 실행되는 순서를 임의로 바꿔 실행하는 이상한 경우가 발생하기도 한다. 다시 말하자면, 동기화 되지 않은 상황에서 메모리 상의 변수를 대상으로 작성해둔 코드가 '반드시 이런 순서로 동작할 것이다' 라고 단정 지을 수 없다.

 

Thread와 CPU Cache / Main Memory


내장된 락을 적절히 활용하면 특정 스레드가 특정 변수를 사용하려 할 때 이전에 동작한 스레드가 해당 변수를 사용하고 난 결과를 상식적으로 예측할 수 있는 상태에서 사용할 수 있다. 락은 상호 배제 뿐만 아니라 정상적인 메모리 가시성을 확보하기 위해서도 사용된다. 변경 가능하면서 여러 스레드가 공유해 사용하는 변수를 각 스레드에서 각자 최신의 정상적인 값으로 활용하려면 동일한 락을 사용해 모두 동기화 시켜야 한다.

 

자바 병렬 프로그래밍 책 이미지 참조

 여러 스레드에서 접근할 수 있고 변경 가능한 모든 변수를 대상으로 해당 변수에 접근할 때는 항상 동일한 락을 먼저 확보한 상태여야 한다. 이 경우 해당 변수는 확보된 락에 의해 보호된다고 말한다. 모든 변경할 수 있는 공유 변수는 정확하게 단 하나의 락으로 보호해야한다. 유지보수 하는 사람이 알 수 있게 어느 락으로 보호하고 있는지를 명확하게 표시하라. 락을 활용함에 있어 일반적인 사용 예는 먼저 모든 변경 가능한 변수를 객체 안에 캡슐화 하고, 해당 객체의 암묵적인 락을 사용해 캡슐화한 변수에 접근하는 모든 코드 경로를 동기화 함으로써 여러스레드가 동시에 접근하는 상태에서 내부 변수를 보호하는 방법이다

public class TestLockProtect {
  private Lock lockForProtect = new Lock();
  private String targetValue = "";

  public String getTargetValue(){
    synchronized(this){
      targetValue = "Value";
      return targetValue;
    }
  }
}

public class TestLockProtect {
  private Lock lockForProtect = new Lock();
  private String targetValue = "";

  public String getTargetValue(){
    lockForProtect.lock();

    targetValue = "Value";

    lockForProtect.unlock();
    return targetValue;
  }
}

public class Lock {
  private boolean isLocked = false;

  public synchronized void lock() {
    throws InterruptedException{
      while(isLocked){
        wait();
      }
      isLocked = true;
    }
  
    public synchronized void unlock(){
      isLocked = false;
      notify();
    }
  }    
}            

// synchronized의 경우 내부 함수의  synchronized에 진입이 가능하다. 그 이유는 'this' 에 대한 동기화를 의미하기 때문이다. 
// 하지만 Lock에 대해서는 내부 함수 내의 lock에 대해서는 재진입이 불가능하다. 

 

  • 단일 동작과 복합동작

a = b ++; 를 봤을 때 동기화가 안되어 있다면 b를 증가하는 연산(a++)과 a에 대입하는 연산(a=...)이 두 개가 있으므로    단일 연산으로 볼 수 없다.
해당 연산이 동기화 되었다면 외부에서 성공과 실패로 나뉘므로 단일 연산이다.

 

가능하면 클래스 상태를 관리하기 위해 AtomicLog 처럼 스레드에 안전하게 이미 만들어져 있는 객체를 사용하는 편이 좋다. 스레드 안전하지 않는 상태 변수를 선언해두고 사용하는 것보다 이미 스레드 안전하게 만들어진 클래스가 가질 수 있는 가능한 상태의 변화를 파악하는 편이 휠씬 쉽고, 스레드 안전성을 더 쉽게 유지하고 검증할 수 있다.

 

 

  • 재진입성

스레드가 다른 스레드가 가진 락을 요청하면 해당 스레드는 대기 상태에 들어간다. 하지만 암묵적인 락은 재진입 가능하기 때문에 특정 스레드가 자기가 이미 획득한 락을 다시 확보할 수 있다. 재진입성은 확보 요청의 단위가 아닌 스레드 단위로 락을 얻는다는 것을 의미한다.

 

public class Reentrancy {
  public synchronized void getA(){
    System.out.println("a");
    // b 가 synchronized 로 선언되어 있지만 a 진입시 이미 락을 획득하였으므로, b를 호출할 수 있다. 
    b();
  }

  public synchronized void b(){
    System.out.println("b");
  }

  public static void main(String[] args){
    new Reentrancy().a();
  }
}

 

컴퓨터 프로그램 또는 서브 루틴에 재진입성이 있으면, 이 서브 루틴은 동시에(병렬) 안전하게 실행 가능하다. 즉 재진입이 가능한 루틴은 동시에 접근해도 언제나 같은 실행결과를 보장한다. 재진입이 가능하려면 함수는 다음 조건을 만족해야 한다.

  • 정적 (전역) 변수를 사용하면 안된다.
  • 정적 (전역) 변수의 주소를 반환하면 안된다.
  • 호출자가 호출시 제공한 매개변수만으로 동작해야한다.
  • 싱글턴 객체의 잠금에 의존하면 안된다.
  • 다른 비-재진입 함수를 호출하면 안된다.

 

  • 원자성

 CPU가 처리하는 하나의 단일 연산을 의미한다. 하나의 Thread에서 읽기와 쓰기, 다른 Thread에서는 읽기만 한다면, 원자성을 고려한 변수를 선언하고 싶은 경우, volatile 변수를 활용한다. ( 메모리 문제 고려해야함 )

 

 Volatile 변수는 약간 다른 형태의 좀더 약한 동기화 기능을 제공하는데, 다시 말해 volatile로 선언된 변수의 값을 바꿨을 때 다른 스레드에서 항상 최신 값을 읽어갈 수 있도록 해준다. 특정 변수를 선언할 때 volatile 키워드를 지정하면 컴파일러와 런타임 모두 '이 변수는 공유해 사용하고, 따라서 실행 순서를 재배치해서는 안된다'라고 이해한다. volatile로 지정된 변수는 프로세스의 레지스터에 캐시 되지도 않고, 프로세서의 외부 캐시에도 들어가지 않기 때문에 volatile 변수의 값을 읽으면 항상 다른 스레드가 보관해둔 최신의 값을 읽어갈 수 있다.

 

Volatile를 사용하기 적합할 때

 

 MultiThread 환경에서 하나의 Thread만 Read&Write를 하고 나머지 Thread가 read 하는 상황에서 가장 최신의 값을 보장합니다.
하지만 하나의 Thread에서만 write하더라도, 그 변수(메모리)에 대한 접근과 수정이 잦다면 다른 스레드에서 read할때 원자성이 보장되지 않을 것 같습니다.

 

Volatile를 사용하기 부적합할 때

 

 하나의 Thread가 아닌 여러 Thread에서 write를 하는 상황에서는 적합하지 않습니다.
이와 같은 경우에는 synchronized를 통해서 read & write의 원자성을 보장해야 합니다.

 

 

  • 스레드 한정

스택 한정 기법은 특정 객체를 로컬 변수를 통해서만 사용할 수 있는 특별한 경우의 스레드 한정 기법이라고 할 수 있다. 변수를 클래스 내부에 숨겨두면 변경상태를 관리하기가 쉬운데, 또한 클래스 내부에 숨겨둔 변수는 특정 스레드에 쉽게 한정시킬 수도 있다. 로컬 변수는 모두 암묵적으로 현재 실행 중인 스레드에 한정되어 있다고 볼 수 있다.

 

public int loadTheArk(Collection<Animal> candidates) {
  SortedSet<Animal> animals;
  int numPairs = 0;
  Animal candidate = null;

  // animals 변수는 메소드에 한정되어 있으며, 유출돼서는 안된다. 
  animals = new TreeSet<Animal> ( new SpeciesGenderComparator());
  animals.addAll(candidates)
  for ( Animal a : animals){
    if(candidate == null || !candidate.isPotentialMate(a))
      candidate = a;
    else {
      ark.load(new AnimalPair(candidate, a));
      ++numPairs;
      candidate = null;
    }
  }

  return numPairs;
}

 

  • Final에 대하여

final을 지정한 변수의 값을 변경할 수 없다. 물론 변수가 가리키는 객체가 불변 객체가 아니라면 해당 객체에 들어있는 값은 변경할 수 있다. final 키워드를 적절하게 사용하면 초기화 안정성을 보장하기 때문에 별다른 동기화 작업 없이 불변 객체를 자유롭게 사용하고 공유할 수 있다.

 

@Immutable 
class OneValueCache {
  private final BigInteger lastNumber;
  private final BigInteger[] lastFactors;

  public OneValueCache(BigInteger i, BigInteger[] factors){
    lastNumber = i;
    lastFactors = Arrays.copyOf(factors, factors.length);
  }

  public BigInteger[] getFactors(BigInteger i) {
    if ( lastNumber == null || !lastNumber.equals(i))
      return null;
    else 
      return Arrays.copyOf(lastFactors, lastFactors.leghth);
  }
}

불변 객체의 요구조건

 

  • 상태를 변경할 수 없어야 하고,
  • 모든 필드의 값이 final로 선언되어야 하며,
  • 적절한 방법으로 생성되어야 한다.

 

 

멀티스레드에 대한 스레드 안정성 


동기화를 처리할 수 있는 3가지 방식

만약 여러 스레드가 변경할 수 있는 하나의 상태 변수를 적절한 동기화 없이 접근하면 그 프로그램은 잘못된 것이다.
이렇게 잘못된 프로그램을 고치는 데는 세가지 방법이 있다.

 

  • 해당 상태 변수를 스레드 간에 공유되지 않거나
  • 해당 상태 변수를 변경할 수 없도록 만들거나
  • 해당 상태 변수에 접근할 때는 언제나 동기화

 스레드 안전한 클래스를 설계할 땐, 바람직한 객체 지향 기법이 왕도다. 캡슐화와 불변객체를 잘 활용하고, 불변 조건을 명확하게 기술해야 한다.

 

 여러 스레드가 클래스에 접근할 때, 실행 환경이 해당 스레드들의 실행을 어떻게 스케쥴하든 어디에 끼워 넣든, 호출하는 쪽에서 추가적인 동기화나 다른 조율 없이도 정확하게 동작하면 해당 클래스는 스레드 안전하다고 말한다.

 

 애당초 단일 스레드 환경에서도 제대로 동작하지 않으면 스레드 안전할 수 없다. 객체가 제대로 구현됬으면 어떤 일련의 작업도 해당 객체의 불변 조건이나 후조건에 위배될 수 없다.

 

 스레드 안전한 클래스는 클라이언트 쪽에서 별도로 동기화할 필요가 없도록 동기화 기능도 캡슐화 한다.

// 상태가 없는 항상 안전한 객체 
// 상태 없는 객체에 접근하는 스레드가 어떤 일을 하든 다른 스레드가 수행하는 동작의 정확성에 영향을 끼칠 수 없기 때문에 객체는 항상 스레드에 안전하다. 
@ThreadSafe
public class StatelessFactorizer implements Servlet {
  public void service(ServletRequest req, SevletRespones resp){
    BigInteger i = extractFromRequest(req);
    BigInteger[] factors = factor(i);
    encodeIntoResponse(resp, factors);
  }
}

 

안전한 공개 방법의 특성

 

 객체를 안전하게 공개하려면 해당 객체에 대한 참조와 객체 내부의 상태를 외부의 스레드에게 동시에 볼 수 있어야 한다. 올바르게 생성 메소드가 실행되고 난 개체는 아래와 같이 처리할 수 있다.

 

  • 객체에 대한 참조를 static 메서드로 초기화 시킨다.
  • 객체에 대한 참조를 volatile 변수 또는 AtomicReference 클래스에 보관한다.
  • 객체에 대한 참조를 올바르게 생성된 클래스 내부의 final 변수에 보관한다.
  • 락을 사용해 올바르게 막혀 있는 변수에 객체에 대한 참조를 보관한다.

HashTable, ConcurrentMap, synchronizedMap을 사용해 만든 Map 객체를 사용하면 그 안에 보관하고 있는 키와 값 모두를 어느 스레드에서라도 항상 안전하게 사용할 수 있다.

 

  • 스레드 한정
  • 읽기 전용 개체를 공유
  • 스레드에 안전한 객체를 공유
  • 동기화 방법 적용

스레드 안전한 클래스 설계

 

 클래스가 스레드 안정성을 확보하도록 설계하고자 할 때에는 아래를 고려해야한다.

  • 객체의 상태를 보관하는 변수가 어떤 것인가?
  • 객체의 상태를 보관하는 변수가 가질 수 있는 값이 어떤 종류, 어떤 범위에 해당하는가?
  • 객체 내부의 값을 동시에 사용하고자 할 때, 그 과정을 관리할 수 잇는 정책
/*
  primitive type을 사용할 경우 아래와 같이 객체의 상태를 완벽하기 동기화 처리 가능하다.                        
*/
@ThreadSafe 
public final class Counter {
  @GuardedBy("this") private long value = 0;

  public synchronized long getValue() {
    return value;
  }

  public synchronized long increment() {
    if ( value == Long.MAX_VALUE ) 
      throw new IllegalStateException("counter overflow");
      return ++value;
  }
}
      

인스턴스 한정

 

객체를 적절하게 캡슐화하는 것으로도 스레드 안정성을 확보할 수 있는데, 이런 경우 흔히 '한정' 이라고 단순하게 부르기도 하는 '인스턴스 한정' 기법을 활용하는 셈이다.

@ThreadSafe
public class PersonSet {
  @GuardedBy("this")
  private final Set<Person> mySet = new HashSet<Person>();

  public synchronized void addPerson(Person p) {
    mySet.add(p);
  }

  public synchronized boolean containsPerson(Person p) {
    return mySet.contains(p);
  }
}                          
  

스레드의 안정성을 확보하는 방법으로 대부분 데코레이터(장식자) 패턴을 활용한다.

 


자바 모니터 패턴

 

자바 모니터 패턴을 따르는 객체는 변경 가능한 데이터를 모두 객체 내부에 숨긴 다음 객체의 암묵적인 락으로 데이터에 대한 동시 접근을 막는다. 자바 모니터 패턴은 단순한 관례에 불과하며 일정한 형태로 스레드 안전성을 확보할 수 있다면 어떤 형태의 락을 사용해도 무방하다.

 

public class PrivateLock {
  private final Object myLock = new Object();
  @GuardedBy("myLock") Widget Widget;

  void someMethod() {
    synchronized (myLock){
      
    }
  }
}

 

'알아보기' 카테고리의 다른 글

0004 Reactive Programming 2  (0) 2019.12.05
0004 Reactive Programming 1  (0) 2019.12.04
0002 Realm 활용하기 2  (0) 2019.11.22
0002 Realm 활용하기 3  (0) 2019.11.22
0002 Realm 활용하기 1  (0) 2019.11.20

모바일 관련하여 개발을 진행하다보니, App DataBase로 Realm을 선정하게 되었고, 이에 대해서 좀더 자세하게 알아보고자 한다. 

 

Realm : SQLite와 Core Data의 대안으로 ORM이나 SQLite보다 더 빠른 성능을 제공한다.

 

지원 가능 언어 ( 2019년 11월 20일 현재 ) 

  • 자바 , Java
  • 스위프트 , Swift
  • 오브젝티브-C , Objective-C
  • 자바스크립트 , Javascript
  • 닷넷 : .NET

이번 블로그는 자바스크립트와 React Native를 기준을 설명하겠다. 


Realm에 대한 상세한 가이드는  https://realm.io/docs/javascript/latest/ 를 참고한다면 더욱 도움이 될 것 같다. 

 

참고로 Expo에서는 Realm를 지원하지 않는다. 따라서 

v0.60을 기준으로 ( 나는 MacBook을 이용해서 실행하였다. )

 

$ npx react-native init <project_name>

 

그후, 해당 <project_name> 폴더로 가서 

 

$ cd <project_name>

$ npm install --save realm

 

이후 ios 폴더에서 

 

$ cd ios

$ pod install 

 

위와 같이 진행하면 realm을 XCode에서 돌리기 위한 기본적인 절차는 끝이 났다.

 

위의 내용과 관련하여 이해가 안될 경우 아래의 링크를 참고하면 도움이 될 것이다.

npx, cocoapod, react-native 설치, XCode 호출에 대해서 알아보기

 

Realm의 공식 API 문서 

https://realm.io/docs/javascript/latest/api/

'알아보기' 카테고리의 다른 글

0004 Reactive Programming 2  (0) 2019.12.05
0004 Reactive Programming 1  (0) 2019.12.04
0002 Realm 활용하기 2  (0) 2019.11.22
0002 Realm 활용하기 3  (0) 2019.11.22
0003 자바를 이용한 병렬 프로그래밍 1  (0) 2019.11.20

React Native와 Realm 

 

로그인 정보를 App Data로 저장하기


로그인 정보를 App내에서 사용하기 위해서 모바일 App Database로 활용할 수 있는 다양한 App을 찾아 본 결과 

 

  • SQLite
  • Realm

크게는 SQLite 와 Realm을 주로 많이 사용하는 것으로 보이며, 이전에 SQLite로 개발했을 때 마치 RDB를 설계하는 듯한 느낌이 들어서

별로였었는데, Realm의 심플함에 반해서 Realm으로 넘어왔다. Mobile Database로 Realm을 추천하는 사례들도 많아졌기에 이번 App을 개발시에 적용해 보려한다. 

 

Realm 설치 


$ cd {React Native 프로젝트 경로}

 

$ npm insall --save realm

Realm 설치

 

$ react-native link realm

realm 연동

$ cd ios 

$ pod install

pod install 실행

 

이후 XCode를 실행시켜  Realm을 사용하는 방법을 알아보겠다.

 

 

 

Realm 사용하기 


Realm을 사용하기위한 DataBase Component 작성 

var Realm = require('realm');

const DataBase = ( props ) => {
    
    const LoginSchema = {
        name : 'Login',
        properties : {
            userId : 'string'
        }
    }

    const CategorySchema = {
        name: 'Category',
        properties: {
            categorySeqNo : 'int',
            categoryName  : 'string',
        }
    };

    const CategoryListSchema = {
        name: 'CategoryList',
        properties : {
            categoryList : { type : 'list' , objectType : 'Category' }
        }
    }

    let realm = new Realm({ schema : [LoginSchema, CategoryListSchema, CategorySchema]});

    return realm;
}

export default DataBase;

로그인의 일부 코드 

  saveUserId 함수를 보면, Realm을 이용해서 데이터를 저장하고, 가져오는 방식을 간단하게 작성해봤다.

  좀더 상세한 활용 방법 및 사용법은 https://realm.io/docs/javascript/latest/ 링크를 접속해서 확인할 수 있다. 

import React, {Component} from 'react';
import { View, Text, TextInput, StyleSheet, TouchableHighlight } from 'react-native';
import { widthPercentageToDP as wp } from 'react-native-responsive-screen';
import DataBase from '../../../components/database';

export default class LoginScreen extends Component {
    
    constructor(props) {
      super(props);
      this.state = {
        email   : '',
        password: '',
      }

      this.realm = DataBase();

      console.log(this.realm)
    }
      
    static navigationOptions = {
        header: null,
    }

    saveUserId = async userIdValue => {
      try {
        let login = this.realm.objects('Login');

        if(!login["0"].userId){
          this.realm.write(() =>{
            this.realm.create('Login', { userId :  userIdValue })
          });
        }
        
        console.log(login);
        console.log(login["0"].userId);

      } catch (error) {
        // Error retrieving data
        console.log(error.message);
      }
    }

로그인 화면에서 id를 입력하고 값을 가져오는 방법 위와 같이 작성하면, 아래와 같은 결과를 얻을 수 있었다. 

shj@lines.com을 4번을 입력하고 그 중 첫번째 userId만 가져오도록 한 결과이다. 

 

Realm에 대해서는 추후에 별도로 정리할 예정이다. 

'따라해보기' 카테고리의 다른 글

0001 React Native App 개발기 6-1  (0) 2019.12.12
0001 React Native App 개발기 5  (0) 2019.12.08
0001 React Native App 개발기 4  (0) 2019.11.22
0001 React Native App 개발기 2  (0) 2019.11.15
0001 React Native App 개발기 1  (0) 2019.11.15

작성일 : 2019년 11월 16일 

 

React-Native 기반하에 운동 Timer를 개발해보고자 한다. 

 


개발에 필요한 것

  • XCode ( Version 11.2 ) 
  • Terminal
  • Visual Studio Code 

개발 시 사용할 Modules

  • realm : App 데이터 저장소 
  • react-native-clean-form 
  • react-native-countdown-component 
  • react-navigation, react-navigation-statck, react-navigation-tabs

이외에도 다양한 모듈이 사용되었지만 메인이되었던 항목들을 나열하였다. 

 

App.js 구성 


React Native App이 진입점은 index.js 파일이고, index.js 파일을 보면 아래와 같이 구성되어 있다. 

/**
 * @format
 */

import {AppRegistry} from 'react-native';
import App from './App';
import {name as appName} from './app.json';

AppRegistry.registerComponent(appName, () => App);

AppRegistry

 

Index.js 파일에서 로딩되는 App 파일을 기점으로 하여 간단하게 Timer App을 만들어보겠다.

 


import에 대해서 알고 싶으면 [더보기]를 클릭하세요.

더보기

import 와 export는 ES6(EcmaScript2015)에서 도입되었다. 현재 (2019년 11월 15일 현재) 브라우저에서 기본으로 제공되고 있지 

않습니다. 따라서 별도의 트랜스파일러(Transpiler)에 의해서 동작할 수 있습니다. 

 

아래는 Import와 Export를 사용하는 간단한 예시이며, 이외에도 다양한 방법으로 사용할 수 있다. 

// import 를 사용하는 간단한 예제 
import example from "example-module";
import example as ex from "example-module";
import { example1 } from "example-modules";
import { example1, example2 } from "example-modules";

// export 를 사용하는 간단한 예제 
export { example1 };
export example1;
export default example1; // 각각의 모듈 당 단하나의 default만 가질 수 있습니다. 
export let example1 = ..., example2 = ...;
export { example1, example2 } from ...;
 

 

개발을 위한 Module 설치


react-navigation은 V4를 사용한다.

 

npm을 통해 아래의 명령어를 실행하고, 

$ npm install --save react-navigation react-navigation-tabs react-navigation-stack

 

pod install 실행 

$ pod install 

 

하지만 제대로 실행되지 않고... 아래와같은 메세지가 뜬다.

pod install 시 발생한 에러 

그래서 아래의 모듈을 다시한번 설치해주고,

 

$ npm install --save @react-native-community/cli-platform-ios

 

이래도 pod install 시에 정상적으로 설치가 되지 않는다. 

pod install 시 에러

그래서 다시 한번 react-native app 폴더에서 npm install 을 진행하였다. 그랬더니 그제서야 정상적으로 pod install 이 동작했다. 

pod install 이 정상적으로 설치

 

 

pod install이 정상적으로 진행되고 난 뒤 xcworkspace 프로젝트를 열어 App을 실행했더니...

기동 이후 화면 로딩 시 에러 발생

 

흠... 알고 봤더니 @react-native-community/cli-platform-ios 이걸 설치하던 부분 부터 다시정리해보면,

react-navivation을 사용하기 위해 필요한 Package.json을 다시 아래와 같이 정의하고,

 

[package.json] 파일의 일부 , 

"react-native-gesture-handler": "^1.5.0",

"react-native-reanimated": "^1.4.0",

"react-navigation": "^4.0.10",

"react-navigation-stack": "^1.10.3",

"react-navigation-tabs": "^2.5.6"

 

package.json 파일 

아래와 같은 작업을 다시 실시하였다. 


  1. $ rm -rf node_modules
  2. package.json 재확인하여 정리 
  3. $ npm install 
  4. $ cd ios
  5. $ pod install 진행 
  6. xcworkspace 재오픈 처리 

최종 컴파일 화면


아래의 소스 코드는 로그인화면의 소스 코드이다. 로그인 화면은 구글링을 통해 제공되던 샘플을 공유한다. 

import React, {Component} from 'react';
import { View, Text, TextInput, StyleSheet, TouchableHighlight } from 'react-native';
import { widthPercentageToDP as wp } from 'react-native-responsive-screen';

export default class LoginScreen extends Component {
    
    constructor(props) {
      super(props);
      this.state = {
        email   : '',
        password: '',
      }
    }
      
    static navigationOptions = {
        header: null,
    }

    saveUserId = async userId => {
      try {
        await AsyncStorage.setItem('userId', userId);
      } catch (error) {
        // Error retrieving data
        console.log(error.message);
      }
    }

    onClickListener = (viewId) => {
      this.getMoviesFromApiAsync(() => { this.props.navigation.replace('TabNavigator') })      
    }

    getMoviesFromApiAsync = (executeFunc) => {

      if(this.state.email == '' || this.state.password  == '' ){
        alert("로그인을 위해서 값을 입력해주세요.")
        return;
      }

      const formData = new FormData();
      formData.append('email', this.state.email);
      formData.append('password', this.state.password);

      this.saveUserId(this.state.email);

      return fetch('http://localhost:9090/v1/accounts/login',
        {
          method: 'POST',
          body: formData
        })
        .then((response) => response.json())
        .then((responseJson) => {
          if(responseJson.userStatus.code == "2000"){
            executeFunc()
          }else{
            alert(responseJson.userStatus.message);
          }

          return responseJson.movies;
        })
        .catch((error) => {
          console.error(error);
        })
    }

    serializeJSON = (data) => {
      return Object.keys(data).map(function (keyName) {
        return encodeURIComponent(keyName) + '=' + encodeURIComponent(data[keyName])
      }).join('&');
    }


    render(){
        return (
        <View style={styles.container}>
            <View style={styles.titleArea}>
                <Text style={styles.title}>Excercise Timer</Text>
            </View>
            <View style={styles.inputContainer}>
              <TextInput style={styles.inputs}
                  placeholder="Email"
                  keyboardType="email-address"
                  autoCapitalize = 'none'
                  underlineColorAndroid='transparent'
                  onChangeText={(email) => this.setState({email})}/>
            </View>
            
            <View style={styles.inputContainer}>
              <TextInput style={styles.inputs}
                  placeholder="Password"
                  secureTextEntry={true}
                  underlineColorAndroid='transparent'
                  onChangeText={(password) => this.setState({password})}/>
            </View>
    
            <TouchableHighlight style={[styles.buttonContainer, styles.loginButton]} onPress={() => this.onClickListener('login')}>
              <Text style={styles.loginText}>Login</Text>
            </TouchableHighlight>


            <TouchableHighlight style={styles.buttonContainer} onPress={() => this.onClickListener('restore_password')}>
                <Text>Forgot your password?</Text>
            </TouchableHighlight>
          </View>
        );
    }
}

const styles = StyleSheet.create({
    container: {
      flex: 1,
      justifyContent: 'center',
      alignItems: 'center',
      backgroundColor: '#616155',
    },
    titleArea: {
        width: '100%',
        padding: wp('10%'),
        alignItems: 'center',
    },
    title: {
        fontSize: wp('10%'),
    },
    inputContainer: {
        borderBottomColor: '#F5FCFF',
        backgroundColor: '#FFFFFF',
        borderRadius:30,
        borderBottomWidth: 1,
        width:250,
        height:45,
        marginBottom:20,
        flexDirection: 'row',
        alignItems:'center'
    },
    inputs:{
        height:45,
        marginLeft:16,
        borderBottomColor: '#FFFFFF',
        flex:1,
    },
    inputIcon:{
      width:30,
      height:30,
      marginLeft:15,
      justifyContent: 'center'
    },
    buttonContainer: {
      height:45,
      flexDirection: 'row',
      justifyContent: 'center',
      alignItems: 'center',
      marginBottom:20,
      width:250,
      borderRadius:30,
    },
    loginButton: {
      backgroundColor: "#00b5ec",
    },
    loginText: {
      color: 'white',
    }
  });
  

'따라해보기' 카테고리의 다른 글

0001 React Native App 개발기 6-1  (0) 2019.12.12
0001 React Native App 개발기 5  (0) 2019.12.08
0001 React Native App 개발기 4  (0) 2019.11.22
0001 React Native App 개발기 3  (1) 2019.11.17
0001 React Native App 개발기 1  (0) 2019.11.15

React Native 를 활용한 App 개발에 대해서 설명하기전에 React Native 사이트에서 제공하는 가이드에 따라서 Mobile App 을 만들어 보겠다. 


2019년 11월 14일 기준 ( React Native Cli 기반 개발 ) - MacOS 버전 

  > 필자의 노트북이 MacBook이다보니 양해 바란다. 

 

 

 

 React Native 기반으로 개발하는 방식으로 Expo Cli 또는 React Native Cli 를 이용하여 App Template을 만들 수 있는데, 가급적이면 React Native Cli를 이용하는 방식을 추천한다. 필자 또한 Expo를 이용하여 개발해봤으나 결국 Native 모듈을 사용함에 있어 어려움을 느껴 RN(React Native)로 넘어왔다.

 

 [더보기]를 누르면 expo eject에 대해서 간단하게 정리해봤다.

더보기

 만약 Expo를 이용해서 App을 개발하던 중에 Native Code를 사용하기 위해서 "ejected"를  진행할수 있다.

 실제로 Expo 를 이용해서 진행하지는 않을 예정이지만, 

 

 $ expo eject 

 

eject 명령에 대해서 아래와 같이 선택할 수 있는데, Bare, ExpoKit, Cancel을 선택할 수 있다. 

expo eject 시 진행되는 절차 

 

홈 화면 및 프로젝트 명 설정

빨간색으로 표시한 네모칸이 실제 Android Studio, XCode를 이용해서 React Native를 기동시킬 수 있는 프로젝트 폴더이다.

expo eject 후 폴더 구성 

 

 

 

React Native Cli를 이용한 모바일 프로젝트 생성 


  • Brew가 없다면 HomeBrew 설치 
  • node 설치 - React Native를 실행시키기 위한 필수 모듈이다.
  • cocoapods 설치 - React Native를 ios 에서 돌릴 때, react-native link {모듈명}으로 설치된 모듈에 대한 pod install 시에도 사용된다.
  • react-native 를 이용한 프로젝트 생성 
  • XCode를 통한 프로젝트 Open
  • XCode를 통한 프로젝트 실행

$brew install node

 

$sudo gem install cocoapods

 

$npx react-native init {프로젝트 명}

Example ) npx react-native init workingTimer 

 

생성된 프로젝트의 폴더 내부를 보면, 

 

react-naive 기준으로 생성된 프로젝트

위의 이미지에서 [ios]가 실제로 XCode에서 프로젝트를 열어서 개발을 진행할 폴더이다. 

[android] 폴더는 Android Studio에서 프로젝트를 열어서 개발을 진행할 폴더이다. 

 

npx, gem, cocoapods에 대해 알아보기


npx, gem 에 대해서 알고 싶다면 [더보기]클릭하라. 

더보기

[npx]

npm 5.2.0 이후부터, 

 새로운 바이너리로 npm 과 함께 npx가 소개되었다. 

 

 npm 은 지년 수년간 packages를 로컬과 전역에 설치하는 방식에 대해서 증진을 이뤘다. 

 그중에 기존에 로컬에 package를 설치하기 위해서 사용하던 dev-dependencies가 있었는데, 이를 없애고자 하는 

 움직임도 있었다. 이런 움직임 속에서 제공되기 시작한 것이 npx이다,. 

 

 npx : npm package runner 로 명시적으로 설치하지 않고 패키지를 실행시키는 것을 돕는다. 

 

  • 일회성 명령을 실행 시킬 때,
  • Gist 기반 scripts를 실행 시킬 때, 
  • npm module의 다른 버전을 사용하고 싶을 때,
  • 전역적으로 설치할 수 있는 권한이 없을 때,

즉 

$ npx react-native init workingTimer 

 

 위의 의미는 react-native라는 모듈을 별도의 설치 없이 패키지를 실행시킨 것이라고 생각하면 된다.

 

[gem]

 

 gem은 ruby에서 쓰이는 라이브러리 패키지이다. 

 

 cocoapods가 RubyGems와 Bundler의 조합에 영감을 받아 만들어졌기 때문에 rubygems를 이용해서 라이브러리

를 관리한다. 

 

cocoaPods 에 대해서 알고 싶다면 [더보기]를 클릭하라. 

더보기

[cocoapods]

CocoaPods는 Swift 및 Objective-C 프로젝트의 라이브러리 관리자

Maven이나 Gradle과 느낌이 유사하다. 

 

$ sudo gem install cocoapods

 

Podfile - 종속성 관리를 위한 파일 : pod install, pod update 등 명령어를 사용할 때 필요한 파일이다. 

 

$ pod install

  새로운 pod를 다운로드 받고 설치한다. pod install의 경우에는 

 

$ pod outdated

 모든 pod에 대해서 Podfile.lock 안에 목록화된 것보다 신규 버전일 경우 모드 목록화 한다. 

 이후 pod install을 하게되면 해당 pod들은 모두 업데이트 될 것이다. 

 

$ pod update {PODNAME} 

  설치되어 있는 Pod들을 PODNAME에 따라서 버전 업데이트에 사용할 수 있으며 POSNAME을 빼면 PodFile의 목록을 기준으로 업데이트 처리한다. 

 

install 명령를 update 명령어 처럼 이미 설치된 pod에 대해서는 사용하지 않는다.

 

 

XCode에서 프로젝트 열기 


ios 폴더 내의 파일

위에서 우리는 XCode에서 xcodeproj, xcworkspace 중에  어떤 파일로 열어야 할까?

 

React Native를 이용한 App을 구성할 때 CocoaPods를 사용했기 때문에 HealthTimer.xcworkspace를 이용해서 프로젝트를 열어야 한다. 이는 cocoapods를 이용하면 생성한 프로젝트를 내부에 별도로 구성하면서 xcworkspace로 구성하고 있다. 

 

xcodeproj와 xcworkpace의 차이를 보려면 [더보기]를 눌러주세요. 

더보기

[xcodeproj]

 HealthTimer.xcodeproj 는 실행할 때도 쓰지만, 프로젝트의 설정파일들이 포함되어 있다. 

 제품을 만들기 위한 모든 파일과 리소스, 정보들을 가지고 있다.

 

[xcworkspace] 

 HealthTimer.xcodeproj 를 포함한 프로젝트를 논리적으로 묶어주는 역할을 한다. 

 즉 xcworkspace는 xcodeproj를 큰 틀에서 묶을 수 있다. 

 

 

Health Timer 폴더을 열었을 때의 구성 

위와 같이 xcworkspace을 이용해서 프로젝트를 열어서 표시된 XCode 편집창이다. 

 

 

프로젝트 실행해보기 


XCode 편집창의 삼각형 실행 버튼을 실행한다. 

 

XCode 편집창의 좌측 실행 버튼 

 

실제 XCode를 통해서 실행을 시키면 최종적으로 아래와 같이 실행된다. 

 

XCode와 연동하여 실행한 React Native 기본 화면 

 

실행시 같이 동작되는 MetroBundler와 XCode의 상관 관계를 알고 싶다면 [더보기]를 눌러주세요.

더보기

[MetroBundler]

 React Native App은 자바스크립트를 기반으로 컴파일되는 App이다. React Native Project를 빌드 할 때, Packager가 MetroBundler를 호출한다. 이는 webpack과 비슷하지만 React Native App을 위한 것이다.

 

 Metro는 기본적으로 8081 포트를 이용해서 React Native App과 통신한다. 

 

 즉, Java나 Swift 처럼 번역되는 것이 아닌 우리가 짠 React Native Code를 Native Modules에 의해서 Javascript Thread가 실행시키는 방식이다. 즉 Metro는 React Native App과 내부적으로 통신을 한다. ( 이때 기본 포트가 8081이다 ) 

 

 XCode를 이용해서 React Native App을 실행 시킨 후에 아래의 Link로 들어가보면 조금 이해될 수 있다.

 http://localhost:8081/index.bundle?platform=ios&dev=true&minify=false

 

 기본 컨셉에 대해서는 링크에서 확인할 수 있다. - https://facebook.github.io/metro/docs/en/concepts

'따라해보기' 카테고리의 다른 글

0001 React Native App 개발기 6-1  (0) 2019.12.12
0001 React Native App 개발기 5  (0) 2019.12.08
0001 React Native App 개발기 4  (0) 2019.11.22
0001 React Native App 개발기 3  (1) 2019.11.17
0001 React Native App 개발기 2  (0) 2019.11.15

+ Recent posts