This is not the current version. View the latest documentation

Realm Xamarin을 사용하면 안전하고 영속적이며 빠른 여러분의 앱 모델 레이어를 효과적으로 작성할 수 있습니다. 다음을 보세요.

// 모델을 일반적인 C# 클래스로 작성합니다
public class Dog : RealmObject
{
    public string Name { get; set; }
    public int Age { get; set; }
    public Person Owner { get; set; }
}

public class Person : RealmObject
{
    public string Name { get; set; }
    public RealmList<Dog> Dogs { get; }
}

var realm = Realm.GetInstance();

// 질의에 LINQ를 사용합니다
var puppies = realm.All<Dog>().Where(d => d.Age < 2);

puppies.Count(); // => 아직 개가 추가되지 않았기 때문에 0

// 스레드 안전한 트랜잭션에서 객체를 갱신하고 영속화하기
realm.Write(() =>
{
    var mydog = realm.CreateObject<Dog>();
    mydog.Name = "Rex";
    mydog.Age = 1;
});

// 질의는 실시간으로 갱신
puppies.Count(); // => 1

// LINQ 질의 문장도 잘 작동합니다
var oldDogs = from d in realm.All<Dog>() where d.Age > 8 select d;

// 어떤 스레드에서나 질의하고 갱신할 수 있습니다
new Thread(() =>
{
    var realm2 = Realm.GetInstance();

    var theDog = realm2.All<Dog>().Where(d => d.Age == 1).First();
    realm2.Write(() => theDog.Age = 3);
}).Start();

시작하기

Realm은 NuGet을 통해 설치하거나 GitHub에서 소스 코드를 볼 수 있습니다.

사전요구

아래 환경을 지원합니다.

  • Visual Studio 2015 이상, Xamarin Studio 버전 6.00.0 이상.
  • 네이티브 UI나 Xamarin Forms을 iOS 7 이상 버전에서 사용하기 위해서 Xamarin iOS 버전 9.6.2.4.
  • 네이티브 UI나 Xamarin Forms을 API 레벨 10이상에서 사용하기 위해서 Xamarin Android 버전 6.0.4.0.
  • Xamarin.Mac는 지원하지 않습니다.

PCL 사용자를 위한 중요한 안내 - NuGet 유인 상술을 쓸 수 있습니다. Realm에서 사용하는 플랫폼 특화 프로젝트의 모든 PCL마다 Realm Nuget 패키지를 설치해야합니다. 예: 여러분의 앱이 iOS와 안드로이드를 지원하고 공유된 프로젝트를 사용한다면 각 플랫폼 특화 프로젝트마다 NuGet을 설치해야 합니다.

설치

  1. Solution 페인의 여러분의 프로젝트에 “Packeses” 노드에서 기어 버튼을 클릭하고 “Add Packages…“를 클릭합니다.
  2. 검색 창에 “Realm”을 입력합니다.
  3. Realm을 선택하여 추가합니다.
  4. 의존성에서 Fody가 추가된 것을 볼 수 있습니다.

Realm 패키지는 Realm을 사용하기 위해 필요한 모든 것을 가지고 있습니다. 이는 Fody 위버에 의존합니다. 이는 여러분의 RealmObject 서브클래스를 영속적인 객체로 만드는 책임을 집니다.

이 시점에서 패키지를 설치해야 합니다. 프로젝트가 이미 Fody를 사용하고 있다면 기존의 FodyWeavers.xml가 갱신된 것을 볼 수 있습니다. 중요한 점은 FodyWeavers.xml 파일이 RealmWeaver를 포함한 당신이 필요한 모든 위버를 가지고 있다는 것입니다.

이미 추가한 다른 위버가 없었고 Realm을 추가했다면 FodyWeavers.xml 파일의 모양은 다음 예제와 같습니다.

<?xml version="1.0" encoding="utf-8" ?>
<Weavers>
    <RealmWeaver />
</Weavers>
  1. 프로젝트에서 Tools - NuGet Package Manager - Manage Packages for Soultion을 선택합니다.
  2. 가능한 패키지에서 Realm 패키지를 선택합니다.
  3. 오른쪽에 여러분의 프로젝트가 선택되고 Install 버튼이 활성화된 것을 확인합시다.
  4. Install을 누릅니다.
  5. Realm과 Fody가 설치되었다는 다이얼로그가 뜨면 OK를 누릅니다.

Realm 패키지는 Realm을 사용하기 위해 필요한 모든 것을 가지고 있습니다. 이는 Fody 위버에 의존합니다. 이는 여러분의 RealmObject 서브클래스를 영속적인 객체로 만드는 책임을 집니다.

이 시점에서 패키지를 설치해야 합니다. 프로젝트가 이미 Fody를 사용하고 있다면 기존의 FodyWeavers.xml가 갱신된 것을 볼 수 있습니다. 중요한 점은 FodyWeavers.xml 파일이 RealmWeaver를 포함한 당신이 필요한 모든 위버를 가지고 있다는 것입니다.

이미 추가한 다른 위버가 없었고 Realm을 추가했다면 FodyWeavers.xml 파일의 모양은 다음 예제와 같습니다.

<?xml version="1.0" encoding="utf-8" ?>
<Weavers>
    <RealmWeaver />
</Weavers>

Realm 브라우저

우리는 .realm 파일을 읽고 편집하기 위해 독립적인 맥 앱 Realm 브라우저를 제공합니다.

Realm Xamarin이 지원하는 파일 포맷은 현재 브라우저보다 낮습니다. 이런 이유로 새 버전의 브라우저로 realm 파일을 열게 되면 Realm Xamarin에서 동작하지 않게 됩니다. 위의 링크는 호환성을 위한 버전입니다.

Realm Browser

메뉴 항목 Tools > Generate demo database.을 이용하면 샘플 데이터로 테스트 데이터베이스를 생성할 수 있습니다.

앱의 Realm 파일에 대한 도움을 찾기를 원한다면 StackOverflow 답변에서 자세한 설명을 확인하세요.

Realm 브라우저는 맥 앱 스토어에서 사용할 수 있습니다.

API 문서

전체 클래스와 메서드는 전체 API 문서에서 살펴보세요.

예제

