본문 바로가기

iOS (스파르타)

파이어베이스 공부하깅 - 2

Apple 플랫폼에서 데이터 목록 다루기

 

FIRDatabaseReference 가져오기

 

데이터베이스에서 데이터를 읽거나 쓰려면 FIRDatabaseReference 인스턴스가 필요하다.

var ref: DatabaseReference!

ref = Database.database().reference()

참고: 기본적으로 인증된 사용자만 데이터를 읽고 쓸 수 있도록 데이터베이스에 대한 읽기 및 쓰기 액세스 권한이 제한됩니다. 공개 액세스 규칙을 구성하면 인증을 설정하지 않고 시작할 수 있습니다. 다만 이렇게 하면 앱을 사용하지 않는 사람을 포함하여 모두에게 데이터베이스가 공개되므로 인증을 설정할 때 데이터베이스를 다시 제한해야 합니다.

 

목록 읽기 및 쓰기

데이터 목록에 추가

멀티 사용자 애플리케이션에서 목록에 데이터를 추가하려면 childByAutoId 메서드를 사용한다.

childByAutoId 메서드는 지정된 Firebase 참조에 새 하위 항목이 추가될 때마다 고유 키를 생성한다.

목록의 새 요소마다 이러한 자동 생성 키를 사용하면 여러 클라이언트에서 쓰기 충돌 없이 동시에 같은 위치에 하위 요소를 추가할 수 있다.

childBuAutoId 가 생성하는 고유 키는 타임스탬프에 기반하므로 목록 항목은 시간순으로 자동 정렬된다.

 

childByAutoId 메서드가 반환하는 새 데이터에 대한 참조를 사용하여 하위 요소의 자동 생성 키 값을 가져오거나 하위 데이터를 설정할 수 있다. childByAutoId 참조에 대해 getKey 를 호출하면 자동 생성키가 반환된다.

 

이러한 자동 생성 키를 사용하면 데이터 구조의 평면화 작업이 단순해진다.

 

 

하위 이벤트 수신 대기

하위 이벤트는 노드의 하위 요소에서 발생하는 특정 작업에 대응하여 트리거된다.

하위 요소가 childByAutoId 메서드를 통해 새로 추가되거나 updateChildValues 메서드를 통해 업데이트되는 경우가 그 예이다.

이러한 메서드는 데이터베이스의 특정 노드에 대한 변경사항을 수신 대기하는 데 유용할 수 있다. 

예를 들어 아래와 같이 소셜 블로깅 앱은 이러한 메서드를 함께 사용하여 게시물의 댓글 활동을 모니터링 할 수 있다.

// Listen for new comments in the Firebase database
commentsRef.observe(.childAdded, with: { (snapshot) -> Void in
  self.comments.append(snapshot)
  self.tableView.insertRows(
    at: [IndexPath(row: self.comments.count - 1, section: self.kSectionComments)]
    with: UITableView.RowAnimation.automatic
  )
})
// Listen for deleted comments in the Firebase database
commentsRef.observe(.childRemoved, with: { (snapshot) -> Void in
  let index = self.indexOfMessage(snapshot)
  self.comments.remove(at: index)
  self.tableView.deleteRows(
    at: [IndexPath(row: index, section: self.kSectionComments)],
    with: UITableView.RowAnimation.automatic
  )
})

 

 

값 이벤트 수신 대기

데이터 목록 읽기에 권장되는 방법은 하위 이벤트를 수신 대기하는 것이지만 상황에 따라서는 목록 참조의 값 이벤트를 수신 대기하는 방법이 유용하다.

 

데이터 목록에 FIRDataEventTypeValue 관찰자를 연결하면 전체 데이터 목록이 단일 DataSnapshot으로 반환되며,

이를 루프 처리하여 개별 하위 요소에 액세스 할 수 있다.

 

쿼리에 일치하는 항목이 단 하나여도 스냅샷은 목록으로 표시된다.

다만 항목이 1개일뿐이다. 항목에 액세스하려면 결과를 루프 처리해야 한다.

_commentsRef.observe(.value) { snapshot in
  for child in snapshot.children {
    ...
  }
}

하위 추가 이벤트를 추가로 수신 대기하는 대신 한 번의 작업으로 목록의 모든 하위 요소를 가져오려는 경우 이 패턴이 유용할 수 있다.

 

 

데이터 정렬 및 필터링

실시간 데이터베이스의 FIRDatabaseQuery 클래스를 사용하여 키, 값  또는 하위 요소 값으로 정렬된 데이터를 검색할 수 있다.

