This is not the current version. View the latest documentation
Realm Xamarin(.NET)을 사용하면 안전하고 영속적이며 빠른 여러분의 앱 모델 레이어를 효과적으로 작성할 수 있습니다. 다음을 보세요.
// 모델을 일반적인 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 IList<Dog> Dogs { get; }
}
var realm = Realm.GetInstance();
// 쿼리에 LINQ를 사용합니다
var puppies = realm.All<Dog>().Where(d => d.Age < 2);
puppies.Count(); // => 아직 개가 추가되지 않았기 때문에 0
// 스레드 안전한 트랜잭션에서 객체를 갱신하고 영속화하기
transaction
realm.Write(() =>
{
realm.Add(new Dog { Name = "Rex", 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에서 소스 코드를 볼 수 있습니다.
나이틀리 빌드도 MyGet에서 받을 수 있습니다. 자세한 것은 아래를 보세요.
요구사항
다음과 같은 플랫폼을 지원합니다.
- iOS 7 이상 Xamarin.iOS - 네이티브 UI나 Xamarin Forms 사용
- API 레벨 10 이상의 Xamarin.Android - 네이티브 UI나 Xamarin Forms 사용
- Windows 8.1 이상을 위한 .NET Framework 4.6.1 이상
- Universal Windows Platform 애플리케이션 - .NET Standard 1.4 이상 사용
- 다음에서의 .NET Core 1.1
- Ubuntu 16.04 이상
- Debian 8 이상
- RHEL 7.1 이상
- macOS 10.11 이상
- Windows 10 이상
또한, 다음 환경도 지원합니다.
- Visual Studio 2015 업데이트 2 이상
- Visual Studio for Mac 7.0 이상
Linux에서 .NET Core를 사용하려면 Realm 플랫폼 프로페셔널이나 엔터프라이즈 에디션을 위한 토큰이 필요합니다.
.NET Core를 위해 빌드하는 경우 dotnet
커맨드 라인 도구를 지원하지 않습니다. 프로젝트를 빌드하려면 Visual Studio나 msbuild
커맨드 라인 도구를 사용하세요.
PCL 사용자를 위한 중요한 안내 NuGet 유인 상술을 쓸 수 있습니다. Realm에서 사용하는 플랫폼 특화 프로젝트의 모든 PCL마다 Realm Nuget 패키지를 설치해야합니다. 예: 여러분의 앱이 iOS와 안드로이드를 지원하고 공유된 프로젝트를 사용한다면 각 플랫폼 특화 프로젝트마다 NuGet을 설치해야 합니다.
안드로이드 ABI 지원
몇몇 인스트럭션 집합의 제한 때문에 armeabi
ABI 설정을 지원하지 않습니다.
새 Xamarin 프로젝트를 만드는 기본 템플릿이 현재 디버그 빌드에서 모든 ABI 설정이 체크되어 있지만 릴리즈 빌드에서는 armeabi
만 체크되어 있습니다. 반드시 여러분의 릴리즈 빌드를 하기 전에 설정을 수정하셔야 합니다.
만약 다른 ABI 체크가 없다면 다른 장비에서 수행할 때 System.TypeInitializationException
가 발생할 수 있습니다. 예를 들어 갤럭시 탭 S2와 같은 64비트 장비에서 armeabi
와 armeabi-v7a
가 체크되어 있고 arm64-v8a
가 체크되지 않다면 에러를 발생시킨다.
다른 ABI들의 링크를 막을 좋은 이유가 없다면 armeabi
을 제외한 모든 ABI들을 체크하는 것이 최선일 겁니다. 그래서 이렇게 설정할 수 있습니다.
armeabi-v7a
arm64-v8a
x86
x86_64
Xamarin 스튜디오에서 이 설정들은 마우스 오른쪽 클릭으로 Options - Build - Android Build - Advanced Tab입니다.
비주얼 스튜디오에서 이 설정들은 마우스 오른쪽 클륵으로 Properties - Android Options - Advanced Tab입니다.
설치
- Solution 페인의 여러분의 프로젝트에 “Packeses” 노드에서 기어 버튼을 클릭하고 “Add Packages…“를 클릭합니다.
- 검색 창에 “Realm”을 입력합니다.
- Realm을 선택하여 추가합니다.
- 의존성에서 Fody가 추가된 것을 볼 수 있습니다.
Realm 패키지는 Realm을 사용하기 위해 필요한 모든 것을 가지고 있습니다. 이는 Fody 위버에 의존합니다. 이는 여러분의 RealmObject 서브클래스를 영속적인 객체로 만드는 책임을 집니다.
이 시점에서 패키지를 설치해야 합니다. 프로젝트가 이미 Fody를 사용하고 있다면 기존의 FodyWeavers.xml
가 갱신된 것을 볼 수 있습니다. 중요한 점은 FodyWeavers.xml
파일이 RealmWeaver를 포함한 당신이 필요한 모든 위버를 가지고 있다는 것입니다.
이미 추가한 다른 위버가 없었고 Realm을 추가했다면 FodyWeavers.xml 파일의 모양은 다음 예제와 같습니다.
<?xml version="1.0" encoding="utf-8" ?>
<Weavers>
<RealmWeaver />
</Weavers>
- 프로젝트에서 Tools - NuGet Package Manager - Manage Packages for Soultion을 선택합니다.
- 가능한 패키지에서 Realm 패키지를 선택합니다.
- 오른쪽에 여러분의 프로젝트가 선택되고 Install 버튼이 활성화된 것을 확인합시다.
- Install을 누릅니다.
- 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>
UWP와 기타 project.json
기반 프로젝트에 대한 참고 사항: NuGet 3.1의 변경 사항으로 패키지가 더 이상 컨텐트 파일을 만들 수 없으므로 FodyWeavers.xml
이 자동으로 생성되지 않습니다. 수동으로 이 파일을 생성하고 위 내용을 붙여 넣으세요. 자세한 내용은 이 포스트를 참조하세요.
Realm Studio
Realm Studio is our premiere developer tool, built so you can easily manage the Realm Database and Realm Platform. With Realm Studio, you can open and edit local and synced Realms, and administer any Realm Object Server instance. It supports Mac, Windows and Linux.
Realm Xamarin이 지원하는 파일 포맷은 현재 브라우저보다 낮습니다. 이런 이유로 새 버전의 브라우저로 realm 파일을 열게 되면 Realm Xamarin에서 동작하지 않게 됩니다. 위의 링크는 호환성을 위한 버전입니다.
메뉴 항목 Tools > Generate demo database.을 이용하면 샘플 데이터로 테스트 데이터베이스를 생성할 수 있습니다.
앱의 Realm 파일에 대한 도움을 찾기를 원한다면 StackOverflow 답변에서 자세한 설명을 확인하세요.
Realm 브라우저는 맥 앱 스토어에서 사용할 수 있습니다.
API 문서
전체 클래스와 메서드는 전체 API 문서에서 살펴보세요.
예제
저장소의 예제 폴더에서 여러 예제를 찾을 수 있습니다.
도움을 구하려면
- 여러분의 코드에서 도움을 구하려면? 스택오버플로우에 물어주세요. 우리는 적극적으로 스택오버플로우의 질문을 확인하고 답변합니다!
- 제보할 버그가 있나요? 저장소에 이슈를 만들어주세요. 가능하면 Realm의 버전과 출력할 수 있는 로그를 포함하고 Realm 파일과 이슈가 발생한 프로젝트를 첨부해주세요.
- 기능 요청이 있나요? 저장소에 이슈를 만들어주세요. 어떤 기능이 있어야 하는지 왜 필요한지를 알려주세요.
모델
Realm 데이터 모델은 프로퍼티를 포함한 전통적인 C# 클래스를 통해 정의합니다. 단순히 RealmObject를 상속받아 Realm 데이터 모델 객체를 생성합니다.
Realm 모델 객체는 대부분의 경우 다른 C# 객체들처럼 동작합니다. 자신만의 메서드 이벤트를 추가할 수 있고 다른 객체들처럼 사용할 수 있습니다. 주요 제약은 생성된 스레드에서만 객체를 사용해야 하고 영속적인 속성은 게터와 세터를 생성해야 한다는 점입니다.
또한 클래스가 퍼블릭이며 파라미터가 없는 생성자를 가져야한다는 점을 주의하세요. 만약 어떤 생성자도 추가하지 않았다면 컴파일러는 생성자를 추가합니다. 만약 최소 하나의 생성자를 추가했다면 공개된 파라미터 없는 생성자가 있는지 확인하세요.
관계와 내부 데이터 구조는 단순히 대상 타입이나 객체들의 리스트를 위한 타입 IList
을 포함합니다.
// 개 모델
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 IList<Dog> Dogs { get; }
}
지원 타입
Realm은 unsigned를 제외한 타입(bool
, char
, byte
, short
, int
, long
, float
, double
)과 string
, DateTimeOffset
을 지원합니다. 널도 마찬가지로 지원합니다. 더 이상의 내용은 선택적 속성을 참고하세요.
날짜 타입
우리는 DateTime
대신 표준적인 DateTimeOffset
형을 날짜를 표현하는 주 자료형으로 사용하고 있습니다.
이는 DateTime
의 모호함 떄문에 마이크로소프트가 한 권고를 따른 것입니다.
이는 100 나노세컨드 단위의 틱의 정밀도로 저장됩니다.
타임존을 붙여 DateTimeOffset
을 지정할 수 있지만 Realm은 이를 UTC 값으로 저장합니다. 이는 다른 언어에서 읽을 때 같은 의미를 가지는 인스턴트를 가질 수 있는 모호하지 않은 표현입니다. UtcTicks
대신에 Ticks
을 사용하여 값을 비교할 때 혼란의 원인이 됩니다.
카운터
Realm은 특수한 정수 타입으로 [RealmInteger
일반적으로 카운터는 (myObject.Counter += 1
)처럼 값을 읽어서 이를 증가하고 설정하는 방식으로 구현해왔습니다. 하지만 이 방식은 두 개의 클라이언트가 오프라인 상황에서 10
이라는 값을 읽고 증가해서 11
을 저장하는 것과 같은 비동기적인 상황에서는 잘 동작하지 않습니다. 이후에 이들이 온라인 상태에서 변경 사항을 병합하려면 예상 카운터 값인 12
이 아닌 11
을 갖게 됩니다.
RealmInteger
는 기존 정수 타입에 기반하기 때문에 속성 타입을 T
에서 RealmInteger<T>
로 변경하려면 마이그레이션이 필요하지 않습니다.
public class MyObject : RealmObject
{
[PrimaryKey]
public int Id { get; set; }
public RealmInteger<int> Counter { get; set; }
}
간단히 Increment를 호출해서 카운터를 증가할 수 있습니다.
var obj = realm.Find<MyObject>(1);
obj.Counter; // 0
obj.Counter.Increment(); // 1
obj.Counter.Decrement(); // 0
obj.Counter.Increment(2); // 2
obj.Counter.Increment(-5); // -3
새 값을 지정하면 카운터를 리셋할 수 있습니다. 이 경우 정규 병합 규칙이 적용되며 앱에서 관련 처리를 해야한다는 것을 기억하세요.
var obj = realm.Find<MyObject>(1);
obj.Counter = 0; // Resetting to 0
RealmInteger<T>
는 T
와 암시적으로 변환이 가능하므로 캐스팅하거나 수동으로 생성하지 않고도 사용할 수 있습니다.
var obj = new MyObject
{
Counter = 3
};
realm.Write(() => realm.Add(obj));
DoSomething(obj.Counter);
public void DoSomething(int value)
{
// ...
}
관계
RealmObject는 RealmObject와 IList 속성을 이용해서 서로 연결할 수 있습니다.
List는 .NET의 표준 IList
제너릭 인터페이스를 구현합니다. 클래스에 IList
프로퍼티를 정의하면 get이 사용될 때 RealmList
가 생성됩니다. get;
자동 메서드만 지정할 수 있고 이런 리스트에 대해 set할수는 없습니다.
대상이 하나인 관계
N대 1이나 1대 1 관계를 위해 단순히 속성을 RealmObject의 서브클래스에 선언하세요.
public class Dog : RealmObject
{
// ... 다른 속성 선언
public Person Owner { get; set; };
}
public class Person : RealmObject
{
public string Name { get; set; }
}
이 속성을 다음처럼 사용할 수 있습니다.
realm.Write(() =>
{
var jim = realm.Add(new Person { Name = "Jim" });
var rex = realm.Add(new Dog { Owner = jim });
});
관계를 끊기 위해서는 단순히 null
을 대입합니다.
rex.Owner = null;
RealmObject 속성을 이용할 때 중첩된 속성을 일반적인 속성 문법을 통해 접근할 수 있습니다. 예를 들어 rex.Owner.Address.Country
는 객체 그래프를 순회하고 필요시 Realm으로 부터 각 객체를 자동으로 가져오게 됩니다.
대상이 여럿인 관계
대상이 여럿인 관계는 IList
속성을 통해 만들 수 있습니다. 이런 속성을 이용할 때 컬렉션은 비어있을 수도 있고 관계된 RealmObject의 타입을 받을 수도 있습니다.
간단히 IList<Dog>
속성을 선언하는 것으로 “Dogs” 속성들을 여러 개와 연결될 수 있는 Person
모델에 추가할 수 있습니다. IList
를 초기화할 필요는 없으며, Realm.CreateObject
가 이런 작업을 대신 처리합니다. 주어진 Person
과 Dog
사이의 관계 정립을 위해 목록으로부터 Dog
객체들을 추가하거나 삭제하면 됩니다.
public class Dog : RealmObject
{
public string Name { get; set; }
}
public class Person : RealmObject
{
// ... 다른 속성 선언
public IList<Dog> Dogs { get; }
}
jim.Dogs.Add(rex);
jim.Dogs.Count(); // => 1 -- rex외에 없음
역관계
관계 연결은 한 방향이므로 대상이 여럿인 속성, Person.Dogs
가 Dog
인스턴스에 연결되고, 대상이 하나인 속성, Dog.Owner
가 Person
에 연결됩니다. 이 연결들은 서로 독립적입니다. Dog
속성을 Person
인스턴스에 추가해도 개의 Owner 속성에 자동으로 해당
Person`이 연결되지 않습니다. 수동으로 관계 쌍을 동기화하는 것은 오류가 발생하기 쉽고 정보가 중복될 수 있으므로, Realm은 이런 역관계를 나타내는 백링크 속성을 제공합니다.
백링크 속성을 사용하면 특정 속성에서 특정 객체에 연결된 모든 객체를 얻을 수 있습니다. 예를 들어 Dog
객체는 Owners
라는 속성을 가질 수 있으며, 이 속성은 모든 자신의 Dog
속성에 해당 Dog
객체를 가진 Person
객체를 모두 포함합니다. 이는 Owners
속성을 IQueryable<Person>
타입으로 만들고, Person
모델 객체에 Owners
의 관계를 나타내기 위한 [Backlink]를 적용해서 만들 수 있습니다.
public class Dog : RealmObject
{
[Backlink(nameof(Person.Dogs))]
public IQueryable<Person> Owners { get; }
}
백링크 속성은 대상이 여럿인 관계인 IList<RealmObject>
속성이나 대상이 하나인 관계인 RealmObject을 가리킬 수 있습니다.
public class Ship : RealmObject
{
public Captain Captain { get; set; }
}
public class Captain : RealmObject
{
[Backlink(nameof(Ship.Captain))]
public IQueryable<Ship> Ships { get; }
}
선택적 속성
string
, byte[]
와 같은 참조 타입과 RealmObject의 값은 null이 될 수 있습니다. 각 속성 형의 선택적인 범위 전체를 가지기 위해 선택적인 DateTimeOffset?
형도 지원합니다.
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 IList<Dog> Dogs { get; }
}
매핑 속성
Realm 파일은 본질적으로 플랫폼에서 독립적이며 사용가능한 모든 언어 바인딩에서 열거나 동기화할 수 있습니다. 이를 위해서는 여러 언어 사이에서 스키마가 속성 이름의 대소문자 등을 포함해서 같도록 해야 합니다. 비표준 C# 클래스를 작성하는 것을 방지하려면 다음처럼 [MapTo]를 사용하세요.
// C# model
public class Person : RealmObject
{
[MapTo("name")]
public string Name { get; set; }
[MapTo("dogs")]
public IList<Dog> Dogs { get; }
}
// Equivalent js model
class Person {}
Person.schema = {
name: 'Person',
properties: {
name: {type: 'string'},
dogs: {type: 'list', objectType: 'Dog'}
}
};
자동 갱신되는 객체
RealmObject 인스턴스가 관리되는 상태로 바뀌자마자 이는 라이브가 되고 내부의 데이터로 자동 갱신됩니다. 이 말은 객체를 리프레쉬할 필요가 없다는 뜻입니다. 객체의 속성을 수정하면 즉각 다른 인스턴스가 참조하는 같은 객체에 반영됩니다.
public class Dog : RealmObject
{
public string Name { get; set; }
public int Age { get; set; }
}
var realm = Realm.GetInstance();
// 설정 끝
var myDog = new Dog { Name = "Fido", Age = 1 };
realm.Write(() =>
{
realm.Add(myDog);
});
var myPuppy = realm.All<Dog>().First(d => d.Age == 1);
realm.Write(() =>
{
myPuppy.age = 2;
}
myDog.Age; // => 2
RealmObject의 이런 특성은 Realm을 빠르고 효과적으로 할 뿐 아니라 여러분의 코드를 단순하고 더 반응적이게 합니다. 예를 들어 UI 코드가 특정 Realm 객체에 기반한다면 UI를 다시 그리기 전에 객체를 리프레쉬하거나 다시 가져올 필요가 없습니다.
Realm 알림을 구독하여 객체의 Realm 데이터가 갱신되는 것을 알 수 있고 이를 이용하여 앱의 UI를 갱신할 수 있습니다.
기본 키 특성
[PrimaryKey] 특성은 RealmObject 클래스의 객체 id를 설정하는 하나의 속성에 정의할 수 있습니다. PrimaryKey를 정의하면 객체들의 참조하고 갱신하는 것이 효과적으로 되고 각 값이 유일한 값을 가져야 합니다.
오직 char
, 정수형, 문자열만이 기본 키가 될 수 있습니다. char
나 작은 정수형 형을 사용할 때 사용되는 특별한 저장소는 없고 성능 상의 이점도 없으며, 단지 그 형으로 된 속성을 이미 가지고 있을 때만 이점이 있습니다.
여러 속성에 [PrimaryKey]를 붙이면 컴파일은 되지만 실행 시에 검증되며 Realm을 열자마자 _Schema validation failed_를 보고하는 예외를 던집니다.
한번 PrimaryKey의 객체가 Realm에 추가되면 PrimaryKey는 변경할 수 없습니다.
같은 키를 가진 다른 객체를 만들면 RealmDuplicatePrimaryKeyValueException을 발생시킵니다.
Realm.ObjectForPrimaryKey
을 사용하면 LINQ를 사용하는 것 보다 더 날씬한 쿼리 생성으로 인덱스를 사용해서 빠르게 객체를 쿼리할 수 있습니다. 문자열, 문자, 정수 키 등으로 오버로드됩니다.
public class Person : RealmObject
{
[PrimaryKey]
public string SSN { get; set; }
public string Name { get; set; }
public IList<Dog> Dogs { get; }
}
var objByPK = realm.Find<Person>("457-55-5462");
모델 상속
Realm Xamarin은 어떤 식으로든 추가로 상속되는 것을 허용하지 않습니다. RealmObject 클래스로부터 간접적으로 상속받은 객체가 발견되면 위버가 실패합니다.
컬렉션
Realm은 객체의 그룹을 표현하기 위해 표준 타입을 사용합니다.
IQueryable<T>
는 쿼리로부터 가져온 객체들을 표현하는 클래스입니다.IList<T>
, 모델에서 대상이 여럿인 관계를 표현하는 클래스입니다.
런타임시 둘 모두 IReadOnlyList<T>
와 INotifyCollectionChanged
를 따르는 IRealmCollection<T> 인터페이스를 따르며 지연 열거를 지원합니다. 컬렉션의 크기만 알려지고 객체는 컬렉션을 순회할 때만 메모리에 적재됩니다.
쓰기
객체에 대한 모든 변경(추가, 수정, 삭제)은 쓰기 트랜잭션 내에서 이루어져야 합니다.
스레드 간에 객체를 공유하거나 앱이 실행된 후 재사용하기 위해 그들을 쓰기 트랜잭션내에 연산을 처리하여 Realm으로 영속화를 해야합니다.
쓰기 트랜잭션은 무시못할 오버헤드를 초래하기 때문에 코드에서 쓰기 트랜잭션의 수를 줄이도록 설계해야 합니다. 예를 들어 루프에서 여러 개의 아이템을 삽입하는 경우 매번 트랜잭션을 만드는 것보다 아래처럼 하나의 트랜잭션에서 하는 것을 권장합니다.
realm.Write(() =>
{
var people = Realm.All<Person>();
foreach (var person in people)
{
person.Age += 1;
}
});
쓰기 트랜잭션은 잠재적으로 다른 디스크 IO 연산처럼 디스크 용량 부족등의 잠재적인 실패 가능성이 있기 때문에 쓰기로 부터 예외를 대비하고 실패를 다루거나 복원하여야 합니다. 다른 복구가능한 에러는 없습니다. 간결함을 위해 우리의 코드 샘플은 이런 에러들을 다루지 않습니다만 제품 어플리케이션에는 포함되어야 합니다.
쓰기 트랜잭션은 Realm.BeginWrite()과 Realm.Write(), 두 가지 방법으로 쉽게 작성할 수 있습니다. 첫번째 Realm.BeginWrite()는 Dispose
패턴을 구현한 트랜잭션을 반환합니다. 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;
});
예외가 발생되지 않으면 기본으로 암시적인 트랜잭션이 커밋됩니다.
스레딩과 관련된 Realm.WriteAsync()에 대한 논의가 진행 중입니다.
객체 만들기
모델을 정의할 때 RealmObject의 서브클래스를 실체화하고 새로운 인스턴스를 Realm에 추가할 수 있습니다. 간단한 모델을 예로 들겠습니다.
public class Dog : RealmObject
{
public string Name { get; set; }
public int Age { get; set; }
}
이 클래스의 인스턴스를 Realm.Add() 메서드를 이용하여 영속적인 라이브 객체로 변환할 수 있습니다.
realm.Write(() =>
{
var myDog = new Dog();
myDog.Name = "Rex";
myDog.Age = 10;
realm.Add(myDog);
});
객체를 만든 이후 실시한 모든 변경은 영속적이게 됩니다. (이 변경들은 쓰기 트랜잭션에서 이루어져야 합니다.) 쓰기 트랜잭션이 완료되면 같은 Realm을 사용해서 다른 스레드에서 변경을 하는 것이 가능합니다.
쓰기가 다른 쓰기를 블록하고 만약 진행 중인 여러 쓰기가 있다면 다른 스레드를 블록할 것이라는 점을 유의하세요. 이 경우 별도의 스레드에서 쓰기를 해서 부담을 줄이는 것을 추천합니다.
Realm이 MVCC 아키텍쳐를 사용하기 때문에 읽기는 트랜잭션이 열려 있어도 방해받지 않습니다. 한번에 여러 스레드에서 쓰기를 같이 하지 않는 한 여러분은 잘게 나눈 많은 쓰기 트랜잭션보다 큰 쓰기 트랜잭션을 선호할 것입니다.
객체 갱신하기
쓰기 트랜잭션에서 속성들을 설정하는 방법으로 객체를 갱신할 수 있습니다.
// 트랜잭션에서 객체를 갱신하기
using (var trans = realm.BeginWrite())
{
author.Name = "Thomas Pynchon";
trans.Commit();
}
[기본 키]를 지정한 객체의 경우 realm.Add에 update: true
를 전달해서 전달된 객체를 추가하거나 기존 객체를 업데이트할 수 있습니다.
public class Person : RealmObject
{
[PrimaryKey]
public int Id { get; set; }
// ... other property declarations
}
realm.Write(() =>
{
realm.Add(new Person
{
Id = 1,
Name = "Kristian"
});
});
var kristianWithC = new Person
{
Id = 1,
Name = "Christian"
};
realm.Write(() => realm.Add(kristianWithC, update: true));
update: true
를 지정하고 기본 키가 없는 클래스를 넘기면, 메서드는 업데이트할 객체를 찾을 수 없으므로 update: false
를 받은 것처럼 동작합니다. 객체가 다른 RealmObject들과 관계를 맺고 있다면 다른 객체들에 기본 키가 지정된 경우 추가 혹은 업데이트되고, 다른 객체들에 기본 키가 없는 경우에는 단순히 추가됩니다.
var kristian = new Person { Id = 1, Name = "Kristian" };
var rex = new Dog { Id = 1, Name = "Rex" };
kristian.Dogs.Add(rex);
realm.Write(() => realm.Add(kristian));
var christian = new Person { Id = 1, Name = "Christian" };
christian.Dogs.Add(new Dog { Id = 1, Name = "Bethoven" });
realm.Write(() => realm.Add(christian, update: true));
var newName = kristian.Name; // Christian
var newDogName = kristian.Dogs.First().Name; // Bethoven
객체 삭제하기
쓰기 트랜잭션에서 [`]Realm.Remove](https://realm.io/docs/dotnet/latest/api/reference/Realms.Realm.html#Realms_Realm_Remove_Realms_RealmObject_) 메서드에 삭제할 객체를 전달합니다.
var cheeseBook = realm.All<Book>().First(b => b.Name == "Cheese");
// 트랜잭션에서 객체를 삭제합니다
using (var trans = realm.BeginWrite())
{
realm.Remove(cheeseBook);
trans.Commit();
}
Realm에 저장된 모든 객체들을 저장할 수 있습니다. Realm 파일은 삭제된 용량을 유지해서 앞으로 쓰일 객체에서 재사용을 효과적으로 만들 것입니다.
쿼리
쿼리는 표준 LINQ 구문을 구현했습니다.
realm.All<T> 메서드를 사용해서 주어진 타입의 모든 객체를 담은 기본적인 타입 클래스를 얻고 Where
를 적용하거나 다른 LINQ 연산을 컬렉션 필터링을 위해 사용할 수 있습니다.
LINQ가 구현된 구체적 범위를 보고싶으면 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();
첫번째 문장은 “John”과 “Peter”가 이름인 사용자를 찾는 IQueryable
를 구현한 클래스의 johnsAndPeters
인스턴스를 돌려줍니다. 이는 표준적인 LINQ 구현 방법으로, 쿼리를 표현하는 객체를 얻을 수 있습니다. 쿼리는 반복이나 결과의 수를 세는 추가적인 호출이 있을 때까지 어떤 일도 하지 않습니다.
이 예제에서 ToList
호출은 Realm 코어에 직접 연결된 쿼리를 수행시킵니다
객체는 복사되지 않습니다. 조건에 맞는 객체들의 레퍼런스 리스트를 받아 쿼리에 맞는 원본 객체를 직접 다룹니다.
결과를 모두 리스트로 받아오는 대신 쿼리 결과를 표준 C#의 foreach
문으로 타고 갈 수 있습니다.
foreach (var person in johnsAndPeters) // 쿼리 순회
{
Debug.WriteLine(person.Name);
}
논리 연산자
LINQ 표현의 표준 C# 논리 연산자로 쿼리를 작성할 수 있습니다.
if
문에 기대하는 전제 조건들을 괄호에 묶인 중첩된 표현식으로도 사용할 수 있습니다.
var complexPeople = realm.All<Person>().Where(p =>
p.LastName == "Doe" &&
(p.FirstName == "Fred" || p.Score > 35));
타입으로 객체를 가져오기
주어진 타입에 대해 모든 객체를 Realm에서 가져오려면 realm.All<T>()를 사용합니다. 이는 쿼리된 모델 클래스의 전체 인스턴스를 가지는 IQueryable
컬렉션을 반환하며, 위의 예제에서는 Person
입니다. 집합에 대해 추가적인 제약을 적용하기 위해 LINQ 쿼리를 사용할 수 있습니다. 컬렉션에 대해 순환을 하기 전까지는 어떠한 오버헤드도 없습니다.
var ap = realm.All<Person>(); // 타입에 관한 전체 아이템
var scorers = ap.Where(p => p.Score > 35); // 첫번째 검색 절로 제한 두기
var scorerDoe = scorers.Where(p => p.LastName == "Doe"); // 효과적인 AND 절
정렬
표준 LINQ 절 OrderBy
, OrderByDescending
, ThenBy
, ThenByDescending
은 여러 단계의 정렬에 사용될 수 있습니다. 이 정렬은 Realm.All에서 반환되거나 후속 LINQ절에서 제공되는 Queryable
의 객체 순서에 영향을 줍니다.
결과에 대한 전체 객체를 읽지는 않으며 효과적인 검색 정렬을 지원하는 내부의 쿼리 엔진을 통해 정렬이 이루어집니다.
주의: 만약 ToList
절을 객체 리스트를 추출하기 위해 사용하고 객체를 위한 LINQ를 이용해서 메모리 상에서 정렬하기 위해 OrderBy
를 사용할 수 있습니다. ToList
를 표현의 마지막에 사용했는지 확인해주세요.
var highestScore = realm.All<Person>().OrderByDescending(p => p.Score).First();
var sortedPeople = realm.All<Person>()
.OrderBy(p => p.LastName)
.ThenBy(p => p.FirstName);
연결 쿼리
결과는 복제되지 않고 요청 시점에 계산되기 때문에 데이터를 단계적으로 필터링하기 위해 효과적으로 연결 쿼리를 할 수 있습니다.
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 관련이나 다른 구현 이유로 쿼리로 부터 특정한 객체의 부분집합을 원한다면 IQueryable
객체를 가져오고 필요한 객체만 읽으면 됩니다.
만약 LINQ Take를 사용하길 원하더라도 아직 쓸 수 없습니다. 이것이 추가되면 한 명령에 요청한 객체 전부를 열거하고 인스턴스화할 수 있을 것입니다.
Realms
Realm은 실제 데이터베이스를 표현합니다. 여러 종류의 객체를 가지고 있고 디스크 상 하나의 파일에 대응합니다. Realm 은 Realm 모바일 데이터 컨테이너의 인스턴스입니다. Realm은 로컬 혹은 동기화 방식으로 사용할 수 있습니다. 동기화 Realm은 Realm 오브젝트 서버를 사용해서 투명하게 내부 컨텐츠를 다른 기기와 동기화할 수 있습니다. 또한 애플리케이션이 동기화 Realm을 로컬 파일처럼 계속 사용하는 동안에도, 해당 Realm에 쓰기 권한을 가진 모든 기기에서 해당 Realm의 데이터를 업데이트할 수 있습니다. 동기화 Realm을 열려면 Realm을 열기 위한 권한을 가진 Realm 오브젝트 서버에 인증된 사용자가 필요합니다.
Realm에 대한 더 자세한 정보를 알고 싶다면 Realm 데이터 모델을 참고하세요.
Realm 열기
새 Realm
객체를 인스턴스화해서 Realm을 열 수 있습니다. 앞서의 예제에도 이미 등장한 패턴입니다.
// Realm 인스턴스 얻기
var realm = Realm.GetInstance();
인자가 없으면 GetInstance
는 기본 Realm을 인스턴스화합니다. 또한, RealmConfiguration
객체를 지정할 수도 있습니다.
Realm 설정하기
Realm.getInstance()
을 호출하면 Realm을 손쉽게 시작할 수 있습니다. Realm이 생성되는 여러 측면을 제어할 수 있는 RealmConfiguration 객체를 생성해 보다 세밀한 제어를 할 수 있습니다.
RealmConfiguration은 다양한 데이터 베이스 경로를 허용합니다. (한번 설정이 생성되면 경로를 바꿀 수 없는 점을 유의하세요.)
아래와 같이 할 수 있습니다.
- 새로운 절대 경로를 전달하여 전체 경로를 변경하기.
- 표준 경로의 서브 디렉토리에 Realm 파일들을 두고 상대 경로를 전달하기.
- 새로운 파일 이름을 전달하여 Realm 파일 이름을 변경하기.
var config = new RealmConfiguration("OtherPath.realm");
var otherRealm = Realm.GetInstance(config);
설정에서 스키마 버전을 지정할 수 있습니다. 여기에 대한 자세한 것은 마이그레이션 섹션을 참조하세요. 개발 중에 ShouldDeleteIfMigrationNeeded 속성을 true
로 설정할 수 있습니다. 이 설정은 열려있는 파일이 여러분의 스키마와 맞지 않다면 Realm.GetInstance()가 기존의 데이터베이스를 삭제하게 됩니다. 이렇게 설정하면 릴리즈 전에 모델을 다루는게 편해집니다. 하지만 이 설정으로 릴리즈하지 마세요. 사고를 막기 위해서는 #if DEBUG
섹션안에 설정할 수 있습니다.
같은 구성의 Realm을 열기 위해 어떤 설정의 인스턴스를 여기 저기에 쓸 수 있습니다. 다른 스레드에서 같은 Realm을 열기 위해 사용하는 일반적인 사용례입니다.
물론 어떤 객체를 전달하지 않고 기본 값을 바꾸기 위해 기본 설정을 재정의할 수 있습니다.
설정 마다 스레드마다 싱글턴으로 Realm 인스턴스가 유지된다는 것이 중요합니다. 같은 스레드에서 같은 설정이라면 Realm.GetInstance()가 매번 같은 인스턴스를 반환합니다.
기본 Realm
realm 변수를 항상 optionalPath
를 지정하지 않고 Realm.GetInstance(string optionalPath)로 초기화한 것을 보았을 것입니다. 정적 메서드는 스레드에 맞는 Realm 인스턴스를 반환합니다. 이 인스턴스는 Environment.SpecialFolder.Personal
에 저장되는 default.realm
파일에 연결됩니다.
아무 인자를 전달하지 않고 Realm.GetInstance()를 호출하면 “기본 Realm” 인스턴스를 반환합니다. 기본 Realm은 Environment.SpecialFolder.Personal
에 있는 default.realm
이라는 파일에 매핑됩니다.
비동기적으로 Realm 열기
만약 Realm을 열면서 마이그레이션을 적용하거나 압축을 하거나 동기화 Realm에서 원격 컨텐츠를 다운로드하는 것처럼 시간이 많이 드는 작업을 한다면 Task<Realm>
반환을 완료하기 전에 백그라운드 스레드에서 Realm을 사용 가능한 상태로 만드는 데 필요한 모든 작업을 수행하도록 Realm.GetInstanceAsync API를 사용하세요. 또한, 읽기 권한만 가진 사용자의 Realm은 Realm.GetInstanceAsync
를 사용해야 합니다.
다음 예제를 확인하세요.
var config = new RealmConfiguration
{
SchemaVersion = 1,
MigrationBlock = (migration, oldSchemaVersion) =>
{
// 길어질 가능성이 있는 데이터 마이그레이션
}
};
try
{
var realm = await Realm.GetInstanceAsync(config);
// 마이그레이션이 백그라운드 스레드에 적용되면서 Realm이 성공적으로 열렸습니다.
}
catch (Exception ex)
{
// Realm을 여는 동안 발생한 예외를 처리합니다.
}
또한, 동기화 Realm은 모든 다운로드 작업이 완료되고 로컬에서 원격 컨텐츠를 사용할 수 있을 때까지 기다립니다.
var serverURL = new Uri("realm://my.realm-server.com:9080/~/default");
var config = new SyncConfiguration(user, serverURL));
try
{
var realm = Realm.GetInstanceAsync(config);
// 모든 원격 데이터를 사용할 수 있는 상태로 Realm이 성공적으로 열렸습니다.
}
catch (Exception ex)
{
// Realm을 열거나 컨텐츠를 다운로드하는 동안 발생한 예외를 처리합니다.
}
Realm 인스턴스 닫기
Realm은 네이티브 메모리 해제와 파일 설명자(descriptors)를 다루기 위해 IDisposable
을 구현합니다. 이렇게 하면 인스턴스는 변수가 스코프에서 사라질 때 자동으로 닫힐 수 있습니다.
Realm 파일 찾기
앱의 Realm 파일을 찾는 것의 도움이 필요하면 자세한 설명을 StackOverflow의 답변에서 확인하세요.
외부 Realm 파일
표준 .realm
파일을 따라 Realm은 내부적인 자체 연산을 위해 추가적인 파일을 생성하고 관리합니다.
.realm.lock
- 리소스 락을 의한 락 파일..realm.note
- 알림을 위한 명명된 파이프(named pipe).
이 파일들은 .realm
데이터베이스 파일에 어떤 영향도 미치지 않습니다. 그들의 부모 데이터베이스 파일이 삭제되거나 대체되어도 어떤 에러를 유발하지 않습니다.
Realm 이슈를 보고할 때는 디버깅을 위해 유용한 정보를 포함한 외부 파일들을 .realm
파일과 함께 포함해주세요.
앱에 Realm 빌드하기
앱에 Realm 파일을 포함하길 원한다면 스택오버플로우의 대단한 답변을 보세요. 어떻게 Xamarin 프로젝트에 리소스로 포함하고 그것을 사용하기 위해 복사하는지 보여줍니다.
클래스 부분집합
특정 Realm에 저장될 클래스를 한정짓고 싶은 경우, RealmConfiguration에 ObjectClasses 속성을 설정해서 할 수 있습니다.
class LoneClass : RealmObject
{
public string Name { get; set;}
}
var config = new RealmConfiguration("RealmWithOneClass.realm");
config.ObjectClasses = new[] { typeof(LoneClass) };
// 또는 Realm에 두개의 클래스를 지정하기
config.ObjectClasses = new[] { typeof(Dog), typeof(Cat) };
Realm 압축하기
애플리케이션을 사용하는 동안 Realm에서 사용하는 메모리가 파편화되어 필요 이상으로 많은 공간을 차지할 수 있습니다. 내부 저장소를 재정렬하고 파일 크기를 줄이려는 시도를 하기 위해 RealmConfiguration.ShouldCompactOnLaunch 콜백을 Realm 파일을 여는 동안 호출할 수 있습니다.
var config = new RealmConfiguration
{
ShouldCompactOnLaunch = (totalBytes, usedBytes) =>
{
// totalBytes는 디스크에 있는 파일의 크기를 바이트 단위로 표시합니다 (데이터 + 여유 공간)
// usedBytes는 파일 데이터에 사용된 바이트 수를 표시합니다
// 파일 크기가 100MB를 넘고 50% 이하가 'used'인 경우 압축합니다
var oneHundredMB = 100 * 1024 * 1024;
return totalBytes > oneHundredMB && (double)usedbytes / totalBytes < 0.5;
}
};
try
{
var realm = Realm.GetInstance(config);
}
catch (Exception e)
{
// Realm을 압축하거나 열면서 발생하는 에러를 처리합니다
}
ShouldCompactOnLaunch
델리게이트로부터 true
가 반환되고 Realm 파일이 현재 사용 중이 아닌 경우에 Realm이 압축됩니다.
아니면 아래 코드처럼 Realm.Compact(config)를 호출해서 Realm 인스턴스를 가져 오지 않고 파일을 압축할 수 있습니다.
var config = new RealmConfiguration("my.realm");
Realm.Compact(config);
압축은 동기화에 필요한 트랜잭션 로그를 보존하지 않기 때문에 동기화 Realm의 압축은 현재 지원하지 않습니다.
압축 시 주의사항
- 파일을 사용하고 있는 열린 상태의 realm 인스턴스가 없어야 합니다.
- 파일 시스템에 Realm 파일 복사를 위해 해당 크기 이상의 최소한의 공간이 있어야 합니다.
- 작업이 실패하면 Realm 파일은 변경되지 않습니다.
- 메서드는 작업이 성공하는 경우
true
를, 동일 파일에 접근하는 Realm 인스턴스가 있는 경우false
를 반환합니다. - 현재로서는 Realm을 압축해서 얻을 수 있는 크기에 대한 예측을 제공하지 않습니다. 애플리케이션 시작 사이에 데이터베이스 크기를 모니터링하고 입계값을 초과하는 경우 압축하는 접근 방법을 권장합니다.
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의 영속적인 인스턴스와, Realm.All에서 반환된 IQueryable
, RealmObject의 IList
속성은 생성된 스레드에서만 사용할 수 있고 그렇지 않으면 예외를 발생합니다. 이는 Realm이 트랜잭션 버전을 고립시키는 한 가지 방식입니다. 그렇지 않으면 잠재적으로 확장된 관계 그래프를 가진 다른 트랜잭션 버전의 스레드들 사이에 객체를 전달할 때 무엇을 해야할지 결정할 수 없습니다.
Realm은 스레드에 제한적인 인스턴스를 안전하게 전달할 수 있는 메커니즘을 다음 세 단계로 제공합니다.
- 스레드 제한 객체로 ThreadSafeReference.Create 오버로드 중 하나를 호출해서 ThreadSafeReference를 얻습니다.
- 해당 ThreadSafeReference를 목적 스레드나 큐로 전달합니다.
- Realm.ResolveReference 오버로드 중 하나를 호출해서 대상 Realm에서 이 참조를 해제니다. 여기서 반환되는 객체를 평소처럼 사용할 수 있습니다.
다음 코드를 확인하세요.
var person = new Person { Name = "Jane" };
realm.Write(() => realm.Add(person));
var personReference = ThreadSafeReference.Create(person);
Task.Run(() =>
{
var otherRealm = Realm.GetInstance(realm.Config);
var otherPerson = otherRealm.ResolveReference(personReference);
if (otherPerson == null)
{
return; // person was deleted
}
realm.Write(() =>
{
otherPerson.Name = "Jane Doe";
});
});
ThreadSafeReference는 한 번만 해제해야 합니다. 해제에 실패하면 참조가 할당 해제될 때까지 소스 버전의 Realm이 고정됩니다. 따라서, ThreadSafeReference는 짧게 사용해야 합니다.
RealmObject
, IList<RealmObject>
, IQueryable<RealmObject
를 위해 ThreadSafeReference 래퍼가 제공됩니다. 본질적으로 이들을 위한 독립 실행형 버전은 스레드 제한이 아니므로 ThreadSafeReference는 영구 인스턴스에만 사용해야 합니다.
스레드 사이에 Realm을 사용하기
같은 Realm 파일을 다른 스레드에서 접근하려면 앱의 다른 스래드마다 다른 인스턴스를 얻기 위해 새로운 Realm을 초기화해야합니다. 같은 설정을 지정하는 한 모든 Realm 인스턴스는 디스크의 같은 파일에 연결됩니다.
여러 스레드에서 Realm 인스턴스를 공유하는 것은 지원되지 않습니다. 같은 realm 파일을 접근하는 Realm 인스턴스는 같은 RealmConfiguration을 사용해야 합니다.
Realm은 많은 양의 데이터를 하나의 트랜잭션에 여러 쓰기를 같이 일괄적으로 기록하는 것이 효과적입니다. Realm 객체는 스레드 안전하지 않고 스레드 사이에 공유될 수 없습니다. 읽기나 쓰기가 필요할 때 각 스레드나 dispatch_queue 마다 Realm 인스턴스를 얻으세요.
비동기적 쓰기
Realm.WriteAsync() 메서드는 백그라운드 스레드에서 쓰기를 수행해서 UI 스레드의 부담을 쉽게 없앨 수 있는 특별한 형태의 Write
입니다.
주의: WriteAsync는 아래 예제처럼 매개 변수로 람다에 전달되는 임시 Realm 인스턴스를 엽니다. 원래 스레드의 Realm이 아닌 람다에서 전달된 인스턴스를 사용하도록 주의하세요. 람다가 호출 컨텍스트를 캡처하는 방식을 사용하므로 이 코드를 변경하지 못하면 컴파일러 오류 없이 런타임 예외가 발생합니다. Be careful to only use the passed-in instance in the lambda and not the original thread의 realm. Due to the way lambdas capture the calling context, failing to change this code will not get a compiler error but instead a runtime exception will be thrown.
await realm.WriteAsync(tempRealm =>
{
var pongo = tempRealm.All<Dog>().Single(d => d.Name == "Pongo");
var missis = tempRealm.All<Dog>().Single(d => d.Name == "Missis");
for (var i = 0; i < 15; i++)
{
tempRealm.Add(new Dog
{
Breed = "Dalmatian",
Mum = missis,
Dad = pongo
});
}
});
스레드 작업이 완료되면 WriteAsync는 일반적으로 UI 스레드인 원래 스레드의 상태를 읽기 위해 Realm.Refresh()도 호출합니다. 해당 스레드는 즉시 백그라운드 스레드의 변경 사항에 대한 알림을 받을 수 있습니다.
별도의 스레드를 생성하지 않고 Write 호출을 전달하기 때문에 이미 워커 스레드에서 실행 중인 코드에서 WriteAsync를 사용하지 않아도 됩니다. 하지만 특별히 오버헤드가 증가하지 않으므로 어느 스레드에서든 안전을 위해 이를 사용하는 것이 좋습니다. 백그라운드 스레드에서는 WriteAsync가 새로운 인스턴스를 생성하는 대신 현재 Realm 인스턴스에서 작업한다는 점을 고려하세요.
일반적으로 Realm 쓰기 작업은 매우 빠르므로, 사용자가 문자열을 입력하거나 체크박스를 선택하는 것과 같은 한 번에 작은 값 변경만이 일어나는 인터페이스 기반 변경에는 Write를 사용하는 것이 좋습니다. WriteAsync는 네트워크에서 객체를 가져 오거나 여러 객체를 배치로 업데이트 처리하는 것과 같이 수백, 수천 개 이상의 변경이 일어날 때 유용하며 동기적으로 사용할 경우 인터페이스의 지연이 일어날 수 있습니다.
알림
안드로이드에서 변경 리스너는 오로지 Looper 스레드에서 작동합니다. 비 Lopper 스레드에서는 대신에 Realm.Refresh()를 수동으로 호출해야 합니다.
Realm 알림
Realm에 데이터를 추가하는 백그라운드 스레드가 있고 UI나 다른 스레드에서 리스너를 추가하여 Realm의 변경을 통보받을 수 있습니다. 변경 리스너는 (다른 스레드나 프로세스에서도 물론) Realm이 변경될 때마다 수행됩니다.
realm.RealmChanged += (s, e) =>
{
// UI를 갱신한다
}
현재 RealmChanged의 구현은 변경 사항에 대한 자세한 정보를 제공하지 않으므로, 이런 기능이 필요한 경우 객체 알림이나 컬렉션 알림을 참고하세요.
Object 알림
RealmObject는 [INotifyPropertyChanged] (https://developer.xamarin.com/api/type/System.ComponentModel.INotifyPropertyChanged/)를 구현하므로, [PropertyChangedEvent] (https://developer.xamarin.com/api/event/System.ComponentModel.INotifyPropertyChanged.PropertyChanged/) 이벤트를 구독해서 변경 사항을 수신할 수 있습니다.
public class Account : RealmObject
{
public long Balance { get; set; }
}
이제 PropertyChangedEvent 이벤트를 클래스에서 구독하면 Balance
속성이 바뀔때 마다 핸들러가 요청됩니다.
var account = new Account();
realm.Write(() => realm.Add(account));
account.PropertyChanged += (sender, e) =>
{
Debug.WriteLine($"New value set for {e.PropertyName}");
}
realm.Write(() => account.Balance = 10); // => "Balance에 새 값을 설정합니다"
Realm에 객체를 추가하기 전에 구독했는지 후에 구독했는지는 중요하지 않습니다. 이벤트는 어느 경우라도 잘 발생합니다.
이렇게 하면 알림에도 적합하며 Xamarin.Forms에도 데이터바인드를 허용할 수 있습니다. 더 많은 정보는 Xamarin 문서의 데이터 바인딩에서 MVVM까지를 참고하세요.
컬렉션 알림
Realm.All<T>()에서 반환되거나 후속 LINQ 절에서 반환된 IQueryable
객체는 라이브 쿼리이므로 항상 최신 상태로 유지됩니다. 컬렉션을 순회할 때마다 최신 변경 사항이 반영된 결과를 받을 수 있습니다. 유사한 방식으로 RealmObject의 IList
속성 역시 라이브로 대상이 여러 개인 관계를 나타냅니다. 즉, 매번 이를 순회할 때마다 가장 최신 컬렉션이나 관련 객체를 받을 수 있습니다.
이런 컬렉션을 모두 지원하는 런타임 객체는 IRealmCollection<T>도 구현하므로, 변경 알림 구독을 위한 두 가지 방법을 제공합니다. 간편한 확장 메서드인 AsRealmCollection을 사용해서 IList<T>
나 IQueryable<T>
를 IRealmCollection<T>
로 캐스팅할 수 있습니다.
만약 MVVM과 데이터 바인딩에 적합한 표준 .NET INotifyCollectionChanged 인터페이스를 사용하려면 뷰에 직접 IRealmCollection<T>을 넘기세요.
혹은 더 많은 변경 정보를 사용하려면, (예를 들어 Xamarin 네이티브 UI 프로젝트에서처럼) UITableView
나 ListView
를 직접 조작하는 것이 유용할 수 있습니다. SubscribeForNotifications 확장 메서드를 사용할 수 있습니다. 이 델리게이트를 메서드에 전달해서 변경 집합에 대해 호출되어, 무엇이 추가되고 지워지고 변경되었는지 알 수 있습니다.
var token = realm.All<Person>().SubscribeForNotifications ((sender, changes, error) =>
{
// changes.InsertedIndices, changes.DeletedIndices, changes.ModifiedIndices에 접근
});
// 더 이상 알림을 받지 않으려는 시점에 사용
token.Dispose();
마이그레이션
어떤 데이터베이스를 쓰더라도 데이터 모델은 시간에 따라 바뀌곤 합니다. Realm의 데이터 모델이 표준 C# 클래스로 정의되었기 때문에 모델의 변경은 다른 클래스를 수정하는 것 만큼 쉽습니다. 예를 들어 아래의 Person
모델을 가지고 있다고 가정해보자.
public class Person : RealmObject
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int Age { get; set; }
}
성과 이름 대신에 FullName
속성을 요구하게 데이터 모델을 갱신하고 싶을 수 있습니다. 그럴 경우 단순히 객체 인터페이스를 아래와 같이 변경합니다.
public class Person : RealmObject
{
public string FullName { get; set; }
public int Age { get; set; }
}
이 시점에서 이전 모델 버전에서 어떤 데이터를 저장한다면, 코드 상에 정의된 Realm과 디스크 보이는 Realm의 불일치가 발생합니다. 불일치가 있고 마이그레이션이 수행되지 않으면 기존 파일을 열 때 예외를 발생시킨다.
마이그레이션 수행하기
기본적으로 스키마 버전을 올려야 합니다. 기존에 지정하지 않았다면 realm은 스키마 버전을 0으로 설정합니다. 스키마를 변경하면 설정에서 스키마 버전을 올려야 합니다. 이 방법으로 실수로 인한 스키마 변경이 잠재적인 데이터를 파괴하지 않도록 막습니다.
var config = new RealmConfiguration() { SchemaVersion = 1 };
var realm = Realm.GetInstance(config);
만약에 이렇게 해두고 기존의 스키마로 생성된 데이터베이스 파일을 실행하면 전체 FirstName
와 LastName
속성이 삭제되고 모든 Person
은 비어있는 FullName
속성을 가지게 됩니다. Age
속성은 양쪽 버전에 있고 같은 형이라 그대로 남게 됩니다. 이런 변화를 의도하지는 않았을테니 Realm에 마이그레이션을 다루는 방법을 지정해야 합니다.
이를 위해서는 MigrationCallback 함수를 지정합니다. 이 함수는 Migration 객체를 받으며, 여기에는 두 개의 Realm 속성, OldRealm과 NewRealm이 있습니다. 이로써 예전 스키마에서 새로운 스키마로 데이터를 복사하고 적용할 수 있습니다.
클래스나 속성만 새로 이름짓는 경우에는 Realm이 이 사실을 감지하지 못하므로 마이그레이션에서 데이터를 복사해야 합니다.
이 콜백 함수는 낮은 버전의 realm이 열릴 때 정확히 한번 호출됩니다. 이 호출 동안 갱신된 클래스 전체를 마이그레이션 해야합니다.
Person
모델은 더 이상 FirstName
와 LastName
속성을 포함하지 않으니 형에 관한 API를 통해 접근할 수 없습니다. 하지만 동적 API를 활용해서 동일한 것을 얻을 수 있습니다.
var config = new RealmConfiguration
{
SchemaVersion = 1,
MigrationCallback = (migration, oldSchemaVersion) =>
{
var newPeople = migration.NewRealm.All<Person>();
// Use the dynamic api for oldPeople so we can access
// .FirstName and .LastName even though they no longer
// exist in the class definition.
var oldPeople = migration.OldRealm.All("Person");
for (var i = 0; i < newPeople.Count(); i++)
{
var oldPerson = oldPeople.ElementAt(i);
var newPerson = newPeople.ElementAt(i);
newPerson.FullName = oldPerson.FirstName + " " + oldPerson.LastName;
}
}
};
var realm = Realm.GetInstance(config);
앱이 오래되어 가며 다른 버전들에서 여러 변경점이 생기면 콜백에서 oldSchemaVersion
인자를 점검하여 마이그레이션의 일련을 만듭니다. Person
모델에서 더 나아가 Age
필드에서 Birthday
필드로 변경해 보겠습니다.
public class Person : RealmObject
{
public string FullName { get; set; }
public int Age { get; set; }
public DateTimeOFfset Birthday { get; set; }
}
스키마 버전 2입니다. 마이그레이션 설정은 아래와 같습니다.
var config = new RealmConfiguration
{
SchemaVersion = 2,
MigrationCallback = (migration, oldSchemaVersion) =>
{
var newPeople = migration.NewRealm.All<Person>();
var oldPeople = migration.OldRealm.All("Person");
for (var i = 0; i < newPeople.Count(); i++)
{
var oldPerson = oldPeople.ElementAt(i);
var newPerson = newPeople.ElementAt(i);
// Migrate Person from version 0 to 1: replace FirstName and LastName with FullName
if (oldSchemaVersion < 1)
{
newPerson.FullName = oldPerson.FirstName + " " + oldPerson.LastName;
}
// Migrate Person from version 1 to 2: replace Age with Birthday
if (oldSchemaVersion < 2)
{
newPerson.Birthday = DateTimeOffset.Now.AddYears(-(int)oldPerson.Age);
}
}
}
};
var realm = Realm.GetInstance(config);
암호화
미국으로부터의 수출 제한이나 금수 조치가 있는 국가에 거주하는 경우 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 플랫폼 (RMP)은 Realm 모바일 데이터 베이스를 네트워크로 확장하여 자동으로 여러 장비간의 데이터를 동기화합니다. 동기화된 Realm 지원을 위해 새로운 타입과 클래스들이 제공됩니다. 새롭게 추가된 객체들은 기존 Realm 데이터베이스에 추가되었고 여기에 다룹니다.
Realm 플랫폼 활성화
Realm Mobile Platform을 사용하려면 명시적 단계가 필요하지 않습니다. 표준 Realm NuGet에 기본적으로 설치됩니다. 일부 기능은 프로페셔널이나 엔터프라이즈 에디션 평가판 가입이나 구입 이후 전송되는 이메일에 첨부된 토큰과 함께 SyncConfiguration.SetFeatureToken을 호출해서 잠금 해제해야 합니다.
아직 동기화 기능이 Windows나 UWP에서 구현되지 않았으며, 현재 iOS, Android, macOS, Linux에서 동작합니다.
Linux에서 동기화 Realm 사용
Linux에서 동기화 Realm을 사용하려면 프로페셔널 에디션 이상이 필요합니다. SyncConfiguration.SetFeatureToken
를 호출하지 않고 동기화 Realm을 열려는 시도를 하면 RealmFeatureUnavailableException이 발생합니다.
사용자
Realm 오브젝트 서버의 중심적인 객체는 Realm 사용자 컨셉입니다. User
는 동기화된 Realm과 관련됩니다. User는 사용자 이름 / 암호 스키마나 여러 서드 파티 인증 방법에 의해 인증됩니다.
사용자를 만들고 로그인하기 위해 두 가지 객체가 필요합니다.
- Realm 인증 서버가 접속할 (문자열로 된) Uri
- 사용자 인증 메카니즘에 적합한 Credentials (예: 사용자 / 암호, 엑세스 키, 기타)
인증 {authentication}
인증은 사용자를 식별하고 로그인하는 데 사용합니다. Realm 플랫폼에서 지원하는 인증 제공자에 대한 정보는 Realm 오브젝트 서버 인증 문서를 확인하세요.
특정 사용자에 대한 credential 정보는 다음과 같은 방법으로 생성합니다.
- 적합한 사용자 이름/암호 조합
- 지원되는 서드 파티 인증 서비스에서 얻은 토큰
- 토큰 및 사용자 지정 인증 공급자(자세한 내용은 사용자 지정 인증을 참조하세요)
사용자 이름 및 비밀번호 인증은 애플리케이션의 사용자를 완벽하게 관리할 수 있도록 Realm 오브젝트 서버에서 전적으로 관리합니다. 다른 인증 방법의 경우 애플리케이션에서 서드 파티 서비스에 로그인하고 인증 토큰을 받아야 합니다.
다양한 공급자에서 자격 증명을 설명하는 몇 가지 예를 소개합니다.
사용자 / 암호
var credentials = Credentials.UsernamePassword(username, password, createUser: false);
UsernamePassword()의 세 번째 인자는 createUser
플래그로, 사용자를 생성해야 한다는 의미로 항상 처음에는 true
로 설정해야 합니다. 한번 사용자가 생성이 되면 인자는 false
여야 합니다.
혹은 관리자 대시 보드를 사용해서 모든 사용자가 서버에 미리 만들어지도록 요청하고, 항상 createUser
매개 변수에 false
를 전달하도록 할 수 있습니다.
User.ChangePasswordAsync를 사용해서 사용자의 암호를 변경할 수 있습니다.
var currentUser = User.Current;
await currentUser.ChangePasswordAsync("new-secure-password");
// 사용자 암호가 변경됐으므로 이전 암호를 사용할 수 없습니다
어드민 사용자는 사용자의 ID를 전달해서 다른 사용자의 암호를 변경할 수 있습니다. 이 기능을 암호 검색을 자동화하는 서버 사이드 앱 등을 위해 사용할 수 있습니다.
var adminUser = await User.LoginAsync(...);
await adminUser.ChangePasswordAsync("some-user-id", "new-secure-password");
// some-user-id ID를 가진 사용자의 암호가 new-secure-password로 변경됐습니다
사용자 이름/암호 인증을 사용할 때는 중요한 사용자 데이터가 다른 사람에 의해 갈취되는 것을 방지하도록 HTTPS를 사용하기를 권장합니다.
var token = "..."; // Google 로그인 API에서 얻은 토큰을 나타내는 문자열
var credentials = Credentials.Google(token);
var token = "..."; // Facebook 로그인 API에서 얻은 토큰을 나타내는 문자열
var credentials = Credentials.Facebook(token);
Azure Active Directory
var token = "..."; // Azure Active Directory로 로그인하면서 얻은 토큰을 나타내는 문자열
var credentials = Credentials.AzureAD(token);
사용자 지정 인증
var token = "..."; // 사용자 지정 인증 제공자에서 얻은 토큰을 나타내는 문자열
var credentials = Credentials.Custom("myAuth", token, null);
세 번째 매개 변수로 제공된 형태로 추가적인 정보를 사용자 지정 인증 제공자에게 전달할 수 있습니다. 자세한 내용은 API를 참조하세요.
사용자 로그인
사용자에 요구되는 모든 인자가 준비되었기 때문에 Realm 오브젝트 서버에 로그인할 수 있습니다.
var authURL = new Uri("http://my.realm-auth-server.com:9080");
var user = await User.LoginAsync(credentials, authURL);
로그아웃
다음처럼 간단하게 동기화 Realm에서 로그아웃할 수 있습니다.
user.LogOut();
사용자가 로그아웃하면 동기화가 중지됩니다. 로그아웃한 사용자는 더 이상 SyncConfiguration를 사용해서 Realm을 열 수 없습니다.
사용자 저장 설정
기본값으로 Realm은 애플리케이션이 시작할 때마다 로그인한 사용자를 저장합니다. User.ConfigurePersistence를 호출해서 해당 동작을 수정할 수 있습니다. 사용할 수 있는 modes는 다음과 같습니다.
Disabled
: 사용자가 Realm에 저장되지 않습니다. 즉 사용자 인증 정보를 수동으로 저장하거나, 매번 사용자에게 로그인을 요청해야 합니다.NotEncrypted
: 사용자 인증 정보가 암호화되지 않은 상태로 저장됩니다. 이 옵션은 사용자 ID를 도용하기 위한 공격에 노출될 수 있으므로 상용 애플리케이션에 권장되지 않습니다.Encrypted
: 사용자 인증 정보가 암호화되어 저장됩니다. 이 옵션을 위해서는 유효한encryptionKey
를 제공해야 합니다.
기본적으로 Realm은 iOS에서 Encrypted
모드를 제공하고 encryptionKey
를 iOS 키체인에 저장합니다. 안드로이드에서는 AndroidKeyStore API가 API 레벨 18 이상에서만 제공되기 때문에 NotEncrypted
가 사용됩니다. 안드로이드에서 Encrypted
모드를 선택해서 애플리케이션마다 고유한 encryptionKey
를 제공하거나, API 레벨 18으로 타깃을 지정하고 KeyStore에 사용자마다 고유한 encryptionKey
를 저장하는 것을 권장합니다.
사용자 작업하기
독립적으로 작동하는 Realm에서는 Realm의 부가사항을 설정하기 위해 RealmConfiguration를 사용합니다. Xamarin용 Realm 플랫폼은 확장된 설정 클래스 SyncConfiguration를 사용합니다.
설정은 인증된 사용자와 싱크 서버 URL에 묶여 있습니다. 싱크 서버 URL은 틸드 문자(“~”)를 포함할 수 있습니다. 이는 사용자의 유일한 식별자를 투명하게 확장하는데 사용될 수 있습니다. 스키마는 손쉽게 개별 사용자를 앱에서 대응할 수 있게 합니다. 디스크에서 저장될 공유 Realm의 위치는 프레임워크에 의해 관리되지만 필요에 의해 변경될 수 있습니다.
var serverURL = new Uri("realm://my.realm-server.com:9080/~/default");
var configuration = new SyncConfiguration(user, serverURL);
사용자 저장을 비활성화하지 않았다면, User.Current를 사용해서 현재 로그인한 사용자를 알 수 있습니다. 로그인한 사용자가 없거나 모두 로그아웃한 경우 null
이 반환됩니다. 로그인한 사용자가 두 명 이상인 경우 RealmException이 발생합니다.
var user = User.Current;
여러 사용자가 로그인할 가능성이 있다면 User.AllLoggedIn를 호출해서 여러 사용자의 컬렉션을 반환할 수 있습니다. 로그인한 사용자가 없다면 비어있습니다.
var users = User.AllLoggedIn;
foreach (var user in users)
{
// do something with the user
}
동기화 Realm 열기
SyncConfiguration가 생성되면 일반적인 Realm을 생성하는 것처럼 동기화된 Realm 인스턴스가 만들어집니다.
var realm = Realm.GetInstance(configuration);
동기화 세션
Realm 오브젝트 서버로의 동기화 Realm의 연결은 세션 객체로 표현됩니다. 세션 객체는 realm.GetSession()을 호출해서 얻을 수 있습니다.
기본 세션의 상태는 State 속성을 통해 확인할 수 있습니다. 이를 통해 세션이 활성화되어 있는지, 서버에 연결되지 않았는지, 혹은 에러 상태인지 알 수 있습니다.
Progress Notifications
세션 객체를 사용하면 IObservable을 등록해서 앱에서 세션이 Realm 오브젝트 서버와 업로드 혹은 다운로드하는 상태를 모니터링할 수 있습니다. 이는 [session.GetProgressObservable(https://realm.io/docs/dotnet/latest/api/reference/Realms.Sync.Session.html#Realms_Sync_Session_GetProgressObservable_Realms_Sync_ProgressDirection_Realms_Sync_ProgressMode_)을 호출해서 얻을 수 있습니다.
구독 콜백은 동기화 서브시스템에 의해 주기적으로 호출됩니다. 필요한 만큼의 구독자들을 세션 객체에 동시에 등록할 수 있습니다. 구독자들은 업로드 진행률을 보고하거나 진행률을 다운로드하도록 구성할 수 있습니다. 구독자가 호출될 때마다 이미 전송된 현재 바이트 수에 대한 정보와, 이미 전송된 바이트 수에 전송 대기 중인 바이트 수를 더한 값으로 정의되는 전송 가능한 총 바이트 수에 대한 정보가 들어있는 SyncProgress 인스턴스가 전달됩니다.
구독자가 등록될 때마다 IDisposable 토큰이 반환됩니다. 프로그레스 알림이 필요하지 않을 때까지 이 토큰의 참조를 저장해야 합니다. 받고있는 알림을 중단하려면 토큰에서 Dispose를 호출하세요.
프로그레스 구독에는 두 가지 모드가 있습니다.
ReportIndefinitely
모드에서는Dispose
가 명시적으로 호출될 때까지 구독이 활성화된 상태이며, 항상 가장 최신의 전송 가능한 바이트 수를 보고합니다. 이 콜백 타입을 사용해서 네트워크 표시기 UI를 제어할 수 있습니다. 예를 들어 업로드나 다운로드가 활발히 진행되는 경우에 색이 변하거나 특정 UI가 등장하도록 할 수 있습니다.
var token = session.GetProgressObservable(ProgressDirection.Download, ProgressMode.ReportIndefinitely)
.Subscribe(progress =>
{
if (progress.TransferredBytes < progress.TransferableBytes)
{
// Show progress indicator
}
else
{
// Hide the progress indicator
}
});
ForCurrentlyOutstandingWork
모드에서는 구독이 등록된 시점에 전송할 수 있는 바이트를 저장하고, 이 값과 관련된 프로그레스를 보고합니다. 전송된 바이트 수가 해당 초기 값 이상이 되면 블록이 자동으로 등록을 취소합니다. 이 타입의 구독을 사용해서 사용자가 로그인한 경우 최초로 다운로드되는 동기화 Realm을 추적하는 프로그레스 바를 제어하고, 로컬 복사본이 최신 상태가 되기까지의 시간을 알려줄 수 있습니다.
var token = session.GetProgressObservable(ProgressDirection.Download, ProgressMode.ForCurrentlyOutstandingWork)
.Subscribe(progress =>
{
var progressPercentage = progress.TransferredBytes / progress.TransferableBytes;
progressBar.SetValue(progressPercentage);
if (progressPercentage == 1)
{
progressBar.Hide();
}
});
위 예제에서는 구독을 단순화하기 위해 ObservableExtensions.Subscribe 확장 메서드를 사용했습니다. 실제 사용에서는 Reactive Extensions 클래스 라이브러리를 사용해서 관찰 가능한 시퀀스 작업을 단순화하는 것을 추천합니다. 예를 들어 업로드와 다운로드 진행율을 모두 추적하고 조절한 다음 메인 스레드에서 최종적으로 전달할 수 있도록 구성할 수 있습니다.
var uploadProgress = session.GetProgressObservable(ProgressDirection.Upload, ProgressMode.ReportIndefinitely);
var downloadProgress = session.GetProgressObservable(ProgressDirection.Download, ProgressMode.ReportIndefinitely);
var token = uploadProgress.CombineLatest(downloadProgress, (upload, download) =>
{
return new
{
TotalTransferred = upload.TransferredBytes + download.TransferredBytes,
TotalTransferable = upload.TransferableBytes + download.TransferableBytes
};
})
.Throttle(TimeSpan.FromSeconds(0.1))
.ObserveOn(SynchronizationContext.Current)
.Subscribe(progress =>
{
if (progress.TotalTransferred < progress.TotalTransferable)
{
// Show spinner
}
else
{
// Hide spinner
}
});
접근 제어
Realm 플랫폼은 유연한 접근 제어 방식을 제공해서 어떤 사용자가 어떤 Realm 파일과 동기화할 수 있는지에 대한 권한을 허용할 수 있게 합니다. 예를 들어 여러 사용자가 동일한 Realm에 쓸 수 있는 협업 앱을 만들 수 있습니다. 또한 한 사용자가 만든 데이터를 읽기 퍼미션을 가진 여러 사용자와 공유하는 게시자/구독자 시나리오에도 활용할 수 있습니다.
Realm의 접근 수준, 즉 권한 제어는 세 가지 축으로 분류됩니다.
Read
Realm으로부터 사용자가 읽을 수 있는지를 표기합니다.Write
Realm에 사용자가 쓸 수 있는지를 표기합니다.Manage
사용자가 Realm의 권한을 변경할 수 있는지 여부를 표기합니다.
권한을 명시적으로 바꾸지 않으면 Realm의 소유자만 접근할 수 있습니다. 예외는 관리자들입니다. 그들은 언제나 서버의 모든 Realm의 모든 권한을 가집니다.
접근 제어에 대한 자세한 내용은 Realm 오브젝트 서버 문서의 접근 제어 섹션을 참조하세요.
권한 검색
User.GetGrantedPermissionsAsync 메서드를 사용하면 사용자가 가진 모든 권한을 가져올 수 있습니다.
var permissions = await user.GetGrantedPermissionsAsync(Recipient.CurrentUser, millisecondTimeout: 2000);
// 정규 쿼리를 사용해서 권한을 가져옵니다
var writePermissions = permissions.Where(p => p.MayWrite);
// 쿼리는 라이브이며 알림을 보냅니다
writePermissions.SubscribeForNotifications((sender, changes, error) =>
{
// 권한 변경을 처리합니다
});
Recipient.OtherUser을 전달하면 사용자가 부여한 사용 권한을 얻을 수 있습니다. millisecondTimeout
인자는 서버의 응답을 기다릴 최대 시간을 제어합니다. 시간을 초과한 경우 마지막으로 알려진 상태가 반환되므로 클라이언트가 현재 오프라인이더라도 일부 데이터가 계속 보일 수 있습니다.
권한 수정
Realm 파일에 대한 접근 제어 설정을 수정하려면 권한을 직접 적용하거나 보냅니다.
권한 부여
User.ApplyPermissionsAsync 메서드를 사용해서 직접 다른 사용자가 Realm에 접근할 수 있는 권한을 상향 혹은 하향 조정해서 권한 부여나 취소와 같은 권한 변경을 할 수 있습니다.
var condition = PermissionCondition.UserId("some-user-id");
var realmUrl = "realm://my-server.com/~/myRealm";
await user.ApplyPermissionsAsync(condition, realmUrl, AccessLevel.Read);
PermissionCondition에는 두 가지 팩토리 메서드가 있습니다.
UserId
- Realm이 생성한 내부 ID인 사용자의 Identity를 기반으로 권한을 적용하는 경우 사용합니다.Email
- 사용자 이름/암호 제공자에서 사용자의 이메일(사용자 이름)으로 지정해서 권한을 변경하는 경우 사용합니다.
마지막 인자는 사용자가 받을 AccessLevel을 제어합니다. 상위 접근 레벨은 낮은 접근 레벨을 포함합니다. 예를 들어 Write
는 Read
를, Admin
은 Read
와 Write
권한을 포함합니다. AccessLevel.None
을 전달하면 해당 Realm에 대한 사용자의 권한을 취소합니다.
권한 보내기와 응답
사용자는 OfferPermissionsAsync에서 반환된 불투명한 토큰을 공유해서 자신의 Realm에 대한 권한을 보낼 수 있습니다.
var realmUrl = "realm://my-server.com/~/myRealm";
var expiration = DateTimeOffset.UtcNow.AddDays(7);
var token = await userA.OfferPermissionsAsync(realmUrl, AccessLevel.Write, expiresAt: expiration);
선택 인자인 expiresAt
은 토큰이 더 이상 사용가능하지 않을 시점을 제어합니다. 즉, 지정된 날짜 이후에 토큰을 사용하면 해당 Realm에 대한 권한을 부여하지 않습니다. 이미 토큰을 사용해서 권한을 받은 사용자는 지정된 날짜 이후에도 권한을 잃지 않습니다. 권한을 취소하려면 ApplyPermissionsAsync를 사용하세요.
메신저나 QR 코드 스캐닝과 같은 방법으로 사용자가 토큰을 받은 이후에는 보낸 권한을 얻기 위해 토큰을 사용해야 합니다.
var token = "...";
var realmUrl = await userB.AcceptPermissionOfferAsync(token);
// Realm을 열기 위해 realmUrl을 사용합니다
var config = new SyncConfiguration(userB, new Uri(realmUrl));
var realm = await Realm.GetInstanceAsync(config);
기존 보내기와 변경 사항 검색
토큰을 취소하거나 앱을 다시 시작한 후에 변경 리스너를 부착하는 것과 같은 행동을 위해 기존 관리 객체 검색이 필요할 수 있습니다.
위 API를 사용할 때 생성되는 객체 유형은 다음 세 가지입니다.
OfferPermissionsAsync
로 생성된 객체를 얻으려면 GetPermissionOffers 메서드를 사용합니다.AcceptPermissionOfferAsync
로 생성된 객체를 얻으려면 GetPermissionOfferResponses 메서드를 사용합니다.ApplyPermissionsAsync
로 생성된 객체를 얻으려면 GetPermissionChanges 메서드를 사용합니다.
위 셋은 모두 ManagementObjectStatus 타입의 params
인자를 받습니다.
NotProcessed
- 클라이언트가 오프라인이었을 때처럼 아직 처리되지 않은 객체를 얻습니다.Success
- 권한이 적용되어 성공적으로 처리된 객체를 얻습니다.Error
- 문자가 있는 객체를 얻습니다.
위 세 메서드는 필터링과 관찰이 가능한 IQueryable
을 반환합니다. (자세한 내용은 컬렉션 알림을 참조하세요.)
InvalidateOfferAsync 메서드를 사용하면 `PermissionOffer를 무효화해서 새로운 사용자가 토큰을 사용하지 못하도록 할 수 있습니다.
// 만료 날짜가 없는 쓰기 권한 부여 보내기를 모두 가져옵니다
var offers = user.GetPermissionOffers(ManagementObjectStatus.Success)
.Where(s => s.MayWrite && s.ExpiresAt == null);
foreach (var offer in offers)
{
await user.InvalidateOfferAsync(offer);
}
에러 보고
특정 동기화 관련 API를 통해 실패할 수 있는 비동기 작업을 수행할 수 있으며, 실패할 경우 예외가 발생해서 try-catch 블록에서 확인할 수 있습니다.
Session.Error 이벤트를 구독할 것을 강력히 추천 합니다. 전역 동기화 서브시스템이나, 서버와 동기화를 위해 열린 Realm을 나타내는 특정 세션과 관련된 오류는 이 에러 핸들러를 통해 보고됩니다. 오류가 발생하면 에러 핸들러는 에러를 포함한 ErrorEventArgs와, 에러가 발생한 세션을 나타내는 Session과 함께 호출됩니다.
Session.Error += (session, errorArgs) =>
{
// handle error
};
Errors
Realm 플랫폼 에러는 SessionExceptions로 표시됩니다. 표준 예외 속성과 함께 에러의 타입 정보를 포함하고 강력한 처리 로직을 만들 수 있는 ErrorCode에 접근할 수 있습니다.
Session.Error += (session, errorArgs) =>
{
var sessionException = (SessionException)errorArgs.Exception;
switch (sessionException.ErrorCode)
{
case ErrorCode.AccessTokenExpired:
case ErrorCode.BadUserAuthentication:
// Ask user for credentials
break;
case ErrorCode.PermissionDenied:
// Tell the user they don't have permissions to work with that Realm
break;
case ErrorCode.Unknown:
// Likely the app version is too old, prompt for update
break;
// ...
}
};
클라이언트 재설정
Realm 객체 서버에 손상이 발생하고 백업에서 복원해야 하는 경우, 동기화 Realm의 클라이언트 재설정 을 앱에서 수행해야 할 수 있습니다. 이는 문제가 되는 Realm의 로컬 버전이 서버의 동일 Realm 버전보다 클 때 발생합니다. 예를 들어 Realm 오브젝트 서버의 백업 이후 애플리케이션이 변경 사항을 만들었지만, 이 변경 사항이 서버 손상 이전에 동기화되지 않았기 때문에 복원이 필요한 경우입니다.
클라이언트를 재설정하는 절차는 다음과 같습니다. 로컬 Realm 파일의 백업 사본이 만들어지면 Realm 파일이 삭제됩니다. 다음에 앱이 Realm 오브젝트 서버에 접속하고 Realm을 열면 새로운 사본이 다운로드됩니다. Realm 오브젝트 서버가 백업된 이후에 변경돼서 아직 서버로 동기화되지 않은 내용은 Realm의 백업 복사본에 보존되지만, Realm을 재다운로드하는 경우에는 존재하지 않습니다.
클라이언트 재설정 필요 사항은 Session.Error 구독자에게 전달되는 에러로 표시됩니다. sessionException.ErrorCode.IsClientResetError의 결과를 확인하거나 예외를 ClientResetException로 안전하게 캐스팅해서 에러 타입을 확인할 수 있습니다. 이 타입은 추가 속성인 BackupFilePath를 가지며, 여기서 클라이언트 재설정 프로세스가 발생한 다음 Realm 파일의 백업 복사본이 위치할 곳을 알 수 있습니다. 또한 클라이언트 재설정 프로세스를 시작할 수 있는 InitiateClientReset 메서드도 포함됩니다.
만약 클라이언트 재설정 프로세스를 시작하기 위해 메서드를 호출한다면, 메그 전에 문제가 있는 모든 Realm 인스턴스를 Dispose()를 호출해서 반드시 무효화하고 없애야 합니다. 이렇게 하면 클라이언트 재설정 프로세스가 완료된 후 Realm을 즉시 다시 열 수 있으므로 다시 동기화를 시작할 수 있습니다.
InitiateClientReset이 호출되지 않은 경우, 클라이언트 재설정 프로세스는 다음에 앱이 시작될 때 자동으로 실행됩니다. 처음에는 Realm 인스턴스에 접근합니다. 필요한 경우 백업 복사본의 위치를 유지하여 나중에 찾을 수 있도록 앱에서 관리해야 합니다.
재설정이 필요한 Realm을 계속해서 읽고 쓸 수는 있지만, 클라이언트 재설정이 완료되고 Realm이 다시 다운로드될 때까지 이후 변경 사항이 서버에 동기화되지 않습니다. 따라서 애플리케이션이 클라이언트 재설정 에러를 관찰하거나, 최소한 재다운로드된 Realm 복사본에 쓸 수 있도록 클라이언트 재설정이 유발된 후 새로 만들어지거나 수정된 사용자 데이터를 저장해야 합니다.
다음 예제에서 클라이언트 재설정 API를 사용해서 클라이언트를 재설정하는 방법을 보여드립니다.
void CloseRealmSafely()
{
// Safely dispose the realm instances, possibly notify the user
}
void SaveBackupRealmPath(string path)
{
// Persist the location of the backup realm
}
void SetupErrorHandling()
{
Session.Error += (session, errorArgs)
{
var sessionException = (SessionException)errorArgs.Exception;
if (sessionException.ErrorCode.IsClientResetError())
{
var clientResetException = (ClientResetException)errorArgs.Exception;
CloseRealmSafely();
SaveBackupRealmPath(clientResetException.BackupFilePath);
clientResetException.InitiateClientReset();
}
// Handle other errors
}
}
Realm 오브젝트 서버가 클라이언트 재설정을 처리하는 방법에 대해 더 자세히 알고 싶다면 서버 문서를 참고하세요.
마이그레이션
동기화 Realm은 자동 마이그레이션이 지원됩니다. 현재까지는 현존 클래스에 클래스나 필드를 추가하는 등의 추가적인 변화만이 지원됩니다. 추가적인 변화가 아니라면 예외가 발생합니다.
마이그레이션은 필요시 자동으로 수행됩니다. 커스텀 마이그레이션 콜백이나 스키마 버전 숫자를 설정할 필요가 없습니다.
충돌 해결
Realm 오브젝트 서버 문서에서 충돌 해결 지원에 관한 내용을 볼 수 있습니다.
현재 제한 사항
Realm은 가능한 제약 사항을 적게 하려고 노력하고 있으며, 커뮤니티의 피드백에 맞춰 새로운 기능을 지속적으로 추가하고 있습니다. 다만 Realm에는 아직까지 제한 사항이 있습니다.
알려준 이슈에 대한 자세한 정보는 GitHub 이슈에서 확인할 수 있습니다.
일반적인 제한
Realm은 유연함과 성능 사이에 균형을 찾는 것을 목표로 합니다. 목적을 이루기 위해 Realm에 정보를 저장하는 여러 측면에 현실적인 제약을 시행하게 됩니다. 예를 들어서 다음과 같습니다.
- 클래스 이름은 0부터 57 바이트 사이의 길이여야 합니다. UTF-8 문자열이 지원됩니다. 앱의 초기화 때 제한을 넘게 되면 예외가 던져집니다.
- 프로퍼티 이름은 0부터 63 바이트 사이의 길이어야 합니다. UTF-8 문자열이 지원됩니다. 앱의 초기화 때 제한을 넘게 되면 예외가 던져집니다.
- iOS 제한: 열린 Realm 파일의 사이즈는 iOS에 연결되어 애플리케이션에 허용된 총 메모리 사이즈보다 클 수 없습니다. 이는 디바이스마다 다르고 메모리 공간이 실행 시점에 어떻게 파편화되었냐에 의존합니다. (여기에 대한 radar 이슈가 있습니다. rdar://17119975) 더 많은 데이터를 저장하길 원한다면 여러 개의 Realm 파일로 분할하고 그들을 필요에 따라 열고 닫을 수 있습니다.
스레드
Realm 파일은 여러 스레드에서 동시에 엑세스할 수 있지만 Realm, Realm 객체, 쿼리와 결과들을 스레드 사이에 넘길 수는 없습니다. 더 자세한 내용은 스레드 섹션을 확인하세요.
파일 사이즈와 중간 버전의 추적
Realm 데이타베이스는 동등한 SQLite 데이터베이스에 비해 일반적으로 디스크 공간을 더 적게 사용합니다. 만약 기대보다 Realm 파일이 크다면 이것은 RealmObject가 데이터베이스 데이터의 오래된 버전을 참조하기 때문입니다.
데이터의 일관된 뷰를 제공하기 위해 Realm은 런 룹 단계의 시작에서만 접근된 활성 버전을 갱신합니다. 만약 Realm으로부터 어떤 데이터를 읽었습니다. 그리고는 다른 스레드에서 Realm에 기록하는 동안 오래 수행되는 연산으로 스레드를 막았습니다. 버전은 갱신되지 못하고 Realm은 당신이 필요하지 않은 중간 버전 데이터를 붙잡고 있습니다. 그 결과로 매 쓰기마다 파일의 크기는 증가하게 됩니다. 여분의 공간은 결국에는 다음 쓰기에 재사용이 됩니다.
저장 공간을 확보하기 위해서는 Realm 압축하기 섹션을 참고하세요.
Windows 데스크탑, UWP, Windows .NET 코어에서의 제한 사항
Realm Xamarin에서 지원하는 모든 플랫폼 간의 기능을 동일하게 유지할 수 있도록 노력하고 있지만 아직 Windows에 특정한 몇 가지 제한 사항이 존재합니다.
- 동기화가 아직 구현되지 않았습니다. Realm의 부모 NuGet 패키지를 Windows 애플리케이션에서 사용하면 빌드타임 에러가 발생합니다. 이 대신 오프라인이 가능한 바이너리를 포함하고 있는 Realm.Database 패키지를 사용하세요.
- 아직 암호화가 지원되지 않습니다. null이 아닌 EncryptionKey를 가진 RealmConfiguration으로 Realm.GetInstance를 호출하면 런타임 예외가 발생합니다.
- 많은 다른 프로세스가 같은 realm 파일을 열면 일부 구독자는 변경 알림을 받지 못할 수 있습니다.
- 아직 Realm 압축을 지원하지 않습니다.
FAQ
일반
Realm은 오픈소스인가요?
맞습니다. Realm 내부의 C++ 저장소 엔진과 그 위의 언어 SDK들은 전적으로 오픈소스이고 Apache 2.0 라이선스입니다. 또 Realm은 선택적으로 사용하는 Realm 플랫폼 확장을 가지고 있고 이는 비공개 소스이자만 Realm을 임베디드 데이터베이스로 사용할 때 필수인 건 아닙니다.
내 앱을 빌드할 때 Mixpanel에 네트워크 호출을 하는 것을 보았는데 이건 무엇인가요?
RealmObject를 포함하고 있는 여러분의 어셈블리에 대해 Realm 어셈블리 위버를 호출할 때 Realm은 익명의 통계를 수집합니다. 완전히 익명이고 어떤 Realm 버전을 쓰고 어떤 OS를 쓰는지 등을 파악해서 어떻게 제품을 향상하고 어떤 지원을 중단할 수 있는지 우리가 파악할 수 있게 합니다. 실제 앱이 수행될 때 혹은 여러분의 디바이스에서 수행될 때는 어떤 호출도 없습니다. - 빌드 과정에 Fody에 의해 여러분의 어셈블리를 위브할때만 호출이 됩니다. 정확히 어떻게 무엇을 수집하는지는 소스 코드에서 확인하실 수 있습니다.
포터블 (PCL) 라이브러리와 함께 쓸 수 있나요?
실행파일에 iOS, 안드로이드, Win32 버전의 Realm을 사용할 필요가 있지만 우리는 PCL을 제공하고 있고 Realm API를 컴파일할 때 사용할 수 있습니다. NuGet 유인 상술입니다. NuGet을 통해 Realm을 PCL 프로젝트에 추가할 때 이는 실행 파일에 추가되고 동작됩니다.
이 PCL이 Realm을 임의의 .NET 플랫폼에서 사용할 수 있다는 의미는 아닙니다. Realm은 각 플랫폼으로 포팅되어야 할 C++ 코어에 근간하고 있습니다.
트러블 슈팅
크래쉬 보고
우리는 애플리케이션에서 크래쉬 리포터를 사용하는 것을 장려합니다. 많은 Realm 연산은 (다른 디스크 IO와 같이) 잠재적으로 실시간에 실패할 수 있습니다. 애플리케이션으로 부터 수집된 크래쉬 보고는 문제가 여러분(이나 우리)에게 있는지 확인할 수 있고 에러 처리를 향상하고 크래쉬 버그를 잡게 합니다.
대부분의 상업 크래쉬 리포터들은 로그 수집 옵션이 있습니다. 우리는 강하게 이 기능을 활성화하는 것을 추천합니다. Realm은 예외가 발생하거나 복구불가능한 상황일 때 (사용자 데이터에 관련되지 않은) 메타데이터 정보를 로그로 남깁니다. 이 메시지들은 문제가 발생했을 때 디버그 할 때 도움이 됩니다.
Realm 이슈 보고하기
Realm에 이슈를 찾으면 우리가 이슈를 이해하거나 재연할 수 있도록 최대한 많은 정보를 담아 GitHub에 이슈를 발행하거나 help@realm.io로 메일을 주세요.
아래의 정보는 우리에게 매우 유용합니다.
- 목표
- 기대 결과
- 실제 결과
- 재연할 수 있는 단계
- 이슈를 특정짓는 코드 샘플 (빌드할 수 있는 전체 Xamarin 프로젝트가 우리에게 이상적입니다)
- Realm, OS X나 Windows, Xamarin/Visual Studio의 버전 정보들
- 대상이 iOS면 Xcode, 대상이 안드로이드인 경우 NDK 정보
- 버그가 발생한 플랫폼, OS 버전, 아키텍쳐 (예: 64 비트 iOS 8.1)
- 크래쉬 로그, 스택 트레이스. 자세한 것은 크래쉬 보고를 살펴보세요.
클래스에 속성이 없다는 예외가 발생하면
System.InvalidOperationException
를 “No properties in class, has linker stripped it?” 메시지와 함께 받을 수 있습니다.
알려진 두가지 원인이 있습니다.
- 거의 Fody가 잘못되어서 위브된 RealmObjects가 없는 경우이거나,
- RealmObjects가 스트립된 속성을 가지고 있어 Realm에 비어있게 전달된 경우.
첫 번째 경우는 https://realm.io/docs/dotnet/latest/api/reference/Realms.Schema.RealmSchema.html에서 예외가 발생합니다. 자세한 내용은 위브에 실패할 때를 보세요.
두 번째 경우는 ObjectSchema로부터 예외가 발생합니다. 몇몇 사용자들이 Linker Beahviour를 Link All로 설정하고 그들의 클래스 정의에 [Preserve(AllMembers = true)]
을 누락했을 때 문제가 발생하는 것을 확인했습니다. 링커들은 코드에서 명시적으로 참조하는 것만 보호합니다. 영속적으로 보관되어야 속성들이 있는데 어디에서도 참조되지 않아 제거되면 데이터베이스가 불일치에 빠질 수 있습니다.
기본적으로 Realm은 어셈블리에 있는 모든 RealmObject로 표현되는 스키마를 빌드합니다. 이는 지연 으로 이뤄지고 GetInstance()가 처음 호출될 때 까지 호출되지 않습니다. 이는 최대 한번 이루어지며 메모리 상에 결과가 캐쉬됩니다.
주어진 어셈블리에서 클래스 중 어떤 것만 저장하고 싶다면 클래스 부분집합을 정의하고 GetInstance(myConf)의 설정을 이용하세요. 이는 전체 빌드를 막고 또 예외도 막습니다.
아니면 사용하지 않는 클래스에 Preserve
특성을 지정하세요.
Fody: 처리되지 않는 예외 발생
이런 기본적인 실패는 이미 빌드된 프로젝트에 새로운 RealmObject 서브클래스를 추가하는 것으로 간단히 발생시킬 수 있습니다.
Build or Run을 선택하면 Fody Weaver를 실행할만큼 충분히 프로젝트를 재빌드하지 않습니다.
단순히 프로젝트에 대해 리빌드를 선택하면 에러없이 빌드됩니다.
위브에 실패할 때
클래스가 위브되지 않았다는 클래스에 관한 빌드 로그에서 워닝을 볼 수 있습니다. 이는 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 패키지 매니저를 최신 버전으로 올리는 것 만으로 이 문제가 해결됩니다.
동작하지 않으면 Fody와 Microsoft.Bcl.Build.targets
사이에 문제일 수 있습니다. .csproj 파일로 부터 아래의 라인을 제거하는게 도움이 될 수 있습니다.
<Import Project="..\..\packages\Microsoft.Bcl.Build.1.0.21\build\Microsoft.Bcl.Build.targets" Condition="Exists('..\..\packages\Microsoft.Bcl.Build.1.0.21\build\Microsoft.Bcl.Build.targets')" />
여기에 대해 자세한 내용은 이 StackOverflow 답변를 살펴보세요.
WriteAsync 이슈 트러블 슈팅
WriteAsync
내에서는 UI 스레드에서 사용하고 있는지 확인하기 위해 null이 아닌 SynchronizationContext.Current를 확인합니다. 이로 인해 워커 스레드에 Current
를 설정한 곳에서 사용하는 프로그래밍 패턴에서 혼선이 있을 수 있습니다. 이 패턴은 2007 MSDN 블로그 글에서 확인할 수 있습니다.
SynchronisationContext.Current
를 설정하더라도 문제가 되지 않지만, WriteAsync
를 다시 스레드 풀에 디스패치해서 다른 워커 스레드를 생성할 수 있습니다. 스레드에서 Current
를 사용한다면 WriteAsync
대신 Write
를 호출하는 것이 좋습니다.
Realm Core Binary 다운로드 실패
Realm을 빌드할 때 처리의 한 부분으로 코어 라이브러리를 정적 라이브러리로 다운받고 Realm 프로젝트에 통합하는 과정이 있습니다. 이것은 wrappers
의 한 부분으로 Makefile
로 빌드됩니다.
어떤 경우에 아래의 에러와 함께 코어 바이너리 다운이 실패합니다.
Downloading core failed. Please try again once you have an Internet connection.
아래의 이유에 따라 에러가 발생합니다.
- 미국의 금수조치 목록에 포함된 지역의 IP 주소를 사용하는 경우. 미국 법률에 따라 Realm은 이 지역에서 사용할 수 없습니다. 자세한 정보를 알고 싶다면 라이선스를 살펴보세요.
- 중국 본토에 있다면 국가 수준의 방화벽 때문에 CloudFlare나 Amazon AWS S3 서비스를 상황에 따라 사용할 수 없을 수 있습니다. 자세한 내용은 우리 다른 제품의 해당 이슈를 참조하세요.
- Amazon AWS S3의 서비스에 이슈가 있을 수 있습니다. AWS Service 상태 대시보드를 참고하고 다시 시도하세요.