저장소의 예제 폴더에서 여러 예제를 찾을 수 있습니다.

도움을 구하려면

모델

Realm 데이터 모델은 프로퍼티를 포함한 전통적인 C# 클래스를 통해 정의합니다. 단순히 RealmObject를 상속받아 Realm 데이터 모델 객체를 생성합니다.

Realm 모델 객체는 대부분의 경우 다른 C# 객체들처럼 동작합니다. 자신만의 메서드 이벤트를 추가할 수 있고 다른 객체들처럼 사용할 수 있습니다. 주요 제약은 생성된 스레드에서만 객체를 사용해야 하고 영속적인 속성은 게터와 세터를 생성해야 한다는 점입니다.

관계와 내부 데이터 구조는 단순히 대상 타입이나 객체들의 리스트를 위한 타입 RealmList을 포함합니다.

// 개 모델
public class Dog : RealmObject
{
    public string Name { get; set; }
    public int Age { get; set; }
    public Person Owner { get; set; }
}

public class Person : RealmObject
{
    public string Name { get; set; }
    public RealmList<Dog> Dogs { get; }
}

지원 타입

Realm은 unsigned를 제외한 타입(bool, char, byte, short, int, long, float, double)과 string, DateTimeOffset를 지원합니다. 널도 지원하고요.

관계

RealmObjectRealmObjectRealmList 속성을 이용해서 서로 연결할 수 있습니다.

RealmList는 .NET의 표준 IList 제너릭 인터페이스를 구현합니다.

대상이 하나인 관계

N대 1이나 1대 1 관계를 위해 단순히 속성을 RealmObject의 서브클래스에 선언하세요.

public class Dog : RealmObject
{
    // ... 다른 속성 선언
    public Person Owner { get; set; };
}

public class Person : RealmObject{}

이 속성을 다음처럼 사용할 수 있습니다.

var jim = realm.CreateObject<Person>()
var rex = realm.CreateObject<Dog>();
rex.Name = "Rex";
rex.Owner = jim;

관계를 끊기 위해서는 단순히 null을 대입합니다.

rex.Owner = null;

RealmObject 속성을 이용할 때 중첩된 속성을 일반적인 속성 문법을 통해 접근할 수 있다. 예를 들어 rex.Owner.Address.Country는 객체 그래프를 순회하고 필요시 Realm으로 부터 각 객체를 자동으로 가져오게 된다.

대상이 여럿인 관계

대상이 여럿인 관계는 RealmList 속성을 통해 만들 수 있다. 이런 속성을 이용할 때 RealmList은 비어있을 수도 있고 관계된 RealmObject의 타입을 받을 수도 있다.

“개” 속성들을 여러 개와 연결될 수 있는 Person 모델에 추가하기 위해서 RealmList<Dog> 속성을 단순히 선언하자.

public class Dog : RealmObject
{
}

public class Person : RealmObject
{
    // ... 다른 속성 선언
    public RealmList<Dog> Dogs { get; }
}

RealmList 속성에 일반적인 IList를 사용하듯 접근하거나 대입할 수 있습니다.

jim.Dogs.Add(rex);
Assert(jim.Dogs.Count == 1);  // rex외에 없음

선택적 속성

string과 같은 참조 타입과 RealmObject의 값은 null이 될 수 있습니다.

int?와 같이 널 값이 허용되는 타입도 전부 지원합니다.

속성 영속성 제어하기

RealmObject을 상속받은 클래스들은 Fody weaver에 의해 컴파일 타임에 처리됩니다. 자동 세터와 게터가 추정될 수 있는 모든 속성은 영구적으로 저장되며 내부의 Realm 저장소와 연결되는 생성된 세터와 게터를 가지게 됩니다.

우리는 몇몇 C# 특성을 영속 제어를 위한 메타데이터로 제공합니다.

속성의 영속화를 막으려면 단순히 [Ignored] 특성을 추가하면 됩니다. 영속화를 막아야 하는 일반적인 예로는 외부 미디어를 사용하기 때문에 바이너리 이미지 대신에 파일의 경로를 저장해야하는 상황등을 영속화를 막아야 하는 상황등이 있습니다.

public string HeadshotPath { get; set; }

// 실행 중 메모리 이미지
[Ignored]
public Image Headshot { get; set; }

커스텀 세터

그들 자체의 세터와 게터 구현을 가진 속성들은 자동으로 무시됩니다. 검증을 위해서는 아래를 사용할 수 있습니다.

private string Email_ { get; set; }

// 영속 Email_ 속성의 검증
public string Email
{
    get { return Email_; }
    set
    {
        if (!value.Contains("@")) throw new Exception("Invalid email address");
        Email_ = value;
    }
}

인덱스가 추가된 속성

현재 스트링, 정수, 불린, DateTimeOffset은 인덱스를 추가할 수 있습니다.

속성을 인덱스하는 것은 속성은 (예를 들어 ==Contains 연산을 사용하여) 동등을 비교하는 것에 비해 많은 성능을 향상시킵니다. 다만 삽입 성능은 조금 느려집니다.

속성을 인덱스화하기 위해 단순히 [Indexed] 특성을 속성 선언에 추가합니다.

public class Person : RealmObject
{
    [Indexed]
    public string Name { get; set; }
    public RealmList<Dog> Dogs { get; }
}

자동 갱신되는 객체

RealmObject 인스턴스는 라이브이고 내부의 데이터로 자동 갱신됩니다. 이 말은 객체를 리프레쉬할 필요가 없다는 뜻입니다. 객체의 속성을 수정하면 즉각 다른 인스턴스가 참조하는 같은 객체에 반영됩니다.

public class Dog : RealmObject
{
    public string Name { get; set; }
    public int Age { get; set; }
}

var realm = Realm.GetInstance();
// 설정 끝

Dog myDog;
realm.Write(() =>
{
    var mydog = realm.CreateObject<Dog>();
    mydog.Name = "Fido";
    mydog.Age = 1;
});