정렬된 결과를 특정 개수로 필터링하거나 키 또는 값 범위에 따라 필터링할 수도 있다.

 

참고: 필터링과 정렬은 특히 클라이언트에서 처리될 때 비용이 많이 들 수 있습니다. 앱에서 쿼리를 사용하는 경우 데이터 색인 생성에 설명된 대로 .indexOn 규칙을 정의하여 서버에서 해당 키의 색인을 생성하고 쿼리 성능을 향상시킬 수 있습니다.

 

 

데이터 정렬

정렬된 데이터를 검색하려면 우선 정렬 기준 메서드 중 하나를 지정하여 결과를 어떤 순서로 정렬할지 결정한다.

정렬 기준 메서드는 한 번에 하나만 사용할 수 있다.

동일한 쿼리에서 정렬 기준 메서드를 여러 번 호출하면 오류가 발생한다.

 

다음 예제에서는 별표 개수를 기준으로 사용자의 최상위 글 목록을 검색하는 방법을 보여 준다.

// My top posts by number of stars
let myTopPostsQuery = ref.child("user-posts").child(getUid()).queryOrdered(byChild: "starCount")

이 쿼리는 사용자 ID를 기준으로 데이터베이스 경로에서 사용자의 글을 검색하고 각 글의 별표 수에 따라 정렬한다.

이와 같이 ID를 색인 키로 사용하는 기법을 데이터 팬아웃이라고 한다.

 

queryOrderedByChild 메서드를 호출할 때는 결과를 정렬하는 기준이 될 하위 키를 지정한다.

이 예시에서는 글이 각 글의 "starCount" 하위 요소 값에 따라 정렬된다.

다음과 같은 데이터가 있다면 중첩된 하위 요소에 따라 쿼리를 정렬할 수도 있다.

"posts": {
  "ts-functions": {
    "metrics": {
      "views" : 1200000,
      "likes" : 251000,
      "shares": 1200,
    },
    "title" : "Why you should use TypeScript for writing Cloud Functions",
    "author": "Doug",
  },
  "android-arch-3": {
    "metrics": {
      "views" : 900000,
      "likes" : 117000,
      "shares": 144,
    },
    "title" : "Using Android Architecture Components with Firebase Realtime Database (Part 3)",
    "author": "Doug",
  }
},

이 예시에서는 queryOrderedByChild 호출에서 중첩된 하위 요소에 대한 상대 경로를 지정하여 metrics 키 아래에 중첩된 값에 따라 목록 요소를 정렬할 수 있다.

let postsByMostPopular = ref.child("posts").queryOrdered(byChild: "metrics/views")

 

 

데이터 필터링

데이터를 필터링하려면 제한 또는 범위 메서드를 정렬 기준 메서드와 조합하여 쿼리를 작성한다.

정렬 기준 메서드와 달리 여러 개의 제한 또는 범위 함수를 조합할 수 있다.

예를 들어 queryStartingAtValue 및 queryEndingAtValue 메서드를 조합하여 결과를 지정된 값 범위로 제한할 수 있다.

 

결과 수 제한

queryLimitedToFirst 및 queryLimitedToLast 메서드를 사용하여 특정 콜백에서 동기화할 하위 요소의 최대 개수를 설정할 수 있다.

예를 들어 queryLimitedToFirst 를 사용하여 제한을 100개로 설정하면 처음에 최대 100개의 FIRDataEventTypeChildAdded 콜백만 수신한다. Firebase 데이터베이스에 저장된 항목이 100개 미만이면 각 항목에 FIRDataEventTypeChildAdded 콜백이 호출된다.

 

항목이 변경됨에 따라 쿼리에 새로 포함되는 항목에 대해 FIRDataEventTypeChildAdded 콜백, 쿼리에서 제외되는 항목에 대해 FIRDataEventTypeChildRemoved 콜백이 수신되며 총 개수는 100개로 유지된다.

 

다음 예시는 예시 블로깅 앱이 모든 사용자의 글 중에서 최근 100개의 목록을 검색하는 방법을 보여준다.

// Last 100 posts, these are automatically the 100 most recent
// due to sorting by push() keys
let recentPostsQuery = (ref?.child("posts").queryLimited(toFirst: 100))!

 

키 또는 값으로 필터링

queryStartingAtValue, queryStartingAfterValue, queryEndingAtValue, queryEndingBeforValue, queryEqualToValue 를 사용하여 쿼리의 시작, 종료, 동일 지점을 임의로 선택할 수 있다.