var myPuppy =  = realm.All<Dog>().First(d => d.Age == 1);
realm.Write(() =>
{
  myPuppy.age = 2
}

Console.WriteLine($"age of my dog: {myDog.age}") // => 2

RealmObject의 이런 특성은 Realm을 빠르고 효과적으로 할 뿐 아니라 여러분의 코드를 단순하고 더 반응적이게 합니다. 예를 들어 UI 코드가 특정 Realm 객체에 기반한다면 UI를 다시 그리기 전에 객체를 리프레쉬하거나 다시 가져올 필요가 없습니다.

Realm 알림을 구독하여 객체의 Realm 데이터가 갱신되는 것을 알 수 있고 이를 이용하여 앱의 UI를 갱신할 수 있습니다.

ObjectId 특성

단일 [ObjectId] 특성은 모델의 객체 id를 설정하는 하나의 속성에 정의할 수 있다. 객체의 id를 선언하면 객체는 참조와 업데이트가 효과적이며 각 값이 유일하다는 것이 강제된다.

오직 char, 정수형, 문자열만이 객체 id가 될 수 있다.

한번 ObjectId의 객체가 Realm에 추가되면 객체 id는 변경할 수 없다.

전통적인 데이터베이스 배경을 가지고 있다면 객체 id가 SQL의 기본 키 개념과 매우 흡사하다고 생각할 수 있다.

객체 모델링 관점에서 오브젝트 id는 객체에 대한 영속적인 포인터와 같다. 객체 id를 이용하면 다른 스레드에서 객체의 레퍼런스를 통해 빨리 참조할 수 있다.

[ObjectId] 특성을 여러 속성에 추가하는 것은 정의되지 않은(undefined) 행위이며 런타임 에러가 발생한다. 이 특성 속성은 한 곳에만 지정하라.

public class Person : RealmObject
{
    [ObjectId]
    public string SSN { get; set; }
    [Indexed]
    public string Name { get; set; }
    public RealmList<Dog> Dogs { get; }
}

무시된 속성

[Ignored] 특성을 사용하여 속성을 남겨두어 표준 C# 속성으로서만 다루게 할 수 있다.

만약 속성에 대해 세터나 게터를 정의하면 그 속성은 자동으로 무시된다.

모델 상속

Realm Xamarin은 어떤 식으로든 추가로 상속되는 것을 허용하지 않는다. CreateObject 제너릭 메서드는 RealmObject으로 부터 직접 상속받은 클래스에만 작동한다.

컬렉션

Realm은 객체의 그룹을 표현하기 위해 “Realm 컬렉션”이라 부르는 두가지 타입을 가지고 있다.

  1. RealmResults, 질의로부터 가져온 객체들을 표현하는 클래스.
  2. RealmList, 모델에서 대상이 여럿인 관계를 표현하는 클래스.

둘다 IEnumerable interface이기 때문에 지연 열거를 지원합니다. 컬렉션의 크기만 알려지고 객체는 컬렉션을 순회할 때만 메모리에 적재됩니다.

쓰기

객체에 대한 모든 변경(추가, 수정, 삭제)은 쓰기 트랜잭션 내에서 이루어져야 합니다.

스레드 간에 객체를 공유하거나 앱이 실행된 후 재사용하기 위해 그들을 쓰기 트랜잭션내에 연산을 처리하여 Realm으로 영속화를 해야합니다.

쓰기 트랜잭션은 무시못할 오버헤드를 초래하기 때문에 코드에서 쓰기 트랜잭션의 수를 줄이도록 궁리할 필요가 있습니다.

쓰기 트랜잭션은 잠재적으로 다른 디스크 IO 연산처럼 디스크 용량 부족등의 잠재적인 실패 가능성이 있기 때문에 쓰기로 부터 예외를 대비하고 실패를 다루거나 복원하여야 합니다. 다른 복구가능한 에러는 없습니다. 간결함을 위해 우리의 코드 샘플은 이런 에러들을 다루지 않습니다만 제품 어플리케이션에는 포함되어야 합니다.

쓰기 트랜잭션을 작성하는 쉬운 방법 두가지가 있습니다. Realm.BeginWrite()Realm.Write()입니다. 첫번째 Realm.BeginWrite()Dispose 패턴을 구현한 Transaction을 반환합니다. using과 함께 사용할 수 있습니다.

using(var transaction = realm.BeginWrite())
{
    person.FirstName = "John";
    person.Age = 56;
    transaction.Commit();
}

트랜잭션을 명시적으로 Commit해야하는 점을 유의하세요. 그렇지 않으면 자동으로 복구됩니다. 다른 방법은 변경 코드를 Realm.Write()로 감싸는 것입니다.

realm.Write(() =>
{
    person.FirstName = "John";
    person.Age = 56;
});

예외가 발생되지 않으면 기본으로 암시적인 트랜잭션이 커밋됩니다.

객체 만들기

모델을 정의할 때 RealmObject의 서브클래스를 실체화하고 새로운 인스턴스를 Realm에 추가할 수 있습니다. 간단한 모델을 고려합시다.

public class Dog : RealmObject
{
    public string Name { get; set; }
    public int Age { get; set; }
}

Realm.CreateObject<>() 메서드를 호출하여 인스턴스를 만들 수 있습니다.

realm.Write(() =>
{
    var mydog = realm.CreateObject<Dog>();  // Realm에 의해 즉시 관리됩니다
    mydog.Name = "Rex";
    myDog.Age = 10;
});

객체를 만든 이후 실시한 모든 변경은 영속적이게 됩니다. (이 변경들은 쓰기 트랜잭션에서 이루어져야 합니다.) 쓰기 트랜잭션이 완료되면 같은 Realm을 사용해서 다른 스레드에서 변경을 하는 것이 가능합니다.

쓰기가 다른 쓰기를 블록하고 만약 진행중인 여러 쓰기가 있다면 다른 스레드를 블록할 것이라는 점을 유의하세요. 이것은 다른 영속적인 도구에서도 비슷합니다. 우리는 이런 상황에서 일반으로 통용되는 최선의 방법인 별도의 스레드에서 쓰기를 해 부담을 감하는 것을 추천합니다.

Realm이 MVCC 아키텍쳐를 사용하기 때문에 읽기는 트랜잭션이 열려 있어도 방해받지 않습니다. 한번에 여러 스레드에서 쓰기를 같이 하지 않는 한 여러분은 잘게 나눈 많은 쓰기 트랜잭션보다 큰 쓰기 트랜잭션을 선호할 것입니다.

객체 갱신하기

쓰기 트랜잭션에서 속성들을 설정하는 방법으로 객체를 갱신할 수 있습니다.

// 트랜잭션에서 객체를 갱신하기
using (var trans = realm.BeginWrite())
{
  author.Name = "Thomas Pynchon";
  trans.Commit();
}

객체 삭제하기

쓰기 트랜잭션에서 Realm.Remove 메서드에 삭제할 객체를 전달합니다.

var cheeseBook = realm.All<Book>().First(b => b.Name == "Cheese");

// 트랜잭션에서 객체를 삭제합니다
using (var trans = realm.BeginWrite())
{
  realm.Remove(cheeseBook);
  trans.Commit();
}

Realm에 저장된 모든 객체들을 저장할 수 있습니다. Realm 파일은 삭제된 용량을 유지해서 앞으로 쓰일 객체에서 재사용을 효과적으로 만들 것입니다.

질의

질의는 표준 LINQ 구문을 구현했습니다.

All 메서드를 사용해서 주어진 타입의 모든 객체를 담은 기본적인 타입 클래스를 얻고 Where를 적용하거나 다른 LINQ 연산을 컬렉션 필터링을 위해 사용할 수 있습니다.

능숙한 혹은 확장된 구문

var oldDogs = realm.All<Dog>().Where(d => d.Age > 8);

질의 표현 구문

var oldDogs = from d in realm.All<Dog>() where  d.Age > 8 select d;

어떤 문구를 선택하든 반환된 RealmResults 컬렉션은 IQueryable 인터페이스입니다. 그래서 그걸로 반복하거나 다른 처리를 할 수 있습니다.

foreach (var d in oldDogs)
{
  Debug.WriteLine(d.Name);
}

더 많은 질의 예제

LINQ 질의에 익숙하지 않은 경우를 위해 여기 기본적인 질의 문구 예제를 여기에 나열합니다. 확장 문구 와 함께 기본적인 질의를 살펴봅시다.

John 혹은 Peter란 이름을 가진 모든 사용자의 리스트를 출력하려면 다음과 같이 합니다.

var johnsAndPeters = realm.All<Person>().Where(p =>
    p.FirstName == "John" ||
    p.FirstName == "Peter");
var peopleList = johnsAndPeters.ToList();

첫번째 문장은 [`RealmResults`](api/class_realms_1_1_realm_results.html) 클래스의 `johnsAndPeters` 인스턴스를 돌려줍니다. 이것은 "John" "Peter" 이름인 사용자를 찾는 `IQueryable`를 생성합니다. 질의는 반복이나 결과의 수를 세는 추가적인 호출이 있을 때까지 어떤 일도 하지 않습니다.

 예제에서 `ToList` 호출은 Realm 코어에 직접 연결된 질의를 _수행시킵니다_

객체는 **복사되지 않습니다**. 조건에 맞는 객체들의 레퍼런스 리스트를 받아 질의에 맞는 원본 객체를 직접 다룹니다.

결과를 모두 리스트로 받아오는 대신 질의 결과를 표준 C#의 `foreach` 문으로 타고   있습니다.

```csharp
int i = 0;
foreach (var pers in johnsAndPeters) {        // 질의를 타고 감
    Assert.That(peopleList[i++], Is.EqualTo(pers));  // 이전 리스트의 반복자를 확인
}

논리 연산자

LINQ 표현의 표준 C# 논리 연산자로 질의를 작성할 수 있습니다.

if 문에 기대하는 전제 조건들을 괄호에 묶인 중첩된 표현식으로도 사용할 수 있습니다.

var ComplexPeople = realm.All<Person>().Where(p =>
    p.LastName == "Doe" &&
    (p.FirstName == "Fred" || p.Score > 35));

타입으로 객체를 가져오기

주어진 타입에 대해 모든 객체를 Realm에서 가져오려면 단순히 realm.All<T>()를 쓰면 된다. 이것은 RealmResults을 반환하는데 질의된 모델 클래스의 전체 인스턴스를 가진다. 위의 예제에서는 Person이다. 집합에 대해 추가적인 제약을 적용하기 위해 LINQ 질의를 사용할 수 있다. RealmResults에 대해 순환을 하기 전까지는 어떠한 오버헤드도 없습니다.

var ap = realm.All<Person> ();  // 타입에 관한 전체 아이템
var scorers = ap.Where(p => p.Score > 35);  // 첫번째 검색 절로 제한 두기
var scorerDoe = scorers.Where(p => p.LastName == "Doe");  // 효과적인 AND 

정렬

RealmResults에 의해 지원되는 객체들의 순서에 영향을 미치는 표준 LINQ 절 OrderBy, OrderByDescending, ThenBy, ThenByDescending은 여러 단계의 검색에 사용될 수 있습니다.

그들은 결과에 대한 전체 객체를 읽지 않으며 효과적인 검색 정렬을 지원하는 내부의 쿼리 엔진을 통해 이루어집니다. 정렬을 하기 위해 Where 질의를 실행할 필요가 없습니다. 단지 All으로 부터 질의를 정렬하세요.

주의: 만약 ToList 절을 객체 리스트를 추출하기 위해 사용하고 객체를 위한 LINQ를 이용해서 메모리 상에서 정렬하기 위해 OrderBy를 사용할 수 있습니다. ToList를 표현의 마지막에 사용했는지 확인해주세요.

var highestScore = realm.All<Person>().OrderByDescending(p => p.Score).First();

// 두 절에서 정렬하고 단언(assertion)에서 비교하기
// (유닛 테스트로부터 중복된 이름이 복사되었습니다)
var sortAD = realm.All<Person>().Where(p => p.IsInteresting).
    OrderBy(p => p.FirstName).
    ThenByDescending(p => p.Latitude).ToList();
var sortADname = sortAD.Select(p => p.FirstName);
Assert.That(sortADname, Is.EqualTo( new [] {"John", "John", "Peter"}) );

연결 질의

결과는 복제되지 않고 요청 시점에 계산되기 때문에 데이터를 단계적으로 필터링하기 위해 효과적으로 연결 질의를 할 수 있습니다.

var teenagers = realm.All<Person>().Where(p => p.Age >= 13 && p.Age <= 20);
var firstJohn = teenagers.Where(p => p.FirstName == "John").First();

제한된 결과

대부분의 다른 데이타베이스 기술들은 질의 결과들을 (SQLite의 LIMIT 키워드와 같이) ‘페이지를 바꾸는’ 기능을 제공합니다. 이는 종종 디스크로 부터 너무 많이 읽는 것을 막거나 한번에 메모리로 많은 결과를 가져오는 것을 막기 위해 필요합니다.

Realm의 질의는 지연되고 질의의 결과로 부터 객체들을 명시적으로 접근될 때만 가져오기 때문에 페이지를 바꾸는 행위같은 것은 전적으로 불필요합니다.

만약 UI 관련이나 다른 구현 이유로 질의로 부터 특정한 객체의 부분집합을 원한다면 RealmResults 객체를 가져오고 필요한 객체만 읽으면 됩니다.

만약 LINQ Take를 사용하길 원하더라도 아직 쓸 수 없습니다. 이것이 추가되면 한 명령에 요청한 객체 전부를 열거하고 인스턴스화할 수 있을 것입니다.

Realms

Realm은 우리의 실제 데이터베이스와 동등합니다. 여러 종류의 객체를 가지고 있고 디스크 상 하나의 파일에 대응합니다.

기본 Realm

realm 변수를 항상 Realm.GetInstance(string optionalPath)으로 초기화한 것을 보았을 것입니다. 정적 생성자는 스레드에 맞는 Realm 인스턴스를 반환합니다. 이 인스턴스는 Environment.SpecialFolder.Personal에 저장되는 default.realm 파일에 연결됩니다.

Realm 인스턴스가 스레드 싱글톤이라는 점은 중요합니다. 정적 생성자는 스래드마다 동일한 인스턴스를 반환합니다.

Realm 설정하기

Realm.getInstance(filename)을 호출하면 Realm을 손쉽게 시작할 수 있습니다. Realm이 생성되는 여러 측면을 제어할 수 있는RealmConfiguration 객체를 생성해 보다 세밀한 제어를 할 수 있습니다.

RealmConfiguration은 다양한 데이터 베이스 경로를 허용합니다. (한번 설정이 생성되면 경로를 바꿀 수 없는 점을 유의하세요.)

아래와 같이 할 수 있습니다.

  1. 새로운 절대 경로를 전달하여 전체 경로를 변경하기.
  2. 표준 경로의 서브 디렉토리에 Realm 파일들을 두고 상대 경로를 전달하기.
  3. 새로운 파일 이름을 전달하여 Realm 파일 이름을 변경하기.

같은 구성의 Realm을 열기 위해 어떤 설정의 인스턴스를 여기 저기에 쓸 수 있습니다. 다른 스레드에서 같은 Realm을 열기 위해 사용하는 일반적인 사용례입니다.

물론 어떤 객체를 전달하지 않고 기본 값을 바꾸기 위해 기본 설정을 재정의할 수 있습니다.

Realm 인스턴스 닫기

Realm은 네이티브 메모리 해제와 파일 설명자(descriptors)를 다루기 위해 IDisposable을 구현합니다. 이렇게 하면 인스턴스는 변수가 스코프에서 사라질 때 자동으로 닫힐 수 있습니다.

Realm.Close()를 호출하는 것은 언제나 안전합니다. 빨리 확실히 닫기 위해 여러 번 호출해도 됩니다.

Realm 파일 찾기

앱의 Realm 파일을 찾는 것의 도움이 필요하면 자세한 설명을 StackOverflow의 답변에서 확인하세요.

외부 Realm 파일

표준 .realm 파일을 따라 Realm은 내부적인 자체 연산을 위해 추가적인 파일을 생성하고 관리합니다.

  • .realm.lock - 리소스 락을 의한 락 파일.
  • .realm.log_a, .realm.log_b - 트랜잭션 로그를 위한 로그 파일.
  • .realm.note - 노티를 위한 명명된 파이프(named pipe).

이 파일들은 .realm 데이터베이스 파일에 어떤 영향도 미치지 않습니다. 그들의 부모 데이터베이스 파일이 삭제되거나 대체되어도 어떤 에러를 유발하지 않습니다.

Realm 이슈를 보고할 때는 디버깅을 위해 유용한 정보를 포함한 외부 파일들을 .realm 파일과 함께 포함해주세요.

Realm 파일 포함하기

앱에 Realm 파일을 포함하길 원한다면 스택오버플로우의 대단한 답변을 보세요. 어떻게 Xamarin 프로젝트에 리소스로 포함하고 그것을 사용하기 위해 복사하는지 보여줍니다.

클래스 부분집합

어떤 시나리오에서 특정 Realm에 저장될 클래스를 한정짓고 싶을 수 있습니다.

이를 RealmConfigurationObjectClass 속성을 설정해서 할 수 있습니다.

class LoneClass : RealmObject
{
    public string Name { get; set;}
}

var config = new RealmConfiguration("RealmWithOneClass.realm");
config.ObjectClasses = new Type[] {typeof(LoneClass)};

// 또는 Realm에 두개의 클래스를 지정하기
config.ObjectClasses = new Type[] {typeof(Dog), typeof(Cat)};

Realm 파일을 삭제하기

캐시를 삭제하거나 전체 데이터세트를 리셋하는 등의 경우에 디스크로부터 Realm 파일을 전체적으로 삭제하는게 적합할 수 있습니다.

다른 파일들과 달리 Realm은 메모리 맵되어 있고 Realm 인스턴스는 인스턴스의 생애 주기 동안 해당 파일들이 존재하기를 기대합니다.

애플리케이션코드가 Realm에 관련한 전체 파일을 아는 것을 피하기 위해 편의를 위한 메서드 Realm.DeleteRealm(RealmConfiguration)를 제공합니다.

var config = new RealmConfiguration("FileWeThrowAway.realm");
Realm.DeleteRealm(config);
var freshRealm = Realm.GetInstance(config);

스레드

개별적인 스레드에서 모든 것을 동시성과 멀티스레드에 대한 걱정없이 일반 객체처럼 다룰 수 있습니다. (다른 스레드에서 동시에 수정되더라도) 데이터에 접근하기 위해 락을 쓰거나 리소스를 조율할 필요가 없습니다. 단 수정을 위한 연산만 트랜잭션으로 감싸야 합니다.

Realm은 개별 스레드가 항상 Realm의 일관된 뷰를 보는 걸 보장함으로서 동시적인 사용이 쉽도록 합니다. 개별 스레드가 자신만의 스냅샷을 가지기 때문에 같은 Realm을 병렬적으로 여러 스레드에서 수행할 수 있습니다. 그들은 서로에게 일관성이 없는 상태를 보게끔 영향을 주지 않습니다.

알아야 할 유일한 부분은 Realm 객체의 같은 인스턴스들은 여러 스레드에서 공유될 수 없다는 점입니다. 만약 여러 스레드에서 같은 객체에 대한 접근이 필요하다면 그들은 각자의 인스턴스들이 필요합니다. (그렇지 못하면 하나의 스레드에서 변경점은 다른 스레드에 불완전하게 보게되거나 불일관된 데이터를 보게 됩니다.)

다른 스레드에서 변경 바라보기

메인 UI 스레드(혹은 runloop이나 looper의 어떤 스레드)에서 객체는 매 runloop의 단계마다 다른 스레드의 변경은 자동으로 객체에 갱신됩니다. 어떤 시점이든 스냅샷을 다루게 되고 개별 메서드는 언제나 일관성있는 뷰를 보게 되고 다른 스레드에서 무엇이 일어나고 있는지 염려할 필요가 없습니다.

스레드에서 Realm을 초기화했다면 이것의 상태는 최근에 성공한 쓰기 커밋에 기반을 하게 되고 갱신되기 전까지는 그 버전을 유지하게 됩니다. Realm은 매 runloop의 매 단계의 시작마다 자동으로 갱신됩니다. (일반적으로 백그라운드 스레드인 경우에) 만약 스레드에 runloop이 없다면 가장 최근 상태의 트랜잭션으로 전진하기 위해 Realm.Refresh()은 수동으로 호출 해야합니다.

Realm은 쓰기 트랜잭션이 Transaction.Commit()으로 커밋되었을 때도 갱신합니다.

일반적인 이유로 Realm 갱신이 실패하면 그 버전에 사용되었던 디스크 공간이 재사용되는 것을 막기 위해서 어떤 트랜잭션 버전이 “고정”될 수 있고 파일 사이즈가 커질 수 있습니다. 자세한 내용은 우리의 현재 제한사항을 참고하세요.

스레드간 인스턴스 전달하기

Realm, RealmObject, RealmResults, RealmList의 영속적인 인스턴스는 그것이 생성된 스레드에서만 사용할 수 있고 그렇지 않으면 예외를 발생합니다. 이는 Realm이 트랜잭션 버전을 고립시키는 한가지 방식입니다. 그렇지 않으면 잠재적으로 확장된 관계 그래프를 가진 다른 트랜잭션 버전의 스레드들 사이에 객체를 전달할 때 무엇을 해야할지 결정할 수 없습니다.

대신에 스레드 사이에 데이터를 전달할 안전한 방법들이 있습니다. 예를 들어 ObjectId의 객체는 그 객체의 id 값으로 표현될 수 있습니다. Realm은 그것의 RealmConfiguration으로 표현 수 있습니다. 대상 스레드는 RealmRealmObject를 그들의 스레드 안전 표현을 이용하여 다시 가져올 수 있습니다. 다시 가져오기는 대상 스레드에 그 버전의 인스턴스를 가져오게 됩니다. 이때 대상 스레드는 원 스레드와 다를 수 있습니다.

스레드 사이에 Realm을 사용하기

같은 Realm 파일을 다른 스레드에서 접근하려면 앱의 다른 스래드마다 다른 인스턴스를 얻기 위해 새로운 Realm을 초기화해야합니다. 같은 설정을 지정하는 한 모든 Realm 인스턴스는 디스크의 같은 파일에 연결됩니다.

여러 스레드에서 Realm 인스턴스를 공유하는 것은 지원되지 않습니다. 같은 realm 파일을 접근하는 Realm 인스턴스는 같은 RealmConfiguration을 사용해야 합니다.

Realm은 많은 양의 데이터를 하나의 트랜잭션에 여러 쓰기를 같이 일괄적으로 기록하는 것이 효과적입니다. Realm 객체는 스레드 안전하지 않고 스레드 사이에 공유될 수 없습니다. 읽기나 쓰기가 필요할 때 각 스레드나 dispatch_queue 마다 Realm 인스턴스를 얻으세요.

알림

안드로이드에서 변경 리스너는 오로지 Looper 스레드에서 작동합니다. 비 Lopper 스레드에서는 대신에 Realm.Refresh()을 수동으로 호출해야 합니다.

Realm에 데이터를 추가하는 백그라운드 스레드가 있고 여러분의 UI나 다른 스레드에서 리스너를 추가하여 Realm의 변경을 통보받을 수 있습니다. 변경 리스너는 (다른 스레드나 프로세스에서) Realm이 변경될 때 마다 수행됩니다.

realm.RealmChanged += () =>
{
    // UI를 갱신한다
}

마이그레이션

어떤 데이터베이스를 쓰더라도 데이터 모델은 시간에 따라 바뀌곤 합니다. Realm의 데이터 모델이 표준 C# 클래스로 정의되었기 때문에 모델의 변경은 다른 클래스를 수정하는 것 만큼 쉽습니다.

Realm은 별다른 처리없이도 자동으로 클래스의 추가, 삭제, 클래스의 속성 추가와 삭제같은 마이그레이션을 수행합니다. 더 복잡한 변경에서 적절한 마이그레이션이 필요합니다. 우리는 아직 적절한 API를 설계하지는 못했지만 여기에 대한 계획을 가지고 있고 곧 추가할 예정입니다.

암호화

미국으로부터의 수출 제한이나 금수 조치가 있는 국가에 거주하는 경우 Realm 사용에 대한 제한이 있을 수 있으므로, 저희 라이센스의 수출 규정 준수 조항을 참고하십시오.

Realm이 생성될 때 64바이트 암호화 키를 제공해 AES-256+SHA2를 이용하여 디스크의 데이터베이스 파일을 암호화하는 것을 제공합니다.

var config = new RealmConfiguration("Mine.realm");
config.EncryptionKey = new byte[64] // 키는 반드시 이 사이즈여야 합니다
{
  0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
  0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
  0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
  0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38,
  0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48,
  0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58,
  0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68,
  0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78
};

var realm = Realm.GetInstance(config);  // 암호화된 "Mine.realm" 생성하거나 읽을 것입니다

필요에 따라 디스크에 저장되는 모든 데이터에 AES-256와 SHA-2 HMAC에 의해 검증되는 암호화와 복호화를 투명하게 적용할 수 있습니다. 이를 적용하면, Realm 인스턴스를 얻을 때 마다 같은 암호키를 전달해야 합니다.

앱에 대해 유일한 키를 사용해야 합니다. 위의 예제에 사용된 키는 절대 안됩니다. 더 나아가 사용자마다 개인화된 키를 원한다면 Xamarin.Auth API를 살펴보세요.

암호화된 Realm에 대해 잘못된 키를 지정하거나 키를 지정하는데 실패하면 GetInstance를 호출 할 때 RealmFileAccessErrorException을 얻게 됩니다.

Realm 암호화를 사용할 때 작은 성능 하락이 있습니다. (일반적으로 10% 이하 느려집니다.)

현재 제한사항

Realm은 현재 베타이고 지속적으로 여러 기능을 추가하고 이슈를 해결하며 1.0 릴리스를 향하고 있습니다. 그때까지 우리의 가장 널리 요청 받은 제약리스트를 엮어 두었습니다.

알려진 이슈에 대한 상세한 목록을 얻기 위해서는 우리의 GitHub issues을 참고하세요.

프리뷰 버전에서 빠졌지만 앞으로 포함될 기능입니다.

  • 한번에 여러 관계를 추가하기 위해 목록 값을 RealmList 필드로 대입하기
  • 비동기 질의
  • 이진 데이터 필드 (예: 사진 저장)
  • 종속적 삭제
  • 컬렉션 알림
  • 기본 값 - 정의를 통하는 일반적인 방법은 위빙 과정에서 지원되지 않습니다
  • 전체 객체 삭제하기
  • 다이나믹 Realm
  • RealmList를 얻는 것 대신에 IList를 정의하기.
  • 마이그레이션
  • 더 많은 LINQ 연산
  • 관계 데이터에 대한 검색
  • 그들의 ObjectId를 이용하여 Object를 갱신하기

일반적인 제한

Realm은 유연함과 성능 사이에 균형을 찾는 것을 목표로 합니다. 목적을 이루기 위해 Realm에 정보를 저장하는 여러 측면에 현실적인 제약을 시행하게 됩니다. 예를 들어서 다음과 같습니다.

  1. 클래스 이름은 0부터 57 바이트 사이의 길이여야 합니다. UTF-8 문자열이 지원됩니다. 앱의 초기화 때 제한을 넘게 되면 예외가 던져집니다.
  2. 프로퍼티 이름은 0부터 63 바이트 사이의 길이어야 합니다. UTF-8 문자열이 지원됩니다. 앱의 초기화 때 제한을 넘게 되면 예외가 던져집니다.
  3. iOS 제한: 열린 Realm 파일의 사이즈는 iOS에 연결되어 애플리케이션에 허용된 총 메모리 사이즈보다 클 수 없습니다. 이는 디바이스마다 다르고 메모리 공간이 실행 시점에 어떻게 파편화되었냐에 의존합니다. (여기에 대한 radar 이슈가 있습니다. rdar://17119975) 더 많은 데이터를 저장하길 원한다면 여러 개의 Realm 파일로 분할하고 그들을 필요에 따라 열고 닫을 수 있습니다.

파일 사이즈와 중간 버전의 추적

Realm 데이타베이스는 동등한 SQLite 데이터베이스에 비해 일반적으로 디스크 공간을 더 적게 사용합니다. 만약 기대보다 Realm 파일이 크다면 이것은 RealmObject가 데이터베이스 데이터의 오래된 버전을 참조하기 때문입니다.

데이터의 일관된 뷰를 제공하기 위해 Realm은 런 룹 단계의 시작에서만 접근된 활성 버전을 갱신합니다. 만약 Realm으로 부터 어떤 데이터를 읽었습니다. 그리고는 다른 스레드에서 Realm에 기록하는 동안 오래 수행되는 연산으로 스레드를 막았습니다. 버전은 갱신되지 못하고 Realm은 당신이 필요하지 않은 중간 버전 데이터를 붙잡고 있습니다. 그 결과로 매 쓰기마다 파일의 크기는 증가하게 되지요. 여분의 공간은 결국에는 다음 쓰기에 재사용이 됩니다.

질의응답

일반

제품 애플리케이션에 Realm을 써도 될까요?

Realm은 2012년 부터 상용 제품의 제작에 상요되었습니다. Xamarin C# 제품은 2015년 12월 부터 베타였지만 C++ 코어는 모든 다른 Realm 제품에서 사용되는 것과 동일합니다.

우리의 C# API가 커뮤니티 피드백에 따라 제품이 발전되어 변화하는 것을 기대할 수 있습니다. 더 많은 기능과 버그 수정도 따라오는 것을 보게 될 것입니다.

Realm을 쓰려면 돈을 내어야 하나요?

아닙니다. Realm은 상업적인 프로젝트에 사용할 때도 완전히 무료입니다.

돈을 어떻게 벌 생각이에요?

우리는 이미 매출을 올릴 엔터프라이즈 제품과 서비스를 우리 기술에 기반해서 만들었습니다. 만약 현재 릴리즈된 것이나 realm-dotnet보다 더 많은 것이 필요하면 이메일을 주세요. 우리는 언제나 환영하고 있습니다. 우리가 개발을 커밋하는 realm-dotnet은 공개되어 있고 이것은 무료로 유지될 것입니다. Apache 2.0 라이선스에 맞추어 오픈 소스로 유지될 것입니다.

코드에서 “코어”라고 참조하는 부분을 보았습니다. 그게 무엇이에요?

코어는 내부의 C++ 스토리지 엔진입니다. 이것은 현재 오픈소스가 아니지만 Apache 2.0 라이선스 개방될 계획이 있습니다. 코드를 정리하고 이름을 변경하고 내부의 주요 기능을 확정할 것입니다.

내 앱을 수행할 때 Mixpanel에 네트워크 호출을 하는 것을 보았는데 이건 무엇인가요?

RealmObject를 포함하고 있는 여러분의 어셈블리에 대해 Realm 어셈블리 위버를 호출할 때 Realm은 익명의 통계를 수집합니다. 완전히 익명이고 어떤 Realm 버전을 쓰고 어떤 OS를 쓰는지 등을 파악해서 어떻게 제품을 향상하고 어떤 지원을 중단할 수 있는지 우리가 파악할 수 있게 합니다. 실제 앱이 수행될 때 혹은 여러분의 디바이스에서 수행될 때는 어떤 호출도 없습니다. - 빌드 과정에 Fody에 의해 여러분의 어셈블리를 위브할때만 호출이 됩니다. 정확히 어떻게 무엇을 수집하는지는 소스 코드에서 확인하실 수 있습니다.

포터블 (PCL) 라이브러리와 함께 쓸 수 있나요?

실행파일에 iOS 또는 안드로이드 버전의 Realm을 사용할 필요가 있지만 우리는 PCL을 제공하고 있고 Realm API를 컴파일할 때 사용할 수 있습니다. NuGet 유인 상술입니다. NuGet을 통해 Realm을 PCL 프로젝트에 추가할 때 이는 실행 파일에 추가되고 동작됩니다.

트러블 슈팅

크래쉬 보고

우리는 애플리케이션에서 크래쉬 리포터를 사용하는 것을 장려합니다. 많은 Realm 연산은 (다른 디스크 IO와 같이) 잠재적으로 실시간에 실패할 수 있습니다. 애플리케이션으로 부터 수집된 크래쉬 보고는 문제가 여러분(이나 우리)에게 있는지 확인할 수 있고 에러 처리를 향상하고 크래쉬 버그를 잡게 합니다.

대부분의 상업 크래쉬 리포터들은 로그 수집 옵션이 있습니다. 우리는 강하게 이 기능을 활성화하는 것을 추천합니다. Realm은 예외가 발생하거나 복구불가능한 상황일 때 (사용자 데이터에 관련되지 않은) 메타데이터 정보를 로그로 남깁니다. 이 메시지들은 문제가 발생했을 때 디버그 할 때 도움이 됩니다.

Realm 이슈 보고하기

Realm에 이슈를 찾으면 우리가 이슈를 이해하거나 재연할 수 있도록 최대한 많은 정보를 담아 GitHub에 이슈를 발행하거나 help@realm.io로 메일을 주세요.

아래의 정보는 우리에게 매우 유용합니다.

  1. 목표.
  2. 기대 결과.
  3. 실제 결과.
  4. 재연할 수 있는 단계.
  5. 이슈를 특정짓는 코드 샘플 (빌드할 수 있는 전체 Xamarin 프로젝트가 우리에게 이상적입니다).
  6. Realm, OS X나 Windows, Xamarin/Visual Studio의 버전 정보들
  7. 대상이 iOS면 Xcode, 대상이 안드로이드인 경우 NDK 정보.
  8. 버그가 발생한 플랫폼, OS 버전, 아키텍쳐 (예: 64 비트 iOS 8.1).
  9. 크래쉬 로그, 스택 트레이스. 자세한 것은 크래쉬 보고를 살펴보세요.

Realm Core Binary 다운로드 실패

Realm을 빌드할 때 처리의 한 부분으로 코어 라이브러리를 스태틱 라이브러리로 다운받아 Realm 프로젝트에 통합하는 것이 포함됩니다. 이것은 wrappers의 한 부분으로 Makefile에 의해 빌드됩니다.

어떤 경우에 아래의 에러와 함께 코어 바이너리 다운이 실패합니다.

Downloading core failed. Please try again once you have an Internet connection.

아래의 이유에 따라 에러가 발생합니다.

  1. 미국의 금수조치 목록에 포함된 지역의 IP 주소를 사용하는 경우. 미국 법률에 따라 Realm은 이 지역에서 사용할 수 없습니다. 자세한 정보를 알고 싶다면 라이선스를 살펴보세요.
  2. 중국 본토에 있다면 국가 수준의 방화벽 때문에 CloudFlare나 Amazon AWS S3 서비스를 상황에 따라 사용할 수 없을 수 있습니다. 자세한 내용은 우리 다른 제품의 해당 이슈를 참조하세요.
  3. Amazon AWS S3의 서비스에 이슈가 있을 수 있습니다. AWS Service 상태 대시보드를 참고하고 다시 시도하세요.

위빙에 실패할 때

클래스가 위빙이 되지 못했을 때 빌드로그에 워닝을 받을 수 있습니다. 이것은 Fody 위빙 패키지가 제대로 설치되지 않은 것을 의미합니다.

첫째로 RealmWeaver 항목에 FodyWeavers.xml 파일이 포함되어있는지 확인해보세요.

설치는 RealmWeaver.Fody.dll를 여러분의 솔루션 디렉토리의 Tools에 직접적으로 복사합니다. (이것은 보통 프로젝트 디렉토리의 이웃입니다.) 이것은 프로젝트의 어디에도 연결되어 있지 않지만 Fody가 DLL을 찾기 위해 그 자리에 있어야 합니다.

Fody의 설치가 실패했을 가능성도 있습니다. Visual Studio 2015와 3.2 버전 이전의 NuGet 패키지 매니저를 쓸 때 경험할 수 있습니다. 이 창이 뜨면 .csproj 파일을 텍스트 에디터에서 열어서 Fody.targets를 포함한 라인이 다음과 같이 있는지 확인해 보세요.

<Import Project="..\packages\Fody.1.29.3\build\portable-net+sl+win+wpa+wp\Fody.targets"
    Condition="Exists('..\packages\Fody.1.29.3\build\portable-net+sl+win+wpa+wp\Fody.targets')" />

단순히 NuGet 패키지 매니저를 최신 버전으로 올리는 것 만으로 이 문제가 해결됩니다.