이 메서드는 특정 값을 갖는 하위 요소로 데이터를 페이지로 나누거나 항목을 찾는 데 유용하다.

 

 

쿼리 데이터의 순서

이 섹션에서는 FIRDatabaseQuery 클래스의 각 정렬 순서 메서드에 따라 데이터를 정렬하는 방법을 설명한다.

 

queryOrderedByKey

queryOrderedByKey 를 사용하여 데이터를 정렬하는 경우 데이터가 키에 따라 오름차순으로 반환된다.

  1. 키가 32비트 정수로 파싱될 수 있는 하위 요소가 맨 처음에 나오며 오름차순으로 정렬된다.
  2. 키가 문자열 값인 하위 요소가 그 다음에 나오며 사전순, 오름차순으로 정렬된다.

queryOrderedByValue

queryOrderedByValue 를 사용하면 하위 요소가 값에 따라 정렬된다.

정렬 기준은 queryOrderedByChild 와 동일하며, 지정된 하위 키의 값 대신 노드의 값이 사용된다는 점이 다르다.

 

queryOrderedByChild

queryOrderedByChild 를 사용하면 지정된 하위 키를 포함하는 데이터가 다음과 같이 정렬된다.

  1. 지정된 하위 키의 값이 nil 인 하위 요소가 맨 처음에 온다.
  2. 지정된 하위 키의 값이 false 인 하위 요소가 그 다음에 온다. 값이 false 인 하위 요소가 여러 개인 경우 키에 따라 사전순으로 정렬된다.
  3. 지정된 하위 키의 값이 true 인 하위 요소가 그 다음에 온다. 값이 true 인 하위 요소가 여러 개인 경우 키에 따라 사전순으로 정렬된다.
  4. 숫자 값을 갖는 하위 요소가 그 다음에 나오며 오름차순으로 정렬된다. 지정된 하위 노드의 숫자 값이 동일한 하위 요소가 여러 개면 키에 따라 정렬된다.
  5. 숫자 다음에는 문자열이 나오며 사전순, 오름차순으로 정렬된다. 지정된 하위 노드의 값이 동일한 하위 요소가 여러 개면 키에 따라 사전순으로 정렬된다.
  6. 마지막으로 객체가 나오며 키에 따라 사전순, 오름차순으로 정렬된다.

 

리스너 분리

ViewController 를 벗어나도 관찰자는 데이터 동기화를 자동으로 중지하지 않는다.

관찰자를 적절히 삭제하지 않으면 데이터가 계속 로컬 메모리와 동기화되고 이벤트 핸들러 클로저에서 캡처된 모든 객체가 유지되므로 메모리 누수가 발생할 수 있다. 관찰자가 더 이상 필요하지 않으면 연결된 FIRDatabaseHandle 을 removeObserverWithHandle 메서드에 전달하여 삭제하십쇼.

 

참조에 콜백 블록을 추가하면 FIRDatabaseHandle 이 반환된다. 이 핸들을 사용하여 콜백 블록을 삭제할 수 있다.

 

하나의 데이터베이스 참조에 여러 리스너를 추가하면 이벤트가 발생할 때 각 리스너가 모두 호출된다.

해당 위치에서의 데이터 동기화를 중지하려면 removeAllObservers 메서드를 호출하여 특정 위치의 모든 관찰자를 삭제해야 한다.

 

리스너에서 removeObserverWithHandle 또는 removeAllObservers 를 호출해도 하위 노드에 등록된 리스너는 자동으로 삭제되지 않는다. 이러한 참조 또는 핸들을 추적하여 삭제해야 한다.

 

 

 

Apple 플랫폼의 오프라인 기능

Firebase 애플리케이션은 일시적으로 네트워크 연결이 끊겨도 정상적으로 작동한다.

또한 Firebase는 로컬에서 데이터를 유지하고, 접속 상태를 관리하고, 지연 시간을 처리하는 도구를 제공한다.

 

 

디스크 지속성

firebase 앱은 일시적인 네트워크 중단을 자동으로 처리한다. 오프라인 상태에서는 캐시된 데이터를 사용할 수 있고,

네트워크 연결이 복원되면 Firebase에서 모든 쓰기 작업을 다시 전송한다.

 

디스크 지속성을 사용 설정하면 앱의 데이터를 기기에 로컬로 저장하므로 오프라인 상태일 때도 앱이 현재 상태를 유지할 수 있으며, 

사용자 또는 운영체제가 앱을 다시 시작하더라도 유지된다.

 

단 한 줄의 코드로 디스크 지속성을 사용 설정할 수 있다.

Database.database().isPersistenceEnabled = true

 

지속성 동작

지속성을 사용 설정하면 Firebase 실시간 데이터베이스 클라이언트가 온라인 상태에서 동기화하는 모든 데이터가 디스크에 유지되고 오프라인 상태에서 사용 가능해지며, 사용자 또는 운영체제가 앱을 다시 시작하더라도 마찬가지이다. 

따라서 캐시에 저장된 로컬 데이터를 사용하여 온라인일 때와 다름없이 앱이 작동한다. 로컬 업데이트 시 리스너 콜백도 계속 발생한다.

 

Firebase 실시간 데이터베이스 클라이언트는 앱이 오프라인일 때 수행된 모든 쓰기 작업을 자동으로 큐에 유지한다.

지속성을 사용 설정하면 이 큐가 디스크에도 유지되므로 사용자 또는 운영체제가 앱을 다시 시작해도 쓰기 작업이 사라지지 않는다.

앱이 다시 연결되면 모든 작업이 Firebase 실시간 데이터베이스 서버로 전송된다.

 

앱이 Firebase 인증을 사용하는 경우 앱을 다시 시작해도 Firebase 실시간 데이터베이스 클라이언트에서 사용자의 인증 토큰을 유지한다.

앱이 오프라인일 때 인증 토큰이 만료되면 앱에서 사용자를 다시 인증할 때까지 클라이언트가 쓰기 작업을 일시중지하며,

인증되지 않으면 보안 규칙으로 인해 쓰기 작업에 실패할 수 있다.

 

 

최신 데이터 유지

Firebase 실시간 데이터베이스는 활성 리스너의 데이터를 동기화하고 로컬 사본을 저장한다. 또한 특정 위치의 동기화를 유지할 수 있다.

let scoresRef = Database.database().reference(withPath: "scores")
scoresRef.keepSynced(true)

Firebase 실시간 데이터베이스 클라이언트는 참조에 활성 리스너가 없어도 이러한 위치의 데이터를 자동으로 다운로드하고 동기화한다.

동기화를 해제하려면 다음 코드를 사용한다.

scoresRef.keepSynced(false)

기본적으로 이전에 동기화한 데이터 중 10MB가 캐시된다. 대부분의 애플리케이션에서는 이 용량으로 충분하다.

구성된 크기보다 캐시가 커지면 Firebase 실시간데이터베이스가 가장 오래전에 사용된 데이터를 삭제한다.

동기화가 유지되는 데이터는 캐시에서 삭제되지 않는다.

 

 

오프라인으로 데이터 쿼리

Firebase 실시간 데이터베이스는 쿼리가 반환한 데이터를 오프라인일 때 사용하기 위해 저장한다.

오프라인일 때 쿼리를 작성한 경우 Firebase 실시간 데이터베이스는 이전에 로드한 데이터를 사용하여 계속 작동한다.

요청한 데이터가 로드되지 않으면 로컬 캐시의 데이터가 로드된다. 네트워크에 다시 연결되면 데이터가 로드되고 쿼리가 반영된다.

 

다음은 점수를 저장하는 Firebase 실시간 데이터베이스에서 마지막 항목 4개를 쿼리하는 코드 예시이다.

let scoresRef = Database.database().reference(withPath: "scores")
scoresRef.queryOrderedByValue().queryLimited(toLast: 4).observe(.childAdded) { snapshot in
  print("The \(snapshot.key) dinosaur's score is \(snapshot.value ?? "null")") // 흠 null?
}

연결이 끊겨서 오프라인으로 전환된 후 앱을 다시 시작했다고 가정해 보자.

계속 오프라인인 상태에서 앱은 같은 위치의 마지막 항목 2개를 쿼리한다. 앱이 위 쿼리에서 항목 4개를 모두 로드했으므로 이 쿼리로 마지막 항목 2개가 성공적으로 반환된다.

scoresRef.queryOrderedByValue().queryLimited(toLast: 2).observe(.childAdded) { snapshot in
  print("The \(snapshot.key) dinosaur's score is \(snapshot.value ?? "null")")
}

위의 예시에서는 Firebase 실시간 데이터베이스 클라이언트가 지속성 캐시를 통해 최고 점수를 기록한 두 dinosaur에 대해 '하위 요소 추가' 이벤트를 발생시킨다. 그러나 온라인 상태에서는 해당 쿼리를 실행하지 않았으므로 '값' 이벤트는 발생하지 않는다.

 

오프라인 상태인 앱에서 마지막 항목 6개를 쿼리하면 캐시된 항목 4개에 대한 '하위 요소 추가' 이벤트가 즉시 발생한다.

기기가 다시 온라인으로 전환되면 Firebase 실시간 데이터베이스 클아이언트가 서버와 동기화되고 마지막 2개인 '하위 요소 추가' 및 '값' 이벤트가 발생한다.

 

 

오프라인으로 트랜잭션 처리

앱이 오프라인일 때 수행되는 모든 트랜잭션은 큐에 추가됩니다.

앱이 네트워크에 다시 연결되면 트랜잭션이 실시간 데이터베이스 서버로 전송된다.

 

트랜잭션은 앱 재시작 시 유지되지 않음

지속성을 사용 설정해도 트랜잭션은 앱을 다시 시작할 때 유지되지 않습니다. 따라서 오프라인일 때 수행된 트랜잭션은 Firebase 실시간 데이터베이스에 커밋된다고 보장할 수 없습니다. 최상의 사용자 환경을 제공하려면 앱에서 트랜잭션이 Firebase 실시간 데이터베이스에 아직 저장되지 않았음을 명시하거나, 앱에서 트랜잭션을 수동으로 저장해 두고 앱이 재시작된 후 다시 실행하도록 해야 합니다.

 

Firebase 실시간 데이터베이스에는 오프라인 시나리오 및 네트워크 연결 상태에 대응하는 여러 가지 기능이 있습니다. 이 가이드의 나머지 부분은 앱의 지속성 사용 설정 여부에 관계없이 적용됩니다.

 

 

접속 상태 관리

실시간 애플리케이션에서는 클라이언트가 연결되거나 연결이 해제되는 시점을 감지하면 유용한 경우가 많습니다.

예를 들어 클라이언트의 연결이 끊기면 사용자를 '오프라인'으로 표시할 수 있다.

 

Firebase 데이터베이스 클라이언트는 Firebase 데이터베이스 서버와 연결이 끊길 때 데이터베이스에 데이터를 쓰는 데 사용할 수 있는 간단한 기본 요소를 제공한다. 이러한 업데이트는 클라이언트 연결이 정상적으로 해제되었는지 여부와 관계없이 발생하므로 연결이 갑자기 끊기거나 클아이언트가 다운되어도 이 업데이트를 사용하여 데이터를 정리할 수 있습니다.

연결이 끊겨도 설정, 업데이트, 삭제 등의 모든 쓰기 작업을 수행할 수 있습니다.

 

다음은 onDisconnect 기본 요소를 사용하여 연결이 끊길 때 데이터를 쓰는 간단한 예시입니다.

let presenceRef = Database.database().reference(withPath: "disconnectmessage");
// Write a string when this client loses connection
presenceRef.onDisconnectSetValue("I disconnected!")

 

 

onDisconnect의 작동 방식

onDisconnect() 작업을 설정하면 Firebase 실시간 데이터베이스 서버에 작업이 상주하게 된다.

서버는 보안 검사를 실행하여 요청된 쓰기 이벤트를 사용자가 수행할 수 있는지 확인하고 문제가 있으면 앱에 알립니다.

이제 서버는 연결 상태를 모니터링하다가 연결이 타임아웃되거나 실시간 데이터베이스 클라이언트에 의해 종료되면 보안 검사를 다시 실행하여 작업이 유효한지 재차 확인한 후 이벤트를 호출합니다.

 

앱에서는 쓰기 작업의 콜백을 사용하여 onDisconnect 가 정상적으로 연결되었는지 확인할 수 있다.

presenceRef.onDisconnectRemoveValue { error, referenc in
  if let error = error {
    print("Could not establish onDisconnect event: \(error)")
  }
}

.cancle() 을 호출하여 onDisconnect 이벤트를 취소할 수도 있다.

presenceRef.onDisconnectSetValue("I disconnected")
// some time later when we change our minds
presenceRef.cancleDisconnectOperations()

 

 

연결 상태 감지

접속 상태 관련 기능에서는 앱의 온라인 또는 오프라인 전환 시점을 확인하면 유용한 경우가 많다.

Firebase 실시간 데이터베이스는 Firebase 실시간 데이터베이스 클아이언트의 연결 상태가 바뀔 때마다 업데이트되는 특수 위치인

/.info/connected 를 제공한다. 예를 들면 다음과 같다.

let connectedRef = Database.database().reference(withPath: ".info/connected")
connectedRef.observe(.value, with: { snapshot in
  if snapshot.value as? Bool ?? false {
    print("Connected")
  } else {
    print("Not connected")
  }
})

/.info/connected 는 부울 값이며, 이 값은 클라이언트의 상태에 좌우되므로 실시간 데이터베이스 클라이언트 간에 동기화되지 않는다.

즉, 클라이언트 중 하나에서 /.info/connected 를 읽은 결과가 false이더라도 다른 클라이언트에서는 다른 값으로 읽힐 수 있다.

 

 

지연 시간 처리

서버 타임스탬프

Firebase 실시간 데이터베이스 서버는 서버에서 생성한 타임스탬프를 데이터로 삽입하는 메커니즘을 제공한다.

이 기능과 onDisconnect 를 함께 사용하면 실시간 데이터베이스 클라이언트의 연결이 끊길 시간을 쉽고 정확하게 기록할 수 있다.

let userLastOnLineRef = Database.database().reference(withPath: "users/morgan/lastOnline")
userLastOnlineRef.onDisconnectSetValue(ServerValue.timestamp())

 

 

시계 보정값

대부분의 읽기/쓰기 작업에서는 firebase.database.ServerValue.TIMESTAMP 가 훨씬 더 정확하고 바람직하지만, Firebase 실시간 데이터베이스 서버를 기준으로 클라이언트 시계 보정값을 측정하면 유용한 경우도 있다.

이 값을 밀리초 단위로 가져오려면 /.info/serverTimeOffset 위치에 콜백을 연결한다.

Firebase 실시간 데이터베이스 클아이언트는 이 값을 로컬 보고 시간(밀리초 단위 에포크 시간)에 더하여 서버 시간을 추정한다.

이 오프셋의 정확성은 네트워크 지연 시간의 영향을 받을 수 있으므로 1초 이상의 상당한 시간 오차를 파악하는 데 주로 사용된다.

let offsetRef = Database.database().reference(withPath: ".info/serverTimeOffset")
offsetRef.observe(.value, with: { snapshot in
  if let offset = snapshot.value as? TimeInterval {
    print("Estimated server time in milliseconds: \(Data().timeIntervalSince1970 * 1000 + offset)")
  }
})

 

 

 

샘플 접속 상태 앱

연결 해제 작업과 연결 상태 모니터링 및 서버 타임스탬프를 결합하여 사용자 접속 상태 시스템을 구축할 수 있다.

이 시스템에서 각 사용자는 특성 데이터베이스 위치에 데이터를 저장하여 실시간 데이터베이스 클라이언트가 온라인 상태인지 여부를 알린다. 클라이언트는 이 위치를 온라인으로 전환될 때 true로, 연결이 끊길 때 타임스탬프로 설정한다. 이 타임스탬프는 사용자가 마지막으로 온라인 상태였던 시간을 나타낸다.

 

앱은 사용자 온라인 표시보다 연결 해제 작업을 큐에서 앞에 두어야 두 명령이 서버로 전송되기 전에 클라이언트의 네트워크 연결이 끊겨도 경합 상태가 발생하지 않는다.

 

다음은 간단한 사용자 접속 상태 시스템이다.

// since I can connect from multiple devices, we store each connection instance separately
// any time that connectionsRef's value is null (i.e. has no children) I am offline
let myConnectionsRef = Database.database().reference(withPath: "users/morgan/connections")

// stores the timestamp of my last disconnect (the last time I was seen online)
let lastOnlineRef = Database.database().reference(withPath: "users/morgan/lastOnline")

let connectedRef = Database.database().reference(withPath: ".info/connected")

connectedRef.observe(.value, with: { snapshot in
  // only handle connection established (or I've reconnected after a loss of connection)
  guard let connected = snapshot.value as? Bool, connected else { return }

  // add this device to my connections list
  let con = myConnectionsRef.childByAutoId()

  // when this device disconnects, remove it.
  con.onDisconnectRemoveValue()

  // The onDisconnect() call is before the call to set() itself. This is to avoid a race condition
  // where you set the user's presence to true and the client disconnects before the
  // onDisconnect() operation takes effect, leaving a ghost user.

  // this value could contain info about the device or a timestamp instead of just true
  con.setValue(true)

  // when I disconnect, update the last time I was seen online
  lastOnlineRef.onDisconnectSetValue(ServerValue.timestamp())
})

 

 

 

여까지가 이제 파이어베이스 사이트에있는 설명들!