* first commit

This commit is contained in:
Marin Todorov
2016-04-19 16:24:14 +02:00
parent 043b0bd024
commit e34d17cbd1
455 changed files with 99041 additions and 8 deletions

9
Example/Podfile Normal file
View File

@@ -0,0 +1,9 @@
source 'https://github.com/CocoaPods/Specs.git'
use_frameworks!
target 'RxRealm_Example', :exclusive => true do
platform :ios, '8.3'
pod 'RxRealm', :path => '../'
pod 'RxSwift'
pod 'RealmSwift'
end

View File

@@ -0,0 +1 @@
../../../../Realm/include/Realm/RLMAccessor.h

View File

@@ -0,0 +1 @@
../../../../Realm/include/Realm/RLMArray.h

View File

@@ -0,0 +1 @@
../../../../Realm/include/Realm/RLMArray_Private.h

View File

@@ -0,0 +1 @@
../../../../Realm/include/Realm/RLMCollection.h

View File

@@ -0,0 +1 @@
../../../../Realm/include/Realm/RLMConstants.h

View File

@@ -0,0 +1 @@
../../../../Realm/include/Realm/RLMDefines.h

View File

@@ -0,0 +1 @@
../../../../Realm/include/Realm/RLMListBase.h

View File

@@ -0,0 +1 @@
../../../../Realm/include/Realm/RLMMigration.h

View File

@@ -0,0 +1 @@
../../../../Realm/include/Realm/RLMMigration_Private.h

View File

@@ -0,0 +1 @@
../../../../Realm/include/Realm/RLMObject.h

View File

@@ -0,0 +1 @@
../../../../Realm/include/Realm/RLMObjectBase.h

View File

@@ -0,0 +1 @@
../../../../Realm/include/Realm/RLMObjectBase_Dynamic.h

View File

@@ -0,0 +1 @@
../../../../Realm/include/Realm/RLMObjectSchema.h

View File

@@ -0,0 +1 @@
../../../../Realm/include/Realm/RLMObjectSchema_Private.h

View File

@@ -0,0 +1 @@
../../../../Realm/include/Realm/RLMObjectStore.h

View File

@@ -0,0 +1 @@
../../../../Realm/include/Realm/RLMObject_Private.h

View File

@@ -0,0 +1 @@
../../../../Realm/include/Realm/RLMOptionalBase.h

View File

@@ -0,0 +1 @@
../../../../Realm/include/Realm/RLMPlatform.h

View File

@@ -0,0 +1 @@
../../../../Realm/include/Realm/RLMProperty.h

View File

@@ -0,0 +1 @@
../../../../Realm/include/Realm/RLMProperty_Private.h

View File

@@ -0,0 +1 @@
../../../../Realm/include/Realm/RLMRealm.h

View File

@@ -0,0 +1 @@
../../../../Realm/include/Realm/RLMRealmConfiguration.h

View File

@@ -0,0 +1 @@
../../../../Realm/include/Realm/RLMRealmConfiguration_Private.h

View File

@@ -0,0 +1 @@
../../../../Realm/include/Realm/RLMRealm_Dynamic.h

View File

@@ -0,0 +1 @@
../../../../Realm/include/Realm/RLMRealm_Private.h

View File

@@ -0,0 +1 @@
../../../../Realm/include/Realm/RLMResults.h

View File

@@ -0,0 +1 @@
../../../../Realm/include/Realm/RLMResults_Private.h

View File

@@ -0,0 +1 @@
../../../../Realm/include/Realm/RLMSchema.h

View File

@@ -0,0 +1 @@
../../../../Realm/include/Realm/RLMSchema_Private.h

View File

@@ -0,0 +1 @@
../../../../Realm/include/Realm/Realm.h

View File

@@ -0,0 +1,35 @@
{
"name": "RxRealm",
"version": "0.1.0",
"summary": "An Rx wrapper of Realm's collection type",
"description": "This is an Rx extension that provides an easy and straight-forward way\nto use Realm's natively reactive collection type as an Observable",
"homepage": "https://github.com/RxSwiftCommunity/RxRealm",
"license": "MIT",
"authors": {
"Marin Todorov": "marin@underplot.com"
},
"source": {
"git": "https://github.com/RxSwiftCommunity/RxRealm.git",
"tag": "0.1.0"
},
"requires_arc": true,
"platforms": {
"ios": "8.3",
"osx": "10.10"
},
"source_files": "Pod/Classes/*.swift",
"resource_bundles": {
"RxRealm": [
"RxRealm/Assets/*.png"
]
},
"frameworks": "Foundation",
"dependencies": {
"RealmSwift": [
],
"RxSwift": [
]
}
}

27
Example/Pods/Manifest.lock generated Normal file
View File

@@ -0,0 +1,27 @@
PODS:
- Realm (0.98.8):
- Realm/Headers (= 0.98.8)
- Realm/Headers (0.98.8)
- RealmSwift (0.98.8):
- Realm (= 0.98.8)
- RxRealm (0.1.0):
- RealmSwift
- RxSwift
- RxSwift (2.4)
DEPENDENCIES:
- RealmSwift
- RxRealm (from `../`)
- RxSwift
EXTERNAL SOURCES:
RxRealm:
:path: "../"
SPEC CHECKSUMS:
Realm: 0e293bb62999730599efc3048896bbd4f2e43bcd
RealmSwift: 064262d38113f23ff3508fb20a0a922e696bec01
RxRealm: 5b53b62a9a973cf62a78b784ee7957dfb0b8dd4e
RxSwift: 67b9ef4e8b34fb394e200e754c6a09cc16559f94
COCOAPODS: 0.39.0

8212
Example/Pods/Pods.xcodeproj/project.pbxproj generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,60 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0700"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForAnalyzing = "YES"
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES">
<BuildableReference
BuildableIdentifier = 'primary'
BlueprintIdentifier = 'F3CB873952BEC83113DF060C'
BlueprintName = 'RxRealm'
ReferencedContainer = 'container:Pods.xcodeproj'
BuildableName = 'RxRealm.framework'>
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
buildConfiguration = "Debug">
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
buildConfiguration = "Debug"
allowLocationSimulation = "YES">
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES"
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES">
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

269
Example/Pods/Realm/LICENSE generated Normal file
View File

@@ -0,0 +1,269 @@
TABLE OF CONTENTS
1. Apache License version 2.0
2. Realm Components
3. Export Compliance
-------------------------------------------------------------------------------
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright {yyyy} {name of copyright owner}
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
REALM COMPONENTS
This software contains components with separate copyright and license terms.
Your use of these components is subject to the terms and conditions of the
following licenses.
For the Realm Core component
Realm Core Binary License
Copyright (c) 2011-2014 Realm Inc All rights reserved
Redistribution and use in binary form, with or without modification, is
permitted provided that the following conditions are met:
1. You agree not to attempt to decompile, disassemble, reverse engineer or
otherwise discover the source code from which the binary code was derived.
You may, however, access and obtain a separate license for most of the
source code from which this Software was created, at
http://realm.io/pricing/.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from this
software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
EXPORT COMPLIANCE
You understand that the Software may contain cryptographic functions that may be
subject to export restrictions, and you represent and warrant that you are not
located in a country that is subject to United States export restriction or embargo,
including Cuba, Iran, North Korea, Sudan, Syria or the Crimea region, and that you
are not on the Department of Commerce list of Denied Persons, Unverified Parties,
or affiliated with a Restricted Entity.
You agree to comply with all export, re-export and import restrictions and
regulations of the Department of Commerce or other agency or authority of the
United States or other applicable countries. You also agree not to transfer, or
authorize the transfer of, directly or indirectly, the Software to any prohibited
country, including Cuba, Iran, North Korea, Sudan, Syria or the Crimea region,
or to any person or organization on or affiliated with the Department of
Commerce lists of Denied Persons, Unverified Parties or Restricted Entities, or
otherwise in violation of any such restrictions or regulations.

70
Example/Pods/Realm/README.md generated Normal file
View File

@@ -0,0 +1,70 @@
![Realm](https://github.com/realm/realm-cocoa/raw/master/logo.png)
Realm is a mobile database that runs directly inside phones, tablets or wearables.
This repository holds the source code for the iOS & OSX versions of Realm, for both Swift & Objective-C.
## Features
* **Mobile-first:** Realm is the first database built from the ground up to run directly inside phones, tablets and wearables.
* **Simple:** Data is directly [exposed as objects](https://realm.io/docs/objc/latest/#models) and [queryable by code](https://realm.io/docs/objc/latest/#queries), removing the need for ORM's riddled with performance & maintenance issues. Plus, we've worked hard to [keep our API down to just 4 common classes](https://realm.io/docs/objc/latest/api/) (Object, Array, Results and Realms) and 1 utility class (Migrations): most of our users pick it up intuitively, getting simple apps up & running in minutes.
* **Modern:** Realm supports relationships, generics, vectorization and even Swift.
* **Fast:** Realm is faster than even raw SQLite on common operations, while maintaining an extremely rich feature set.
## Getting Started
Please see the detailed instructions in our docs to add [Realm Objective-C](https://realm.io/docs/objc/latest/#installation) _or_ [Realm Swift](https://realm.io/docs/swift/latest/#installation) to your Xcode project.
## Documentation
### Realm Objective-C
The documentation can be found at [realm.io/docs/objc/latest](https://realm.io/docs/objc/latest).
The API reference is located at [realm.io/docs/objc/latest/api](https://realm.io/docs/objc/latest/api).
### Realm Swift
The documentation can be found at [realm.io/docs/swift/latest](https://realm.io/docs/swift/latest).
The API reference is located at [realm.io/docs/swift/latest/api](https://realm.io/docs/swift/latest/api).
## Getting Help
- **Need help with your code?**: Look for previous questions on the [#realm tag](https://stackoverflow.com/questions/tagged/realm?sort=newest) — or [ask a new question](https://stackoverflow.com/questions/ask?tags=realm). We activtely monitor & answer questions on SO!
- **Have a bug to report?** [Open an issue](https://github.com/realm/realm-cocoa/issues/new). If possible, include the version of Realm, a full log, the Realm file, and a project that shows the issue.
- **Have a feature request?** [Open an issue](https://github.com/realm/realm-cocoa/issues/new). Tell us what the feature should do, and why you want the feature.
- Sign up for our [**Community Newsletter**](http://eepurl.com/VEKCn) to get regular tips, learn about other use-cases and get alerted of blogposts and tutorials about Realm.
## Building Realm
In case you don't want to use the precompiled version, you can build Realm yourself from source.
Prerequisites:
* Building Realm requires Xcode 6.4-7.2.
* Building Realm documentation requires [jazzy](https://github.com/realm/jazzy)
Once you have all the necessary prerequisites, building Realm.framework just takes a single command: `sh build.sh build`. You'll need an internet connection the first time you build Realm to download the core binary.
Run `sh build.sh help` to see all the actions you can perform (build ios/osx, generate docs, test, etc.).
Executing the examples under the `examples/` folder, requires that you have built the `Realm.framework`.
## Contributing
See [CONTRIBUTING.md](CONTRIBUTING.md) for more details!
## License
Realm Objective-C & Realm Swift are published under the Apache 2.0 license.
The underlying core is available under the [Realm Core Binary License](https://github.com/realm/realm-cocoa/blob/master/LICENSE#L210-L243) while we [work to open-source it under the Apache 2.0 license](https://realm.io/docs/objc/latest/#faq).
**This product is not being made available to any person located in Cuba, Iran,
North Korea, Sudan, Syria or the Crimea region, or to any other person that is
not eligible to receive the product under U.S. law.**
## Feedback
**_If you use Realm and are happy with it, all we ask is that you please consider sending out a tweet mentioning [@realm](https://twitter.com/realm), announce your app on [our mailing-list](https://groups.google.com/forum/#!forum/realm-cocoa), or email [help@realm.io](mailto:help@realm.io) to let us know about it!_**
**_And if you don't like it, please let us know what you would like improved, so we can fix it!_**
![analytics](https://ga-beacon.appspot.com/UA-50247013-2/realm-cocoa/README?pixel)

View File

@@ -0,0 +1,94 @@
////////////////////////////////////////////////////////////////////////////
//
// Copyright 2015 Realm Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////
#include "impl/cached_realm.hpp"
#include "shared_realm.hpp"
using namespace realm;
using namespace realm::_impl;
CachedRealm::CachedRealm(const std::shared_ptr<Realm>& realm, bool cache)
: CachedRealmBase(realm, cache)
{
struct RefCountedWeakPointer {
std::weak_ptr<Realm> realm;
std::atomic<size_t> ref_count = {1};
};
CFRunLoopSourceContext ctx{};
ctx.info = new RefCountedWeakPointer{realm};
ctx.perform = [](void* info) {
if (auto realm = static_cast<RefCountedWeakPointer*>(info)->realm.lock()) {
realm->notify();
}
};
ctx.retain = [](const void* info) {
static_cast<RefCountedWeakPointer*>(const_cast<void*>(info))->ref_count.fetch_add(1, std::memory_order_relaxed);
return info;
};
ctx.release = [](const void* info) {
auto ptr = static_cast<RefCountedWeakPointer*>(const_cast<void*>(info));
if (ptr->ref_count.fetch_add(-1, std::memory_order_acq_rel) == 1) {
delete ptr;
}
};
m_runloop = CFRunLoopGetCurrent();
CFRetain(m_runloop);
m_signal = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &ctx);
CFRunLoopAddSource(m_runloop, m_signal, kCFRunLoopDefaultMode);
}
CachedRealm::CachedRealm(CachedRealm&& rgt)
: CachedRealmBase(std::move(rgt))
, m_runloop(rgt.m_runloop)
, m_signal(rgt.m_signal)
{
rgt.m_runloop = nullptr;
rgt.m_signal = nullptr;
}
CachedRealm& CachedRealm::operator=(CachedRealm&& rgt)
{
CachedRealmBase::operator=(std::move(rgt));
m_runloop = rgt.m_runloop;
m_signal = rgt.m_signal;
rgt.m_runloop = nullptr;
rgt.m_signal = nullptr;
return *this;
}
CachedRealm::~CachedRealm()
{
if (m_signal) {
CFRunLoopSourceInvalidate(m_signal);
CFRelease(m_signal);
CFRelease(m_runloop);
}
}
void CachedRealm::notify()
{
CFRunLoopSourceSignal(m_signal);
// Signalling the source makes it run the next time the runloop gets
// to it, but doesn't make the runloop start if it's currently idle
// waiting for events
CFRunLoopWakeUp(m_runloop);
}

View File

@@ -0,0 +1,226 @@
////////////////////////////////////////////////////////////////////////////
//
// Copyright 2015 Realm Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////
#include "impl/external_commit_helper.hpp"
#include "impl/realm_coordinator.hpp"
#include <asl.h>
#include <assert.h>
#include <fcntl.h>
#include <sstream>
#include <sys/event.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <system_error>
#include <unistd.h>
using namespace realm;
using namespace realm::_impl;
namespace {
// Write a byte to a pipe to notify anyone waiting for data on the pipe
void notify_fd(int fd)
{
while (true) {
char c = 0;
ssize_t ret = write(fd, &c, 1);
if (ret == 1) {
break;
}
// If the pipe's buffer is full, we need to read some of the old data in
// it to make space. We don't just read in the code waiting for
// notifications so that we can notify multiple waiters with a single
// write.
assert(ret == -1 && errno == EAGAIN);
char buff[1024];
read(fd, buff, sizeof buff);
}
}
} // anonymous namespace
void ExternalCommitHelper::FdHolder::close()
{
if (m_fd != -1) {
::close(m_fd);
}
m_fd = -1;
}
// Inter-thread and inter-process notifications of changes are done using a
// named pipe in the filesystem next to the Realm file. Everyone who wants to be
// notified of commits waits for data to become available on the pipe, and anyone
// who commits a write transaction writes data to the pipe after releasing the
// write lock. Note that no one ever actually *reads* from the pipe: the data
// actually written is meaningless, and trying to read from a pipe from multiple
// processes at once is fraught with race conditions.
// When a RLMRealm instance is created, we add a CFRunLoopSource to the current
// thread's runloop. On each cycle of the run loop, the run loop checks each of
// its sources for work to do, which in the case of CFRunLoopSource is just
// checking if CFRunLoopSourceSignal has been called since the last time it ran,
// and if so invokes the function pointer supplied when the source is created,
// which in our case just invokes `[realm handleExternalChange]`.
// Listening for external changes is done using kqueue() on a background thread.
// kqueue() lets us efficiently wait until the amount of data which can be read
// from one or more file descriptors has changed, and tells us which of the file
// descriptors it was that changed. We use this to wait on both the shared named
// pipe, and a local anonymous pipe. When data is written to the named pipe, we
// signal the runloop source and wake up the target runloop, and when data is
// written to the anonymous pipe the background thread removes the runloop
// source from the runloop and and shuts down.
ExternalCommitHelper::ExternalCommitHelper(RealmCoordinator& parent)
: m_parent(parent)
{
m_kq = kqueue();
if (m_kq == -1) {
throw std::system_error(errno, std::system_category());
}
#if !TARGET_OS_TV
auto path = parent.get_path() + ".note";
// Create and open the named pipe
int ret = mkfifo(path.c_str(), 0600);
if (ret == -1) {
int err = errno;
if (err == ENOTSUP) {
// Filesystem doesn't support named pipes, so try putting it in tmp instead
// Hash collisions are okay here because they just result in doing
// extra work, as opposed to correctness problems
std::ostringstream ss;
ss << getenv("TMPDIR");
ss << "realm_" << std::hash<std::string>()(path) << ".note";
path = ss.str();
ret = mkfifo(path.c_str(), 0600);
err = errno;
}
// the fifo already existing isn't an error
if (ret == -1 && err != EEXIST) {
throw std::system_error(err, std::system_category());
}
}
m_notify_fd = open(path.c_str(), O_RDWR);
if (m_notify_fd == -1) {
throw std::system_error(errno, std::system_category());
}
// Make writing to the pipe return -1 when the pipe's buffer is full
// rather than blocking until there's space available
ret = fcntl(m_notify_fd, F_SETFL, O_NONBLOCK);
if (ret == -1) {
throw std::system_error(errno, std::system_category());
}
#else // !TARGET_OS_TV
// tvOS does not support named pipes, so use an anonymous pipe instead
int notification_pipe[2];
int ret = pipe(notification_pipe);
if (ret == -1) {
throw std::system_error(errno, std::system_category());
}
m_notify_fd = notification_pipe[0];
m_notify_fd_write = notification_pipe[1];
#endif // TARGET_OS_TV
// Create the anonymous pipe for shutdown notifications
int shutdown_pipe[2];
ret = pipe(shutdown_pipe);
if (ret == -1) {
throw std::system_error(errno, std::system_category());
}
m_shutdown_read_fd = shutdown_pipe[0];
m_shutdown_write_fd = shutdown_pipe[1];
m_thread = std::async(std::launch::async, [=] {
try {
listen();
}
catch (std::exception const& e) {
fprintf(stderr, "uncaught exception in notifier thread: %s: %s\n", typeid(e).name(), e.what());
asl_log(nullptr, nullptr, ASL_LEVEL_ERR, "uncaught exception in notifier thread: %s: %s", typeid(e).name(), e.what());
throw;
}
catch (...) {
fprintf(stderr, "uncaught exception in notifier thread\n");
asl_log(nullptr, nullptr, ASL_LEVEL_ERR, "uncaught exception in notifier thread");
throw;
}
});
}
ExternalCommitHelper::~ExternalCommitHelper()
{
notify_fd(m_shutdown_write_fd);
m_thread.wait(); // Wait for the thread to exit
}
void ExternalCommitHelper::listen()
{
pthread_setname_np("RLMRealm notification listener");
// Set up the kqueue
// EVFILT_READ indicates that we care about data being available to read
// on the given file descriptor.
// EV_CLEAR makes it wait for the amount of data available to be read to
// change rather than just returning when there is any data to read.
struct kevent ke[2];
EV_SET(&ke[0], m_notify_fd, EVFILT_READ, EV_ADD | EV_CLEAR, 0, 0, 0);
EV_SET(&ke[1], m_shutdown_read_fd, EVFILT_READ, EV_ADD | EV_CLEAR, 0, 0, 0);
int ret = kevent(m_kq, ke, 2, nullptr, 0, nullptr);
assert(ret == 0);
while (true) {
struct kevent event;
// Wait for data to become on either fd
// Return code is number of bytes available or -1 on error
ret = kevent(m_kq, nullptr, 0, &event, 1, nullptr);
assert(ret >= 0);
if (ret == 0) {
// Spurious wakeup; just wait again
continue;
}
// Check which file descriptor had activity: if it's the shutdown
// pipe, then someone called -stop; otherwise it's the named pipe
// and someone committed a write transaction
if (event.ident == (uint32_t)m_shutdown_read_fd) {
return;
}
assert(event.ident == (uint32_t)m_notify_fd);
m_parent.on_change();
}
}
void ExternalCommitHelper::notify_others()
{
if (m_notify_fd_write != -1) {
notify_fd(m_notify_fd_write);
}
else {
notify_fd(m_notify_fd);
}
}

View File

@@ -0,0 +1,290 @@
////////////////////////////////////////////////////////////////////////////
//
// Copyright 2015 Realm Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////
#include "impl/async_query.hpp"
#include "impl/realm_coordinator.hpp"
#include "results.hpp"
using namespace realm;
using namespace realm::_impl;
AsyncQuery::AsyncQuery(Results& target)
: m_target_results(&target)
, m_realm(target.get_realm().shared_from_this())
, m_sort(target.get_sort())
, m_sg_version(Realm::Internal::get_shared_group(*m_realm).get_version_of_current_transaction())
{
Query q = target.get_query();
m_query_handover = Realm::Internal::get_shared_group(*m_realm).export_for_handover(q, MutableSourcePayload::Move);
}
AsyncQuery::~AsyncQuery()
{
// unregister() may have been called from a different thread than we're being
// destroyed on, so we need to synchronize access to the interesting fields
// modified there
std::lock_guard<std::mutex> lock(m_target_mutex);
m_realm = nullptr;
}
size_t AsyncQuery::add_callback(std::function<void (std::exception_ptr)> callback)
{
m_realm->verify_thread();
auto next_token = [=] {
size_t token = 0;
for (auto& callback : m_callbacks) {
if (token <= callback.token) {
token = callback.token + 1;
}
}
return token;
};
std::lock_guard<std::mutex> lock(m_callback_mutex);
auto token = next_token();
m_callbacks.push_back({std::move(callback), token, -1ULL});
if (m_callback_index == npos) { // Don't need to wake up if we're already sending notifications
Realm::Internal::get_coordinator(*m_realm).send_commit_notifications();
m_have_callbacks = true;
}
return token;
}
void AsyncQuery::remove_callback(size_t token)
{
Callback old;
{
std::lock_guard<std::mutex> lock(m_callback_mutex);
REALM_ASSERT(m_error || m_callbacks.size() > 0);
auto it = find_if(begin(m_callbacks), end(m_callbacks),
[=](const auto& c) { return c.token == token; });
// We should only fail to find the callback if it was removed due to an error
REALM_ASSERT(m_error || it != end(m_callbacks));
if (it == end(m_callbacks)) {
return;
}
size_t idx = distance(begin(m_callbacks), it);
if (m_callback_index != npos && m_callback_index >= idx) {
--m_callback_index;
}
old = std::move(*it);
m_callbacks.erase(it);
m_have_callbacks = !m_callbacks.empty();
}
}
void AsyncQuery::unregister() noexcept
{
std::lock_guard<std::mutex> lock(m_target_mutex);
m_target_results = nullptr;
m_realm = nullptr;
}
void AsyncQuery::release_query() noexcept
{
{
std::lock_guard<std::mutex> lock(m_target_mutex);
REALM_ASSERT(!m_realm && !m_target_results);
}
m_query = nullptr;
}
bool AsyncQuery::is_alive() const noexcept
{
std::lock_guard<std::mutex> lock(m_target_mutex);
return m_target_results != nullptr;
}
// Most of the inter-thread synchronization for run(), prepare_handover(),
// attach_to(), detach(), release_query() and deliver() is done by
// RealmCoordinator external to this code, which has some potentially
// non-obvious results on which members are and are not safe to use without
// holding a lock.
//
// attach_to(), detach(), run(), prepare_handover(), and release_query() are
// all only ever called on a single thread. call_callbacks() and deliver() are
// called on the same thread. Calls to prepare_handover() and deliver() are
// guarded by a lock.
//
// In total, this means that the safe data flow is as follows:
// - prepare_handover(), attach_to(), detach() and release_query() can read
// members written by each other
// - deliver() can read members written to in prepare_handover(), deliver(),
// and call_callbacks()
// - call_callbacks() and read members written to in deliver()
//
// Separately from this data flow for the query results, all uses of
// m_target_results, m_callbacks, and m_callback_index must be done with the
// appropriate mutex held to avoid race conditions when the Results object is
// destroyed while the background work is running, and to allow removing
// callbacks from any thread.
void AsyncQuery::run()
{
REALM_ASSERT(m_sg);
{
std::lock_guard<std::mutex> target_lock(m_target_mutex);
// Don't run the query if the results aren't actually going to be used
if (!m_target_results || (!m_have_callbacks && !m_target_results->wants_background_updates())) {
return;
}
}
REALM_ASSERT(!m_tv.is_attached());
// If we've run previously, check if we need to rerun
if (m_initial_run_complete) {
// Make an empty tableview from the query to get the table version, since
// Query doesn't expose it
if (m_query->find_all(0, 0, 0).sync_if_needed() == m_handed_over_table_version) {
return;
}
}
m_tv = m_query->find_all();
if (m_sort) {
m_tv.sort(m_sort.columnIndices, m_sort.ascending);
}
}
void AsyncQuery::prepare_handover()
{
m_sg_version = m_sg->get_version_of_current_transaction();
if (!m_tv.is_attached()) {
return;
}
REALM_ASSERT(m_tv.is_in_sync());
m_initial_run_complete = true;
m_handed_over_table_version = m_tv.sync_if_needed();
m_tv_handover = m_sg->export_for_handover(m_tv, MutableSourcePayload::Move);
// detach the TableView as we won't need it again and keeping it around
// makes advance_read() much more expensive
m_tv = TableView();
}
bool AsyncQuery::deliver(SharedGroup& sg, std::exception_ptr err)
{
if (!is_for_current_thread()) {
return false;
}
std::lock_guard<std::mutex> target_lock(m_target_mutex);
// Target results being null here indicates that it was destroyed while we
// were in the process of advancing the Realm version and preparing for
// delivery, i.e. it was destroyed from the "wrong" thread
if (!m_target_results) {
return false;
}
// We can get called before the query has actually had the chance to run if
// we're added immediately before a different set of async results are
// delivered
if (!m_initial_run_complete && !err) {
return false;
}
if (err) {
m_error = err;
return m_have_callbacks;
}
REALM_ASSERT(!m_query_handover);
auto realm_sg_version = Realm::Internal::get_shared_group(*m_realm).get_version_of_current_transaction();
if (m_sg_version != realm_sg_version) {
// Realm version can be newer if a commit was made on our thread or the
// user manually called refresh(), or older if a commit was made on a
// different thread and we ran *really* fast in between the check for
// if the shared group has changed and when we pick up async results
return false;
}
if (m_tv_handover) {
m_tv_handover->version = m_sg_version;
Results::Internal::set_table_view(*m_target_results,
std::move(*sg.import_from_handover(std::move(m_tv_handover))));
m_delivered_table_version = m_handed_over_table_version;
}
REALM_ASSERT(!m_tv_handover);
return m_have_callbacks;
}
void AsyncQuery::call_callbacks()
{
REALM_ASSERT(is_for_current_thread());
while (auto fn = next_callback()) {
fn(m_error);
}
if (m_error) {
// Remove all the callbacks as we never need to call anything ever again
// after delivering an error
std::lock_guard<std::mutex> callback_lock(m_callback_mutex);
m_callbacks.clear();
}
}
std::function<void (std::exception_ptr)> AsyncQuery::next_callback()
{
std::lock_guard<std::mutex> callback_lock(m_callback_mutex);
for (++m_callback_index; m_callback_index < m_callbacks.size(); ++m_callback_index) {
auto& callback = m_callbacks[m_callback_index];
if (m_error || callback.delivered_version != m_delivered_table_version) {
callback.delivered_version = m_delivered_table_version;
return callback.fn;
}
}
m_callback_index = npos;
return nullptr;
}
void AsyncQuery::attach_to(realm::SharedGroup& sg)
{
REALM_ASSERT(!m_sg);
REALM_ASSERT(m_query_handover);
m_query = sg.import_from_handover(std::move(m_query_handover));
m_sg = &sg;
}
void AsyncQuery::detatch()
{
REALM_ASSERT(m_sg);
REALM_ASSERT(m_query);
REALM_ASSERT(!m_tv.is_attached());
m_query_handover = m_sg->export_for_handover(*m_query, MutableSourcePayload::Move);
m_sg = nullptr;
m_query = nullptr;
}

View File

@@ -0,0 +1,480 @@
////////////////////////////////////////////////////////////////////////////
//
// Copyright 2015 Realm Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////
#include "impl/realm_coordinator.hpp"
#include "impl/async_query.hpp"
#include "impl/cached_realm.hpp"
#include "impl/external_commit_helper.hpp"
#include "impl/transact_log_handler.hpp"
#include "object_store.hpp"
#include "schema.hpp"
#include <realm/commit_log.hpp>
#include <realm/group_shared.hpp>
#include <realm/lang_bind_helper.hpp>
#include <realm/query.hpp>
#include <realm/table_view.hpp>
#include <cassert>
#include <unordered_map>
#include <unordered_map>
using namespace realm;
using namespace realm::_impl;
static std::mutex s_coordinator_mutex;
static std::unordered_map<std::string, std::weak_ptr<RealmCoordinator>> s_coordinators_per_path;
std::shared_ptr<RealmCoordinator> RealmCoordinator::get_coordinator(StringData path)
{
std::lock_guard<std::mutex> lock(s_coordinator_mutex);
auto& weak_coordinator = s_coordinators_per_path[path];
if (auto coordinator = weak_coordinator.lock()) {
return coordinator;
}
auto coordinator = std::make_shared<RealmCoordinator>();
weak_coordinator = coordinator;
return coordinator;
}
std::shared_ptr<RealmCoordinator> RealmCoordinator::get_existing_coordinator(StringData path)
{
std::lock_guard<std::mutex> lock(s_coordinator_mutex);
auto it = s_coordinators_per_path.find(path);
return it == s_coordinators_per_path.end() ? nullptr : it->second.lock();
}
std::shared_ptr<Realm> RealmCoordinator::get_realm(Realm::Config config)
{
std::lock_guard<std::mutex> lock(m_realm_mutex);
if ((!m_config.read_only && !m_notifier) || (m_config.read_only && m_cached_realms.empty())) {
m_config = config;
if (!config.read_only && !m_notifier) {
try {
m_notifier = std::make_unique<ExternalCommitHelper>(*this);
}
catch (std::system_error const& ex) {
throw RealmFileException(RealmFileException::Kind::AccessError, config.path, ex.code().message());
}
}
}
else {
if (m_config.read_only != config.read_only) {
throw MismatchedConfigException("Realm at path already opened with different read permissions.");
}
if (m_config.in_memory != config.in_memory) {
throw MismatchedConfigException("Realm at path already opened with different inMemory settings.");
}
if (m_config.encryption_key != config.encryption_key) {
throw MismatchedConfigException("Realm at path already opened with a different encryption key.");
}
if (m_config.schema_version != config.schema_version && config.schema_version != ObjectStore::NotVersioned) {
throw MismatchedConfigException("Realm at path already opened with different schema version.");
}
// FIXME: verify that schema is compatible
// Needs to verify that all tables present in both are identical, and
// then updated m_config with any tables present in config but not in
// it
// Public API currently doesn't make it possible to have non-matching
// schemata so it's not a huge issue
if ((false) && m_config.schema != config.schema) {
throw MismatchedConfigException("Realm at path already opened with different schema");
}
}
if (config.cache) {
for (auto& cachedRealm : m_cached_realms) {
if (cachedRealm.is_cached_for_current_thread()) {
// can be null if we jumped in between ref count hitting zero and
// unregister_realm() getting the lock
if (auto realm = cachedRealm.realm()) {
return realm;
}
}
}
}
auto realm = std::make_shared<Realm>(std::move(config));
realm->init(shared_from_this());
m_cached_realms.emplace_back(realm, m_config.cache);
return realm;
}
std::shared_ptr<Realm> RealmCoordinator::get_realm()
{
return get_realm(m_config);
}
const Schema* RealmCoordinator::get_schema() const noexcept
{
return m_cached_realms.empty() ? nullptr : m_config.schema.get();
}
void RealmCoordinator::update_schema(Schema const& schema)
{
// FIXME: this should probably be doing some sort of validation and
// notifying all Realm instances of the new schema in some way
m_config.schema = std::make_unique<Schema>(schema);
}
RealmCoordinator::RealmCoordinator() = default;
RealmCoordinator::~RealmCoordinator()
{
std::lock_guard<std::mutex> coordinator_lock(s_coordinator_mutex);
for (auto it = s_coordinators_per_path.begin(); it != s_coordinators_per_path.end(); ) {
if (it->second.expired()) {
it = s_coordinators_per_path.erase(it);
}
else {
++it;
}
}
}
void RealmCoordinator::unregister_realm(Realm* realm)
{
std::lock_guard<std::mutex> lock(m_realm_mutex);
for (size_t i = 0; i < m_cached_realms.size(); ++i) {
auto& cached_realm = m_cached_realms[i];
if (!cached_realm.expired() && !cached_realm.is_for_realm(realm)) {
continue;
}
if (i + 1 < m_cached_realms.size()) {
cached_realm = std::move(m_cached_realms.back());
}
m_cached_realms.pop_back();
}
}
void RealmCoordinator::clear_cache()
{
std::vector<WeakRealm> realms_to_close;
{
std::lock_guard<std::mutex> lock(s_coordinator_mutex);
for (auto& weak_coordinator : s_coordinators_per_path) {
auto coordinator = weak_coordinator.second.lock();
if (!coordinator) {
continue;
}
coordinator->m_notifier = nullptr;
// Gather a list of all of the realms which will be removed
for (auto& cached_realm : coordinator->m_cached_realms) {
if (auto realm = cached_realm.realm()) {
realms_to_close.push_back(realm);
}
}
}
s_coordinators_per_path.clear();
}
// Close all of the previously cached Realms. This can't be done while
// s_coordinator_mutex is held as it may try to re-lock it.
for (auto& weak_realm : realms_to_close) {
if (auto realm = weak_realm.lock()) {
realm->close();
}
}
}
void RealmCoordinator::send_commit_notifications()
{
REALM_ASSERT(!m_config.read_only);
m_notifier->notify_others();
}
void RealmCoordinator::pin_version(uint_fast64_t version, uint_fast32_t index)
{
if (m_async_error) {
return;
}
SharedGroup::VersionID versionid(version, index);
if (!m_advancer_sg) {
try {
std::unique_ptr<Group> read_only_group;
Realm::open_with_config(m_config, m_advancer_history, m_advancer_sg, read_only_group);
REALM_ASSERT(!read_only_group);
m_advancer_sg->begin_read(versionid);
}
catch (...) {
m_async_error = std::current_exception();
m_advancer_sg = nullptr;
m_advancer_history = nullptr;
}
}
else if (m_new_queries.empty()) {
// If this is the first query then we don't already have a read transaction
m_advancer_sg->begin_read(versionid);
}
else if (versionid < m_advancer_sg->get_version_of_current_transaction()) {
// Ensure we're holding a readlock on the oldest version we have a
// handover object for, as handover objects don't
m_advancer_sg->end_read();
m_advancer_sg->begin_read(versionid);
}
}
void RealmCoordinator::register_query(std::shared_ptr<AsyncQuery> query)
{
auto version = query->version();
auto& self = Realm::Internal::get_coordinator(query->get_realm());
{
std::lock_guard<std::mutex> lock(self.m_query_mutex);
self.pin_version(version.version, version.index);
self.m_new_queries.push_back(std::move(query));
}
}
void RealmCoordinator::clean_up_dead_queries()
{
auto swap_remove = [&](auto& container) {
bool did_remove = false;
for (size_t i = 0; i < container.size(); ++i) {
if (container[i]->is_alive())
continue;
// Ensure the query is destroyed here even if there's lingering refs
// to the async query elsewhere
container[i]->release_query();
if (container.size() > i + 1)
container[i] = std::move(container.back());
container.pop_back();
--i;
did_remove = true;
}
return did_remove;
};
if (swap_remove(m_queries)) {
// Make sure we aren't holding on to read versions needlessly if there
// are no queries left, but don't close them entirely as opening shared
// groups is expensive
if (m_queries.empty() && m_query_sg) {
m_query_sg->end_read();
}
}
if (swap_remove(m_new_queries)) {
if (m_new_queries.empty() && m_advancer_sg) {
m_advancer_sg->end_read();
}
}
}
void RealmCoordinator::on_change()
{
run_async_queries();
std::lock_guard<std::mutex> lock(m_realm_mutex);
for (auto& realm : m_cached_realms) {
realm.notify();
}
}
void RealmCoordinator::run_async_queries()
{
std::unique_lock<std::mutex> lock(m_query_mutex);
clean_up_dead_queries();
if (m_queries.empty() && m_new_queries.empty()) {
return;
}
if (!m_async_error) {
open_helper_shared_group();
}
if (m_async_error) {
move_new_queries_to_main();
return;
}
advance_helper_shared_group_to_latest();
// Make a copy of the queries vector so that we can release the lock while
// we run the queries
auto queries_to_run = m_queries;
lock.unlock();
for (auto& query : queries_to_run) {
query->run();
}
// Reacquire the lock while updating the fields that are actually read on
// other threads
{
lock.lock();
for (auto& query : queries_to_run) {
query->prepare_handover();
}
}
clean_up_dead_queries();
}
void RealmCoordinator::open_helper_shared_group()
{
if (!m_query_sg) {
try {
std::unique_ptr<Group> read_only_group;
Realm::open_with_config(m_config, m_query_history, m_query_sg, read_only_group);
REALM_ASSERT(!read_only_group);
m_query_sg->begin_read();
}
catch (...) {
// Store the error to be passed to the async queries
m_async_error = std::current_exception();
m_query_sg = nullptr;
m_query_history = nullptr;
}
}
else if (m_queries.empty()) {
m_query_sg->begin_read();
}
}
void RealmCoordinator::move_new_queries_to_main()
{
m_queries.reserve(m_queries.size() + m_new_queries.size());
std::move(m_new_queries.begin(), m_new_queries.end(), std::back_inserter(m_queries));
m_new_queries.clear();
}
void RealmCoordinator::advance_helper_shared_group_to_latest()
{
if (m_new_queries.empty()) {
LangBindHelper::advance_read(*m_query_sg);
return;
}
// Sort newly added queries by their source version so that we can pull them
// all forward to the latest version in a single pass over the transaction log
std::sort(m_new_queries.begin(), m_new_queries.end(), [](auto const& lft, auto const& rgt) {
return lft->version() < rgt->version();
});
// Import all newly added queries to our helper SG
for (auto& query : m_new_queries) {
LangBindHelper::advance_read(*m_advancer_sg, query->version());
query->attach_to(*m_advancer_sg);
}
// Advance both SGs to the newest version
LangBindHelper::advance_read(*m_advancer_sg);
LangBindHelper::advance_read(*m_query_sg, m_advancer_sg->get_version_of_current_transaction());
// Transfer all new queries over to the main SG
for (auto& query : m_new_queries) {
query->detatch();
query->attach_to(*m_query_sg);
}
move_new_queries_to_main();
m_advancer_sg->end_read();
}
void RealmCoordinator::advance_to_ready(Realm& realm)
{
decltype(m_queries) queries;
auto& sg = Realm::Internal::get_shared_group(realm);
auto get_query_version = [&] {
for (auto& query : m_queries) {
auto version = query->version();
if (version != SharedGroup::VersionID{}) {
return version;
}
}
return SharedGroup::VersionID{};
};
SharedGroup::VersionID version;
{
std::lock_guard<std::mutex> lock(m_query_mutex);
version = get_query_version();
}
// no async queries; just advance to latest
if (version.version == std::numeric_limits<uint_fast64_t>::max()) {
transaction::advance(sg, realm.m_binding_context.get());
return;
}
// async results are out of date; ignore
if (version < sg.get_version_of_current_transaction()) {
return;
}
while (true) {
// Advance to the ready version without holding any locks because it
// may end up calling user code (in did_change() notifications)
transaction::advance(sg, realm.m_binding_context.get(), version);
// Reacquire the lock and recheck the query version, as the queries may
// have advanced to a later version while we didn't hold the lock. If
// so, we need to release the lock and re-advance
std::lock_guard<std::mutex> lock(m_query_mutex);
version = get_query_version();
if (version.version == std::numeric_limits<uint_fast64_t>::max())
return;
if (version != sg.get_version_of_current_transaction())
continue;
// Query version now matches the SG version, so we can deliver them
for (auto& query : m_queries) {
if (query->deliver(sg, m_async_error)) {
queries.push_back(query);
}
}
break;
}
for (auto& query : queries) {
query->call_callbacks();
}
}
void RealmCoordinator::process_available_async(Realm& realm)
{
auto& sg = Realm::Internal::get_shared_group(realm);
decltype(m_queries) queries;
{
std::lock_guard<std::mutex> lock(m_query_mutex);
for (auto& query : m_queries) {
if (query->deliver(sg, m_async_error)) {
queries.push_back(query);
}
}
}
for (auto& query : queries) {
query->call_callbacks();
}
}

View File

@@ -0,0 +1,467 @@
////////////////////////////////////////////////////////////////////////////
//
// Copyright 2015 Realm Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////
#include "impl/transact_log_handler.hpp"
#include "binding_context.hpp"
#include <realm/commit_log.hpp>
#include <realm/group_shared.hpp>
#include <realm/lang_bind_helper.hpp>
using namespace realm;
namespace {
// A transaction log handler that just validates that all operations made are
// ones supported by the object store
class TransactLogValidator {
// Index of currently selected table
size_t m_current_table = 0;
// Tables which were created during the transaction being processed, which
// can have columns inserted without a schema version bump
std::vector<size_t> m_new_tables;
REALM_NORETURN
REALM_NOINLINE
void schema_error()
{
throw std::runtime_error("Schema mismatch detected: another process has modified the Realm file's schema in an incompatible way");
}
// Throw an exception if the currently modified table already existed before
// the current set of modifications
bool schema_error_unless_new_table()
{
if (std::find(begin(m_new_tables), end(m_new_tables), m_current_table) == end(m_new_tables)) {
schema_error();
}
return true;
}
protected:
size_t current_table() const noexcept { return m_current_table; }
public:
// Schema changes which don't involve a change in the schema version are
// allowed
bool add_search_index(size_t) { return true; }
bool remove_search_index(size_t) { return true; }
// Creating entirely new tables without a schema version bump is allowed, so
// we need to track if new columns are being added to a new table or an
// existing one
bool insert_group_level_table(size_t table_ndx, size_t, StringData)
{
// Shift any previously added tables after the new one
for (auto& table : m_new_tables) {
if (table >= table_ndx)
++table;
}
m_new_tables.push_back(table_ndx);
return true;
}
bool insert_column(size_t, DataType, StringData, bool) { return schema_error_unless_new_table(); }
bool insert_link_column(size_t, DataType, StringData, size_t, size_t) { return schema_error_unless_new_table(); }
bool add_primary_key(size_t) { return schema_error_unless_new_table(); }
bool set_link_type(size_t, LinkType) { return schema_error_unless_new_table(); }
// Removing or renaming things while a Realm is open is never supported
bool erase_group_level_table(size_t, size_t) { schema_error(); }
bool rename_group_level_table(size_t, StringData) { schema_error(); }
bool erase_column(size_t) { schema_error(); }
bool erase_link_column(size_t, size_t, size_t) { schema_error(); }
bool rename_column(size_t, StringData) { schema_error(); }
bool remove_primary_key() { schema_error(); }
bool move_column(size_t, size_t) { schema_error(); }
bool move_group_level_table(size_t, size_t) { schema_error(); }
bool select_descriptor(int levels, const size_t*)
{
// subtables not supported
return levels == 0;
}
bool select_table(size_t group_level_ndx, int, const size_t*) noexcept
{
m_current_table = group_level_ndx;
return true;
}
bool select_link_list(size_t, size_t, size_t) { return true; }
// Non-schema changes are all allowed
void parse_complete() { }
bool insert_empty_rows(size_t, size_t, size_t, bool) { return true; }
bool erase_rows(size_t, size_t, size_t, bool) { return true; }
bool swap_rows(size_t, size_t) { return true; }
bool clear_table() noexcept { return true; }
bool link_list_set(size_t, size_t) { return true; }
bool link_list_insert(size_t, size_t) { return true; }
bool link_list_erase(size_t) { return true; }
bool link_list_nullify(size_t) { return true; }
bool link_list_clear(size_t) { return true; }
bool link_list_move(size_t, size_t) { return true; }
bool link_list_swap(size_t, size_t) { return true; }
bool set_int(size_t, size_t, int_fast64_t) { return true; }
bool set_bool(size_t, size_t, bool) { return true; }
bool set_float(size_t, size_t, float) { return true; }
bool set_double(size_t, size_t, double) { return true; }
bool set_string(size_t, size_t, StringData) { return true; }
bool set_binary(size_t, size_t, BinaryData) { return true; }
bool set_date_time(size_t, size_t, DateTime) { return true; }
bool set_table(size_t, size_t) { return true; }
bool set_mixed(size_t, size_t, const Mixed&) { return true; }
bool set_link(size_t, size_t, size_t, size_t) { return true; }
bool set_null(size_t, size_t) { return true; }
bool nullify_link(size_t, size_t, size_t) { return true; }
bool insert_substring(size_t, size_t, size_t, StringData) { return true; }
bool erase_substring(size_t, size_t, size_t, size_t) { return true; }
bool optimize_table() { return true; }
bool set_int_unique(size_t, size_t, size_t, int_fast64_t) { return true; }
bool set_string_unique(size_t, size_t, size_t, StringData) { return true; }
bool change_link_targets(size_t, size_t) { return true; }
};
// Extends TransactLogValidator to also track changes and report it to the
// binding context if any properties are being observed
class TransactLogObserver : public TransactLogValidator {
using ColumnInfo = BindingContext::ColumnInfo;
using ObserverState = BindingContext::ObserverState;
// Observed table rows which need change information
std::vector<ObserverState> m_observers;
// Userdata pointers for rows which have been deleted
std::vector<void *> invalidated;
// Delegate to send change information to
BindingContext* m_context;
// Change information for the currently selected LinkList, if any
ColumnInfo* m_active_linklist = nullptr;
// Tables which were created during the transaction being processed, which
// can have columns inserted without a schema version bump
std::vector<size_t> m_new_tables;
// Get the change info for the given column, creating it if needed
static ColumnInfo& get_change(ObserverState& state, size_t i)
{
if (state.changes.size() <= i) {
state.changes.resize(std::max(state.changes.size() * 2, i + 1));
}
return state.changes[i];
}
// Loop over the columns which were changed in an observer state
template<typename Func>
static void for_each(ObserverState& state, Func&& f)
{
for (size_t i = 0; i < state.changes.size(); ++i) {
auto const& change = state.changes[i];
if (change.changed) {
f(i, change);
}
}
}
// Mark the given row/col as needing notifications sent
bool mark_dirty(size_t row_ndx, size_t col_ndx)
{
auto it = lower_bound(begin(m_observers), end(m_observers), ObserverState{current_table(), row_ndx, nullptr});
if (it != end(m_observers) && it->table_ndx == current_table() && it->row_ndx == row_ndx) {
get_change(*it, col_ndx).changed = true;
}
return true;
}
// Remove the given observer from the list of observed objects and add it
// to the listed of invalidated objects
void invalidate(ObserverState *o)
{
invalidated.push_back(o->info);
m_observers.erase(m_observers.begin() + (o - &m_observers[0]));
}
public:
template<typename Func>
TransactLogObserver(BindingContext* context, SharedGroup& sg, Func&& func, bool validate_schema_changes)
: m_context(context)
{
if (!context) {
if (validate_schema_changes) {
// The handler functions are non-virtual, so the parent class's
// versions are called if we don't need to track changes to observed
// objects
func(static_cast<TransactLogValidator&>(*this));
}
else {
func();
}
return;
}
m_observers = context->get_observed_rows();
if (m_observers.empty()) {
auto old_version = sg.get_version_of_current_transaction();
if (validate_schema_changes) {
func(static_cast<TransactLogValidator&>(*this));
}
else {
func();
}
if (old_version != sg.get_version_of_current_transaction()) {
context->did_change({}, {});
}
return;
}
func(*this);
context->did_change(m_observers, invalidated);
}
// Called at the end of the transaction log immediately before the version
// is advanced
void parse_complete()
{
m_context->will_change(m_observers, invalidated);
}
bool insert_group_level_table(size_t table_ndx, size_t prior_size, StringData name)
{
for (auto& observer : m_observers) {
if (observer.table_ndx >= table_ndx)
++observer.table_ndx;
}
TransactLogValidator::insert_group_level_table(table_ndx, prior_size, name);
return true;
}
bool insert_empty_rows(size_t, size_t, size_t, bool)
{
// rows are only inserted at the end, so no need to do anything
return true;
}
bool erase_rows(size_t row_ndx, size_t, size_t last_row_ndx, bool unordered)
{
for (size_t i = 0; i < m_observers.size(); ++i) {
auto& o = m_observers[i];
if (o.table_ndx == current_table()) {
if (o.row_ndx == row_ndx) {
invalidate(&o);
--i;
}
else if (unordered && o.row_ndx == last_row_ndx) {
o.row_ndx = row_ndx;
}
else if (!unordered && o.row_ndx > row_ndx) {
o.row_ndx -= 1;
}
}
}
return true;
}
bool clear_table()
{
for (size_t i = 0; i < m_observers.size(); ) {
auto& o = m_observers[i];
if (o.table_ndx == current_table()) {
invalidate(&o);
}
else {
++i;
}
}
return true;
}
bool select_link_list(size_t col, size_t row, size_t)
{
m_active_linklist = nullptr;
for (auto& o : m_observers) {
if (o.table_ndx == current_table() && o.row_ndx == row) {
m_active_linklist = &get_change(o, col);
break;
}
}
return true;
}
void append_link_list_change(ColumnInfo::Kind kind, size_t index) {
ColumnInfo *o = m_active_linklist;
if (!o || o->kind == ColumnInfo::Kind::SetAll) {
// Active LinkList isn't observed or already has multiple kinds of changes
return;
}
if (o->kind == ColumnInfo::Kind::None) {
o->kind = kind;
o->changed = true;
o->indices.add(index);
}
else if (o->kind == kind) {
if (kind == ColumnInfo::Kind::Remove) {
o->indices.add_shifted(index);
}
else if (kind == ColumnInfo::Kind::Insert) {
o->indices.insert_at(index);
}
else {
o->indices.add(index);
}
}
else {
// Array KVO can only send a single kind of change at a time, so
// if there's multiple just give up and send "Set"
o->indices.set(0);
o->kind = ColumnInfo::Kind::SetAll;
}
}
bool link_list_set(size_t index, size_t)
{
append_link_list_change(ColumnInfo::Kind::Set, index);
return true;
}
bool link_list_insert(size_t index, size_t)
{
append_link_list_change(ColumnInfo::Kind::Insert, index);
return true;
}
bool link_list_erase(size_t index)
{
append_link_list_change(ColumnInfo::Kind::Remove, index);
return true;
}
bool link_list_nullify(size_t index)
{
append_link_list_change(ColumnInfo::Kind::Remove, index);
return true;
}
bool link_list_swap(size_t index1, size_t index2)
{
append_link_list_change(ColumnInfo::Kind::Set, index1);
append_link_list_change(ColumnInfo::Kind::Set, index2);
return true;
}
bool link_list_clear(size_t old_size)
{
ColumnInfo *o = m_active_linklist;
if (!o || o->kind == ColumnInfo::Kind::SetAll) {
return true;
}
if (o->kind == ColumnInfo::Kind::Remove)
old_size += o->indices.size();
else if (o->kind == ColumnInfo::Kind::Insert)
old_size -= o->indices.size();
o->indices.set(old_size);
o->kind = ColumnInfo::Kind::Remove;
o->changed = true;
return true;
}
bool link_list_move(size_t from, size_t to)
{
ColumnInfo *o = m_active_linklist;
if (!o || o->kind == ColumnInfo::Kind::SetAll) {
return true;
}
if (from > to) {
std::swap(from, to);
}
if (o->kind == ColumnInfo::Kind::None) {
o->kind = ColumnInfo::Kind::Set;
o->changed = true;
}
if (o->kind == ColumnInfo::Kind::Set) {
for (size_t i = from; i <= to; ++i)
o->indices.add(i);
}
else {
o->indices.set(0);
o->kind = ColumnInfo::Kind::SetAll;
}
return true;
}
// Things that just mark the field as modified
bool set_int(size_t col, size_t row, int_fast64_t) { return mark_dirty(row, col); }
bool set_bool(size_t col, size_t row, bool) { return mark_dirty(row, col); }
bool set_float(size_t col, size_t row, float) { return mark_dirty(row, col); }
bool set_double(size_t col, size_t row, double) { return mark_dirty(row, col); }
bool set_string(size_t col, size_t row, StringData) { return mark_dirty(row, col); }
bool set_binary(size_t col, size_t row, BinaryData) { return mark_dirty(row, col); }
bool set_date_time(size_t col, size_t row, DateTime) { return mark_dirty(row, col); }
bool set_table(size_t col, size_t row) { return mark_dirty(row, col); }
bool set_mixed(size_t col, size_t row, const Mixed&) { return mark_dirty(row, col); }
bool set_link(size_t col, size_t row, size_t, size_t) { return mark_dirty(row, col); }
bool set_null(size_t col, size_t row) { return mark_dirty(row, col); }
bool nullify_link(size_t col, size_t row, size_t) { return mark_dirty(row, col); }
bool set_int_unique(size_t col, size_t row, size_t, int_fast64_t) { return mark_dirty(row, col); }
bool set_string_unique(size_t col, size_t row, size_t, StringData) { return mark_dirty(row, col); }
bool insert_substring(size_t col, size_t row, size_t, StringData) { return mark_dirty(row, col); }
bool erase_substring(size_t col, size_t row, size_t, size_t) { return mark_dirty(row, col); }
};
} // anonymous namespace
namespace realm {
namespace _impl {
namespace transaction {
void advance(SharedGroup& sg, BindingContext* context, SharedGroup::VersionID version)
{
TransactLogObserver(context, sg, [&](auto&&... args) {
LangBindHelper::advance_read(sg, std::move(args)..., version);
}, true);
}
void begin(SharedGroup& sg, BindingContext* context, bool validate_schema_changes)
{
TransactLogObserver(context, sg, [&](auto&&... args) {
LangBindHelper::promote_to_write(sg, std::move(args)...);
}, validate_schema_changes);
}
void commit(SharedGroup& sg, BindingContext* context)
{
LangBindHelper::commit_and_continue_as_read(sg);
if (context) {
context->did_change({}, {});
}
}
void cancel(SharedGroup& sg, BindingContext* context)
{
TransactLogObserver(context, sg, [&](auto&&... args) {
LangBindHelper::rollback_and_continue_as_read(sg, std::move(args)...);
}, false);
}
} // namespace transaction
} // namespace _impl
} // namespace realm

View File

@@ -0,0 +1,92 @@
////////////////////////////////////////////////////////////////////////////
//
// Copyright 2015 Realm Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////
#include "index_set.hpp"
using namespace realm;
IndexSet::iterator IndexSet::find(size_t index)
{
for (auto it = m_ranges.begin(), end = m_ranges.end(); it != end; ++it) {
if (it->second > index)
return it;
}
return m_ranges.end();
}
void IndexSet::add(size_t index)
{
do_add(find(index), index);
}
void IndexSet::do_add(iterator it, size_t index)
{
bool more_before = it != m_ranges.begin(), valid = it != m_ranges.end();
if (valid && it->first <= index && it->second > index) {
// index is already in set
}
else if (more_before && (it - 1)->second == index) {
// index is immediately after an existing range
++(it - 1)->second;
}
else if (more_before && valid && (it - 1)->second == it->first) {
// index joins two existing ranges
(it - 1)->second = it->second;
m_ranges.erase(it);
}
else if (valid && it->first == index + 1) {
// index is immediately before an existing range
--it->first;
}
else {
// index is not next to an existing range
m_ranges.insert(it, {index, index + 1});
}
}
void IndexSet::set(size_t len)
{
m_ranges.clear();
if (len) {
m_ranges.push_back({0, len});
}
}
void IndexSet::insert_at(size_t index)
{
auto pos = find(index);
if (pos != m_ranges.end()) {
if (pos->first >= index)
++pos->first;
++pos->second;
for (auto it = pos + 1; it != m_ranges.end(); ++it) {
++it->first;
++it->second;
}
}
do_add(pos, index);
}
void IndexSet::add_shifted(size_t index)
{
auto it = m_ranges.begin();
for (auto end = m_ranges.end(); it != end && it->first <= index; ++it) {
index += it->second - it->first;
}
do_add(it, index);
}

View File

@@ -0,0 +1,86 @@
////////////////////////////////////////////////////////////////////////////
//
// Copyright 2015 Realm Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////
#include "object_schema.hpp"
#include "object_store.hpp"
#include "property.hpp"
#include <realm/group_shared.hpp>
#include <realm/link_view.hpp>
using namespace realm;
ObjectSchema::~ObjectSchema() = default;
ObjectSchema::ObjectSchema(std::string name, std::string primary_key, std::initializer_list<Property> properties)
: name(std::move(name))
, properties(properties)
, primary_key(std::move(primary_key))
{
set_primary_key_property();
}
ObjectSchema::ObjectSchema(const Group *group, const std::string &name) : name(name) {
ConstTableRef table = ObjectStore::table_for_object_type(group, name);
size_t count = table->get_column_count();
properties.reserve(count);
for (size_t col = 0; col < count; col++) {
Property property;
property.name = table->get_column_name(col).data();
property.type = (PropertyType)table->get_column_type(col);
property.is_indexed = table->has_search_index(col);
property.is_primary = false;
property.is_nullable = table->is_nullable(col) || property.type == PropertyTypeObject;
property.table_column = col;
if (property.type == PropertyTypeObject || property.type == PropertyTypeArray) {
// set link type for objects and arrays
ConstTableRef linkTable = table->get_link_target(col);
property.object_type = ObjectStore::object_type_for_table_name(linkTable->get_name().data());
}
properties.push_back(std::move(property));
}
primary_key = realm::ObjectStore::get_primary_key_for_object(group, name);
set_primary_key_property();
}
Property *ObjectSchema::property_for_name(StringData name) {
for (auto& prop : properties) {
if (StringData(prop.name) == name) {
return &prop;
}
}
return nullptr;
}
const Property *ObjectSchema::property_for_name(StringData name) const {
return const_cast<ObjectSchema *>(this)->property_for_name(name);
}
void ObjectSchema::set_primary_key_property()
{
if (primary_key.length()) {
auto primary_key_prop = primary_key_property();
if (!primary_key_prop) {
throw InvalidPrimaryKeyException(name, primary_key);
}
primary_key_prop->is_primary = true;
}
}

View File

@@ -0,0 +1,603 @@
////////////////////////////////////////////////////////////////////////////
//
// Copyright 2015 Realm Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////
#include "object_store.hpp"
#include "schema.hpp"
#include <realm/group.hpp>
#include <realm/link_view.hpp>
#include <realm/table.hpp>
#include <realm/table_view.hpp>
#include <realm/util/assert.hpp>
#include <string.h>
using namespace realm;
namespace {
const char * const c_metadataTableName = "metadata";
const char * const c_versionColumnName = "version";
const size_t c_versionColumnIndex = 0;
const char * const c_primaryKeyTableName = "pk";
const char * const c_primaryKeyObjectClassColumnName = "pk_table";
const size_t c_primaryKeyObjectClassColumnIndex = 0;
const char * const c_primaryKeyPropertyNameColumnName = "pk_property";
const size_t c_primaryKeyPropertyNameColumnIndex = 1;
const size_t c_zeroRowIndex = 0;
const char c_object_table_prefix[] = "class_";
}
const uint64_t ObjectStore::NotVersioned = std::numeric_limits<uint64_t>::max();
bool ObjectStore::has_metadata_tables(const Group *group) {
return group->get_table(c_primaryKeyTableName) && group->get_table(c_metadataTableName);
}
void ObjectStore::create_metadata_tables(Group *group) {
TableRef table = group->get_or_add_table(c_primaryKeyTableName);
if (table->get_column_count() == 0) {
table->add_column(type_String, c_primaryKeyObjectClassColumnName);
table->add_column(type_String, c_primaryKeyPropertyNameColumnName);
}
table = group->get_or_add_table(c_metadataTableName);
if (table->get_column_count() == 0) {
table->add_column(type_Int, c_versionColumnName);
// set initial version
table->add_empty_row();
table->set_int(c_versionColumnIndex, c_zeroRowIndex, ObjectStore::NotVersioned);
}
}
uint64_t ObjectStore::get_schema_version(const Group *group) {
ConstTableRef table = group->get_table(c_metadataTableName);
if (!table || table->get_column_count() == 0) {
return ObjectStore::NotVersioned;
}
return table->get_int(c_versionColumnIndex, c_zeroRowIndex);
}
void ObjectStore::set_schema_version(Group *group, uint64_t version) {
TableRef table = group->get_or_add_table(c_metadataTableName);
table->set_int(c_versionColumnIndex, c_zeroRowIndex, version);
}
StringData ObjectStore::get_primary_key_for_object(const Group *group, StringData object_type) {
ConstTableRef table = group->get_table(c_primaryKeyTableName);
if (!table) {
return "";
}
size_t row = table->find_first_string(c_primaryKeyObjectClassColumnIndex, object_type);
if (row == not_found) {
return "";
}
return table->get_string(c_primaryKeyPropertyNameColumnIndex, row);
}
void ObjectStore::set_primary_key_for_object(Group *group, StringData object_type, StringData primary_key) {
TableRef table = group->get_table(c_primaryKeyTableName);
// get row or create if new object and populate
size_t row = table->find_first_string(c_primaryKeyObjectClassColumnIndex, object_type);
if (row == not_found && primary_key.size()) {
row = table->add_empty_row();
table->set_string(c_primaryKeyObjectClassColumnIndex, row, object_type);
}
// set if changing, or remove if setting to nil
if (primary_key.size() == 0) {
if (row != not_found) {
table->remove(row);
}
}
else {
table->set_string(c_primaryKeyPropertyNameColumnIndex, row, primary_key);
}
}
StringData ObjectStore::object_type_for_table_name(StringData table_name) {
if (table_name.begins_with(c_object_table_prefix)) {
return table_name.substr(sizeof(c_object_table_prefix) - 1);
}
return StringData();
}
std::string ObjectStore::table_name_for_object_type(StringData object_type) {
return std::string(c_object_table_prefix) + object_type.data();
}
TableRef ObjectStore::table_for_object_type(Group *group, StringData object_type) {
return group->get_table(table_name_for_object_type(object_type));
}
ConstTableRef ObjectStore::table_for_object_type(const Group *group, StringData object_type) {
return group->get_table(table_name_for_object_type(object_type));
}
TableRef ObjectStore::table_for_object_type_create_if_needed(Group *group, StringData object_type, bool &created) {
return group->get_or_add_table(table_name_for_object_type(object_type), &created);
}
static inline bool property_has_changed(Property const& p1, Property const& p2) {
return p1.type != p2.type
|| p1.name != p2.name
|| p1.object_type != p2.object_type
|| p1.is_nullable != p2.is_nullable;
}
static inline bool property_can_be_migrated_to_nullable(const Property& old_property, const Property& new_property) {
return old_property.type == new_property.type
&& !old_property.is_nullable
&& new_property.is_nullable
&& new_property.name == old_property.name;
}
void ObjectStore::verify_schema(Schema const& actual_schema, Schema& target_schema, bool allow_missing_tables) {
std::vector<ObjectSchemaValidationException> errors;
for (auto &object_schema : target_schema) {
auto matching_schema = actual_schema.find(object_schema);
if (matching_schema == actual_schema.end()) {
if (!allow_missing_tables) {
errors.emplace_back(ObjectSchemaValidationException(object_schema.name,
"Missing table for object type '" + object_schema.name + "'."));
}
continue;
}
auto more_errors = verify_object_schema(*matching_schema, object_schema);
errors.insert(errors.end(), more_errors.begin(), more_errors.end());
}
if (errors.size()) {
throw SchemaMismatchException(errors);
}
}
std::vector<ObjectSchemaValidationException> ObjectStore::verify_object_schema(ObjectSchema const& table_schema,
ObjectSchema& target_schema) {
std::vector<ObjectSchemaValidationException> exceptions;
// check to see if properties are the same
for (auto& current_prop : table_schema.properties) {
auto target_prop = target_schema.property_for_name(current_prop.name);
if (!target_prop) {
exceptions.emplace_back(MissingPropertyException(table_schema.name, current_prop));
continue;
}
if (property_has_changed(current_prop, *target_prop)) {
exceptions.emplace_back(MismatchedPropertiesException(table_schema.name, current_prop, *target_prop));
continue;
}
// create new property with aligned column
target_prop->table_column = current_prop.table_column;
}
// check for change to primary key
if (table_schema.primary_key != target_schema.primary_key) {
exceptions.emplace_back(ChangedPrimaryKeyException(table_schema.name, table_schema.primary_key, target_schema.primary_key));
}
// check for new missing properties
for (auto& target_prop : target_schema.properties) {
if (!table_schema.property_for_name(target_prop.name)) {
exceptions.emplace_back(ExtraPropertyException(table_schema.name, target_prop));
}
}
return exceptions;
}
template <typename T>
static void copy_property_values(const Property& old_property, const Property& new_property, Table& table,
T (Table::*getter)(std::size_t, std::size_t) const noexcept,
void (Table::*setter)(std::size_t, std::size_t, T)) {
size_t old_column = old_property.table_column, new_column = new_property.table_column;
size_t count = table.size();
for (size_t i = 0; i < count; i++) {
(table.*setter)(new_column, i, (table.*getter)(old_column, i));
}
}
static void copy_property_values(const Property& source, const Property& destination, Table& table) {
switch (destination.type) {
case PropertyTypeInt:
copy_property_values(source, destination, table, &Table::get_int, &Table::set_int);
break;
case PropertyTypeBool:
copy_property_values(source, destination, table, &Table::get_bool, &Table::set_bool);
break;
case PropertyTypeFloat:
copy_property_values(source, destination, table, &Table::get_float, &Table::set_float);
break;
case PropertyTypeDouble:
copy_property_values(source, destination, table, &Table::get_double, &Table::set_double);
break;
case PropertyTypeString:
copy_property_values(source, destination, table, &Table::get_string, &Table::set_string);
break;
case PropertyTypeData:
copy_property_values(source, destination, table, &Table::get_binary, &Table::set_binary);
break;
case PropertyTypeDate:
copy_property_values(source, destination, table, &Table::get_datetime, &Table::set_datetime);
break;
default:
break;
}
}
// set references to tables on targetSchema and create/update any missing or out-of-date tables
// if update existing is true, updates existing tables, otherwise validates existing tables
// NOTE: must be called from within write transaction
void ObjectStore::create_tables(Group *group, Schema &target_schema, bool update_existing) {
// first pass to create missing tables
std::vector<ObjectSchema *> to_update;
for (auto& object_schema : target_schema) {
bool created = false;
ObjectStore::table_for_object_type_create_if_needed(group, object_schema.name, created);
// we will modify tables for any new objectSchema (table was created) or for all if update_existing is true
if (update_existing || created) {
to_update.push_back(&object_schema);
}
}
// second pass adds/removes columns for out of date tables
for (auto& target_object_schema : to_update) {
TableRef table = table_for_object_type(group, target_object_schema->name);
ObjectSchema current_schema(group, target_object_schema->name);
std::vector<Property> &target_props = target_object_schema->properties;
// handle columns changing from required to optional
for (auto& current_prop : current_schema.properties) {
auto target_prop = target_object_schema->property_for_name(current_prop.name);
if (!target_prop || !property_can_be_migrated_to_nullable(current_prop, *target_prop))
continue;
target_prop->table_column = current_prop.table_column;
current_prop.table_column = current_prop.table_column + 1;
table->insert_column(target_prop->table_column, DataType(target_prop->type), target_prop->name, target_prop->is_nullable);
copy_property_values(current_prop, *target_prop, *table);
table->remove_column(current_prop.table_column);
current_prop.table_column = target_prop->table_column;
}
bool inserted_placeholder_column = false;
// remove extra columns
size_t deleted = 0;
for (auto& current_prop : current_schema.properties) {
current_prop.table_column -= deleted;
auto target_prop = target_object_schema->property_for_name(current_prop.name);
if (!target_prop || (property_has_changed(current_prop, *target_prop)
&& !property_can_be_migrated_to_nullable(current_prop, *target_prop))) {
if (deleted == current_schema.properties.size() - 1) {
// We're about to remove the last column from the table. Insert a placeholder column to preserve
// the number of rows in the table for the addition of new columns below.
table->add_column(type_Bool, "placeholder");
inserted_placeholder_column = true;
}
table->remove_column(current_prop.table_column);
++deleted;
current_prop.table_column = npos;
}
}
// add missing columns
for (auto& target_prop : target_props) {
auto current_prop = current_schema.property_for_name(target_prop.name);
// add any new properties (no old column or old column was removed due to not matching)
if (!current_prop || current_prop->table_column == npos) {
switch (target_prop.type) {
// for objects and arrays, we have to specify target table
case PropertyTypeObject:
case PropertyTypeArray: {
TableRef link_table = ObjectStore::table_for_object_type(group, target_prop.object_type);
REALM_ASSERT(link_table);
target_prop.table_column = table->add_column_link(DataType(target_prop.type), target_prop.name, *link_table);
break;
}
default:
target_prop.table_column = table->add_column(DataType(target_prop.type),
target_prop.name,
target_prop.is_nullable);
break;
}
}
else {
target_prop.table_column = current_prop->table_column;
}
}
if (inserted_placeholder_column) {
// We inserted a placeholder due to removing all columns from the table. Remove it, and update the indices
// of any columns that we inserted after it.
table->remove_column(0);
for (auto& target_prop : target_props) {
target_prop.table_column--;
}
}
// update table metadata
if (target_object_schema->primary_key.length()) {
// if there is a primary key set, check if it is the same as the old key
if (current_schema.primary_key != target_object_schema->primary_key) {
set_primary_key_for_object(group, target_object_schema->name, target_object_schema->primary_key);
}
}
else if (current_schema.primary_key.length()) {
// there is no primary key, so if there was one nil out
set_primary_key_for_object(group, target_object_schema->name, "");
}
}
}
bool ObjectStore::is_schema_at_version(const Group *group, uint64_t version) {
uint64_t old_version = get_schema_version(group);
if (old_version > version && old_version != NotVersioned) {
throw InvalidSchemaVersionException(old_version, version);
}
return old_version == version;
}
bool ObjectStore::needs_update(Schema const& old_schema, Schema const& schema) {
for (auto const& target_schema : schema) {
auto matching_schema = old_schema.find(target_schema);
if (matching_schema == end(old_schema)) {
// Table doesn't exist
return true;
}
if (matching_schema->properties.size() != target_schema.properties.size()) {
// If the number of properties don't match then a migration is required
return false;
}
// Check that all of the property indexes are up to date
for (size_t i = 0, count = target_schema.properties.size(); i < count; ++i) {
if (target_schema.properties[i].is_indexed != matching_schema->properties[i].is_indexed) {
return true;
}
}
}
return false;
}
void ObjectStore::update_realm_with_schema(Group *group, Schema const& old_schema,
uint64_t version, Schema &schema,
MigrationFunction migration) {
// Recheck the schema version after beginning the write transaction as
// another process may have done the migration after we opened the read
// transaction
bool migrating = !is_schema_at_version(group, version);
// create tables
create_metadata_tables(group);
create_tables(group, schema, migrating);
if (!migrating) {
// If we aren't migrating, then verify that all of the tables which
// were already present are valid (newly created ones always are)
verify_schema(old_schema, schema, true);
}
update_indexes(group, schema);
if (!migrating) {
return;
}
// apply the migration block if provided and there's any old data
if (get_schema_version(group) != ObjectStore::NotVersioned) {
migration(group, schema);
validate_primary_column_uniqueness(group, schema);
}
set_schema_version(group, version);
}
Schema ObjectStore::schema_from_group(const Group *group) {
std::vector<ObjectSchema> schema;
for (size_t i = 0; i < group->size(); i++) {
std::string object_type = object_type_for_table_name(group->get_table_name(i));
if (object_type.length()) {
schema.emplace_back(group, object_type);
}
}
return schema;
}
bool ObjectStore::update_indexes(Group *group, Schema &schema) {
bool changed = false;
for (auto& object_schema : schema) {
TableRef table = table_for_object_type(group, object_schema.name);
if (!table) {
continue;
}
for (auto& property : object_schema.properties) {
if (property.requires_index() == table->has_search_index(property.table_column)) {
continue;
}
changed = true;
if (property.requires_index()) {
try {
table->add_search_index(property.table_column);
}
catch (LogicError const&) {
throw PropertyTypeNotIndexableException(object_schema.name, property);
}
}
else {
table->remove_search_index(property.table_column);
}
}
}
return changed;
}
void ObjectStore::validate_primary_column_uniqueness(const Group *group, Schema const& schema) {
for (auto& object_schema : schema) {
auto primary_prop = object_schema.primary_key_property();
if (!primary_prop) {
continue;
}
ConstTableRef table = table_for_object_type(group, object_schema.name);
if (table->get_distinct_view(primary_prop->table_column).size() != table->size()) {
throw DuplicatePrimaryKeyValueException(object_schema.name, *primary_prop);
}
}
}
void ObjectStore::delete_data_for_object(Group *group, StringData object_type) {
TableRef table = table_for_object_type(group, object_type);
if (table) {
group->remove_table(table->get_index_in_group());
set_primary_key_for_object(group, object_type, "");
}
}
bool ObjectStore::is_empty(const Group *group) {
for (size_t i = 0; i < group->size(); i++) {
ConstTableRef table = group->get_table(i);
std::string object_type = object_type_for_table_name(table->get_name());
if (!object_type.length()) {
continue;
}
if (!table->is_empty()) {
return false;
}
}
return true;
}
InvalidSchemaVersionException::InvalidSchemaVersionException(uint64_t old_version, uint64_t new_version) :
m_old_version(old_version), m_new_version(new_version)
{
m_what = "Provided schema version " + std::to_string(new_version) + " is less than last set version " + std::to_string(old_version) + ".";
}
DuplicatePrimaryKeyValueException::DuplicatePrimaryKeyValueException(std::string const& object_type, Property const& property) :
m_object_type(object_type), m_property(property)
{
m_what = "Primary key property '" + property.name + "' has duplicate values after migration.";
}
SchemaValidationException::SchemaValidationException(std::vector<ObjectSchemaValidationException> const& errors) :
m_validation_errors(errors)
{
m_what = "Schema validation failed due to the following errors: ";
for (auto const& error : errors) {
m_what += std::string("\n- ") + error.what();
}
}
SchemaMismatchException::SchemaMismatchException(std::vector<ObjectSchemaValidationException> const& errors) :
m_validation_errors(errors)
{
m_what ="Migration is required due to the following errors: ";
for (auto const& error : errors) {
m_what += std::string("\n- ") + error.what();
}
}
PropertyTypeNotIndexableException::PropertyTypeNotIndexableException(std::string const& object_type, Property const& property) :
ObjectSchemaPropertyException(object_type, property)
{
m_what = "Can't index property " + object_type + "." + property.name + ": indexing a property of type '" + string_for_property_type(property.type) + "' is currently not supported";
}
ExtraPropertyException::ExtraPropertyException(std::string const& object_type, Property const& property) :
ObjectSchemaPropertyException(object_type, property)
{
m_what = "Property '" + property.name + "' has been added to latest object model.";
}
MissingPropertyException::MissingPropertyException(std::string const& object_type, Property const& property) :
ObjectSchemaPropertyException(object_type, property)
{
m_what = "Property '" + property.name + "' is missing from latest object model.";
}
InvalidNullabilityException::InvalidNullabilityException(std::string const& object_type, Property const& property) :
ObjectSchemaPropertyException(object_type, property)
{
if (property.type == PropertyTypeObject) {
m_what = "'Object' property '" + property.name + "' must be nullable.";
}
else {
m_what = "Array or Mixed property '" + property.name + "' cannot be nullable";
}
}
MissingObjectTypeException::MissingObjectTypeException(std::string const& object_type, Property const& property) :
ObjectSchemaPropertyException(object_type, property)
{
m_what = "Target type '" + property.object_type + "' doesn't exist for property '" + property.name + "'.";
}
MismatchedPropertiesException::MismatchedPropertiesException(std::string const& object_type, Property const& old_property, Property const& new_property) :
ObjectSchemaValidationException(object_type), m_old_property(old_property), m_new_property(new_property)
{
if (new_property.type != old_property.type) {
m_what = "Property types for '" + old_property.name + "' property do not match. Old type '" + string_for_property_type(old_property.type) +
"', new type '" + string_for_property_type(new_property.type) + "'";
}
else if (new_property.object_type != old_property.object_type) {
m_what = "Target object type for property '" + old_property.name + "' do not match. Old type '" + old_property.object_type + "', new type '" + new_property.object_type + "'";
}
else if (new_property.is_nullable != old_property.is_nullable) {
m_what = "Nullability for property '" + old_property.name + "' has changed from '" + std::to_string(old_property.is_nullable) + "' to '" + std::to_string(new_property.is_nullable) + "'.";
}
}
ChangedPrimaryKeyException::ChangedPrimaryKeyException(std::string const& object_type, std::string const& old_primary, std::string const& new_primary) : ObjectSchemaValidationException(object_type), m_old_primary(old_primary), m_new_primary(new_primary)
{
if (old_primary.size()) {
m_what = "Property '" + old_primary + "' is no longer a primary key.";
}
else {
m_what = "Property '" + new_primary + "' has been made a primary key.";
}
}
InvalidPrimaryKeyException::InvalidPrimaryKeyException(std::string const& object_type, std::string const& primary) :
ObjectSchemaValidationException(object_type), m_primary_key(primary)
{
m_what = "Specified primary key property '" + primary + "' does not exist.";
}
DuplicatePrimaryKeysException::DuplicatePrimaryKeysException(std::string const& object_type) : ObjectSchemaValidationException(object_type)
{
m_what = "Duplicate primary keys for object '" + object_type + "'.";
}

View File

@@ -0,0 +1,424 @@
////////////////////////////////////////////////////////////////////////////
//
// Copyright 2015 Realm Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////
#include "results.hpp"
#include "impl/async_query.hpp"
#include "impl/realm_coordinator.hpp"
#include "object_store.hpp"
#include <stdexcept>
using namespace realm;
#ifdef __has_cpp_attribute
#define REALM_HAS_CCP_ATTRIBUTE(attr) __has_cpp_attribute(attr)
#else
#define REALM_HAS_CCP_ATTRIBUTE(attr) 0
#endif
#if REALM_HAS_CCP_ATTRIBUTE(clang::fallthrough)
#define REALM_FALLTHROUGH [[clang::fallthrough]]
#else
#define REALM_FALLTHROUGH
#endif
Results::Results(SharedRealm r, Query q, SortOrder s)
: m_realm(std::move(r))
, m_query(std::move(q))
, m_table(m_query.get_table().get())
, m_sort(std::move(s))
, m_mode(Mode::Query)
{
}
Results::Results(SharedRealm r, Table& table)
: m_realm(std::move(r))
, m_table(&table)
, m_mode(Mode::Table)
{
}
Results::Results(SharedRealm r, SortOrder s, TableView tv)
: m_realm(std::move(r))
, m_table_view(std::move(tv))
, m_table(&m_table_view.get_parent())
, m_sort(std::move(s))
, m_mode(Mode::TableView)
{
}
Results::~Results()
{
if (m_background_query) {
m_background_query->unregister();
}
}
void Results::validate_read() const
{
if (m_realm)
m_realm->verify_thread();
if (m_table && !m_table->is_attached())
throw InvalidatedException();
if (m_mode == Mode::TableView && !m_table_view.is_attached())
throw InvalidatedException();
}
void Results::validate_write() const
{
validate_read();
if (!m_realm || !m_realm->is_in_transaction())
throw InvalidTransactionException("Must be in a write transaction");
}
size_t Results::size()
{
validate_read();
switch (m_mode) {
case Mode::Empty: return 0;
case Mode::Table: return m_table->size();
case Mode::Query: return m_query.count();
case Mode::TableView:
update_tableview();
return m_table_view.size();
}
REALM_UNREACHABLE();
}
RowExpr Results::get(size_t row_ndx)
{
validate_read();
switch (m_mode) {
case Mode::Empty: break;
case Mode::Table:
if (row_ndx < m_table->size())
return m_table->get(row_ndx);
break;
case Mode::Query:
case Mode::TableView:
update_tableview();
if (row_ndx < m_table_view.size())
return m_table_view.get(row_ndx);
break;
}
throw OutOfBoundsIndexException{row_ndx, size()};
}
util::Optional<RowExpr> Results::first()
{
validate_read();
switch (m_mode) {
case Mode::Empty:
return none;
case Mode::Table:
return m_table->size() == 0 ? util::none : util::make_optional(m_table->front());
case Mode::Query:
case Mode::TableView:
update_tableview();
return m_table_view.size() == 0 ? util::none : util::make_optional(m_table_view.front());
}
REALM_UNREACHABLE();
}
util::Optional<RowExpr> Results::last()
{
validate_read();
switch (m_mode) {
case Mode::Empty:
return none;
case Mode::Table:
return m_table->size() == 0 ? util::none : util::make_optional(m_table->back());
case Mode::Query:
case Mode::TableView:
update_tableview();
return m_table_view.size() == 0 ? util::none : util::make_optional(m_table_view.back());
}
REALM_UNREACHABLE();
}
void Results::update_tableview()
{
validate_read();
switch (m_mode) {
case Mode::Empty:
case Mode::Table:
return;
case Mode::Query:
m_table_view = m_query.find_all();
if (m_sort) {
m_table_view.sort(m_sort.columnIndices, m_sort.ascending);
}
m_mode = Mode::TableView;
break;
case Mode::TableView:
if (!m_background_query && !m_realm->is_in_transaction() && m_realm->can_deliver_notifications()) {
m_background_query = std::make_shared<_impl::AsyncQuery>(*this);
_impl::RealmCoordinator::register_query(m_background_query);
}
m_has_used_table_view = true;
m_table_view.sync_if_needed();
break;
}
}
size_t Results::index_of(Row const& row)
{
validate_read();
if (!row) {
throw DetatchedAccessorException{};
}
if (m_table && row.get_table() != m_table) {
throw IncorrectTableException{
ObjectStore::object_type_for_table_name(m_table->get_name()),
ObjectStore::object_type_for_table_name(row.get_table()->get_name())};
}
return index_of(row.get_index());
}
size_t Results::index_of(size_t row_ndx)
{
validate_read();
switch (m_mode) {
case Mode::Empty:
return not_found;
case Mode::Table:
return row_ndx;
case Mode::Query:
case Mode::TableView:
update_tableview();
return m_table_view.find_by_source_ndx(row_ndx);
}
REALM_UNREACHABLE();
}
template<typename Int, typename Float, typename Double, typename DateTime>
util::Optional<Mixed> Results::aggregate(size_t column, bool return_none_for_empty,
Int agg_int, Float agg_float,
Double agg_double, DateTime agg_datetime)
{
validate_read();
if (!m_table)
return none;
if (column > m_table->get_column_count())
throw OutOfBoundsIndexException{column, m_table->get_column_count()};
auto do_agg = [&](auto const& getter) -> util::Optional<Mixed> {
switch (m_mode) {
case Mode::Empty:
return none;
case Mode::Table:
if (return_none_for_empty && m_table->size() == 0)
return none;
return util::Optional<Mixed>(getter(*m_table));
case Mode::Query:
case Mode::TableView:
this->update_tableview();
if (return_none_for_empty && m_table_view.size() == 0)
return none;
return util::Optional<Mixed>(getter(m_table_view));
}
REALM_UNREACHABLE();
};
switch (m_table->get_column_type(column))
{
case type_DateTime: return do_agg(agg_datetime);
case type_Double: return do_agg(agg_double);
case type_Float: return do_agg(agg_float);
case type_Int: return do_agg(agg_int);
default:
throw UnsupportedColumnTypeException{column, m_table};
}
}
util::Optional<Mixed> Results::max(size_t column)
{
return aggregate(column, true,
[=](auto const& table) { return table.maximum_int(column); },
[=](auto const& table) { return table.maximum_float(column); },
[=](auto const& table) { return table.maximum_double(column); },
[=](auto const& table) { return table.maximum_datetime(column); });
}
util::Optional<Mixed> Results::min(size_t column)
{
return aggregate(column, true,
[=](auto const& table) { return table.minimum_int(column); },
[=](auto const& table) { return table.minimum_float(column); },
[=](auto const& table) { return table.minimum_double(column); },
[=](auto const& table) { return table.minimum_datetime(column); });
}
util::Optional<Mixed> Results::sum(size_t column)
{
return aggregate(column, false,
[=](auto const& table) { return table.sum_int(column); },
[=](auto const& table) { return table.sum_float(column); },
[=](auto const& table) { return table.sum_double(column); },
[=](auto const&) -> util::None { throw UnsupportedColumnTypeException{column, m_table}; });
}
util::Optional<Mixed> Results::average(size_t column)
{
return aggregate(column, true,
[=](auto const& table) { return table.average_int(column); },
[=](auto const& table) { return table.average_float(column); },
[=](auto const& table) { return table.average_double(column); },
[=](auto const&) -> util::None { throw UnsupportedColumnTypeException{column, m_table}; });
}
void Results::clear()
{
switch (m_mode) {
case Mode::Empty:
return;
case Mode::Table:
validate_write();
m_table->clear();
break;
case Mode::Query:
// Not using Query:remove() because building the tableview and
// clearing it is actually significantly faster
case Mode::TableView:
validate_write();
update_tableview();
m_table_view.clear(RemoveMode::unordered);
break;
}
}
Query Results::get_query() const
{
validate_read();
switch (m_mode) {
case Mode::Empty:
case Mode::Query:
return m_query;
case Mode::TableView:
return m_table_view.get_query();
case Mode::Table:
return m_table->where();
}
REALM_UNREACHABLE();
}
TableView Results::get_tableview()
{
validate_read();
switch (m_mode) {
case Mode::Empty:
return {};
case Mode::Query:
case Mode::TableView:
update_tableview();
return m_table_view;
case Mode::Table:
return m_table->where().find_all();
}
REALM_UNREACHABLE();
}
StringData Results::get_object_type() const noexcept
{
return ObjectStore::object_type_for_table_name(m_table->get_name());
}
Results Results::sort(realm::SortOrder&& sort) const
{
return Results(m_realm, get_query(), std::move(sort));
}
Results Results::filter(Query&& q) const
{
return Results(m_realm, get_query().and_query(std::move(q)), get_sort());
}
AsyncQueryCancelationToken Results::async(std::function<void (std::exception_ptr)> target)
{
if (m_realm->config().read_only) {
throw InvalidTransactionException("Cannot create asynchronous query for read-only Realms");
}
if (m_realm->is_in_transaction()) {
throw InvalidTransactionException("Cannot create asynchronous query while in a write transaction");
}
if (!m_background_query) {
m_background_query = std::make_shared<_impl::AsyncQuery>(*this);
_impl::RealmCoordinator::register_query(m_background_query);
}
return {m_background_query, m_background_query->add_callback(std::move(target))};
}
void Results::Internal::set_table_view(Results& results, realm::TableView &&tv)
{
// If the previous TableView was never actually used, then stop generating
// new ones until the user actually uses the Results object again
if (results.m_mode == Mode::TableView) {
results.m_wants_background_updates = results.m_has_used_table_view;
}
results.m_table_view = std::move(tv);
results.m_mode = Mode::TableView;
results.m_has_used_table_view = false;
// needs https://github.com/realm/realm-core/pull/1392
// REALM_ASSERT(results.m_table_view.is_in_sync());
}
Results::UnsupportedColumnTypeException::UnsupportedColumnTypeException(size_t column, const Table* table)
{
column_index = column;
column_name = table->get_column_name(column);
column_type = table->get_column_type(column);
}
AsyncQueryCancelationToken::AsyncQueryCancelationToken(std::shared_ptr<_impl::AsyncQuery> query, size_t token)
: m_query(std::move(query)), m_token(token)
{
}
AsyncQueryCancelationToken::~AsyncQueryCancelationToken()
{
// m_query itself (and not just the pointed-to thing) needs to be accessed
// atomically to ensure that there are no data races when the token is
// destroyed after being modified on a different thread.
// This is needed despite the token not being thread-safe in general as
// users find it very surpringing for obj-c objects to care about what
// thread they are deallocated on.
if (auto query = std::atomic_load(&m_query)) {
query->remove_callback(m_token);
}
}
AsyncQueryCancelationToken::AsyncQueryCancelationToken(AsyncQueryCancelationToken&& rgt)
: m_query(std::atomic_exchange(&rgt.m_query, {})), m_token(rgt.m_token)
{
}
AsyncQueryCancelationToken& AsyncQueryCancelationToken::operator=(realm::AsyncQueryCancelationToken&& rgt)
{
if (this != &rgt) {
if (auto query = std::atomic_load(&m_query)) {
query->remove_callback(m_token);
}
std::atomic_store(&m_query, std::atomic_exchange(&rgt.m_query, {}));
m_token = rgt.m_token;
}
return *this;
}

View File

@@ -0,0 +1,102 @@
////////////////////////////////////////////////////////////////////////////
//
// Copyright 2015 Realm Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////
#include "schema.hpp"
#include "object_schema.hpp"
#include "object_store.hpp"
#include "property.hpp"
using namespace realm;
static bool compare_by_name(ObjectSchema const& lft, ObjectSchema const& rgt) {
return lft.name < rgt.name;
}
Schema::Schema(base types) : base(std::move(types)) {
std::sort(begin(), end(), compare_by_name);
}
Schema::iterator Schema::find(std::string const& name)
{
ObjectSchema cmp;
cmp.name = name;
return find(cmp);
}
Schema::const_iterator Schema::find(std::string const& name) const
{
return const_cast<Schema *>(this)->find(name);
}
Schema::iterator Schema::find(ObjectSchema const& object) noexcept
{
auto it = std::lower_bound(begin(), end(), object, compare_by_name);
if (it != end() && it->name != object.name) {
it = end();
}
return it;
}
Schema::const_iterator Schema::find(ObjectSchema const& object) const noexcept
{
return const_cast<Schema *>(this)->find(object);
}
void Schema::validate() const
{
std::vector<ObjectSchemaValidationException> exceptions;
for (auto const& object : *this) {
const Property *primary = nullptr;
for (auto const& prop : object.properties) {
// check object_type existence
if (!prop.object_type.empty() && find(prop.object_type) == end()) {
exceptions.emplace_back(MissingObjectTypeException(object.name, prop));
}
// check nullablity
if (prop.is_nullable) {
if (prop.type == PropertyTypeArray || prop.type == PropertyTypeAny) {
exceptions.emplace_back(InvalidNullabilityException(object.name, prop));
}
}
else if (prop.type == PropertyTypeObject) {
exceptions.emplace_back(InvalidNullabilityException(object.name, prop));
}
// check primary keys
if (prop.is_primary) {
if (primary) {
exceptions.emplace_back(DuplicatePrimaryKeysException(object.name));
}
primary = &prop;
}
// check indexable
if (prop.is_indexed) {
if (!prop.is_indexable()) {
exceptions.emplace_back(PropertyTypeNotIndexableException(object.name, prop));
}
}
}
}
if (exceptions.size()) {
throw SchemaValidationException(exceptions);
}
}

View File

@@ -0,0 +1,457 @@
////////////////////////////////////////////////////////////////////////////
//
// Copyright 2015 Realm Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////
#include "shared_realm.hpp"
#include "binding_context.hpp"
#include "impl/external_commit_helper.hpp"
#include "impl/realm_coordinator.hpp"
#include "impl/transact_log_handler.hpp"
#include "object_store.hpp"
#include "schema.hpp"
#include <realm/commit_log.hpp>
#include <realm/group_shared.hpp>
#include <mutex>
using namespace realm;
using namespace realm::_impl;
Realm::Config::Config(const Config& c)
: path(c.path)
, read_only(c.read_only)
, in_memory(c.in_memory)
, cache(c.cache)
, disable_format_upgrade(c.disable_format_upgrade)
, encryption_key(c.encryption_key)
, schema_version(c.schema_version)
, migration_function(c.migration_function)
{
if (c.schema) {
schema = std::make_unique<Schema>(*c.schema);
}
}
Realm::Config::Config() : schema_version(ObjectStore::NotVersioned) { }
Realm::Config::Config(Config&&) = default;
Realm::Config::~Config() = default;
Realm::Config& Realm::Config::operator=(realm::Realm::Config const& c)
{
if (&c != this) {
*this = Config(c);
}
return *this;
}
Realm::Realm(Config config)
: m_config(std::move(config))
{
open_with_config(m_config, m_history, m_shared_group, m_read_only_group);
if (m_read_only_group) {
m_group = m_read_only_group.get();
}
}
void Realm::open_with_config(const Config& config,
std::unique_ptr<Replication>& history,
std::unique_ptr<SharedGroup>& shared_group,
std::unique_ptr<Group>& read_only_group)
{
try {
if (config.read_only) {
read_only_group = std::make_unique<Group>(config.path, config.encryption_key.data(), Group::mode_ReadOnly);
}
else {
history = realm::make_client_history(config.path, config.encryption_key.data());
SharedGroup::DurabilityLevel durability = config.in_memory ? SharedGroup::durability_MemOnly :
SharedGroup::durability_Full;
shared_group = std::make_unique<SharedGroup>(*history, durability, config.encryption_key.data(), !config.disable_format_upgrade);
}
}
catch (util::File::PermissionDenied const& ex) {
throw RealmFileException(RealmFileException::Kind::PermissionDenied, ex.get_path(),
"Unable to open a realm at path '" + ex.get_path() +
"'. Please use a path where your app has " + (config.read_only ? "read" : "read-write") + " permissions.");
}
catch (util::File::Exists const& ex) {
throw RealmFileException(RealmFileException::Kind::Exists, ex.get_path(),
"File at path '" + ex.get_path() + "' already exists.");
}
catch (util::File::NotFound const& ex) {
throw RealmFileException(RealmFileException::Kind::NotFound, ex.get_path(),
"File at path '" + ex.get_path() + "' does not exist.");
}
catch (util::File::AccessError const& ex) {
throw RealmFileException(RealmFileException::Kind::AccessError, ex.get_path(),
"Unable to open a realm at path '" + ex.get_path() + "'");
}
catch (IncompatibleLockFile const& ex) {
throw RealmFileException(RealmFileException::Kind::IncompatibleLockFile, config.path,
"Realm file is currently open in another process "
"which cannot share access with this process. All processes sharing a single file must be the same architecture.");
}
catch (FileFormatUpgradeRequired const& ex) {
throw RealmFileException(RealmFileException::Kind::FormatUpgradeRequired, config.path,
"The Realm file format must be allowed to be upgraded "
"in order to proceed.");
}
}
void Realm::init(std::shared_ptr<RealmCoordinator> coordinator)
{
m_coordinator = std::move(coordinator);
// if there is an existing realm at the current path steal its schema/column mapping
if (auto existing = m_coordinator->get_schema()) {
m_config.schema = std::make_unique<Schema>(*existing);
return;
}
try {
// otherwise get the schema from the group
auto target_schema = std::move(m_config.schema);
auto target_schema_version = m_config.schema_version;
m_config.schema_version = ObjectStore::get_schema_version(read_group());
m_config.schema = std::make_unique<Schema>(ObjectStore::schema_from_group(read_group()));
// if a target schema is supplied, verify that it matches or migrate to
// it, as neeeded
if (target_schema) {
if (m_config.read_only) {
if (m_config.schema_version == ObjectStore::NotVersioned) {
throw UnitializedRealmException("Can't open an un-initialized Realm without a Schema");
}
target_schema->validate();
ObjectStore::verify_schema(*m_config.schema, *target_schema, true);
m_config.schema = std::move(target_schema);
}
else {
update_schema(std::move(target_schema), target_schema_version);
}
if (!m_config.read_only) {
// End the read transaction created to validation/update the
// schema to avoid pinning the version even if the user never
// actually reads data
invalidate();
}
}
}
catch (...) {
// Trying to unregister from the coordinator before we finish
// construction will result in a deadlock
m_coordinator = nullptr;
throw;
}
}
Realm::~Realm()
{
if (m_coordinator) {
m_coordinator->unregister_realm(this);
}
}
Group *Realm::read_group()
{
if (!m_group) {
m_group = &const_cast<Group&>(m_shared_group->begin_read());
}
return m_group;
}
SharedRealm Realm::get_shared_realm(Config config)
{
return RealmCoordinator::get_coordinator(config.path)->get_realm(std::move(config));
}
void Realm::update_schema(std::unique_ptr<Schema> schema, uint64_t version)
{
schema->validate();
auto needs_update = [&] {
// If the schema version matches, just verify that the schema itself also matches
bool needs_write = !m_config.read_only && (m_config.schema_version != version || ObjectStore::needs_update(*m_config.schema, *schema));
if (needs_write) {
return true;
}
ObjectStore::verify_schema(*m_config.schema, *schema, m_config.read_only);
m_config.schema = std::move(schema);
m_config.schema_version = version;
m_coordinator->update_schema(*m_config.schema);
return false;
};
if (!needs_update()) {
return;
}
read_group();
transaction::begin(*m_shared_group, m_binding_context.get(),
/* error on schema changes */ false);
struct WriteTransactionGuard {
Realm& realm;
~WriteTransactionGuard() {
if (realm.is_in_transaction()) {
realm.cancel_transaction();
}
}
} write_transaction_guard{*this};
// Recheck the schema version after beginning the write transaction
// If it changed then someone else initialized the schema and we need to
// recheck everything
auto current_schema_version = ObjectStore::get_schema_version(read_group());
if (current_schema_version != m_config.schema_version) {
m_config.schema_version = current_schema_version;
*m_config.schema = ObjectStore::schema_from_group(read_group());
if (!needs_update()) {
cancel_transaction();
return;
}
}
Config old_config(m_config);
auto migration_function = [&](Group*, Schema&) {
SharedRealm old_realm(new Realm(old_config));
// Need to open in read-write mode so that it uses a SharedGroup, but
// users shouldn't actually be able to write via the old realm
old_realm->m_config.read_only = true;
if (m_config.migration_function) {
m_config.migration_function(old_realm, shared_from_this());
}
};
try {
m_config.schema = std::move(schema);
m_config.schema_version = version;
ObjectStore::update_realm_with_schema(read_group(), *old_config.schema,
version, *m_config.schema,
migration_function);
commit_transaction();
}
catch (...) {
m_config.schema = std::move(old_config.schema);
m_config.schema_version = old_config.schema_version;
throw;
}
m_coordinator->update_schema(*m_config.schema);
}
static void check_read_write(Realm *realm)
{
if (realm->config().read_only) {
throw InvalidTransactionException("Can't perform transactions on read-only Realms.");
}
}
void Realm::verify_thread() const
{
if (m_thread_id != std::this_thread::get_id()) {
throw IncorrectThreadException();
}
}
void Realm::verify_in_write() const
{
if (!is_in_transaction()) {
throw InvalidTransactionException("Cannot modify persisted objects outside of a write transaction.");
}
}
bool Realm::is_in_transaction() const noexcept
{
if (!m_shared_group) {
return false;
}
return m_shared_group->get_transact_stage() == SharedGroup::transact_Writing;
}
void Realm::begin_transaction()
{
check_read_write(this);
verify_thread();
if (is_in_transaction()) {
throw InvalidTransactionException("The Realm is already in a write transaction");
}
// make sure we have a read transaction
read_group();
transaction::begin(*m_shared_group, m_binding_context.get());
}
void Realm::commit_transaction()
{
check_read_write(this);
verify_thread();
if (!is_in_transaction()) {
throw InvalidTransactionException("Can't commit a non-existing write transaction");
}
transaction::commit(*m_shared_group, m_binding_context.get());
m_coordinator->send_commit_notifications();
}
void Realm::cancel_transaction()
{
check_read_write(this);
verify_thread();
if (!is_in_transaction()) {
throw InvalidTransactionException("Can't cancel a non-existing write transaction");
}
transaction::cancel(*m_shared_group, m_binding_context.get());
}
void Realm::invalidate()
{
verify_thread();
check_read_write(this);
if (is_in_transaction()) {
cancel_transaction();
}
if (!m_group) {
return;
}
m_shared_group->end_read();
m_group = nullptr;
}
bool Realm::compact()
{
verify_thread();
if (m_config.read_only) {
throw InvalidTransactionException("Can't compact a read-only Realm");
}
if (is_in_transaction()) {
throw InvalidTransactionException("Can't compact a Realm within a write transaction");
}
Group* group = read_group();
for (auto &object_schema : *m_config.schema) {
ObjectStore::table_for_object_type(group, object_schema.name)->optimize();
}
m_shared_group->end_read();
m_group = nullptr;
return m_shared_group->compact();
}
void Realm::notify()
{
verify_thread();
if (m_shared_group->has_changed()) { // Throws
if (m_binding_context) {
m_binding_context->changes_available();
}
if (m_auto_refresh) {
if (m_group) {
m_coordinator->advance_to_ready(*this);
}
else if (m_binding_context) {
m_binding_context->did_change({}, {});
}
}
}
else {
m_coordinator->process_available_async(*this);
}
}
bool Realm::refresh()
{
verify_thread();
check_read_write(this);
// can't be any new changes if we're in a write transaction
if (is_in_transaction()) {
return false;
}
// advance transaction if database has changed
if (!m_shared_group->has_changed()) { // Throws
return false;
}
if (m_group) {
transaction::advance(*m_shared_group, m_binding_context.get());
m_coordinator->process_available_async(*this);
}
else {
// Create the read transaction
read_group();
}
return true;
}
bool Realm::can_deliver_notifications() const noexcept
{
if (m_config.read_only) {
return false;
}
if (m_binding_context && !m_binding_context->can_deliver_notifications()) {
return false;
}
return true;
}
uint64_t Realm::get_schema_version(const realm::Realm::Config &config)
{
auto coordinator = RealmCoordinator::get_existing_coordinator(config.path);
if (coordinator) {
return coordinator->get_schema_version();
}
return ObjectStore::get_schema_version(Realm(config).read_group());
}
void Realm::close()
{
invalidate();
if (m_coordinator) {
m_coordinator->unregister_realm(this);
}
m_group = nullptr;
m_shared_group = nullptr;
m_history = nullptr;
m_read_only_group = nullptr;
m_binding_context = nullptr;
m_coordinator = nullptr;
}

909
Example/Pods/Realm/Realm/RLMAccessor.mm generated Normal file
View File

@@ -0,0 +1,909 @@
////////////////////////////////////////////////////////////////////////////
//
// Copyright 2014 Realm Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////
#import "RLMAccessor.h"
#import "RLMArray_Private.hpp"
#import "RLMObservation.hpp"
#import "RLMObjectSchema_Private.hpp"
#import "RLMObjectStore.h"
#import "RLMObject_Private.hpp"
#import "RLMProperty_Private.h"
#import "RLMRealm_Private.hpp"
#import "RLMSchema_Private.h"
#import "RLMUtil.hpp"
#import <objc/runtime.h>
#import <realm/descriptor.hpp>
typedef NS_ENUM(char, RLMAccessorCode) {
RLMAccessorCodeByte,
RLMAccessorCodeShort,
RLMAccessorCodeInt,
RLMAccessorCodeLong,
RLMAccessorCodeLongLong,
RLMAccessorCodeFloat,
RLMAccessorCodeDouble,
RLMAccessorCodeBool,
RLMAccessorCodeString,
RLMAccessorCodeDate,
RLMAccessorCodeData,
RLMAccessorCodeLink,
RLMAccessorCodeArray,
RLMAccessorCodeAny,
RLMAccessorCodeIntObject,
RLMAccessorCodeFloatObject,
RLMAccessorCodeDoubleObject,
RLMAccessorCodeBoolObject,
};
// long getter/setter
static inline long long RLMGetLong(__unsafe_unretained RLMObjectBase *const obj, NSUInteger colIndex) {
RLMVerifyAttached(obj);
return obj->_row.get_int(colIndex);
}
static inline void RLMSetValue(__unsafe_unretained RLMObjectBase *const obj, NSUInteger colIndex, long long val) {
RLMVerifyInWriteTransaction(obj);
obj->_row.set_int(colIndex, val);
}
static inline void RLMSetValueUnique(__unsafe_unretained RLMObjectBase *const obj, NSUInteger colIndex, NSString *propName, long long val) {
RLMVerifyInWriteTransaction(obj);
size_t row = obj->_row.get_table()->find_first_int(colIndex, val);
if (row == obj->_row.get_index()) {
return;
}
if (row != realm::not_found) {
@throw RLMException(@"Can't set primary key property '%@' to existing value '%lld'.", propName, val);
}
obj->_row.set_int(colIndex, val);
}
// float getter/setter
static inline float RLMGetFloat(__unsafe_unretained RLMObjectBase *const obj, NSUInteger colIndex) {
RLMVerifyAttached(obj);
return obj->_row.get_float(colIndex);
}
static inline void RLMSetValue(__unsafe_unretained RLMObjectBase *const obj, NSUInteger colIndex, float val) {
RLMVerifyInWriteTransaction(obj);
obj->_row.set_float(colIndex, val);
}
// double getter/setter
static inline double RLMGetDouble(__unsafe_unretained RLMObjectBase *const obj, NSUInteger colIndex) {
RLMVerifyAttached(obj);
return obj->_row.get_double(colIndex);
}
static inline void RLMSetValue(__unsafe_unretained RLMObjectBase *const obj, NSUInteger colIndex, double val) {
RLMVerifyInWriteTransaction(obj);
obj->_row.set_double(colIndex, val);
}
// bool getter/setter
static inline bool RLMGetBool(__unsafe_unretained RLMObjectBase *const obj, NSUInteger colIndex) {
RLMVerifyAttached(obj);
return obj->_row.get_bool(colIndex);
}
static inline void RLMSetValue(__unsafe_unretained RLMObjectBase *const obj, NSUInteger colIndex, BOOL val) {
RLMVerifyInWriteTransaction(obj);
obj->_row.set_bool(colIndex, val);
}
// string getter/setter
static inline NSString *RLMGetString(__unsafe_unretained RLMObjectBase *const obj, NSUInteger colIndex) {
RLMVerifyAttached(obj);
return RLMStringDataToNSString(obj->_row.get_string(colIndex));
}
static inline void RLMSetValue(__unsafe_unretained RLMObjectBase *const obj, NSUInteger colIndex, __unsafe_unretained NSString *const val) {
RLMVerifyInWriteTransaction(obj);
try {
obj->_row.set_string(colIndex, RLMStringDataWithNSString(val));
}
catch (std::exception const& e) {
@throw RLMException(e);
}
}
static inline void RLMSetValueUnique(__unsafe_unretained RLMObjectBase *const obj, NSUInteger colIndex, NSString *propName,
__unsafe_unretained NSString *const val) {
RLMVerifyInWriteTransaction(obj);
realm::StringData str = RLMStringDataWithNSString(val);
size_t row = obj->_row.get_table()->find_first_string(colIndex, str);
if (row == obj->_row.get_index()) {
return;
}
if (row != realm::not_found) {
@throw RLMException(@"Can't set primary key property '%@' to existing value '%@'.", propName, val);
}
try {
obj->_row.set_string(colIndex, str);
}
catch (std::exception const& e) {
@throw RLMException(e);
}
}
// date getter/setter
static inline NSDate *RLMGetDate(__unsafe_unretained RLMObjectBase *const obj, NSUInteger colIndex) {
RLMVerifyAttached(obj);
if (obj->_row.is_null(colIndex)) {
return nil;
}
realm::DateTime dt = obj->_row.get_datetime(colIndex);
return RLMDateTimeToNSDate(dt);
}
static inline void RLMSetValue(__unsafe_unretained RLMObjectBase *const obj, NSUInteger colIndex, __unsafe_unretained NSDate *const date) {
RLMVerifyInWriteTransaction(obj);
if (date) {
realm::DateTime dt = RLMDateTimeForNSDate(date);
obj->_row.set_datetime(colIndex, dt);
}
else {
obj->_row.set_null(colIndex);
}
}
// data getter/setter
static inline NSData *RLMGetData(__unsafe_unretained RLMObjectBase *const obj, NSUInteger colIndex) {
RLMVerifyAttached(obj);
realm::BinaryData data = obj->_row.get_binary(colIndex);
return RLMBinaryDataToNSData(data);
}
static inline void RLMSetValue(__unsafe_unretained RLMObjectBase *const obj, NSUInteger colIndex, __unsafe_unretained NSData *const data) {
RLMVerifyInWriteTransaction(obj);
try {
obj->_row.set_binary(colIndex, RLMBinaryDataForNSData(data));
}
catch (std::exception const& e) {
@throw RLMException(e);
}
}
static inline RLMObjectBase *RLMGetLinkedObjectForValue(__unsafe_unretained RLMRealm *const realm,
__unsafe_unretained NSString *const className,
__unsafe_unretained id const value,
RLMCreationOptions creationOptions) NS_RETURNS_RETAINED;
static inline RLMObjectBase *RLMGetLinkedObjectForValue(__unsafe_unretained RLMRealm *const realm,
__unsafe_unretained NSString *const className,
__unsafe_unretained id const value,
RLMCreationOptions creationOptions) {
RLMObjectBase *link = RLMDynamicCast<RLMObjectBase>(value);
if (!link || ![link->_objectSchema.className isEqualToString:className]) {
// create from non-rlmobject
return RLMCreateObjectInRealmWithValue(realm, className, value, creationOptions & RLMCreationOptionsCreateOrUpdate);
}
if (link.isInvalidated) {
@throw RLMException(@"Adding a deleted or invalidated object to a Realm is not permitted");
}
if (link->_realm == realm) {
return link;
}
if (creationOptions & RLMCreationOptionsPromoteStandalone) {
if (!link->_realm) {
RLMAddObjectToRealm(link, realm, creationOptions & RLMCreationOptionsCreateOrUpdate);
return link;
}
@throw RLMException(@"Can not add objects from a different Realm");
}
// copy from another realm or copy from standalone
return RLMCreateObjectInRealmWithValue(realm, className, link, creationOptions & RLMCreationOptionsCreateOrUpdate);
}
// link getter/setter
static inline RLMObjectBase *RLMGetLink(__unsafe_unretained RLMObjectBase *const obj, NSUInteger colIndex, __unsafe_unretained NSString *const objectClassName) {
RLMVerifyAttached(obj);
if (obj->_row.is_null_link(colIndex)) {
return nil;
}
NSUInteger index = obj->_row.get_link(colIndex);
return RLMCreateObjectAccessor(obj->_realm, obj->_realm.schema[objectClassName], index);
}
static inline void RLMSetValue(__unsafe_unretained RLMObjectBase *const obj, NSUInteger colIndex,
__unsafe_unretained RLMObjectBase *const val) {
RLMVerifyInWriteTransaction(obj);
if (!val) {
obj->_row.nullify_link(colIndex);
}
else {
// make sure it is the correct type
RLMObjectSchema *valSchema = val->_objectSchema;
RLMObjectSchema *objSchema = obj->_objectSchema;
if (![[objSchema.properties[colIndex] objectClassName] isEqualToString:valSchema.className]) {
@throw RLMException(@"Can't set object of type '%@' to property of type '%@'",
valSchema.className, [objSchema.properties[colIndex] objectClassName]);
}
RLMObjectBase *link = RLMGetLinkedObjectForValue(obj->_realm, valSchema.className, val, RLMCreationOptionsPromoteStandalone);
obj->_row.set_link(colIndex, link->_row.get_index());
}
}
// array getter/setter
static inline RLMArray *RLMGetArray(__unsafe_unretained RLMObjectBase *const obj,
NSUInteger colIndex,
__unsafe_unretained NSString *const objectClassName,
__unsafe_unretained NSString *const propName) {
RLMVerifyAttached(obj);
realm::LinkViewRef linkView = obj->_row.get_linklist(colIndex);
return [RLMArrayLinkView arrayWithObjectClassName:objectClassName
view:linkView
realm:obj->_realm
key:propName
parentSchema:obj->_objectSchema];
}
static inline void RLMSetValue(__unsafe_unretained RLMObjectBase *const obj, NSUInteger colIndex,
__unsafe_unretained id<NSFastEnumeration> const array) {
RLMVerifyInWriteTransaction(obj);
realm::LinkViewRef linkView = obj->_row.get_linklist(colIndex);
// remove all old
// FIXME: make sure delete rules don't purge objects
linkView->clear();
for (RLMObjectBase *link in array) {
RLMObjectBase * addedLink = RLMGetLinkedObjectForValue(obj->_realm, link->_objectSchema.className, link, RLMCreationOptionsPromoteStandalone);
linkView->add(addedLink->_row.get_index());
}
}
static inline NSNumber<RLMInt> *RLMGetIntObject(__unsafe_unretained RLMObjectBase *const obj, NSUInteger colIndex) {
RLMVerifyAttached(obj);
if (obj->_row.is_null(colIndex)) {
return nil;
}
return @(obj->_row.get_int(colIndex));
}
static inline void RLMSetValue(__unsafe_unretained RLMObjectBase *const obj, NSUInteger colIndex,
__unsafe_unretained NSNumber<RLMInt> *const intObject) {
RLMVerifyInWriteTransaction(obj);
if (intObject) {
obj->_row.set_int(colIndex, intObject.longLongValue);
}
else {
obj->_row.set_null(colIndex);
}
}
static inline void RLMSetValueUnique(__unsafe_unretained RLMObjectBase *const obj, NSUInteger colIndex, NSString *propName,
__unsafe_unretained NSNumber<RLMInt> *const intObject) {
RLMVerifyInWriteTransaction(obj);
long long longLongValue = 0;
size_t row;
if (intObject) {
longLongValue = intObject.longLongValue;
row = obj->_row.get_table()->find_first_int(colIndex, longLongValue);
}
else {
row = obj->_row.get_table()->find_first_null(colIndex);
}
if (row == obj->_row.get_index()) {
return;
}
if (row != realm::not_found) {
@throw RLMException(@"Can't set primary key property '%@' to existing value '%@'.", propName, intObject);
}
if (intObject) {
obj->_row.set_int(colIndex, longLongValue);
}
else {
obj->_row.set_null(colIndex);
}
}
static inline NSNumber<RLMFloat> *RLMGetFloatObject(__unsafe_unretained RLMObjectBase *const obj, NSUInteger colIndex) {
RLMVerifyAttached(obj);
if (obj->_row.is_null(colIndex)) {
return nil;
}
return @(obj->_row.get_float(colIndex));
}
static inline void RLMSetValue(__unsafe_unretained RLMObjectBase *const obj, NSUInteger colIndex,
__unsafe_unretained NSNumber<RLMFloat> *const floatObject) {
RLMVerifyInWriteTransaction(obj);
if (floatObject) {
obj->_row.set_float(colIndex, floatObject.floatValue);
}
else {
obj->_row.set_null(colIndex);
}
}
static inline NSNumber<RLMDouble> *RLMGetDoubleObject(__unsafe_unretained RLMObjectBase *const obj, NSUInteger colIndex) {
RLMVerifyAttached(obj);
if (obj->_row.is_null(colIndex)) {
return nil;
}
return @(obj->_row.get_double(colIndex));
}
static inline void RLMSetValue(__unsafe_unretained RLMObjectBase *const obj, NSUInteger colIndex,
__unsafe_unretained NSNumber<RLMDouble> *const doubleObject) {
RLMVerifyInWriteTransaction(obj);
if (doubleObject) {
obj->_row.set_double(colIndex, doubleObject.doubleValue);
}
else {
obj->_row.set_null(colIndex);
}
}
static inline NSNumber<RLMBool> *RLMGetBoolObject(__unsafe_unretained RLMObjectBase *const obj, NSUInteger colIndex) {
RLMVerifyAttached(obj);
if (obj->_row.is_null(colIndex)) {
return nil;
}
return @(obj->_row.get_bool(colIndex));
}
static inline void RLMSetValue(__unsafe_unretained RLMObjectBase *const obj, NSUInteger colIndex,
__unsafe_unretained NSNumber<RLMBool> *const boolObject) {
RLMVerifyInWriteTransaction(obj);
if (boolObject) {
obj->_row.set_bool(colIndex, boolObject.boolValue);
}
else {
obj->_row.set_null(colIndex);
}
}
// any getter/setter
static inline id RLMGetAnyProperty(__unsafe_unretained RLMObjectBase *const obj, NSUInteger col_ndx) {
RLMVerifyAttached(obj);
return RLMMixedToObjc(obj->_row.get_mixed(col_ndx));
}
static inline void RLMSetValue(__unsafe_unretained RLMObjectBase *const obj, NSUInteger col_ndx, __unsafe_unretained id val) {
RLMVerifyInWriteTransaction(obj);
// FIXME - enable when Any supports links
// if (obj == nil) {
// table.nullify_link(col_ndx, row_ndx);
// return;
// }
if (NSString *str = RLMDynamicCast<NSString>(val)) {
obj->_row.set_mixed(col_ndx, RLMStringDataWithNSString(str));
return;
}
if (NSDate *date = RLMDynamicCast<NSDate>(val)) {
obj->_row.set_mixed(col_ndx, RLMDateTimeForNSDate(date));
return;
}
if (NSData *data = RLMDynamicCast<NSData>(val)) {
obj->_row.set_mixed(col_ndx, RLMBinaryDataForNSData(data));
return;
}
if (NSNumber *number = RLMDynamicCast<NSNumber>(val)) {
switch (number.objCType[0]) {
case 'i':
case 's':
case 'l':
case 'q':
obj->_row.set_mixed(col_ndx, number.longLongValue);
return;
case 'f':
obj->_row.set_mixed(col_ndx, number.floatValue);
return;
case 'd':
obj->_row.set_mixed(col_ndx, number.doubleValue);
return;
case 'B':
case 'c':
obj->_row.set_mixed(col_ndx, (bool)number.boolValue);
return;
}
}
@throw RLMException(@"Inserting invalid object of class %@ for an RLMPropertyTypeAny property (%@).", [val class], [obj->_objectSchema.properties[col_ndx] name]);
}
// dynamic getter with column closure
static IMP RLMAccessorGetter(RLMProperty *prop, RLMAccessorCode accessorCode) {
NSUInteger colIndex = prop.column;
NSString *name = prop.name;
NSString *objectClassName = prop.objectClassName;
switch (accessorCode) {
case RLMAccessorCodeByte:
return imp_implementationWithBlock(^(__unsafe_unretained RLMObjectBase *const obj) {
return (char)RLMGetLong(obj, colIndex);
});
case RLMAccessorCodeShort:
return imp_implementationWithBlock(^(__unsafe_unretained RLMObjectBase *const obj) {
return (short)RLMGetLong(obj, colIndex);
});
case RLMAccessorCodeInt:
return imp_implementationWithBlock(^(__unsafe_unretained RLMObjectBase *const obj) {
return (int)RLMGetLong(obj, colIndex);
});
case RLMAccessorCodeLongLong:
return imp_implementationWithBlock(^(__unsafe_unretained RLMObjectBase *const obj) {
return RLMGetLong(obj, colIndex);
});
case RLMAccessorCodeLong:
return imp_implementationWithBlock(^(__unsafe_unretained RLMObjectBase *const obj) {
return (long)RLMGetLong(obj, colIndex);
});
case RLMAccessorCodeFloat:
return imp_implementationWithBlock(^(__unsafe_unretained RLMObjectBase *const obj) {
return RLMGetFloat(obj, colIndex);
});
case RLMAccessorCodeDouble:
return imp_implementationWithBlock(^(__unsafe_unretained RLMObjectBase *const obj) {
return RLMGetDouble(obj, colIndex);
});
case RLMAccessorCodeBool:
return imp_implementationWithBlock(^(__unsafe_unretained RLMObjectBase *const obj) {
return RLMGetBool(obj, colIndex);
});
case RLMAccessorCodeString:
return imp_implementationWithBlock(^(__unsafe_unretained RLMObjectBase *const obj) {
return RLMGetString(obj, colIndex);
});
case RLMAccessorCodeDate:
return imp_implementationWithBlock(^(__unsafe_unretained RLMObjectBase *const obj) {
return RLMGetDate(obj, colIndex);
});
case RLMAccessorCodeData:
return imp_implementationWithBlock(^(__unsafe_unretained RLMObjectBase *const obj) {
return RLMGetData(obj, colIndex);
});
case RLMAccessorCodeLink:
return imp_implementationWithBlock(^id(__unsafe_unretained RLMObjectBase *const obj) {
return RLMGetLink(obj, colIndex, objectClassName);
});
case RLMAccessorCodeArray:
return imp_implementationWithBlock(^(__unsafe_unretained RLMObjectBase *const obj) {
return RLMGetArray(obj, colIndex, objectClassName, name);
});
case RLMAccessorCodeAny:
return imp_implementationWithBlock(^(__unsafe_unretained RLMObjectBase *const obj) {
return RLMGetAnyProperty(obj, colIndex);
});
case RLMAccessorCodeIntObject:
return imp_implementationWithBlock(^(__unsafe_unretained RLMObjectBase *const obj) {
return RLMGetIntObject(obj, colIndex);
});
case RLMAccessorCodeFloatObject:
return imp_implementationWithBlock(^(__unsafe_unretained RLMObjectBase *const obj) {
return RLMGetFloatObject(obj, colIndex);
});
case RLMAccessorCodeDoubleObject:
return imp_implementationWithBlock(^(__unsafe_unretained RLMObjectBase *const obj) {
return RLMGetDoubleObject(obj, colIndex);
});
case RLMAccessorCodeBoolObject:
return imp_implementationWithBlock(^(__unsafe_unretained RLMObjectBase *const obj) {
return RLMGetBoolObject(obj, colIndex);
});
}
}
template<typename Function>
static void RLMWrapSetter(__unsafe_unretained RLMObjectBase *const obj, __unsafe_unretained NSString *const name, Function&& f) {
if (RLMObservationInfo *info = RLMGetObservationInfo(obj->_observationInfo, obj->_row.get_index(), obj->_objectSchema)) {
info->willChange(name);
f();
info->didChange(name);
}
else {
f();
}
}
template<typename ArgType, typename StorageType=ArgType>
static IMP RLMMakeSetter(RLMProperty *prop) {
NSUInteger colIndex = prop.column;
NSString *name = prop.name;
if (prop.isPrimary) {
return imp_implementationWithBlock(^(__unused RLMObjectBase *obj, __unused ArgType val) {
@throw RLMException(@"Primary key can't be changed after an object is inserted.");
});
}
return imp_implementationWithBlock(^(__unsafe_unretained RLMObjectBase *const obj, ArgType val) {
RLMWrapSetter(obj, name, [&] {
RLMSetValue(obj, colIndex, static_cast<StorageType>(val));
});
});
}
// dynamic setter with column closure
static IMP RLMAccessorSetter(RLMProperty *prop, RLMAccessorCode accessorCode) {
switch (accessorCode) {
case RLMAccessorCodeByte: return RLMMakeSetter<char, long long>(prop);
case RLMAccessorCodeShort: return RLMMakeSetter<short, long long>(prop);
case RLMAccessorCodeInt: return RLMMakeSetter<int, long long>(prop);
case RLMAccessorCodeLong: return RLMMakeSetter<long, long long>(prop);
case RLMAccessorCodeLongLong: return RLMMakeSetter<long long>(prop);
case RLMAccessorCodeFloat: return RLMMakeSetter<float>(prop);
case RLMAccessorCodeDouble: return RLMMakeSetter<double>(prop);
case RLMAccessorCodeBool: return RLMMakeSetter<BOOL>(prop);
case RLMAccessorCodeString: return RLMMakeSetter<NSString *>(prop);
case RLMAccessorCodeDate: return RLMMakeSetter<NSDate *>(prop);
case RLMAccessorCodeData: return RLMMakeSetter<NSData *>(prop);
case RLMAccessorCodeLink: return RLMMakeSetter<RLMObjectBase *>(prop);
case RLMAccessorCodeArray: return RLMMakeSetter<RLMArray *>(prop);
case RLMAccessorCodeAny: return RLMMakeSetter<id>(prop);
case RLMAccessorCodeIntObject: return RLMMakeSetter<NSNumber<RLMInt> *>(prop);
case RLMAccessorCodeFloatObject: return RLMMakeSetter<NSNumber<RLMFloat> *>(prop);
case RLMAccessorCodeDoubleObject: return RLMMakeSetter<NSNumber<RLMDouble> *>(prop);
case RLMAccessorCodeBoolObject: return RLMMakeSetter<NSNumber<RLMBool> *>(prop);
}
}
// call getter for superclass for property at colIndex
static id RLMSuperGet(RLMObjectBase *obj, NSString *propName) {
typedef id (*getter_type)(RLMObjectBase *, SEL);
RLMProperty *prop = obj->_objectSchema[propName];
Class superClass = class_getSuperclass(obj.class);
getter_type superGetter = (getter_type)[superClass instanceMethodForSelector:prop.getterSel];
return superGetter(obj, prop.getterSel);
}
// call setter for superclass for property at colIndex
static void RLMSuperSet(RLMObjectBase *obj, NSString *propName, id val) {
typedef void (*setter_type)(RLMObjectBase *, SEL, RLMArray *ar);
RLMProperty *prop = obj->_objectSchema[propName];
Class superClass = class_getSuperclass(obj.class);
setter_type superSetter = (setter_type)[superClass instanceMethodForSelector:prop.setterSel];
superSetter(obj, prop.setterSel, val);
}
// getter/setter for standalone
static IMP RLMAccessorStandaloneGetter(RLMProperty *prop, RLMAccessorCode accessorCode) {
// only override getters for RLMArray properties
if (accessorCode == RLMAccessorCodeArray) {
NSString *objectClassName = prop.objectClassName;
NSString *propName = prop.name;
return imp_implementationWithBlock(^(RLMObjectBase *obj) {
id val = RLMSuperGet(obj, propName);
if (!val) {
val = [[RLMArray alloc] initWithObjectClassName:objectClassName];
RLMSuperSet(obj, propName, val);
}
return val;
});
}
return nil;
}
static IMP RLMAccessorStandaloneSetter(RLMProperty *prop, RLMAccessorCode accessorCode) {
// only override getters for RLMArray properties
if (accessorCode == RLMAccessorCodeArray) {
NSString *propName = prop.name;
NSString *objectClassName = prop.objectClassName;
return imp_implementationWithBlock(^(RLMObjectBase *obj, id<NSFastEnumeration> ar) {
// make copy when setting (as is the case for all other variants)
RLMArray *standaloneAr = [[RLMArray alloc] initWithObjectClassName:objectClassName];
[standaloneAr addObjects:ar];
RLMSuperSet(obj, propName, standaloneAr);
});
}
return nil;
}
// macros/helpers to generate objc type strings for registering methods
#define GETTER_TYPES(C) C "@:"
#define SETTER_TYPES(C) "v@:" C
// getter type strings
// NOTE: this typecode is really the the first charachter of the objc/runtime.h type
// the @ type maps to multiple core types (string, date, array, mixed, any which are id in objc)
static const char *getterTypeStringForObjcCode(char code) {
switch (code) {
case 's': return GETTER_TYPES("s");
case 'i': return GETTER_TYPES("i");
case 'l': return GETTER_TYPES("l");
case 'q': return GETTER_TYPES("q");
case 'f': return GETTER_TYPES("f");
case 'd': return GETTER_TYPES("d");
case 'B': return GETTER_TYPES("B");
case 'c': return GETTER_TYPES("c");
case '@': return GETTER_TYPES("@");
default: @throw RLMException(@"Invalid accessor code");
}
}
// setter type strings
// NOTE: this typecode is really the the first charachter of the objc/runtime.h type
// the @ type maps to multiple core types (string, date, array, mixed, any which are id in objc)
static const char *setterTypeStringForObjcCode(char code) {
switch (code) {
case 's': return SETTER_TYPES("s");
case 'i': return SETTER_TYPES("i");
case 'l': return SETTER_TYPES("l");
case 'q': return SETTER_TYPES("q");
case 'f': return SETTER_TYPES("f");
case 'd': return SETTER_TYPES("d");
case 'B': return SETTER_TYPES("B");
case 'c': return SETTER_TYPES("c");
case '@': return SETTER_TYPES("@");
default: @throw RLMException(@"Invalid accessor code");
}
}
// get accessor lookup code based on objc type and rlm type
static RLMAccessorCode accessorCodeForType(char objcTypeCode, RLMPropertyType rlmType) {
switch (objcTypeCode) {
case 't': return RLMAccessorCodeArray;
case '@': // custom accessors for strings and subtables
switch (rlmType) { // custom accessor codes for types that map to objc objects
case RLMPropertyTypeObject: return RLMAccessorCodeLink;
case RLMPropertyTypeString: return RLMAccessorCodeString;
case RLMPropertyTypeArray: return RLMAccessorCodeArray;
case RLMPropertyTypeDate: return RLMAccessorCodeDate;
case RLMPropertyTypeData: return RLMAccessorCodeData;
case RLMPropertyTypeAny: return RLMAccessorCodeAny;
case RLMPropertyTypeBool: return RLMAccessorCodeBoolObject;
case RLMPropertyTypeDouble: return RLMAccessorCodeDoubleObject;
case RLMPropertyTypeFloat: return RLMAccessorCodeFloatObject;
case RLMPropertyTypeInt: return RLMAccessorCodeIntObject;
default: break;
}
case 'c':
switch (rlmType) {
case RLMPropertyTypeInt: return RLMAccessorCodeByte;
case RLMPropertyTypeBool: return RLMAccessorCodeBool;
default: break;
}
case 'B': return RLMAccessorCodeBool;
case 's': return RLMAccessorCodeShort;
case 'i': return RLMAccessorCodeInt;
case 'l': return RLMAccessorCodeLong;
case 'q': return RLMAccessorCodeLongLong;
case 'f': return RLMAccessorCodeFloat;
case 'd': return RLMAccessorCodeDouble;
default:
@throw RLMException(@"Invalid type for objc typecode");
}
}
// implement the class method className on accessors to return the className of the
// base object
void RLMReplaceClassNameMethod(Class accessorClass, NSString *className) {
Class metaClass = object_getClass(accessorClass);
IMP imp = imp_implementationWithBlock(^(Class){ return className; });
class_addMethod(metaClass, @selector(className), imp, "@@:");
}
// implement the shared schema method
void RLMReplaceSharedSchemaMethod(Class accessorClass, RLMObjectSchema *schema) {
Class metaClass = object_getClass(accessorClass);
IMP imp = imp_implementationWithBlock(^(Class cls) {
// This can be called on a subclass of the class that we overrode it on
// if that class hasn't been initialized yet
if (cls == accessorClass) {
return schema;
}
return [RLMSchema sharedSchemaForClass:cls];
});
class_addMethod(metaClass, @selector(sharedSchema), imp, "@@:");
}
static NSMutableSet *s_generatedClasses = [NSMutableSet new];
static void RLMMarkClassAsGenerated(Class cls) {
@synchronized (s_generatedClasses) {
[s_generatedClasses addObject:cls];
}
}
bool RLMIsGeneratedClass(Class cls) {
@synchronized (s_generatedClasses) {
return [s_generatedClasses containsObject:cls];
}
}
static Class RLMCreateAccessorClass(Class objectClass,
RLMObjectSchema *schema,
NSString *accessorClassPrefix,
IMP (*getterGetter)(RLMProperty *, RLMAccessorCode),
IMP (*setterGetter)(RLMProperty *, RLMAccessorCode)) {
// throw if no schema, prefix, or object class
if (!objectClass || !schema || !accessorClassPrefix) {
@throw RLMException(@"Missing arguments");
}
if (!RLMIsKindOfClass(objectClass, RLMObjectBase.class)) {
@throw RLMException(@"objectClass must derive from RLMObject or Object");
}
// create and register proxy class which derives from object class
NSString *accessorClassName = [accessorClassPrefix stringByAppendingString:schema.className];
Class accClass = objc_getClass(accessorClassName.UTF8String);
if (!accClass) {
accClass = objc_allocateClassPair(objectClass, accessorClassName.UTF8String, 0);
objc_registerClassPair(accClass);
}
// override getters/setters for each propery
for (unsigned int propNum = 0; propNum < schema.properties.count; propNum++) {
RLMProperty *prop = schema.properties[propNum];
RLMAccessorCode accessorCode = accessorCodeForType(prop.objcType, prop.type);
if (prop.getterSel && getterGetter) {
IMP getterImp = getterGetter(prop, accessorCode);
if (getterImp) {
class_replaceMethod(accClass, prop.getterSel, getterImp, getterTypeStringForObjcCode(prop.objcType));
}
}
if (prop.setterSel && setterGetter) {
IMP setterImp = setterGetter(prop, accessorCode);
if (setterImp) {
class_replaceMethod(accClass, prop.setterSel, setterImp, setterTypeStringForObjcCode(prop.objcType));
}
}
}
RLMMarkClassAsGenerated(accClass);
return accClass;
}
Class RLMAccessorClassForObjectClass(Class objectClass, RLMObjectSchema *schema, NSString *prefix) {
return RLMCreateAccessorClass(objectClass, schema, prefix, RLMAccessorGetter, RLMAccessorSetter);
}
Class RLMStandaloneAccessorClassForObjectClass(Class objectClass, RLMObjectSchema *schema) {
return RLMCreateAccessorClass(objectClass, schema, @"RLMStandalone_",
RLMAccessorStandaloneGetter, RLMAccessorStandaloneSetter);
}
void RLMDynamicValidatedSet(RLMObjectBase *obj, NSString *propName, id val) {
RLMObjectSchema *schema = obj->_objectSchema;
RLMProperty *prop = schema[propName];
if (!prop) {
@throw RLMException(@"Invalid property name `%@` for class `%@`.", propName, obj->_objectSchema.className);
}
if (prop.isPrimary) {
@throw RLMException(@"Primary key can't be changed to '%@' after an object is inserted.", val);
}
if (!RLMIsObjectValidForProperty(val, prop)) {
@throw RLMException(@"Invalid property value `%@` for property `%@` of class `%@`", val, propName, obj->_objectSchema.className);
}
RLMDynamicSet(obj, prop, RLMCoerceToNil(val), RLMCreationOptionsPromoteStandalone);
}
void RLMDynamicSet(__unsafe_unretained RLMObjectBase *const obj, __unsafe_unretained RLMProperty *const prop,
__unsafe_unretained id const val, RLMCreationOptions creationOptions) {
NSUInteger col = prop.column;
RLMWrapSetter(obj, prop.name, [&] {
switch (accessorCodeForType(prop.objcType, prop.type)) {
case RLMAccessorCodeByte:
case RLMAccessorCodeShort:
case RLMAccessorCodeInt:
case RLMAccessorCodeLong:
case RLMAccessorCodeLongLong:
if (prop.isPrimary) {
RLMSetValueUnique(obj, col, prop.name, [val longLongValue]);
}
else {
RLMSetValue(obj, col, [val longLongValue]);
}
break;
case RLMAccessorCodeFloat:
RLMSetValue(obj, col, [val floatValue]);
break;
case RLMAccessorCodeDouble:
RLMSetValue(obj, col, [val doubleValue]);
break;
case RLMAccessorCodeBool:
RLMSetValue(obj, col, [val boolValue]);
break;
case RLMAccessorCodeIntObject:
if (prop.isPrimary) {
RLMSetValueUnique(obj, col, prop.name, (NSNumber<RLMInt> *)val);
}
else {
RLMSetValue(obj, col, (NSNumber<RLMInt> *)val);
}
break;
case RLMAccessorCodeFloatObject:
RLMSetValue(obj, col, (NSNumber<RLMFloat> *)val);
break;
case RLMAccessorCodeDoubleObject:
RLMSetValue(obj, col, (NSNumber<RLMDouble> *)val);
break;
case RLMAccessorCodeBoolObject:
RLMSetValue(obj, col, (NSNumber<RLMBool> *)val);
break;
case RLMAccessorCodeString:
if (prop.isPrimary) {
RLMSetValueUnique(obj, col, prop.name, (NSString *)val);
}
else {
RLMSetValue(obj, col, (NSString *)val);
}
break;
case RLMAccessorCodeDate:
RLMSetValue(obj, col, (NSDate *)val);
break;
case RLMAccessorCodeData:
RLMSetValue(obj, col, (NSData *)val);
break;
case RLMAccessorCodeLink: {
if (!val || val == NSNull.null) {
RLMSetValue(obj, col, (RLMObjectBase *)nil);
}
else {
RLMSetValue(obj, col, RLMGetLinkedObjectForValue(obj->_realm, prop.objectClassName, val, creationOptions));
}
break;
}
case RLMAccessorCodeArray:
if (!val || val == NSNull.null) {
RLMSetValue(obj, col, (id<NSFastEnumeration>)nil);
}
else {
id<NSFastEnumeration> rawLinks = val;
NSMutableArray *links = [NSMutableArray array];
for (id rawLink in rawLinks) {
[links addObject:RLMGetLinkedObjectForValue(obj->_realm, prop.objectClassName, rawLink, creationOptions)];
}
RLMSetValue(obj, col, links);
}
break;
case RLMAccessorCodeAny:
RLMSetValue(obj, col, val);
break;
}
});
}
RLMProperty *RLMValidatedGetProperty(__unsafe_unretained RLMObjectBase *const obj, __unsafe_unretained NSString *const propName) {
RLMProperty *prop = obj->_objectSchema[propName];
if (!prop) {
@throw RLMException(@"Invalid property name `%@` for class `%@`.", propName, obj->_objectSchema.className);
}
return prop;
}
id RLMDynamicGet(__unsafe_unretained RLMObjectBase *obj, __unsafe_unretained RLMProperty *prop) {
NSUInteger col = prop.column;
switch (accessorCodeForType(prop.objcType, prop.type)) {
case RLMAccessorCodeByte: return @((char)RLMGetLong(obj, col));
case RLMAccessorCodeShort: return @((short)RLMGetLong(obj, col));
case RLMAccessorCodeInt: return @((int)RLMGetLong(obj, col));
case RLMAccessorCodeLong: return @((long)RLMGetLong(obj, col));
case RLMAccessorCodeLongLong: return @(RLMGetLong(obj, col));
case RLMAccessorCodeFloat: return @(RLMGetFloat(obj, col));
case RLMAccessorCodeDouble: return @(RLMGetDouble(obj, col));
case RLMAccessorCodeBool: return @(RLMGetBool(obj, col));
case RLMAccessorCodeString: return RLMGetString(obj, col);
case RLMAccessorCodeDate: return RLMGetDate(obj, col);
case RLMAccessorCodeData: return RLMGetData(obj, col);
case RLMAccessorCodeLink: return RLMGetLink(obj, col, prop.objectClassName);
case RLMAccessorCodeArray: return RLMGetArray(obj, col, prop.objectClassName, prop.name);
case RLMAccessorCodeAny: return RLMGetAnyProperty(obj, col);
case RLMAccessorCodeIntObject: return RLMGetIntObject(obj, col);
case RLMAccessorCodeFloatObject: return RLMGetFloatObject(obj, col);
case RLMAccessorCodeDoubleObject: return RLMGetDoubleObject(obj, col);
case RLMAccessorCodeBoolObject: return RLMGetBoolObject(obj, col);
}
}

240
Example/Pods/Realm/Realm/RLMAnalytics.mm generated Normal file
View File

@@ -0,0 +1,240 @@
////////////////////////////////////////////////////////////////////////////
//
// Copyright 2015 Realm Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////
// Asynchronously submits build information to Realm if running in an iOS
// simulator or on OS X if a debugger is attached. Does nothing if running on an
// iOS / watchOS device or if a debugger is *not* attached.
//
// To be clear: this does *not* run when your app is in production or on
// your end-users devices; it will only run in the simulator or when a debugger
// is attached.
//
// Why are we doing this? In short, because it helps us build a better product
// for you. None of the data personally identifies you, your employer or your
// app, but it *will* help us understand what language you use, what iOS
// versions you target, etc. Having this info will help prioritizing our time,
// adding new features and deprecating old features. Collecting an anonymized
// bundle & anonymized MAC is the only way for us to count actual usage of the
// other metrics accurately. If we dont have a way to deduplicate the info
// reported, it will be useless, as a single developer building their Swift app
// 10 times would report 10 times more than a single Objective-C developer that
// only builds once, making the data all but useless.
// No one likes sharing data unless its necessary, we get it, and weve
// debated adding this for a long long time. Since Realm is a free product
// without an email signup, we feel this is a necessary step so we can collect
// relevant data to build a better product for you. If you truly, absolutely
// feel compelled to not send this data back to Realm, then you can set an env
// variable named REALM_DISABLE_ANALYTICS. Since Realm is free we believe
// letting these analytics run is a small price to pay for the product & support
// we give you.
//
// Currently the following information is reported:
// - What version of Realm is being used, and from which language (obj-c or Swift).
// - What version of OS X it's running on (in case Xcode aggressively drops
// support for older versions again, we need to know what we need to support).
// - The minimum iOS/OS X version that the application is targeting (again, to
// help us decide what versions we need to support).
// - An anonymous MAC address and bundle ID to aggregate the other information on.
// - What version of Swift is being used (if applicable).
#import "RLMAnalytics.hpp"
#import <Foundation/Foundation.h>
#if TARGET_IPHONE_SIMULATOR || TARGET_OS_MAC || (TARGET_OS_WATCH && TARGET_OS_SIMULATOR) || (TARGET_OS_TV && TARGET_OS_SIMULATOR)
#import "RLMRealm.h"
#import "RLMUtil.hpp"
#import <array>
#import <sys/socket.h>
#import <sys/sysctl.h>
#import <net/if.h>
#import <net/if_dl.h>
#import <CommonCrypto/CommonDigest.h>
#ifndef REALM_COCOA_VERSION
#import "RLMVersion.h"
#endif
// Declared for RealmSwiftObjectUtil
@interface NSObject (SwiftVersion)
+ (NSString *)swiftVersion;
@end
// Wrapper for sysctl() that handles the memory management stuff
static auto RLMSysCtl(int *mib, u_int mibSize, size_t *bufferSize) {
std::unique_ptr<void, decltype(&free)> buffer(nullptr, &free);
int ret = sysctl(mib, mibSize, nullptr, bufferSize, nullptr, 0);
if (ret != 0) {
return buffer;
}
buffer.reset(malloc(*bufferSize));
if (!buffer) {
return buffer;
}
ret = sysctl(mib, mibSize, buffer.get(), bufferSize, nullptr, 0);
if (ret != 0) {
buffer.reset();
}
return buffer;
}
// Get the version of OS X we're running on (even in the simulator this gives
// the OS X version and not the simulated iOS version)
static NSString *RLMOSVersion() {
std::array<int, 2> mib = {CTL_KERN, KERN_OSRELEASE};
size_t bufferSize;
auto buffer = RLMSysCtl(&mib[0], mib.size(), &bufferSize);
if (!buffer) {
return nil;
}
return [[NSString alloc] initWithBytesNoCopy:buffer.release()
length:bufferSize - 1
encoding:NSUTF8StringEncoding
freeWhenDone:YES];
}
// Hash the data in the given buffer and convert it to a hex-format string
static NSString *RLMHashData(const void *bytes, size_t length) {
unsigned char buffer[CC_SHA256_DIGEST_LENGTH];
CC_SHA256(bytes, static_cast<CC_LONG>(length), buffer);
char formatted[CC_SHA256_DIGEST_LENGTH * 2 + 1];
for (int i = 0; i < CC_SHA256_DIGEST_LENGTH; ++i) {
sprintf(formatted + i * 2, "%02x", buffer[i]);
}
return [[NSString alloc] initWithBytes:formatted
length:CC_SHA256_DIGEST_LENGTH * 2
encoding:NSUTF8StringEncoding];
}
// Returns the hash of the MAC address of the first network adaptor since the
// vendorIdentifier isn't constant between iOS simulators.
static NSString *RLMMACAddress() {
int en0 = static_cast<int>(if_nametoindex("en0"));
if (!en0) {
return nil;
}
std::array<int, 6> mib = {CTL_NET, PF_ROUTE, 0, AF_LINK, NET_RT_IFLIST, en0};
size_t bufferSize;
auto buffer = RLMSysCtl(&mib[0], mib.size(), &bufferSize);
if (!buffer) {
return nil;
}
// sockaddr_dl struct is immediately after the if_msghdr struct in the buffer
auto sockaddr = reinterpret_cast<sockaddr_dl *>(static_cast<if_msghdr *>(buffer.get()) + 1);
auto mac = reinterpret_cast<const unsigned char *>(sockaddr->sdl_data + sockaddr->sdl_nlen);
return RLMHashData(mac, 6);
}
static NSDictionary *RLMAnalyticsPayload() {
NSBundle *appBundle = NSBundle.mainBundle;
NSString *hashedBundleID = appBundle.bundleIdentifier;
// Main bundle isn't always the one of interest (e.g. when running tests
// it's xctest rather than the app's bundle), so look for one with a bundle ID
if (!hashedBundleID) {
for (NSBundle *bundle in NSBundle.allBundles) {
if ((hashedBundleID = bundle.bundleIdentifier)) {
appBundle = bundle;
break;
}
}
}
// If we found a bundle ID anywhere, hash it as it could contain sensitive
// information (e.g. the name of an unnanounced product)
if (hashedBundleID) {
NSData *data = [hashedBundleID dataUsingEncoding:NSUTF8StringEncoding];
hashedBundleID = RLMHashData(data.bytes, data.length);
}
NSString *osVersionString = [[NSProcessInfo processInfo] operatingSystemVersionString];
Class swiftObjectUtilClass = NSClassFromString(@"RealmSwiftObjectUtil");
BOOL isSwift = swiftObjectUtilClass != nil;
NSString *swiftVersion = isSwift ? [swiftObjectUtilClass swiftVersion] : @"N/A";
static NSString *kUnknownString = @"unknown";
NSString *hashedMACAddress = RLMMACAddress() ?: kUnknownString;
return @{
@"event": @"Run",
@"properties": @{
// MixPanel properties
@"token": @"ce0fac19508f6c8f20066d345d360fd0",
// Anonymous identifiers to deduplicate events
@"distinct_id": hashedMACAddress,
@"Anonymized MAC Address": hashedMACAddress,
@"Anonymized Bundle ID": hashedBundleID ?: kUnknownString,
// Which version of Realm is being used
@"Binding": @"cocoa",
@"Language": isSwift ? @"swift" : @"objc",
@"Realm Version": REALM_COCOA_VERSION,
#if TARGET_OS_WATCH
@"Target OS Type": @"watchos",
#elif TARGET_OS_TV
@"Target OS Type": @"tvos",
#elif TARGET_OS_IPHONE
@"Target OS Type": @"ios",
#else
@"Target OS Type": @"osx",
#endif
@"Swift Version": swiftVersion,
// Current OS version the app is targetting
@"Target OS Version": osVersionString,
// Minimum OS version the app is targetting
@"Target OS Minimum Version": appBundle.infoDictionary[@"MinimumOSVersion"] ?: kUnknownString,
// Host OS version being built on
@"Host OS Type": @"osx",
@"Host OS Version": RLMOSVersion() ?: kUnknownString,
}
};
}
void RLMSendAnalytics() {
if (getenv("REALM_DISABLE_ANALYTICS") || !RLMIsDebuggerAttached() || RLMIsRunningInPlayground()) {
return;
}
NSData *payload = [NSJSONSerialization dataWithJSONObject:RLMAnalyticsPayload() options:0 error:nil];
NSString *url = [NSString stringWithFormat:@"https://api.mixpanel.com/track/?data=%@&ip=1", [payload base64EncodedStringWithOptions:0]];
// No error handling or anything because logging errors annoyed people for no
// real benefit, and it's not clear what else we could do
[[NSURLSession.sharedSession dataTaskWithURL:[NSURL URLWithString:url]] resume];
}
#else
void RLMSendAnalytics() {}
#endif

470
Example/Pods/Realm/Realm/RLMArray.mm generated Normal file
View File

@@ -0,0 +1,470 @@
////////////////////////////////////////////////////////////////////////////
//
// Copyright 2014 Realm Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////
#import "RLMArray_Private.hpp"
#import "RLMObject_Private.h"
#import "RLMObjectStore.h"
#import "RLMObjectSchema.h"
#import "RLMQueryUtil.hpp"
#import "RLMSwiftSupport.h"
#import "RLMUtil.hpp"
#import <realm/link_view.hpp>
// See -countByEnumeratingWithState:objects:count
@interface RLMArrayHolder : NSObject {
@public
std::unique_ptr<id[]> items;
}
@end
@implementation RLMArrayHolder
@end
@implementation RLMArray {
@public
// array for standalone
NSMutableArray *_backingArray;
}
template<typename IndexSetFactory>
static void changeArray(__unsafe_unretained RLMArray *const ar, NSKeyValueChange kind, dispatch_block_t f, IndexSetFactory&& is) {
if (!ar->_backingArray) {
ar->_backingArray = [NSMutableArray new];
}
if (RLMObjectBase *parent = ar->_parentObject) {
NSIndexSet *indexes = is();
[parent willChange:kind valuesAtIndexes:indexes forKey:ar->_key];
f();
[parent didChange:kind valuesAtIndexes:indexes forKey:ar->_key];
}
else {
f();
}
}
static void changeArray(__unsafe_unretained RLMArray *const ar, NSKeyValueChange kind, NSUInteger index, dispatch_block_t f) {
changeArray(ar, kind, f, [=] { return [NSIndexSet indexSetWithIndex:index]; });
}
static void changeArray(__unsafe_unretained RLMArray *const ar, NSKeyValueChange kind, NSRange range, dispatch_block_t f) {
changeArray(ar, kind, f, [=] { return [NSIndexSet indexSetWithIndexesInRange:range]; });
}
static void changeArray(__unsafe_unretained RLMArray *const ar, NSKeyValueChange kind, NSIndexSet *is, dispatch_block_t f) {
changeArray(ar, kind, f, [=] { return is; });
}
- (instancetype)initWithObjectClassName:(NSString *)objectClassName {
self = [super init];
if (self) {
_objectClassName = objectClassName;
}
return self;
}
- (RLMRealm *)realm {
return nil;
}
//
// Generic implementations for all RLMArray variants
//
- (id)firstObject {
if (self.count) {
return [self objectAtIndex:0];
}
return nil;
}
- (id)lastObject {
NSUInteger count = self.count;
if (count) {
return [self objectAtIndex:count-1];
}
return nil;
}
- (void)addObjects:(id<NSFastEnumeration>)objects {
for (id obj in objects) {
[self addObject:obj];
}
}
- (void)addObject:(RLMObject *)object {
[self insertObject:object atIndex:self.count];
}
- (void)removeLastObject {
NSUInteger count = self.count;
if (count) {
[self removeObjectAtIndex:count-1];
}
}
- (id)objectAtIndexedSubscript:(NSUInteger)index {
return [self objectAtIndex:index];
}
- (void)setObject:(id)newValue atIndexedSubscript:(NSUInteger)index {
[self replaceObjectAtIndex:index withObject:newValue];
}
//
// Standalone RLMArray implementation
//
static void RLMValidateMatchingObjectType(RLMArray *array, RLMObject *object) {
if (!object) {
@throw RLMException(@"Object must not be nil");
}
if (![array->_objectClassName isEqualToString:object->_objectSchema.className]) {
@throw RLMException(@"Object type '%@' does not match RLMArray type '%@'.", object->_objectSchema.className, array->_objectClassName);
}
}
static void RLMValidateArrayBounds(__unsafe_unretained RLMArray *const ar,
NSUInteger index, bool allowOnePastEnd=false) {
NSUInteger max = ar->_backingArray.count + allowOnePastEnd;
if (index >= max) {
@throw RLMException(@"Index %llu is out of bounds (must be less than %llu).",
(unsigned long long)index, (unsigned long long)max);
}
}
- (id)objectAtIndex:(NSUInteger)index {
RLMValidateArrayBounds(self, index);
if (!_backingArray) {
_backingArray = [NSMutableArray new];
}
return [_backingArray objectAtIndex:index];
}
- (NSUInteger)count {
return _backingArray.count;
}
- (BOOL)isInvalidated {
return NO;
}
- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(__unused __unsafe_unretained id [])buffer count:(__unused NSUInteger)len {
if (state->state != 0) {
return 0;
}
// We need to enumerate a copy of the backing array so that it doesn't
// reflect changes made during enumeration. This copy has to be autoreleased
// (since there's nowhere for us to store a strong reference), and uses
// RLMArrayHolder rather than an NSArray because NSArray doesn't guarantee
// that it'll use a single contiguous block of memory, and if it doesn't
// we'd need to forward multiple calls to this method to the same NSArray,
// which would require holding a reference to it somewhere.
__autoreleasing RLMArrayHolder *copy = [[RLMArrayHolder alloc] init];
copy->items = std::make_unique<id[]>(self.count);
NSUInteger i = 0;
for (id object in _backingArray) {
copy->items[i++] = object;
}
state->itemsPtr = (__unsafe_unretained id *)(void *)copy->items.get();
// needs to point to something valid, but the whole point of this is so
// that it can't be changed
state->mutationsPtr = state->extra;
state->state = i;
return i;
}
- (void)addObjectsFromArray:(NSArray *)array {
for (id obj in array) {
RLMValidateMatchingObjectType(self, obj);
}
changeArray(self, NSKeyValueChangeInsertion, NSMakeRange(_backingArray.count, array.count), ^{
[_backingArray addObjectsFromArray:array];
});
}
- (void)insertObject:(RLMObject *)anObject atIndex:(NSUInteger)index {
RLMValidateMatchingObjectType(self, anObject);
RLMValidateArrayBounds(self, index, true);
changeArray(self, NSKeyValueChangeInsertion, index, ^{
[_backingArray insertObject:anObject atIndex:index];
});
}
- (void)insertObjects:(id<NSFastEnumeration>)objects atIndexes:(NSIndexSet *)indexes {
changeArray(self, NSKeyValueChangeInsertion, indexes, ^{
NSUInteger currentIndex = [indexes firstIndex];
for (RLMObject *obj in objects) {
RLMValidateMatchingObjectType(self, obj);
[_backingArray insertObject:obj atIndex:currentIndex];
currentIndex = [indexes indexGreaterThanIndex:currentIndex];
}
});
}
- (void)removeObjectAtIndex:(NSUInteger)index {
RLMValidateArrayBounds(self, index);
changeArray(self, NSKeyValueChangeRemoval, index, ^{
[_backingArray removeObjectAtIndex:index];
});
}
- (void)removeObjectsAtIndexes:(NSIndexSet *)indexes {
changeArray(self, NSKeyValueChangeRemoval, indexes, ^{
[_backingArray removeObjectsAtIndexes:indexes];
});
}
- (void)replaceObjectAtIndex:(NSUInteger)index withObject:(id)anObject {
RLMValidateMatchingObjectType(self, anObject);
RLMValidateArrayBounds(self, index);
changeArray(self, NSKeyValueChangeReplacement, index, ^{
[_backingArray replaceObjectAtIndex:index withObject:anObject];
});
}
- (void)moveObjectAtIndex:(NSUInteger)sourceIndex toIndex:(NSUInteger)destinationIndex {
RLMValidateArrayBounds(self, sourceIndex);
RLMValidateArrayBounds(self, destinationIndex);
RLMObjectBase *original = _backingArray[sourceIndex];
auto start = std::min(sourceIndex, destinationIndex);
auto len = std::max(sourceIndex, destinationIndex) - start + 1;
changeArray(self, NSKeyValueChangeReplacement, {start, len}, ^{
[_backingArray removeObjectAtIndex:sourceIndex];
[_backingArray insertObject:original atIndex:destinationIndex];
});
}
- (void)exchangeObjectAtIndex:(NSUInteger)index1 withObjectAtIndex:(NSUInteger)index2 {
RLMValidateArrayBounds(self, index1);
RLMValidateArrayBounds(self, index2);
changeArray(self, NSKeyValueChangeReplacement, ^{
[_backingArray exchangeObjectAtIndex:index1 withObjectAtIndex:index2];
}, [=] {
NSMutableIndexSet *set = [[NSMutableIndexSet alloc] initWithIndex:index1];
[set addIndex:index2];
return set;
});
}
- (NSUInteger)indexOfObject:(RLMObject *)object {
RLMValidateMatchingObjectType(self, object);
NSUInteger index = 0;
for (RLMObject *cmp in _backingArray) {
if (RLMObjectBaseAreEqual(object, cmp)) {
return index;
}
index++;
}
return NSNotFound;
}
- (void)removeAllObjects {
changeArray(self, NSKeyValueChangeRemoval, NSMakeRange(0, _backingArray.count), ^{
[_backingArray removeAllObjects];
});
}
- (RLMResults *)objectsWhere:(NSString *)predicateFormat, ...
{
va_list args;
va_start(args, predicateFormat);
RLMResults *results = [self objectsWhere:predicateFormat args:args];
va_end(args);
return results;
}
- (RLMResults *)objectsWhere:(NSString *)predicateFormat args:(va_list)args
{
return [self objectsWithPredicate:[NSPredicate predicateWithFormat:predicateFormat arguments:args]];
}
- (id)valueForKeyPath:(NSString *)keyPath {
if (!_backingArray) {
return [super valueForKeyPath:keyPath];
}
// Although delegating to valueForKeyPath: here would allow to support
// nested key paths as well, limiting functionality gives consistency
// between standalone and persisted arrays.
if ([keyPath characterAtIndex:0] == '@') {
NSRange operatorRange = [keyPath rangeOfString:@"." options:NSLiteralSearch];
if (operatorRange.location != NSNotFound) {
NSString *operatorKeyPath = [keyPath substringFromIndex:operatorRange.location + 1];
if ([operatorKeyPath rangeOfString:@"."].location != NSNotFound) {
@throw RLMException(@"Nested key paths are not supported yet for KVC collection operators.");
}
}
}
return [_backingArray valueForKeyPath:keyPath];
}
- (id)valueForKey:(NSString *)key {
if ([key isEqualToString:RLMInvalidatedKey]) {
return @NO; // Standalone arrays are never invalidated
}
if (!_backingArray) {
return @[];
}
return [_backingArray valueForKey:key];
}
- (void)setValue:(id)value forKey:(NSString *)key {
[_backingArray setValue:value forKey:key];
}
- (NSUInteger)indexOfObjectWithPredicate:(NSPredicate *)predicate {
if (!_backingArray) {
return NSNotFound;
}
return [_backingArray indexOfObjectPassingTest:^BOOL(id obj, NSUInteger, BOOL *) {
return [predicate evaluateWithObject:obj];
}];
}
- (NSArray *)objectsAtIndexes:(NSIndexSet *)indexes {
if (!_backingArray) {
_backingArray = [NSMutableArray new];
}
return [_backingArray objectsAtIndexes:indexes];
}
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context {
RLMValidateArrayObservationKey(keyPath, self);
[super addObserver:observer forKeyPath:keyPath options:options context:context];
}
//
// Methods unsupported on standalone RLMArray instances
//
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-parameter"
- (RLMResults *)objectsWithPredicate:(NSPredicate *)predicate
{
@throw RLMException(@"This method can only be called on RLMArray instances retrieved from an RLMRealm");
}
- (RLMResults *)sortedResultsUsingProperty:(NSString *)property ascending:(BOOL)ascending
{
return [self sortedResultsUsingDescriptors:@[[RLMSortDescriptor sortDescriptorWithProperty:property ascending:ascending]]];
}
- (RLMResults *)sortedResultsUsingDescriptors:(NSArray *)properties
{
@throw RLMException(@"This method can only be called on RLMArray instances retrieved from an RLMRealm");
}
// The compiler complains about the method's argument type not matching due to
// it not having the generic type attached, but it doesn't seem to be possible
// to actually include the generic type
// http://www.openradar.me/radar?id=6135653276319744
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wmismatched-parameter-types"
- (RLMNotificationToken *)addNotificationBlock:(void (^)(RLMArray *, NSError *))block {
@throw RLMException(@"This method can only be called on RLMArray instances retrieved from an RLMRealm");
}
#pragma clang diagnostic pop
#pragma GCC diagnostic pop
- (NSUInteger)indexOfObjectWhere:(NSString *)predicateFormat, ...
{
va_list args;
va_start(args, predicateFormat);
NSUInteger index = [self indexOfObjectWhere:predicateFormat args:args];
va_end(args);
return index;
}
- (NSUInteger)indexOfObjectWhere:(NSString *)predicateFormat args:(va_list)args
{
return [self indexOfObjectWithPredicate:[NSPredicate predicateWithFormat:predicateFormat
arguments:args]];
}
#pragma mark - Superclass Overrides
- (NSString *)description
{
return [self descriptionWithMaxDepth:RLMDescriptionMaxDepth];
}
- (NSString *)descriptionWithMaxDepth:(NSUInteger)depth {
if (depth == 0) {
return @"<Maximum depth exceeded>";
}
const NSUInteger maxObjects = 100;
NSMutableString *mString = [NSMutableString stringWithFormat:@"RLMArray <%p> (\n", self];
unsigned long index = 0, skipped = 0;
for (id obj in self) {
NSString *sub;
if ([obj respondsToSelector:@selector(descriptionWithMaxDepth:)]) {
sub = [obj descriptionWithMaxDepth:depth - 1];
}
else {
sub = [obj description];
}
// Indent child objects
NSString *objDescription = [sub stringByReplacingOccurrencesOfString:@"\n" withString:@"\n\t"];
[mString appendFormat:@"\t[%lu] %@,\n", index++, objDescription];
if (index >= maxObjects) {
skipped = self.count - maxObjects;
break;
}
}
// Remove last comma and newline characters
if(self.count > 0)
[mString deleteCharactersInRange:NSMakeRange(mString.length-2, 2)];
if (skipped) {
[mString appendFormat:@"\n\t... %lu objects skipped.", skipped];
}
[mString appendFormat:@"\n)"];
return [NSString stringWithString:mString];
}
@end
@interface RLMSortDescriptor ()
@property (nonatomic, strong) NSString *property;
@property (nonatomic, assign) BOOL ascending;
@end
@implementation RLMSortDescriptor
+ (instancetype)sortDescriptorWithProperty:(NSString *)propertyName ascending:(BOOL)ascending {
RLMSortDescriptor *desc = [[RLMSortDescriptor alloc] init];
desc->_property = propertyName;
desc->_ascending = ascending;
return desc;
}
- (instancetype)reversedSortDescriptor {
return [self.class sortDescriptorWithProperty:_property ascending:!_ascending];
}
@end

View File

@@ -0,0 +1,487 @@
////////////////////////////////////////////////////////////////////////////
//
// Copyright 2014 Realm Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////
#import "RLMArray_Private.hpp"
#import "RLMObjectSchema_Private.hpp"
#import "RLMObjectStore.h"
#import "RLMObject_Private.hpp"
#import "RLMObservation.hpp"
#import "RLMProperty_Private.h"
#import "RLMQueryUtil.hpp"
#import "RLMRealm_Private.hpp"
#import "RLMSchema.h"
#import "RLMUtil.hpp"
#import "results.hpp"
#import <realm/table_view.hpp>
#import <objc/runtime.h>
//
// RLMArray implementation
//
@implementation RLMArrayLinkView {
@public
realm::LinkViewRef _backingLinkView;
RLMRealm *_realm;
__unsafe_unretained RLMObjectSchema *_containingObjectSchema;
std::unique_ptr<RLMObservationInfo> _observationInfo;
}
+ (RLMArrayLinkView *)arrayWithObjectClassName:(NSString *)objectClassName
view:(realm::LinkViewRef)view
realm:(RLMRealm *)realm
key:(NSString *)key
parentSchema:(RLMObjectSchema *)parentSchema {
RLMArrayLinkView *ar = [[RLMArrayLinkView alloc] initWithObjectClassName:objectClassName];
ar->_backingLinkView = view;
ar->_realm = realm;
ar->_objectSchema = ar->_realm.schema[objectClassName];
ar->_containingObjectSchema = parentSchema;
ar->_key = key;
return ar;
}
void RLMValidateArrayObservationKey(__unsafe_unretained NSString *const keyPath,
__unsafe_unretained RLMArray *const array) {
if (![keyPath isEqualToString:RLMInvalidatedKey]) {
@throw RLMException(@"[<%@ %p> addObserver:forKeyPath:options:context:] is not supported. Key path: %@",
[array class], array, keyPath);
}
}
void RLMEnsureArrayObservationInfo(std::unique_ptr<RLMObservationInfo>& info,
__unsafe_unretained NSString *const keyPath,
__unsafe_unretained RLMArray *const array,
__unsafe_unretained id const observed) {
RLMValidateArrayObservationKey(keyPath, array);
if (!info && array.class == [RLMArrayLinkView class]) {
RLMArrayLinkView *lv = static_cast<RLMArrayLinkView *>(array);
info = std::make_unique<RLMObservationInfo>(lv->_containingObjectSchema,
lv->_backingLinkView->get_origin_row_index(),
observed);
}
}
//
// validation helpers
//
static inline void RLMLinkViewArrayValidateAttached(__unsafe_unretained RLMArrayLinkView *const ar) {
if (!ar->_backingLinkView->is_attached()) {
@throw RLMException(@"RLMArray is no longer valid");
}
[ar->_realm verifyThread];
}
static inline void RLMLinkViewArrayValidateInWriteTransaction(__unsafe_unretained RLMArrayLinkView *const ar) {
// first verify attached
RLMLinkViewArrayValidateAttached(ar);
if (!ar->_realm.inWriteTransaction) {
@throw RLMException(@"Can't mutate a persisted array outside of a write transaction.");
}
}
static inline void RLMValidateObjectClass(__unsafe_unretained RLMObjectBase *const obj, __unsafe_unretained NSString *const expected) {
if (!obj) {
@throw RLMException(@"Cannot add `nil` to RLMArray<%@>", expected);
}
NSString *objectClassName = obj->_objectSchema.className;
if (![objectClassName isEqualToString:expected]) {
@throw RLMException(@"Cannot add object of type '%@' to RLMArray<%@>", objectClassName, expected);
}
}
template<typename IndexSetFactory>
static void changeArray(__unsafe_unretained RLMArrayLinkView *const ar, NSKeyValueChange kind, dispatch_block_t f, IndexSetFactory&& is) {
RLMObservationInfo *info = RLMGetObservationInfo(ar->_observationInfo.get(),
ar->_backingLinkView->get_origin_row_index(),
ar->_containingObjectSchema);
if (info) {
NSIndexSet *indexes = is();
info->willChange(ar->_key, kind, indexes);
f();
info->didChange(ar->_key, kind, indexes);
}
else {
f();
}
}
static void changeArray(__unsafe_unretained RLMArrayLinkView *const ar, NSKeyValueChange kind, NSUInteger index, dispatch_block_t f) {
changeArray(ar, kind, f, [=] { return [NSIndexSet indexSetWithIndex:index]; });
}
static void changeArray(__unsafe_unretained RLMArrayLinkView *const ar, NSKeyValueChange kind, NSRange range, dispatch_block_t f) {
changeArray(ar, kind, f, [=] { return [NSIndexSet indexSetWithIndexesInRange:range]; });
}
static void changeArray(__unsafe_unretained RLMArrayLinkView *const ar, NSKeyValueChange kind, NSIndexSet *is, dispatch_block_t f) {
changeArray(ar, kind, f, [=] { return is; });
}
//
// public method implementations
//
- (RLMRealm *)realm {
return _realm;
}
- (NSUInteger)count {
RLMLinkViewArrayValidateAttached(self);
return _backingLinkView->size();
}
- (BOOL)isInvalidated {
return !_backingLinkView->is_attached();
}
// These two methods take advantage of that LinkViews are interned, so there's
// only ever at most one LinkView object per SharedGroup for a given row+col.
- (BOOL)isEqual:(id)object {
if (RLMArrayLinkView *linkView = RLMDynamicCast<RLMArrayLinkView>(object)) {
return linkView->_backingLinkView.get() == _backingLinkView.get();
}
return NO;
}
- (NSUInteger)hash {
return reinterpret_cast<NSUInteger>(_backingLinkView.get());
}
- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state
objects:(__unused __unsafe_unretained id [])buffer
count:(NSUInteger)len {
__autoreleasing RLMFastEnumerator *enumerator;
if (state->state == 0) {
RLMLinkViewArrayValidateAttached(self);
enumerator = [[RLMFastEnumerator alloc] initWithCollection:self objectSchema:_objectSchema];
state->extra[0] = (long)enumerator;
state->extra[1] = self.count;
}
else {
enumerator = (__bridge id)(void *)state->extra[0];
}
return [enumerator countByEnumeratingWithState:state count:len];
}
static void RLMValidateArrayBounds(__unsafe_unretained RLMArrayLinkView *const ar,
NSUInteger index, bool allowOnePastEnd=false) {
NSUInteger max = ar->_backingLinkView->size() + allowOnePastEnd;
if (index >= max) {
@throw RLMException(@"Index %llu is out of bounds (must be less than %llu).",
(unsigned long long)index, (unsigned long long)max);
}
}
- (id)objectAtIndex:(NSUInteger)index {
RLMLinkViewArrayValidateAttached(self);
RLMValidateArrayBounds(self, index);
return RLMCreateObjectAccessor(_realm, _objectSchema, _backingLinkView->get(index).get_index());
}
static void RLMInsertObject(RLMArrayLinkView *ar, RLMObject *object, NSUInteger index) {
RLMLinkViewArrayValidateInWriteTransaction(ar);
RLMValidateObjectClass(object, ar.objectClassName);
if (index == NSUIntegerMax) {
index = ar->_backingLinkView->size();
}
else {
RLMValidateArrayBounds(ar, index, true);
}
if (object->_realm != ar.realm) {
[ar.realm addObject:object];
}
else if (object->_realm) {
if (!object->_row.is_attached()) {
@throw RLMException(@"Object has been deleted or invalidated.");
}
}
changeArray(ar, NSKeyValueChangeInsertion, index, ^{
ar->_backingLinkView->insert(index, object->_row.get_index());
});
}
- (void)addObject:(RLMObject *)object {
RLMInsertObject(self, object, NSUIntegerMax);
}
- (void)insertObject:(RLMObject *)object atIndex:(NSUInteger)index {
RLMInsertObject(self, object, index);
}
- (void)insertObjects:(id<NSFastEnumeration>)objects atIndexes:(NSIndexSet *)indexes {
RLMLinkViewArrayValidateInWriteTransaction(self);
changeArray(self, NSKeyValueChangeInsertion, indexes, ^{
NSUInteger index = [indexes firstIndex];
for (RLMObject *obj in objects) {
if (index > _backingLinkView->size()) {
@throw RLMException(@"Trying to insert object at invalid index");
}
if (obj->_realm != _realm) {
[_realm addObject:obj];
}
else if (!obj->_row.is_attached()) {
@throw RLMException(@"Object has been deleted or invalidated.");
}
_backingLinkView->insert(index, obj->_row.get_index());
index = [indexes indexGreaterThanIndex:index];
}
});
}
- (void)removeObjectAtIndex:(NSUInteger)index {
RLMLinkViewArrayValidateInWriteTransaction(self);
RLMValidateArrayBounds(self, index);
changeArray(self, NSKeyValueChangeRemoval, index, ^{
_backingLinkView->remove(index);
});
}
- (void)removeObjectsAtIndexes:(NSIndexSet *)indexes {
RLMLinkViewArrayValidateInWriteTransaction(self);
changeArray(self, NSKeyValueChangeRemoval, indexes, ^{
for (NSUInteger index = [indexes lastIndex]; index != NSNotFound; index = [indexes indexLessThanIndex:index]) {
if (index >= _backingLinkView->size()) {
@throw RLMException(@"Trying to remove object at invalid index");
}
_backingLinkView->remove(index);
}
});
}
- (void)addObjectsFromArray:(NSArray *)array {
RLMLinkViewArrayValidateInWriteTransaction(self);
changeArray(self, NSKeyValueChangeInsertion, NSMakeRange(_backingLinkView->size(), array.count), ^{
for (RLMObject *obj in array) {
RLMValidateObjectClass(obj, _objectClassName);
if (obj->_realm != _realm) {
[_realm addObject:obj];
}
_backingLinkView->add(obj->_row.get_index());
}
});
}
- (void)removeAllObjects {
RLMLinkViewArrayValidateInWriteTransaction(self);
changeArray(self, NSKeyValueChangeRemoval, NSMakeRange(0, _backingLinkView->size()), ^{
_backingLinkView->clear();
});
}
- (void)replaceObjectAtIndex:(NSUInteger)index withObject:(RLMObject *)object {
RLMLinkViewArrayValidateInWriteTransaction(self);
RLMValidateObjectClass(object, self.objectClassName);
RLMValidateArrayBounds(self, index);
if (object->_realm != self.realm) {
[self.realm addObject:object];
}
changeArray(self, NSKeyValueChangeReplacement, index, ^{
_backingLinkView->set(index, object->_row.get_index());
});
}
- (void)moveObjectAtIndex:(NSUInteger)sourceIndex toIndex:(NSUInteger)destinationIndex {
RLMLinkViewArrayValidateInWriteTransaction(self);
RLMValidateArrayBounds(self, sourceIndex);
RLMValidateArrayBounds(self, destinationIndex);
auto start = std::min(sourceIndex, destinationIndex);
auto len = std::max(sourceIndex, destinationIndex) - start + 1;
changeArray(self, NSKeyValueChangeReplacement, {start, len}, ^{
_backingLinkView->move(sourceIndex, destinationIndex);
});
}
- (void)exchangeObjectAtIndex:(NSUInteger)index1 withObjectAtIndex:(NSUInteger)index2 {
RLMLinkViewArrayValidateInWriteTransaction(self);
RLMValidateArrayBounds(self, index1);
RLMValidateArrayBounds(self, index2);
changeArray(self, NSKeyValueChangeReplacement, ^{
_backingLinkView->swap(index1, index2);
}, [=] {
NSMutableIndexSet *set = [[NSMutableIndexSet alloc] initWithIndex:index1];
[set addIndex:index2];
return set;
});
}
- (NSUInteger)indexOfObject:(RLMObject *)object {
// check attached for table and object
RLMLinkViewArrayValidateAttached(self);
if (object->_realm && !object->_row.is_attached()) {
@throw RLMException(@"RLMObject is no longer valid");
}
// check that object types align
if (![_objectClassName isEqualToString:object->_objectSchema.className]) {
@throw RLMException(@"Object of type (%@) does not match RLMArray type (%@)",
object->_objectSchema.className, _objectClassName);
}
// if different tables then no match
if (object->_row.get_table() != &_backingLinkView->get_target_table()) {
return NSNotFound;
}
// call find on backing array
size_t object_ndx = object->_row.get_index();
return RLMConvertNotFound(_backingLinkView->find(object_ndx));
}
- (id)valueForKeyPath:(NSString *)keyPath {
if ([keyPath hasPrefix:@"@"]) {
// Delegate KVC collection operators to RLMResults
realm::Query query = _backingLinkView->get_target_table().where(_backingLinkView);
RLMResults *results = [RLMResults resultsWithObjectSchema:_realm.schema[self.objectClassName]
results:realm::Results(_realm->_realm, std::move(query))];
return [results valueForKeyPath:keyPath];
}
return [super valueForKeyPath:keyPath];
}
- (id)valueForKey:(NSString *)key {
// Ideally we'd use "@invalidated" for this so that "invalidated" would use
// normal array KVC semantics, but observing @things works very oddly (when
// it's part of a key path, it's triggered automatically when array index
// changes occur, and can't be sent explicitly, but works normally when it's
// the entire key path), and an RLMArrayLinkView *can't* have objects where
// invalidated is true, so we're not losing much.
if ([key isEqualToString:RLMInvalidatedKey]) {
return @(!_backingLinkView->is_attached());
}
RLMLinkViewArrayValidateAttached(self);
return RLMCollectionValueForKey(self, key);
}
- (void)setValue:(id)value forKey:(NSString *)key {
RLMLinkViewArrayValidateInWriteTransaction(self);
RLMCollectionSetValueForKey(self, key, value);
}
- (void)deleteObjectsFromRealm {
RLMLinkViewArrayValidateInWriteTransaction(self);
// delete all target rows from the realm
RLMTrackDeletions(_realm, ^{
_backingLinkView->remove_all_target_rows();
});
}
- (RLMResults *)sortedResultsUsingDescriptors:(NSArray *)properties {
RLMLinkViewArrayValidateAttached(self);
auto results = realm::Results(_realm->_realm,
_backingLinkView->get_target_table().where(_backingLinkView),
RLMSortOrderFromDescriptors(_realm.schema[_objectClassName], properties));
return [RLMResults resultsWithObjectSchema:_realm.schema[self.objectClassName]
results:std::move(results)];
}
- (RLMResults *)objectsWithPredicate:(NSPredicate *)predicate {
RLMLinkViewArrayValidateAttached(self);
realm::Query query = _backingLinkView->get_target_table().where(_backingLinkView);
RLMUpdateQueryWithPredicate(&query, predicate, _realm.schema, _realm.schema[self.objectClassName]);
return [RLMResults resultsWithObjectSchema:_realm.schema[self.objectClassName]
results:realm::Results(_realm->_realm, std::move(query))];
}
- (NSUInteger)indexOfObjectWithPredicate:(NSPredicate *)predicate {
RLMLinkViewArrayValidateAttached(self);
realm::Query query = _backingLinkView->get_target_table().where(_backingLinkView);
RLMUpdateQueryWithPredicate(&query, predicate, _realm.schema, _realm.schema[self.objectClassName]);
return RLMConvertNotFound(query.find());
}
- (NSArray *)objectsAtIndexes:(__unused NSIndexSet *)indexes {
// FIXME: this is called by KVO when array changes are made. It's not clear
// why, and returning nil seems to work fine.
return nil;
}
- (void)addObserver:(id)observer
forKeyPath:(NSString *)keyPath
options:(NSKeyValueObservingOptions)options
context:(void *)context {
RLMEnsureArrayObservationInfo(_observationInfo, keyPath, self, self);
[super addObserver:observer forKeyPath:keyPath options:options context:context];
}
- (NSUInteger)indexInSource:(NSUInteger)index {
return _backingLinkView->get(index).get_index();
}
- (realm::TableView)tableView {
return _backingLinkView->get_target_table().where(_backingLinkView).find_all();
}
// The compiler complains about the method's argument type not matching due to
// it not having the generic type attached, but it doesn't seem to be possible
// to actually include the generic type
// http://www.openradar.me/radar?id=6135653276319744
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wmismatched-parameter-types"
- (RLMNotificationToken *)addNotificationBlock:(void (^)(RLMArray *, NSError *))block {
[_realm verifyNotificationsAreSupported];
__block uint_fast64_t prevVersion = -1;
auto noteBlock = ^(NSString *notification, RLMRealm *) {
if (notification != RLMRealmDidChangeNotification) {
return;
}
if (!_backingLinkView->is_attached()) {
return;
}
auto version = _backingLinkView->get_origin_table().get_version_counter();
if (version != prevVersion) {
block(self, nil);
prevVersion = version;
}
};
CFRunLoopPerformBlock(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, ^{
noteBlock(RLMRealmDidChangeNotification, nil);
});
return [_realm addNotificationBlock:noteBlock];
}
#pragma clang diagnostic pop
@end

32
Example/Pods/Realm/Realm/RLMConstants.m generated Normal file
View File

@@ -0,0 +1,32 @@
////////////////////////////////////////////////////////////////////////////
//
// Copyright 2014 Realm Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////
#import <Realm/RLMConstants.h>
NSString * const RLMRealmRefreshRequiredNotification = @"RLMRealmRefreshRequiredNotification";
NSString * const RLMRealmDidChangeNotification = @"RLMRealmDidChangeNotification";
NSString * const RLMErrorDomain = @"io.realm";
NSString * const RLMExceptionName = @"RLMException";
NSString * const RLMRealmVersionKey = @"RLMRealmVersion";
NSString * const RLMRealmCoreVersionKey = @"RLMRealmCoreVersion";
NSString * const RLMInvalidatedKey = @"invalidated";

60
Example/Pods/Realm/Realm/RLMListBase.mm generated Normal file
View File

@@ -0,0 +1,60 @@
////////////////////////////////////////////////////////////////////////////
//
// Copyright 2015 Realm Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////
#import "RLMListBase.h"
#import "RLMArray_Private.hpp"
#import "RLMObservation.hpp"
@interface RLMArray (KVO)
- (NSArray *)objectsAtIndexes:(__unused NSIndexSet *)indexes;
@end
@implementation RLMListBase {
std::unique_ptr<RLMObservationInfo> _observationInfo;
}
- (instancetype)initWithArray:(RLMArray *)array {
self = [super init];
if (self) {
__rlmArray = array;
}
return self;
}
- (id)valueForKey:(NSString *)key {
return [__rlmArray valueForKey:key];
}
- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id __unsafe_unretained [])buffer count:(NSUInteger)len {
return [__rlmArray countByEnumeratingWithState:state objects:buffer count:len];
}
- (NSArray *)objectsAtIndexes:(NSIndexSet *)indexes {
return [__rlmArray objectsAtIndexes:indexes];
}
- (void)addObserver:(id)observer
forKeyPath:(NSString *)keyPath
options:(NSKeyValueObservingOptions)options
context:(void *)context {
RLMEnsureArrayObservationInfo(_observationInfo, keyPath, __rlmArray, self);
[super addObserver:observer forKeyPath:keyPath options:options context:context];
}
@end

149
Example/Pods/Realm/Realm/RLMMigration.mm generated Normal file
View File

@@ -0,0 +1,149 @@
////////////////////////////////////////////////////////////////////////////
//
// Copyright 2014 Realm Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////
#import "RLMMigration_Private.h"
#import "RLMAccessor.h"
#import "RLMObject.h"
#import "RLMObjectSchema_Private.hpp"
#import "RLMObjectStore.h"
#import "RLMProperty_Private.h"
#import "RLMRealm_Dynamic.h"
#import "RLMRealm_Private.hpp"
#import "RLMResults_Private.h"
#import "RLMSchema_Private.h"
#import "object_store.hpp"
#import "shared_realm.hpp"
using namespace realm;
// The source realm for a migration has to use a SharedGroup to be able to share
// the file with the destination realm, but we don't want to let the user call
// beginWriteTransaction on it as that would make no sense.
@interface RLMMigrationRealm : RLMRealm
@end
@implementation RLMMigrationRealm
- (BOOL)readonly {
return YES;
}
- (void)beginWriteTransaction {
@throw RLMException(@"Cannot modify the source Realm in a migration");
}
@end
@implementation RLMMigration
- (instancetype)initWithRealm:(RLMRealm *)realm oldRealm:(RLMRealm *)oldRealm {
self = [super init];
if (self) {
// create rw realm to migrate with current on disk table
_realm = realm;
_oldRealm = oldRealm;
object_setClass(_oldRealm, RLMMigrationRealm.class);
}
return self;
}
- (RLMSchema *)oldSchema {
return self.oldRealm.schema;
}
- (RLMSchema *)newSchema {
return self.realm.schema;
}
- (void)enumerateObjects:(NSString *)className block:(RLMObjectMigrationBlock)block {
// get all objects
RLMResults *objects = [_realm.schema schemaForClassName:className] ? [_realm allObjects:className] : nil;
RLMResults *oldObjects = [_oldRealm.schema schemaForClassName:className] ? [_oldRealm allObjects:className] : nil;
if (objects && oldObjects) {
for (long i = oldObjects.count - 1; i >= 0; i--) {
@autoreleasepool {
block(oldObjects[i], objects[i]);
}
}
}
else if (objects) {
for (long i = objects.count - 1; i >= 0; i--) {
@autoreleasepool {
block(nil, objects[i]);
}
}
}
else if (oldObjects) {
for (long i = oldObjects.count - 1; i >= 0; i--) {
@autoreleasepool {
block(oldObjects[i], nil);
}
}
}
}
- (void)execute:(RLMMigrationBlock)block {
@autoreleasepool {
// disable all primary keys for migration
for (RLMObjectSchema *objectSchema in _realm.schema.objectSchema) {
objectSchema.primaryKeyProperty.isPrimary = NO;
}
// apply block and set new schema version
uint64_t oldVersion = _oldRealm->_realm->config().schema_version;
block(self, oldVersion);
_oldRealm = nil;
_realm = nil;
}
}
-(RLMObject *)createObject:(NSString *)className withValue:(id)value {
return [_realm createObject:className withValue:value];
}
- (RLMObject *)createObject:(NSString *)className withObject:(id)object {
return [self createObject:className withValue:object];
}
- (void)deleteObject:(RLMObject *)object {
[_realm deleteObject:object];
}
- (BOOL)deleteDataForClassName:(NSString *)name {
if (!name) {
return false;
}
TableRef table = ObjectStore::table_for_object_type(_realm.group, name.UTF8String);
if (!table) {
return false;
}
if ([_realm.schema schemaForClassName:name]) {
table->clear();
}
else {
realm::ObjectStore::delete_data_for_object(_realm.group, name.UTF8String);
}
return true;
}
@end

217
Example/Pods/Realm/Realm/RLMObject.mm generated Normal file
View File

@@ -0,0 +1,217 @@
////////////////////////////////////////////////////////////////////////////
//
// Copyright 2014 Realm Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////
#import "RLMAccessor.h"
#import "RLMObject_Private.h"
#import "RLMObjectSchema_Private.hpp"
#import "RLMObjectStore.h"
#import "RLMSchema_Private.h"
#import "RLMRealm_Private.hpp"
#import "RLMQueryUtil.hpp"
// We declare things in RLMObject which are actually implemented in RLMObjectBase
// for documentation's sake, which leads to -Wunimplemented-method warnings.
// Other alternatives to this would be to disable -Wunimplemented-method for this
// file (but then we could miss legitimately missing things), or declaring the
// inherited things in a category (but they currently aren't nicely grouped for
// that).
@implementation RLMObject
// synthesized in RLMObjectBase
@dynamic invalidated, realm, objectSchema;
#pragma mark - Designated Initializers
- (instancetype)init {
return [super init];
}
- (instancetype)initWithValue:(id)value schema:(RLMSchema *)schema {
return [super initWithValue:value schema:schema];
}
- (instancetype)initWithRealm:(__unsafe_unretained RLMRealm *const)realm
schema:(__unsafe_unretained RLMObjectSchema *const)schema {
return [super initWithRealm:realm schema:schema];
}
#pragma mark - Convenience Initializers
- (instancetype)initWithValue:(id)value {
[self.class sharedSchema]; // ensure this class' objectSchema is loaded in the partialSharedSchema
RLMSchema *schema = RLMSchema.partialSharedSchema;
return [super initWithValue:value schema:schema];
}
- (instancetype)initWithObject:(id)object {
return [self initWithValue:object];
}
#pragma mark - Class-based Object Creation
+ (instancetype)createInDefaultRealmWithValue:(id)value {
return (RLMObject *)RLMCreateObjectInRealmWithValue([RLMRealm defaultRealm], [self className], value, false);
}
+ (instancetype)createInDefaultRealmWithObject:(id)object {
return [self createInDefaultRealmWithValue:object];
}
+ (instancetype)createInRealm:(RLMRealm *)realm withValue:(id)value {
return (RLMObject *)RLMCreateObjectInRealmWithValue(realm, [self className], value, false);
}
+ (instancetype)createInRealm:(RLMRealm *)realm withObject:(id)object {
return [self createInRealm:realm withValue:object];
}
+ (instancetype)createOrUpdateInDefaultRealmWithValue:(id)value {
return [self createOrUpdateInRealm:[RLMRealm defaultRealm] withValue:value];
}
+ (instancetype)createOrUpdateInDefaultRealmWithObject:(id)object {
return [self createOrUpdateInDefaultRealmWithValue:object];
}
+ (instancetype)createOrUpdateInRealm:(RLMRealm *)realm withValue:(id)value {
// verify primary key
RLMObjectSchema *schema = [self sharedSchema];
if (!schema.primaryKeyProperty) {
NSString *reason = [NSString stringWithFormat:@"'%@' does not have a primary key and can not be updated", schema.className];
@throw [NSException exceptionWithName:@"RLMExecption" reason:reason userInfo:nil];
}
return (RLMObject *)RLMCreateObjectInRealmWithValue(realm, [self className], value, true);
}
+ (instancetype)createOrUpdateInRealm:(RLMRealm *)realm withObject:(id)object {
return [self createOrUpdateInRealm:realm withValue:object];
}
#pragma mark - Subscripting
- (id)objectForKeyedSubscript:(NSString *)key {
return RLMObjectBaseObjectForKeyedSubscript(self, key);
}
- (void)setObject:(id)obj forKeyedSubscript:(NSString *)key {
RLMObjectBaseSetObjectForKeyedSubscript(self, key, obj);
}
#pragma mark - Getting & Querying
+ (RLMResults *)allObjects {
return RLMGetObjects(RLMRealm.defaultRealm, self.className, nil);
}
+ (RLMResults *)allObjectsInRealm:(RLMRealm *)realm {
return RLMGetObjects(realm, self.className, nil);
}
+ (RLMResults *)objectsWhere:(NSString *)predicateFormat, ... {
va_list args;
va_start(args, predicateFormat);
RLMResults *results = [self objectsWhere:predicateFormat args:args];
va_end(args);
return results;
}
+ (RLMResults *)objectsWhere:(NSString *)predicateFormat args:(va_list)args {
return [self objectsWithPredicate:[NSPredicate predicateWithFormat:predicateFormat arguments:args]];
}
+ (RLMResults *)objectsInRealm:(RLMRealm *)realm where:(NSString *)predicateFormat, ... {
va_list args;
va_start(args, predicateFormat);
RLMResults *results = [self objectsInRealm:realm where:predicateFormat args:args];
va_end(args);
return results;
}
+ (RLMResults *)objectsInRealm:(RLMRealm *)realm where:(NSString *)predicateFormat args:(va_list)args {
return [self objectsInRealm:realm withPredicate:[NSPredicate predicateWithFormat:predicateFormat arguments:args]];
}
+ (RLMResults *)objectsWithPredicate:(NSPredicate *)predicate {
return RLMGetObjects(RLMRealm.defaultRealm, self.className, predicate);
}
+ (RLMResults *)objectsInRealm:(RLMRealm *)realm withPredicate:(NSPredicate *)predicate {
return RLMGetObjects(realm, self.className, predicate);
}
+ (instancetype)objectForPrimaryKey:(id)primaryKey {
return RLMGetObject(RLMRealm.defaultRealm, self.className, primaryKey);
}
+ (instancetype)objectInRealm:(RLMRealm *)realm forPrimaryKey:(id)primaryKey {
return RLMGetObject(realm, self.className, primaryKey);
}
#pragma mark - Other Instance Methods
- (NSArray *)linkingObjectsOfClass:(NSString *)className forProperty:(NSString *)property {
return RLMObjectBaseLinkingObjectsOfClass(self, className, property);
}
- (BOOL)isEqualToObject:(RLMObject *)object {
return [object isKindOfClass:RLMObject.class] && RLMObjectBaseAreEqual(self, object);
}
+ (NSString *)className {
return [super className];
}
#pragma mark - Default values for schema definition
+ (NSArray *)indexedProperties {
return @[];
}
+ (NSDictionary *)defaultPropertyValues {
return nil;
}
+ (NSString *)primaryKey {
return nil;
}
+ (NSArray *)ignoredProperties {
return nil;
}
+ (NSArray *)requiredProperties {
return nil;
}
@end
@implementation RLMDynamicObject
+ (BOOL)shouldIncludeInDefaultSchema {
return NO;
}
- (id)valueForUndefinedKey:(NSString *)key {
return RLMDynamicGet(self, RLMValidatedGetProperty(self, key));
}
- (void)setValue:(id)value forUndefinedKey:(NSString *)key {
RLMDynamicValidatedSet(self, key, value);
}
@end

480
Example/Pods/Realm/Realm/RLMObjectBase.mm generated Normal file
View File

@@ -0,0 +1,480 @@
////////////////////////////////////////////////////////////////////////////
//
// Copyright 2014 Realm Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////
#import "RLMObject_Private.hpp"
#import "RLMAccessor.h"
#import "RLMArray_Private.hpp"
#import "RLMListBase.h"
#import "RLMObjectSchema_Private.hpp"
#import "RLMObjectStore.h"
#import "RLMObservation.hpp"
#import "RLMOptionalBase.h"
#import "RLMProperty_Private.h"
#import "RLMRealm_Private.hpp"
#import "RLMSchema_Private.h"
#import "RLMSwiftSupport.h"
#import "RLMUtil.hpp"
using namespace realm;
const NSUInteger RLMDescriptionMaxDepth = 5;
static bool RLMInitializedObjectSchema(RLMObjectBase *obj) {
obj->_objectSchema = [obj.class sharedSchema];
if (!obj->_objectSchema) {
return false;
}
// set default values
if (!obj->_objectSchema.isSwiftClass) {
NSDictionary *dict = RLMDefaultValuesForObjectSchema(obj->_objectSchema);
for (NSString *key in dict) {
[obj setValue:dict[key] forKey:key];
}
}
// set standalone accessor class
object_setClass(obj, obj->_objectSchema.standaloneClass);
return true;
}
@implementation RLMObjectBase
// standalone init
- (instancetype)init {
self = [super init];
if (self) {
RLMInitializedObjectSchema(self);
}
return self;
}
- (void)dealloc {
// This can't be a unique_ptr because associated objects are removed
// *after* c++ members are destroyed, and we need it to still be alive when
// that happens
delete _observationInfo;
}
static id RLMValidatedObjectForProperty(id obj, RLMProperty *prop, RLMSchema *schema) {
if (RLMIsObjectValidForProperty(obj, prop)) {
return obj;
}
// check for object or array of properties
if (prop.type == RLMPropertyTypeObject) {
// for object create and try to initialize with obj
RLMObjectSchema *objSchema = schema[prop.objectClassName];
return [[objSchema.objectClass alloc] initWithValue:obj schema:schema];
}
else if (prop.type == RLMPropertyTypeArray && [obj conformsToProtocol:@protocol(NSFastEnumeration)]) {
// for arrays, create objects for each element and return new array
RLMObjectSchema *objSchema = schema[prop.objectClassName];
RLMArray *objects = [[RLMArray alloc] initWithObjectClassName:objSchema.className];
for (id el in obj) {
[objects addObject:[[objSchema.objectClass alloc] initWithValue:el schema:schema]];
}
return objects;
}
// if not convertible to prop throw
@throw RLMException(@"Invalid value '%@' for property '%@'", obj, prop.name);
}
- (instancetype)initWithValue:(id)value schema:(RLMSchema *)schema {
if (!(self = [super init])) {
return self;
}
if (!RLMInitializedObjectSchema(self)) {
// Don't populate fields from the passed-in object if we're called
// during schema init
return self;
}
NSArray *properties = _objectSchema.properties;
if (NSArray *array = RLMDynamicCast<NSArray>(value)) {
if (array.count != properties.count) {
@throw RLMException(@"Invalid array input. Number of array elements does not match number of properties.");
}
for (NSUInteger i = 0; i < array.count; i++) {
id propertyValue = RLMValidatedObjectForProperty(array[i], properties[i], schema);
[self setValue:RLMCoerceToNil(propertyValue) forKeyPath:[properties[i] name]];
}
}
else {
// assume our object is an NSDictionary or an object with kvc properties
NSDictionary *defaultValues = nil;
for (RLMProperty *prop in properties) {
id obj = RLMValidatedValueForProperty(value, prop.name, _objectSchema.className);
// get default for nil object
if (!obj) {
if (!defaultValues) {
defaultValues = RLMDefaultValuesForObjectSchema(_objectSchema);
}
obj = defaultValues[prop.name];
}
obj = RLMValidatedObjectForProperty(obj, prop, schema);
[self setValue:RLMCoerceToNil(obj) forKeyPath:prop.name];
}
}
return self;
}
- (instancetype)initWithRealm:(__unsafe_unretained RLMRealm *const)realm
schema:(__unsafe_unretained RLMObjectSchema *const)schema {
self = [super init];
if (self) {
_realm = realm;
_objectSchema = schema;
}
return self;
}
- (id)valueForKey:(NSString *)key {
if (_observationInfo) {
return _observationInfo->valueForKey(key);
}
return [super valueForKey:key];
}
// Generic Swift properties can't be dynamic, so KVO doesn't work for them by default
- (id)valueForUndefinedKey:(NSString *)key {
if (Ivar ivar = _objectSchema[key].swiftIvar) {
return RLMCoerceToNil(object_getIvar(self, ivar));
}
return [super valueForUndefinedKey:key];
}
- (void)setValue:(id)value forUndefinedKey:(NSString *)key {
RLMProperty *property = _objectSchema[key];
if (Ivar ivar = property.swiftIvar) {
if (property.type == RLMPropertyTypeArray && [value conformsToProtocol:@protocol(NSFastEnumeration)]) {
RLMArray *array = [object_getIvar(self, ivar) _rlmArray];
[array removeAllObjects];
[array addObjects:value];
}
else if (property.optional) {
RLMOptionalBase *optional = object_getIvar(self, ivar);
optional.underlyingValue = value;
}
return;
}
[super setValue:value forUndefinedKey:key];
}
// overridden at runtime per-class for performance
+ (NSString *)className {
NSString *className = NSStringFromClass(self);
if ([RLMSwiftSupport isSwiftClassName:className]) {
className = [RLMSwiftSupport demangleClassName:className];
}
return className;
}
// overridden at runtime per-class for performance
+ (RLMObjectSchema *)sharedSchema {
return [RLMSchema sharedSchemaForClass:self.class];
}
+ (Class)objectUtilClass:(BOOL)isSwift {
return RLMObjectUtilClass(isSwift);
}
- (NSString *)description
{
if (self.isInvalidated) {
return @"[invalid object]";
}
return [self descriptionWithMaxDepth:RLMDescriptionMaxDepth];
}
- (NSString *)descriptionWithMaxDepth:(NSUInteger)depth {
if (depth == 0) {
return @"<Maximum depth exceeded>";
}
NSString *baseClassName = _objectSchema.className;
NSMutableString *mString = [NSMutableString stringWithFormat:@"%@ {\n", baseClassName];
for (RLMProperty *property in _objectSchema.properties) {
id object = RLMObjectBaseObjectForKeyedSubscript(self, property.name);
NSString *sub;
if ([object respondsToSelector:@selector(descriptionWithMaxDepth:)]) {
sub = [object descriptionWithMaxDepth:depth - 1];
}
else if (property.type == RLMPropertyTypeData) {
static NSUInteger maxPrintedDataLength = 24;
NSData *data = object;
NSUInteger length = data.length;
if (length > maxPrintedDataLength) {
data = [NSData dataWithBytes:data.bytes length:maxPrintedDataLength];
}
NSString *dataDescription = [data description];
sub = [NSString stringWithFormat:@"<%@ — %lu total bytes>", [dataDescription substringWithRange:NSMakeRange(1, dataDescription.length - 2)], (unsigned long)length];
}
else {
sub = [object description];
}
[mString appendFormat:@"\t%@ = %@;\n", property.name, [sub stringByReplacingOccurrencesOfString:@"\n" withString:@"\n\t"]];
}
[mString appendString:@"}"];
return [NSString stringWithString:mString];
}
- (RLMRealm *)realm {
return _realm;
}
- (RLMObjectSchema *)objectSchema {
return _objectSchema;
}
- (BOOL)isInvalidated {
// if not standalone and our accessor has been detached, we have been deleted
return self.class == _objectSchema.accessorClass && !_row.is_attached();
}
- (BOOL)isEqual:(id)object {
if (RLMObjectBase *other = RLMDynamicCast<RLMObjectBase>(object)) {
if (_objectSchema.primaryKeyProperty) {
return RLMObjectBaseAreEqual(self, other);
}
}
return [super isEqual:object];
}
- (NSUInteger)hash {
if (_objectSchema.primaryKeyProperty) {
id primaryProperty = [self valueForKey:_objectSchema.primaryKeyProperty.name];
// modify the hash of our primary key value to avoid potential (although unlikely) collisions
return [primaryProperty hash] ^ 1;
}
else {
return [super hash];
}
}
+ (BOOL)shouldIncludeInDefaultSchema {
return RLMIsObjectSubclass(self);
}
- (id)mutableArrayValueForKey:(NSString *)key {
id obj = [self valueForKey:key];
if ([obj isKindOfClass:[RLMArray class]]) {
return obj;
}
return [super mutableArrayValueForKey:key];
}
- (void)addObserver:(id)observer
forKeyPath:(NSString *)keyPath
options:(NSKeyValueObservingOptions)options
context:(void *)context {
if (!_observationInfo) {
_observationInfo = new RLMObservationInfo(self);
}
_observationInfo->recordObserver(_row, _objectSchema, keyPath);
[super addObserver:observer forKeyPath:keyPath options:options context:context];
}
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath {
[super removeObserver:observer forKeyPath:keyPath];
_observationInfo->removeObserver();
}
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {
const char *className = class_getName(self);
const char accessorClassPrefix[] = "RLMAccessor_";
if (!strncmp(className, accessorClassPrefix, sizeof(accessorClassPrefix) - 1)) {
if (self.sharedSchema[key]) {
return NO;
}
}
return [super automaticallyNotifiesObserversForKey:key];
}
@end
void RLMObjectBaseSetRealm(__unsafe_unretained RLMObjectBase *object, __unsafe_unretained RLMRealm *realm) {
if (object) {
object->_realm = realm;
}
}
RLMRealm *RLMObjectBaseRealm(__unsafe_unretained RLMObjectBase *object) {
return object ? object->_realm : nil;
}
void RLMObjectBaseSetObjectSchema(__unsafe_unretained RLMObjectBase *object, __unsafe_unretained RLMObjectSchema *objectSchema) {
if (object) {
object->_objectSchema = objectSchema;
}
}
RLMObjectSchema *RLMObjectBaseObjectSchema(__unsafe_unretained RLMObjectBase *object) {
return object ? object->_objectSchema : nil;
}
NSArray *RLMObjectBaseLinkingObjectsOfClass(RLMObjectBase *object, NSString *className, NSString *property) {
if (!object) {
return nil;
}
if (!object->_realm) {
@throw RLMException(@"Linking object only available for objects in a Realm.");
}
[object->_realm verifyThread];
if (!object->_row.is_attached()) {
@throw RLMException(@"Object has been deleted or invalidated and is no longer valid.");
}
RLMObjectSchema *schema = object->_realm.schema[className];
RLMProperty *prop = schema[property];
if (!prop) {
@throw RLMException(@"Invalid property '%@'", property);
}
if (![prop.objectClassName isEqualToString:object->_objectSchema.className]) {
@throw RLMException(@"Property '%@' of '%@' expected to be an RLMObject or RLMArray property pointing to type '%@'", property, className, object->_objectSchema.className);
}
Table *table = schema.table;
if (!table) {
return @[];
}
size_t col = prop.column;
NSUInteger count = object->_row.get_backlink_count(*table, col);
NSMutableArray *links = [NSMutableArray arrayWithCapacity:count];
for (NSUInteger i = 0; i < count; i++) {
[links addObject:RLMCreateObjectAccessor(object->_realm, schema, object->_row.get_backlink(*table, col, i))];
}
return [links copy];
}
id RLMObjectBaseObjectForKeyedSubscript(RLMObjectBase *object, NSString *key) {
if (!object) {
return nil;
}
if (object->_realm) {
return RLMDynamicGet(object, RLMValidatedGetProperty(object, key));
}
else {
return [object valueForKey:key];
}
}
void RLMObjectBaseSetObjectForKeyedSubscript(RLMObjectBase *object, NSString *key, id obj) {
if (!object) {
return;
}
if (object->_realm) {
RLMDynamicValidatedSet(object, key, obj);
}
else {
[object setValue:obj forKey:key];
}
}
BOOL RLMObjectBaseAreEqual(RLMObjectBase *o1, RLMObjectBase *o2) {
// if not the correct types throw
if ((o1 && ![o1 isKindOfClass:RLMObjectBase.class]) || (o2 && ![o2 isKindOfClass:RLMObjectBase.class])) {
@throw RLMException(@"Can only compare objects of class RLMObjectBase");
}
// if identical object (or both are nil)
if (o1 == o2) {
return YES;
}
// if one is nil
if (o1 == nil || o2 == nil) {
return NO;
}
// if not in realm or differing realms
if (o1->_realm == nil || o1->_realm != o2->_realm) {
return NO;
}
// if either are detached
if (!o1->_row.is_attached() || !o2->_row.is_attached()) {
return NO;
}
// if table and index are the same
return o1->_row.get_table() == o2->_row.get_table()
&& o1->_row.get_index() == o2->_row.get_index();
}
id RLMValidatedValueForProperty(id object, NSString *key, NSString *className) {
@try {
return [object valueForKey:key];
}
@catch (NSException *e) {
if ([e.name isEqualToString:NSUndefinedKeyException]) {
@throw RLMException(@"Invalid value '%@' to initialize object of type '%@': missing key '%@'",
object, className, key);
}
@throw;
}
}
Class RLMObjectUtilClass(BOOL isSwift) {
static Class objectUtilObjc = [RLMObjectUtil class];
static Class objectUtilSwift = NSClassFromString(@"RealmSwiftObjectUtil");
return isSwift && objectUtilSwift ? objectUtilSwift : objectUtilObjc;
}
@implementation RLMObjectUtil
+ (NSArray *)ignoredPropertiesForClass:(Class)cls {
return [cls ignoredProperties];
}
+ (NSArray *)indexedPropertiesForClass:(Class)cls {
return [cls indexedProperties];
}
+ (NSArray *)getGenericListPropertyNames:(__unused id)obj {
return nil;
}
+ (void)initializeListProperty:(__unused RLMObjectBase *)object property:(__unused RLMProperty *)property array:(__unused RLMArray *)array {
}
+ (void)initializeOptionalProperty:(__unused RLMObjectBase *)object property:(__unused RLMProperty *)property {
}
+ (NSDictionary *)getOptionalProperties:(__unused id)obj {
return nil;
}
+ (NSArray *)requiredPropertiesForClass:(Class)cls {
return [cls requiredProperties];
}
@end

View File

@@ -0,0 +1,405 @@
////////////////////////////////////////////////////////////////////////////
//
// Copyright 2014 Realm Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////
#import "RLMObjectSchema_Private.hpp"
#import "RLMArray.h"
#import "RLMListBase.h"
#import "RLMObject_Private.h"
#import "RLMProperty_Private.h"
#import "RLMRealm_Dynamic.h"
#import "RLMRealm_Private.hpp"
#import "RLMSchema_Private.h"
#import "RLMSwiftSupport.h"
#import "RLMUtil.hpp"
#import "object_store.hpp"
using namespace realm;
// private properties
@interface RLMObjectSchema ()
@property (nonatomic, readwrite) NSDictionary RLM_GENERIC(id, RLMProperty *) *propertiesByName;
@property (nonatomic, readwrite) NSString *className;
@end
@implementation RLMObjectSchema {
// table accessor optimization
realm::TableRef _table;
NSArray *_propertiesInDeclaredOrder;
}
- (instancetype)initWithClassName:(NSString *)objectClassName objectClass:(Class)objectClass properties:(NSArray *)properties {
self = [super init];
self.className = objectClassName;
self.properties = properties;
self.objectClass = objectClass;
self.accessorClass = objectClass;
self.standaloneClass = objectClass;
return self;
}
// return properties by name
-(RLMProperty *)objectForKeyedSubscript:(id <NSCopying>)key {
return _propertiesByName[key];
}
// create property map when setting property array
-(void)setProperties:(NSArray *)properties {
_properties = properties;
NSMutableDictionary *map = [NSMutableDictionary dictionaryWithCapacity:_properties.count];
for (RLMProperty *prop in _properties) {
map[prop.name] = prop;
if (prop.isPrimary) {
self.primaryKeyProperty = prop;
}
}
_propertiesByName = map;
_propertiesInDeclaredOrder = nil;
}
- (void)setPrimaryKeyProperty:(RLMProperty *)primaryKeyProperty {
_primaryKeyProperty.isPrimary = NO;
primaryKeyProperty.isPrimary = YES;
_primaryKeyProperty = primaryKeyProperty;
}
+ (instancetype)schemaForObjectClass:(Class)objectClass {
RLMObjectSchema *schema = [RLMObjectSchema new];
// determine classname from objectclass as className method has not yet been updated
NSString *className = NSStringFromClass(objectClass);
bool isSwift = [RLMSwiftSupport isSwiftClassName:className];
if (isSwift) {
className = [RLMSwiftSupport demangleClassName:className];
}
schema.className = className;
schema.objectClass = objectClass;
schema.accessorClass = RLMDynamicObject.class;
schema.isSwiftClass = isSwift;
// create array of RLMProperties, inserting properties of superclasses first
Class cls = objectClass;
Class superClass = class_getSuperclass(cls);
NSArray *props = @[];
while (superClass && superClass != RLMObjectBase.class) {
props = [[RLMObjectSchema propertiesForClass:cls isSwift:isSwift] arrayByAddingObjectsFromArray:props];
cls = superClass;
superClass = class_getSuperclass(superClass);
}
NSUInteger index = 0;
for (RLMProperty *prop in props) {
prop.declarationIndex = index++;
}
schema.properties = props;
// verify that we didn't add any properties twice due to inheritance
if (props.count != [NSSet setWithArray:[props valueForKey:@"name"]].count) {
NSCountedSet *countedPropertyNames = [NSCountedSet setWithArray:[props valueForKey:@"name"]];
NSSet *duplicatePropertyNames = [countedPropertyNames filteredSetUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id object, NSDictionary *) {
return [countedPropertyNames countForObject:object] > 1;
}]];
if (duplicatePropertyNames.count == 1) {
@throw RLMException(@"Property '%@' is declared multiple times in the class hierarchy of '%@'", duplicatePropertyNames.allObjects.firstObject, className);
} else {
@throw RLMException(@"Object '%@' has properties that are declared multiple times in its class hierarchy: '%@'", className, [duplicatePropertyNames.allObjects componentsJoinedByString:@"', '"]);
}
}
if (NSString *primaryKey = [objectClass primaryKey]) {
for (RLMProperty *prop in schema.properties) {
if ([primaryKey isEqualToString:prop.name]) {
prop.indexed = YES;
schema.primaryKeyProperty = prop;
break;
}
}
if (!schema.primaryKeyProperty) {
@throw RLMException(@"Primary key property '%@' does not exist on object '%@'", primaryKey, className);
}
if (schema.primaryKeyProperty.type != RLMPropertyTypeInt && schema.primaryKeyProperty.type != RLMPropertyTypeString) {
@throw RLMException(@"Only 'string' and 'int' properties can be designated the primary key");
}
}
for (RLMProperty *prop in schema.properties) {
RLMPropertyType type = prop.type;
if (prop.optional && !RLMPropertyTypeIsNullable(type)) {
NSString *error = [NSString stringWithFormat:@"Only 'string', 'binary', and 'object' properties can be made optional, and property '%@' is of type '%@'.", prop.name, RLMTypeToString(type)];
if (prop.type == RLMPropertyTypeAny && isSwift) {
error = [error stringByAppendingString:@"\nIf this is a 'String?' property, it must be declared as 'NSString?' instead."];
}
@throw RLMException(@"%@", error);
}
}
return schema;
}
+ (NSArray *)propertiesForClass:(Class)objectClass isSwift:(bool)isSwiftClass {
Class objectUtil = [objectClass objectUtilClass:isSwiftClass];
NSArray *ignoredProperties = [objectUtil ignoredPropertiesForClass:objectClass];
// For Swift classes we need an instance of the object when parsing properties
id swiftObjectInstance = isSwiftClass ? [[objectClass alloc] init] : nil;
unsigned int count;
objc_property_t *props = class_copyPropertyList(objectClass, &count);
NSMutableArray *propArray = [NSMutableArray arrayWithCapacity:count];
NSSet *indexed = [[NSSet alloc] initWithArray:[objectUtil indexedPropertiesForClass:objectClass]];
for (unsigned int i = 0; i < count; i++) {
NSString *propertyName = @(property_getName(props[i]));
if ([ignoredProperties containsObject:propertyName]) {
continue;
}
RLMProperty *prop = nil;
if (isSwiftClass) {
prop = [[RLMProperty alloc] initSwiftPropertyWithName:propertyName
indexed:[indexed containsObject:propertyName]
property:props[i]
instance:swiftObjectInstance];
}
else {
prop = [[RLMProperty alloc] initWithName:propertyName indexed:[indexed containsObject:propertyName] property:props[i]];
}
if (prop) {
[propArray addObject:prop];
}
}
free(props);
if (isSwiftClass) {
// List<> properties don't show up as objective-C properties due to
// being generic, so use Swift reflection to get a list of them, and
// then access their ivars directly
for (NSString *propName in [objectUtil getGenericListPropertyNames:swiftObjectInstance]) {
Ivar ivar = class_getInstanceVariable(objectClass, propName.UTF8String);
id value = object_getIvar(swiftObjectInstance, ivar);
NSString *className = [value _rlmArray].objectClassName;
NSUInteger existing = [propArray indexOfObjectPassingTest:^BOOL(RLMProperty *obj, __unused NSUInteger idx, __unused BOOL *stop) {
return [obj.name isEqualToString:propName];
}];
if (existing != NSNotFound) {
[propArray removeObjectAtIndex:existing];
}
[propArray addObject:[[RLMProperty alloc] initSwiftListPropertyWithName:propName
ivar:ivar
objectClassName:className]];
}
}
if (auto optionalProperties = [objectUtil getOptionalProperties:swiftObjectInstance]) {
for (RLMProperty *property in propArray) {
property.optional = false;
}
[optionalProperties enumerateKeysAndObjectsUsingBlock:^(NSString *propertyName, NSNumber *propertyType, __unused BOOL *stop) {
if ([ignoredProperties containsObject:propertyName]) {
return;
}
NSUInteger existing = [propArray indexOfObjectPassingTest:^BOOL(RLMProperty *obj, __unused NSUInteger idx, __unused BOOL *stop) {
return [obj.name isEqualToString:propertyName];
}];
RLMProperty *property;
if (existing != NSNotFound) {
property = propArray[existing];
property.optional = true;
}
if (auto type = RLMCoerceToNil(propertyType)) {
if (existing == NSNotFound) {
property = [[RLMProperty alloc] initSwiftOptionalPropertyWithName:propertyName
indexed:[indexed containsObject:propertyName]
ivar:class_getInstanceVariable(objectClass, propertyName.UTF8String)
propertyType:RLMPropertyType(type.intValue)];
[propArray addObject:property];
}
else {
property.type = RLMPropertyType(type.intValue);
}
}
}];
}
if (auto requiredProperties = [objectUtil requiredPropertiesForClass:objectClass]) {
for (RLMProperty *property in propArray) {
bool required = [requiredProperties containsObject:property.name];
if (required && property.type == RLMPropertyTypeObject) {
@throw RLMException(@"Object properties cannot be made required, "
"but '+[%@ requiredProperties]' included '%@'", objectClass, property.name);
}
property.optional &= !required;
}
}
for (RLMProperty *property in propArray) {
if (!property.optional && property.type == RLMPropertyTypeObject) { // remove if/when core supports required link columns
@throw RLMException(@"The `%@.%@` property must be marked as being optional.", [objectClass className], property.name);
}
}
return propArray;
}
- (id)copyWithZone:(NSZone *)zone {
RLMObjectSchema *schema = [[RLMObjectSchema allocWithZone:zone] init];
schema->_objectClass = _objectClass;
schema->_className = _className;
schema->_objectClass = _objectClass;
schema->_accessorClass = _accessorClass;
schema->_standaloneClass = _standaloneClass;
schema->_isSwiftClass = _isSwiftClass;
// call property setter to reset map and primary key
schema.properties = [[NSArray allocWithZone:zone] initWithArray:_properties copyItems:YES];
// _table not copied as it's realm::Group-specific
return schema;
}
- (instancetype)shallowCopy {
RLMObjectSchema *schema = [[RLMObjectSchema alloc] init];
schema->_objectClass = _objectClass;
schema->_className = _className;
schema->_objectClass = _objectClass;
schema->_accessorClass = _accessorClass;
schema->_standaloneClass = _standaloneClass;
schema->_isSwiftClass = _isSwiftClass;
// reuse propery array, map, and primary key instnaces
schema->_properties = _properties;
schema->_propertiesByName = _propertiesByName;
schema->_primaryKeyProperty = _primaryKeyProperty;
// _table not copied as it's realm::Group-specific
return schema;
}
- (BOOL)isEqualToObjectSchema:(RLMObjectSchema *)objectSchema {
if (objectSchema.properties.count != _properties.count) {
return NO;
}
// compare ordered list of properties
NSArray *otherProperties = objectSchema.properties;
for (NSUInteger i = 0; i < _properties.count; i++) {
RLMProperty *p1 = _properties[i], *p2 = otherProperties[i];
if (p1.type != p2.type ||
p1.column != p2.column ||
p1.isPrimary != p2.isPrimary ||
p1.optional != p2.optional ||
![p1.name isEqualToString:p2.name] ||
!(p1.objectClassName == p2.objectClassName || [p1.objectClassName isEqualToString:p2.objectClassName])) {
return NO;
}
}
return YES;
}
- (NSString *)description {
NSMutableString *propertiesString = [NSMutableString string];
for (RLMProperty *property in self.properties) {
[propertiesString appendFormat:@"\t%@\n", [property.description stringByReplacingOccurrencesOfString:@"\n" withString:@"\n\t"]];
}
return [NSString stringWithFormat:@"%@ {\n%@}", self.className, propertiesString];
}
- (realm::Table *)table {
if (!_table) {
_table = ObjectStore::table_for_object_type(_realm.group, _className.UTF8String);
}
return _table.get();
}
- (void)setTable:(realm::Table *)table {
_table.reset(table);
}
- (realm::ObjectSchema)objectStoreCopy {
ObjectSchema objectSchema;
objectSchema.name = _className.UTF8String;
objectSchema.primary_key = _primaryKeyProperty ? _primaryKeyProperty.name.UTF8String : "";
for (RLMProperty *prop in _properties) {
Property p;
p.name = prop.name.UTF8String;
p.type = (PropertyType)prop.type;
p.object_type = prop.objectClassName ? prop.objectClassName.UTF8String : "";
p.is_indexed = prop.indexed;
p.is_primary = (prop == _primaryKeyProperty);
p.is_nullable = prop.optional;
objectSchema.properties.push_back(std::move(p));
}
return objectSchema;
}
+ (instancetype)objectSchemaForObjectStoreSchema:(realm::ObjectSchema &)objectSchema {
RLMObjectSchema *schema = [RLMObjectSchema new];
schema.className = @(objectSchema.name.c_str());
// create array of RLMProperties
NSMutableArray *propArray = [NSMutableArray arrayWithCapacity:objectSchema.properties.size()];
for (Property &prop : objectSchema.properties) {
RLMProperty *property = [[RLMProperty alloc] initWithName:@(prop.name.c_str())
type:(RLMPropertyType)prop.type
objectClassName:prop.object_type.length() ? @(prop.object_type.c_str()) : nil
indexed:prop.is_indexed
optional:prop.is_nullable];
property.isPrimary = (prop.name == objectSchema.primary_key);
[propArray addObject:property];
}
schema.properties = propArray;
// get primary key from realm metadata
if (objectSchema.primary_key.length()) {
NSString *primaryKeyString = [NSString stringWithUTF8String:objectSchema.primary_key.c_str()];
schema.primaryKeyProperty = schema[primaryKeyString];
if (!schema.primaryKeyProperty) {
@throw RLMException(@"No property matching primary key '%@'", primaryKeyString);
}
}
// for dynamic schema use vanilla RLMDynamicObject accessor classes
schema.objectClass = RLMObject.class;
schema.accessorClass = RLMDynamicObject.class;
schema.standaloneClass = RLMObject.class;
return schema;
}
- (void)sortPropertiesByColumn {
_properties = [_properties sortedArrayUsingComparator:^NSComparisonResult(RLMProperty *p1, RLMProperty *p2) {
if (p1.column < p2.column) return NSOrderedAscending;
if (p1.column > p2.column) return NSOrderedDescending;
return NSOrderedSame;
}];
// No need to update the dictionary
}
- (NSArray *)propertiesInDeclaredOrder {
if (!_propertiesInDeclaredOrder) {
_propertiesInDeclaredOrder = [_properties sortedArrayUsingComparator:^NSComparisonResult(RLMProperty *p1, RLMProperty *p2) {
if (p1.declarationIndex < p2.declarationIndex) return NSOrderedAscending;
if (p1.declarationIndex > p2.declarationIndex) return NSOrderedDescending;
return NSOrderedSame;
}];
}
return _propertiesInDeclaredOrder;
}
@end

544
Example/Pods/Realm/Realm/RLMObjectStore.mm generated Normal file
View File

@@ -0,0 +1,544 @@
////////////////////////////////////////////////////////////////////////////
//
// Copyright 2014 Realm Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////
#import "RLMObjectStore.h"
#import "RLMAccessor.h"
#import "RLMArray_Private.hpp"
#import "RLMListBase.h"
#import "RLMObservation.hpp"
#import "RLMObject_Private.hpp"
#import "RLMObjectSchema_Private.hpp"
#import "RLMOptionalBase.h"
#import "RLMProperty_Private.h"
#import "RLMQueryUtil.hpp"
#import "RLMRealm_Private.hpp"
#import "RLMSchema_Private.h"
#import "RLMSwiftSupport.h"
#import "RLMUtil.hpp"
#import "object_store.hpp"
#import "results.hpp"
#import "shared_realm.hpp"
#import <objc/message.h>
using namespace realm;
// Schema used to created generated accessors
static NSMutableArray * const s_accessorSchema = [NSMutableArray new];
void RLMRealmCreateAccessors(RLMSchema *schema) {
// create accessors for non-dynamic realms
RLMSchema *matchingSchema = nil;
for (RLMSchema *accessorSchema in s_accessorSchema) {
if ([schema isEqualToSchema:accessorSchema]) {
matchingSchema = accessorSchema;
break;
}
}
if (matchingSchema) {
// reuse accessors
for (RLMObjectSchema *objectSchema in schema.objectSchema) {
objectSchema.accessorClass = matchingSchema[objectSchema.className].accessorClass;
}
}
else {
// create accessors and cache in s_accessorSchema
for (RLMObjectSchema *objectSchema in schema.objectSchema) {
if (objectSchema.table) {
NSString *prefix = [NSString stringWithFormat:@"RLMAccessor_v%lu_",
(unsigned long)s_accessorSchema.count];
objectSchema.accessorClass = RLMAccessorClassForObjectClass(objectSchema.objectClass, objectSchema, prefix);
}
}
[s_accessorSchema addObject:schema];
}
}
void RLMClearAccessorCache() {
[s_accessorSchema removeAllObjects];
}
static inline void RLMVerifyRealmRead(__unsafe_unretained RLMRealm *const realm) {
if (!realm) {
@throw RLMException(@"Realm must not be nil");
}
[realm verifyThread];
}
static inline void RLMVerifyInWriteTransaction(__unsafe_unretained RLMRealm *const realm) {
RLMVerifyRealmRead(realm);
// if realm is not writable throw
if (!realm.inWriteTransaction) {
@throw RLMException(@"Can only add, remove, or create objects in a Realm in a write transaction - call beginWriteTransaction on an RLMRealm instance first.");
}
}
void RLMInitializeSwiftAccessorGenerics(__unsafe_unretained RLMObjectBase *const object) {
if (!object || !object->_row || !object->_objectSchema.isSwiftClass) {
return;
}
static Class s_swiftObjectClass = NSClassFromString(@"RealmSwiftObject");
if (![object isKindOfClass:s_swiftObjectClass]) {
return; // Is a Swift class using the obj-c API
}
for (RLMProperty *prop in object->_objectSchema.properties) {
if (prop.type == RLMPropertyTypeArray) {
RLMArray *array = [RLMArrayLinkView arrayWithObjectClassName:prop.objectClassName
view:object->_row.get_linklist(prop.column)
realm:object->_realm
key:prop.name
parentSchema:object->_objectSchema];
[RLMObjectUtilClass(YES) initializeListProperty:object property:prop array:array];
} else if (prop.swiftIvar) {
[RLMObjectUtilClass(YES) initializeOptionalProperty:object property:prop];
}
}
}
template<typename F>
static inline NSUInteger RLMCreateOrGetRowForObject(__unsafe_unretained RLMObjectSchema *const schema, F primaryValueGetter, bool createOrUpdate, bool &created) {
// try to get existing row if updating
size_t rowIndex = realm::not_found;
realm::Table &table = *schema.table;
RLMProperty *primaryProperty = schema.primaryKeyProperty;
if (createOrUpdate && primaryProperty) {
// get primary value
id primaryValue = primaryValueGetter(primaryProperty);
if (primaryValue == NSNull.null) {
primaryValue = nil;
}
// search for existing object based on primary key type
if (primaryProperty.type == RLMPropertyTypeString) {
rowIndex = table.find_first_string(primaryProperty.column, RLMStringDataWithNSString(primaryValue));
}
else {
rowIndex = table.find_first_int(primaryProperty.column, [primaryValue longLongValue]);
}
}
// if no existing, create row
created = NO;
if (rowIndex == realm::not_found) {
rowIndex = table.add_empty_row();
created = YES;
}
// get accessor
return rowIndex;
}
void RLMAddObjectToRealm(__unsafe_unretained RLMObjectBase *const object,
__unsafe_unretained RLMRealm *const realm,
bool createOrUpdate) {
RLMVerifyInWriteTransaction(realm);
// verify that object is standalone
if (object.invalidated) {
@throw RLMException(@"Adding a deleted or invalidated object to a Realm is not permitted");
}
if (object->_realm) {
if (object->_realm == realm) {
// no-op
return;
}
// for differing realms users must explicitly create the object in the second realm
@throw RLMException(@"Object is already persisted in a Realm");
}
if (object->_observationInfo && object->_observationInfo->hasObservers()) {
@throw RLMException(@"Cannot add an object with observers to a Realm");
}
// set the realm and schema
NSString *objectClassName = object->_objectSchema.className;
RLMObjectSchema *schema = [realm.schema schemaForClassName:objectClassName];
if (!schema) {
@throw RLMException(@"Object type '%@' is not persisted in the Realm. "
@"If using a custom `objectClasses` / `objectTypes` array in your configuration, "
@"add `%@` to the list of `objectClasses` / `objectTypes`.",
objectClassName, objectClassName);
}
object->_objectSchema = schema;
object->_realm = realm;
// get or create row
bool created;
auto primaryGetter = [=](__unsafe_unretained RLMProperty *const p) { return [object valueForKey:p.getterName]; };
object->_row = (*schema.table)[RLMCreateOrGetRowForObject(schema, primaryGetter, createOrUpdate, created)];
RLMCreationOptions creationOptions = RLMCreationOptionsPromoteStandalone;
if (createOrUpdate) {
creationOptions |= RLMCreationOptionsCreateOrUpdate;
}
// populate all properties
for (RLMProperty *prop in schema.properties) {
// get object from ivar using key value coding
id value = nil;
if (prop.swiftIvar) {
if (prop.type == RLMPropertyTypeArray) {
value = static_cast<RLMListBase *>(object_getIvar(object, prop.swiftIvar))._rlmArray;
}
else { // optional
value = static_cast<RLMOptionalBase *>(object_getIvar(object, prop.swiftIvar)).underlyingValue;
}
}
else if ([object respondsToSelector:prop.getterSel]) {
value = [object valueForKey:prop.getterName];
}
// FIXME: Add condition to check for Mixed once it can support a nil value.
if (!value && !prop.optional) {
@throw RLMException(@"No value or default value specified for property '%@' in '%@'",
prop.name, schema.className);
}
// set in table with out validation
// skip primary key when updating since it doesn't change
if (created || !prop.isPrimary) {
RLMDynamicSet(object, prop, RLMCoerceToNil(value), creationOptions);
}
// set the ivars for object and array properties to nil as otherwise the
// accessors retain objects that are no longer accessible via the properties
// this is mainly an issue when the object graph being added has cycles,
// as it's not obvious that the user has to set the *ivars* to nil to
// avoid leaking memory
if (prop.type == RLMPropertyTypeObject || prop.type == RLMPropertyTypeArray) {
if (!prop.swiftIvar) {
((void(*)(id, SEL, id))objc_msgSend)(object, prop.setterSel, nil);
}
}
}
// set to proper accessor class
object_setClass(object, schema.accessorClass);
RLMInitializeSwiftAccessorGenerics(object);
}
static void RLMValidateValueForProperty(__unsafe_unretained id const obj,
__unsafe_unretained RLMProperty *const prop,
__unsafe_unretained RLMSchema *const schema,
bool validateNested,
bool allowMissing);
static void RLMValidateValueForObjectSchema(__unsafe_unretained id const value,
__unsafe_unretained RLMObjectSchema *const objectSchema,
__unsafe_unretained RLMSchema *const schema,
bool validateNested,
bool allowMissing);
static void RLMValidateNestedObject(__unsafe_unretained id const obj,
__unsafe_unretained NSString *const className,
__unsafe_unretained RLMSchema *const schema,
bool validateNested,
bool allowMissing) {
if (obj != nil && obj != NSNull.null) {
if (RLMObjectBase *objBase = RLMDynamicCast<RLMObjectBase>(obj)) {
RLMObjectSchema *objectSchema = objBase->_objectSchema;
if (![className isEqualToString:objectSchema.className]) {
// if not the right object class treat as literal
RLMValidateValueForObjectSchema(objBase, schema[className], schema, validateNested, allowMissing);
}
if (objBase.isInvalidated) {
@throw RLMException(@"Adding a deleted or invalidated object to a Realm is not permitted");
}
}
else {
RLMValidateValueForObjectSchema(obj, schema[className], schema, validateNested, allowMissing);
}
}
}
static void RLMValidateValueForProperty(__unsafe_unretained id const obj,
__unsafe_unretained RLMProperty *const prop,
__unsafe_unretained RLMSchema *const schema,
bool validateNested,
bool allowMissing) {
switch (prop.type) {
case RLMPropertyTypeString:
case RLMPropertyTypeBool:
case RLMPropertyTypeDate:
case RLMPropertyTypeInt:
case RLMPropertyTypeFloat:
case RLMPropertyTypeDouble:
case RLMPropertyTypeData:
case RLMPropertyTypeAny:
if (!RLMIsObjectValidForProperty(obj, prop)) {
@throw RLMException(@"Invalid value '%@' for property '%@'", obj, prop.name);
}
break;
case RLMPropertyTypeObject:
if (validateNested) {
RLMValidateNestedObject(obj, prop.objectClassName, schema, validateNested, allowMissing);
}
break;
case RLMPropertyTypeArray: {
if (obj != nil && obj != NSNull.null) {
if (![obj conformsToProtocol:@protocol(NSFastEnumeration)]) {
@throw RLMException(@"Array property value (%@) is not enumerable.", obj);
}
if (validateNested) {
id<NSFastEnumeration> array = obj;
for (id el in array) {
RLMValidateNestedObject(el, prop.objectClassName, schema, validateNested, allowMissing);
}
}
}
break;
}
}
}
static void RLMValidateValueForObjectSchema(__unsafe_unretained id const value,
__unsafe_unretained RLMObjectSchema *const objectSchema,
__unsafe_unretained RLMSchema *const schema,
bool validateNested,
bool allowMissing) {
NSArray *props = objectSchema.properties;
if (NSArray *array = RLMDynamicCast<NSArray>(value)) {
if (array.count != props.count) {
@throw RLMException(@"Invalid array input. Number of array elements does not match number of properties.");
}
for (NSUInteger i = 0; i < array.count; i++) {
RLMProperty *prop = props[i];
RLMValidateValueForProperty(array[i], prop, schema, validateNested, allowMissing);
}
}
else {
NSDictionary *defaults;
for (RLMProperty *prop in props) {
id obj = [value valueForKey:prop.name];
// get default for nil object
if (!obj) {
if (!defaults) {
defaults = RLMDefaultValuesForObjectSchema(objectSchema);
}
obj = defaults[prop.name];
}
if (obj || prop.isPrimary || !allowMissing) {
RLMValidateValueForProperty(obj, prop, schema, true, allowMissing);
}
}
}
}
RLMObjectBase *RLMCreateObjectInRealmWithValue(RLMRealm *realm, NSString *className, id value, bool createOrUpdate = false) {
if (createOrUpdate && RLMIsObjectSubclass([value class])) {
RLMObjectBase *obj = value;
if ([obj->_objectSchema.className isEqualToString:className] && obj->_realm == realm) {
// This is a no-op if value is an RLMObject of the same type already backed by the target realm.
return value;
}
}
// verify writable
RLMVerifyInWriteTransaction(realm);
// create the object
RLMSchema *schema = realm.schema;
RLMObjectSchema *objectSchema = [realm.schema schemaForClassName:className];
if (!objectSchema) {
@throw RLMException(@"Object type '%@' is not persisted in the Realm. "
@"If using a custom `objectClasses` / `objectTypes` array in your configuration, "
@"add `%@` to the list of `objectClasses` / `objectTypes`.",
className, className);
}
RLMObjectBase *object = [[objectSchema.accessorClass alloc] initWithRealm:realm schema:objectSchema];
RLMCreationOptions creationOptions = createOrUpdate ? RLMCreationOptionsCreateOrUpdate : RLMCreationOptionsNone;
// create row, and populate
if (NSArray *array = RLMDynamicCast<NSArray>(value)) {
// get or create our accessor
bool created;
auto primaryGetter = [=](__unsafe_unretained RLMProperty *const p) { return array[p.column]; };
object->_row = (*objectSchema.table)[RLMCreateOrGetRowForObject(objectSchema, primaryGetter, createOrUpdate, created)];
// populate
NSArray *props = objectSchema.propertiesInDeclaredOrder;
for (NSUInteger i = 0; i < array.count; i++) {
RLMProperty *prop = props[i];
// skip primary key when updating since it doesn't change
if (created || !prop.isPrimary) {
id val = array[i];
RLMValidateValueForProperty(val, prop, schema, false, false);
RLMDynamicSet(object, prop, RLMCoerceToNil(val), creationOptions);
}
}
}
else {
// get or create our accessor
bool created;
auto primaryGetter = [=](RLMProperty *p) { return [value valueForKey:p.name]; };
object->_row = (*objectSchema.table)[RLMCreateOrGetRowForObject(objectSchema, primaryGetter, createOrUpdate, created)];
// populate
NSDictionary *defaultValues = nil;
for (RLMProperty *prop in objectSchema.properties) {
id propValue = RLMValidatedValueForProperty(value, prop.name, objectSchema.className);
if (!propValue && created) {
if (!defaultValues) {
defaultValues = RLMDefaultValuesForObjectSchema(objectSchema);
}
propValue = defaultValues[prop.name];
if (!propValue && (prop.type == RLMPropertyTypeObject || prop.type == RLMPropertyTypeArray)) {
propValue = NSNull.null;
}
}
if (propValue) {
if (created || !prop.isPrimary) {
// skip missing properties and primary key when updating since it doesn't change
RLMValidateValueForProperty(propValue, prop, schema, false, false);
RLMDynamicSet(object, prop, RLMCoerceToNil(propValue), creationOptions);
}
}
else if (created && !prop.optional) {
@throw RLMException(@"Property '%@' of object of type '%@' cannot be nil.", prop.name, objectSchema.className);
}
}
}
RLMInitializeSwiftAccessorGenerics(object);
return object;
}
void RLMDeleteObjectFromRealm(__unsafe_unretained RLMObjectBase *const object,
__unsafe_unretained RLMRealm *const realm) {
if (realm != object->_realm) {
@throw RLMException(@"Can only delete an object from the Realm it belongs to.");
}
RLMVerifyInWriteTransaction(object->_realm);
// move last row to row we are deleting
if (object->_row.is_attached()) {
RLMTrackDeletions(realm, ^{
object->_row.get_table()->move_last_over(object->_row.get_index());
});
}
// set realm to nil
object->_realm = nil;
}
void RLMDeleteAllObjectsFromRealm(RLMRealm *realm) {
RLMVerifyInWriteTransaction(realm);
// clear table for each object schema
for (RLMObjectSchema *objectSchema in realm.schema.objectSchema) {
RLMClearTable(objectSchema);
}
}
RLMResults *RLMGetObjects(RLMRealm *realm, NSString *objectClassName, NSPredicate *predicate) {
RLMVerifyRealmRead(realm);
// create view from table and predicate
RLMObjectSchema *objectSchema = realm.schema[objectClassName];
if (!objectSchema.table) {
// read-only realms may be missing tables since we can't add any
// missing ones on init
return [RLMResults resultsWithObjectSchema:objectSchema results:{}];
}
if (predicate) {
realm::Query query = objectSchema.table->where();
RLMUpdateQueryWithPredicate(&query, predicate, realm.schema, objectSchema);
// create and populate array
return [RLMResults resultsWithObjectSchema:objectSchema
results:realm::Results(realm->_realm, std::move(query))];
}
return [RLMResults resultsWithObjectSchema:objectSchema
results:realm::Results(realm->_realm, *objectSchema.table)];
}
id RLMGetObject(RLMRealm *realm, NSString *objectClassName, id key) {
RLMVerifyRealmRead(realm);
RLMObjectSchema *objectSchema = realm.schema[objectClassName];
RLMProperty *primaryProperty = objectSchema.primaryKeyProperty;
if (!primaryProperty) {
@throw RLMException(@"%@ does not have a primary key", objectClassName);
}
if (!objectSchema.table) {
// read-only realms may be missing tables since we can't add any
// missing ones on init
return nil;
}
key = RLMCoerceToNil(key);
size_t row = realm::not_found;
if (primaryProperty.type == RLMPropertyTypeString) {
NSString *str = RLMDynamicCast<NSString>(key);
if (str || (!key && primaryProperty.optional)) {
row = objectSchema.table->find_first_string(primaryProperty.column, RLMStringDataWithNSString(str));
}
else {
@throw RLMException(@"Invalid value '%@' for primary key", key);
}
}
else {
NSNumber *number = RLMDynamicCast<NSNumber>(key);
if (number) {
row = objectSchema.table->find_first_int(primaryProperty.column, number.longLongValue);
}
else if (!key && primaryProperty.optional) {
row = objectSchema.table->find_first_null(primaryProperty.column);
}
else {
@throw RLMException(@"Invalid value '%@' for primary key", key);
}
}
if (row == realm::not_found) {
return nil;
}
return RLMCreateObjectAccessor(realm, objectSchema, row);
}
RLMObjectBase *RLMCreateObjectAccessor(__unsafe_unretained RLMRealm *const realm,
__unsafe_unretained RLMObjectSchema *const objectSchema,
NSUInteger index) {
return RLMCreateObjectAccessor(realm, objectSchema, (*objectSchema.table)[index]);
}
// Create accessor and register with realm
RLMObjectBase *RLMCreateObjectAccessor(__unsafe_unretained RLMRealm *const realm,
__unsafe_unretained RLMObjectSchema *const objectSchema,
realm::RowExpr row) {
RLMObjectBase *accessor = [[objectSchema.accessorClass alloc] initWithRealm:realm schema:objectSchema];
accessor->_row = row;
RLMInitializeSwiftAccessorGenerics(accessor);
return accessor;
}

484
Example/Pods/Realm/Realm/RLMObservation.mm generated Normal file
View File

@@ -0,0 +1,484 @@
////////////////////////////////////////////////////////////////////////////
//
// Copyright 2015 Realm Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////
#import "RLMObservation.hpp"
#import "RLMAccessor.h"
#import "RLMArray_Private.hpp"
#import "RLMListBase.h"
#import "RLMObjectSchema_Private.hpp"
#import "RLMProperty_Private.h"
#import "RLMRealm_Private.hpp"
#import "RLMSchema.h"
#import <realm/lang_bind_helper.hpp>
using namespace realm;
namespace {
template<typename Iterator>
struct IteratorPair {
Iterator first;
Iterator second;
};
template<typename Iterator>
Iterator begin(IteratorPair<Iterator> const& p) {
return p.first;
}
template<typename Iterator>
Iterator end(IteratorPair<Iterator> const& p) {
return p.second;
}
template<typename Container>
auto reverse(Container const& c) {
return IteratorPair<typename Container::const_reverse_iterator>{c.rbegin(), c.rend()};
}
}
RLMObservationInfo::RLMObservationInfo(RLMObjectSchema *objectSchema, std::size_t row, id object)
: object(object)
, objectSchema(objectSchema)
{
REALM_ASSERT_DEBUG(objectSchema);
setRow(*objectSchema.table, row);
}
RLMObservationInfo::RLMObservationInfo(id object)
: object(object)
{
}
RLMObservationInfo::~RLMObservationInfo() {
if (prev) {
// Not the head of the linked list, so just detach from the list
REALM_ASSERT_DEBUG(prev->next == this);
prev->next = next;
if (next) {
REALM_ASSERT_DEBUG(next->prev == this);
next->prev = prev;
}
}
else if (objectSchema) {
// The head of the list, so remove self from the object schema's array
// of observation info, either replacing self with the next info or
// removing entirely if there is no next
auto end = objectSchema->_observedObjects.end();
auto it = find(objectSchema->_observedObjects.begin(), end, this);
if (it != end) {
if (next) {
*it = next;
next->prev = nullptr;
}
else {
iter_swap(it, std::prev(end));
objectSchema->_observedObjects.pop_back();
}
}
}
// Otherwise the observed object was standalone, so nothing to do
#ifdef DEBUG
// ensure that incorrect cleanup fails noisily
object = (__bridge id)(void *)-1;
prev = (RLMObservationInfo *)-1;
next = (RLMObservationInfo *)-1;
#endif
}
void RLMObservationInfo::willChange(NSString *key, NSKeyValueChange kind, NSIndexSet *indexes) const {
if (indexes) {
forEach([=](__unsafe_unretained auto o) {
[o willChange:kind valuesAtIndexes:indexes forKey:key];
});
}
else {
forEach([=](__unsafe_unretained auto o) {
[o willChangeValueForKey:key];
});
}
}
void RLMObservationInfo::didChange(NSString *key, NSKeyValueChange kind, NSIndexSet *indexes) const {
if (indexes) {
forEach([=](__unsafe_unretained auto o) {
[o didChange:kind valuesAtIndexes:indexes forKey:key];
});
}
else {
forEach([=](__unsafe_unretained auto o) {
[o didChangeValueForKey:key];
});
}
}
void RLMObservationInfo::prepareForInvalidation() {
REALM_ASSERT_DEBUG(objectSchema);
REALM_ASSERT_DEBUG(!prev);
for (auto info = this; info; info = info->next)
info->invalidated = true;
}
void RLMObservationInfo::setRow(realm::Table &table, size_t newRow) {
REALM_ASSERT_DEBUG(!row);
REALM_ASSERT_DEBUG(objectSchema);
row = table[newRow];
for (auto info : objectSchema->_observedObjects) {
if (info->row && info->row.get_index() == row.get_index()) {
prev = info;
next = info->next;
if (next)
next->prev = this;
info->next = this;
return;
}
}
objectSchema->_observedObjects.push_back(this);
}
void RLMObservationInfo::recordObserver(realm::Row& objectRow,
__unsafe_unretained RLMObjectSchema *const objectSchema,
__unsafe_unretained NSString *const keyPath) {
++observerCount;
// add ourselves to the list of observed objects if this is the first time
// an observer is being added to a persisted object
if (objectRow && !row) {
this->objectSchema = objectSchema;
setRow(*objectRow.get_table(), objectRow.get_index());
}
if (!row) {
// Arrays need a reference to their containing object to avoid having to
// go through the awful proxy object from mutableArrayValueForKey.
// For persisted objects we do this when the object is added or created
// (and have to to support notifications from modifying an object which
// was never observed), but for Swift classes (both RealmSwift and
// RLMObject) we can't do it then because we don't know what the parent
// object is.
NSUInteger sep = [keyPath rangeOfString:@"."].location;
NSString *key = sep == NSNotFound ? keyPath : [keyPath substringToIndex:sep];
RLMProperty *prop = objectSchema[key];
if (prop && prop.type == RLMPropertyTypeArray) {
id value = valueForKey(key);
RLMArray *array = [value isKindOfClass:[RLMListBase class]] ? [value _rlmArray] : value;
array->_key = key;
array->_parentObject = object;
}
else if (auto swiftIvar = prop.swiftIvar) {
if (auto optional = RLMDynamicCast<RLMOptionalBase>(object_getIvar(object, swiftIvar))) {
optional.property = prop;
optional.object = object;
}
}
}
}
void RLMObservationInfo::removeObserver() {
--observerCount;
}
id RLMObservationInfo::valueForKey(NSString *key) {
if (invalidated) {
if ([key isEqualToString:RLMInvalidatedKey]) {
return @YES;
}
return cachedObjects[key];
}
if (key != lastKey) {
lastKey = key;
lastProp = objectSchema[key];
}
static auto superValueForKey = reinterpret_cast<id(*)(id, SEL, NSString *)>([NSObject methodForSelector:@selector(valueForKey:)]);
if (!lastProp) {
return RLMCoerceToNil(superValueForKey(object, @selector(valueForKey:), key));
}
auto getSuper = [&] {
return row ? RLMDynamicGet(object, lastProp) : RLMCoerceToNil(superValueForKey(object, @selector(valueForKey:), key));
};
// We need to return the same object each time for observing over keypaths to work
if (lastProp.type == RLMPropertyTypeArray) {
RLMArray *value = cachedObjects[key];
if (!value) {
value = getSuper();
if (!cachedObjects) {
cachedObjects = [NSMutableDictionary new];
}
cachedObjects[key] = value;
}
return value;
}
if (lastProp.type == RLMPropertyTypeObject) {
if (row.is_null_link(lastProp.column)) {
[cachedObjects removeObjectForKey:key];
return nil;
}
RLMObjectBase *value = cachedObjects[key];
if (value && value->_row.get_index() == row.get_link(lastProp.column)) {
return value;
}
value = getSuper();
if (!cachedObjects) {
cachedObjects = [NSMutableDictionary new];
}
cachedObjects[key] = value;
return value;
}
return getSuper();
}
RLMObservationInfo *RLMGetObservationInfo(RLMObservationInfo *info,
size_t row,
__unsafe_unretained RLMObjectSchema *objectSchema) {
if (info) {
return info;
}
for (RLMObservationInfo *info : objectSchema->_observedObjects) {
if (info->isForRow(row)) {
return info;
}
}
return nullptr;
}
void RLMClearTable(RLMObjectSchema *objectSchema) {
for (auto info : objectSchema->_observedObjects) {
info->willChange(RLMInvalidatedKey);
}
RLMTrackDeletions(objectSchema.realm, ^{
objectSchema.table->clear();
for (auto info : objectSchema->_observedObjects) {
info->prepareForInvalidation();
}
});
for (auto info : reverse(objectSchema->_observedObjects)) {
info->didChange(RLMInvalidatedKey);
}
objectSchema->_observedObjects.clear();
}
void RLMTrackDeletions(__unsafe_unretained RLMRealm *const realm, dispatch_block_t block) {
std::vector<std::vector<RLMObservationInfo *> *> observers;
// Build up an array of observation info arrays which is indexed by table
// index (the object schemata may be in an entirely different order)
for (RLMObjectSchema *objectSchema in realm.schema.objectSchema) {
if (objectSchema->_observedObjects.empty()) {
continue;
}
size_t ndx = objectSchema.table->get_index_in_group();
if (ndx >= observers.size()) {
observers.resize(std::max(observers.size() * 2, ndx + 1));
}
observers[ndx] = &objectSchema->_observedObjects;
}
// No need for change tracking if no objects are observed
if (observers.empty()) {
block();
return;
}
struct change {
RLMObservationInfo *info;
__unsafe_unretained NSString *property;
NSMutableIndexSet *indexes;
};
std::vector<change> changes;
std::vector<RLMObservationInfo *> invalidated;
// This callback is called by core with a list of row deletions and
// resulting link nullifications immediately before things are deleted and nullified
realm.group->set_cascade_notification_handler([&](realm::Group::CascadeNotification const& cs) {
for (auto const& link : cs.links) {
size_t table_ndx = link.origin_table->get_index_in_group();
if (table_ndx >= observers.size() || !observers[table_ndx]) {
// The modified table has no observers
continue;
}
for (auto observer : *observers[table_ndx]) {
if (!observer->isForRow(link.origin_row_ndx)) {
continue;
}
RLMProperty *prop = observer->getObjectSchema().properties[link.origin_col_ndx];
NSString *name = prop.name;
if (prop.type != RLMPropertyTypeArray) {
changes.push_back({observer, name});
continue;
}
auto c = find_if(begin(changes), end(changes), [&](auto const& c) {
return c.info == observer && c.property == name;
});
if (c == end(changes)) {
changes.push_back({observer, name, [NSMutableIndexSet new]});
c = prev(end(changes));
}
// We know what row index is being removed from the LinkView,
// but what we actually want is the indexes in the LinkView that
// are going away
auto linkview = observer->getRow().get_linklist(prop.column);
size_t start = 0, index;
while ((index = linkview->find(link.old_target_row_ndx, start)) != realm::not_found) {
[c->indexes addIndex:index];
start = index + 1;
}
}
}
for (auto const& row : cs.rows) {
if (row.table_ndx >= observers.size() || !observers[row.table_ndx]) {
// The modified table has no observers
continue;
}
for (auto observer : *observers[row.table_ndx]) {
if (observer->isForRow(row.row_ndx)) {
invalidated.push_back(observer);
break;
}
}
}
// The relative order of these loops is very important
for (auto info : invalidated) {
info->willChange(RLMInvalidatedKey);
}
for (auto const& change : changes) {
change.info->willChange(change.property, NSKeyValueChangeRemoval, change.indexes);
}
for (auto info : invalidated) {
info->prepareForInvalidation();
}
});
block();
for (auto const& change : reverse(changes)) {
change.info->didChange(change.property, NSKeyValueChangeRemoval, change.indexes);
}
for (auto info : reverse(invalidated)) {
info->didChange(RLMInvalidatedKey);
}
realm.group->set_cascade_notification_handler(nullptr);
}
namespace {
template<typename Func>
void forEach(realm::BindingContext::ObserverState const& state, Func&& func) {
for (size_t i = 0, size = state.changes.size(); i < size; ++i) {
if (state.changes[i].changed) {
func(i, state.changes[i], static_cast<RLMObservationInfo *>(state.info));
}
}
}
}
std::vector<realm::BindingContext::ObserverState> RLMGetObservedRows(NSArray RLM_GENERIC(RLMObjectSchema *) *schema) {
std::vector<realm::BindingContext::ObserverState> observers;
for (RLMObjectSchema *objectSchema in schema) {
for (auto info : objectSchema->_observedObjects) {
auto const& row = info->getRow();
if (!row.is_attached())
continue;
observers.push_back({
row.get_table()->get_index_in_group(),
row.get_index(),
info});
}
}
sort(begin(observers), end(observers));
return observers;
}
static NSKeyValueChange convert(realm::BindingContext::ColumnInfo::Kind kind) {
switch (kind) {
case realm::BindingContext::ColumnInfo::Kind::None:
case realm::BindingContext::ColumnInfo::Kind::SetAll:
return NSKeyValueChangeSetting;
case realm::BindingContext::ColumnInfo::Kind::Set:
return NSKeyValueChangeReplacement;
case realm::BindingContext::ColumnInfo::Kind::Insert:
return NSKeyValueChangeInsertion;
case realm::BindingContext::ColumnInfo::Kind::Remove:
return NSKeyValueChangeRemoval;
}
}
static NSIndexSet *convert(realm::IndexSet const& in, NSMutableIndexSet *out) {
if (in.empty()) {
return nil;
}
[out removeAllIndexes];
for (auto range : in) {
[out addIndexesInRange:{range.first, range.second - range.first}];
}
return out;
}
void RLMWillChange(std::vector<realm::BindingContext::ObserverState> const& observed,
std::vector<void *> const& invalidated) {
NSMutableIndexSet *indexes = [NSMutableIndexSet new];
for (auto info : invalidated) {
static_cast<RLMObservationInfo *>(info)->willChange(RLMInvalidatedKey);
}
for (auto const& o : observed) {
forEach(o, [&](size_t i, auto const& change, RLMObservationInfo *info) {
info->willChange([info->getObjectSchema().properties[i] name],
convert(change.kind), convert(change.indices, indexes));
});
}
for (auto info : invalidated) {
static_cast<RLMObservationInfo *>(info)->prepareForInvalidation();
}
}
void RLMDidChange(std::vector<realm::BindingContext::ObserverState> const& observed,
std::vector<void *> const& invalidated) {
// Loop in reverse order to avoid O(N^2) behavior in Foundation
NSMutableIndexSet *indexes = [NSMutableIndexSet new];
for (auto const& o : reverse(observed)) {
forEach(o, [&](size_t i, auto const& change, RLMObservationInfo *info) {
info->didChange([info->getObjectSchema().properties[i] name],
convert(change.kind), convert(change.indices, indexes));
});
}
for (auto const& info : reverse(invalidated)) {
static_cast<RLMObservationInfo *>(info)->didChange(RLMInvalidatedKey);
}
}

View File

@@ -0,0 +1,86 @@
////////////////////////////////////////////////////////////////////////////
//
// Copyright 2015 Realm Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////
#import "RLMAccessor.h"
#import "RLMOptionalBase.h"
#import "RLMObject_Private.h"
#import "RLMObjectStore.h"
#import "RLMProperty.h"
#import "RLMUtil.hpp"
#import <objc/runtime.h>
@interface RLMOptionalBase ()
@property (nonatomic) id standaloneValue;
@end
@implementation RLMOptionalBase
- (instancetype)init {
return self;
}
- (id)underlyingValue {
if ((_object && _object->_realm) || _object.isInvalidated) {
return RLMDynamicGet(_object, _property);
}
else {
return _standaloneValue;
}
}
- (void)setUnderlyingValue:(id)underlyingValue {
if ((_object && _object->_realm) || _object.isInvalidated) {
RLMDynamicSet(_object, _property, underlyingValue, RLMCreationOptionsNone);
}
else {
NSString *propertyName = _property.name;
[_object willChangeValueForKey:propertyName];
_standaloneValue = underlyingValue;
[_object didChangeValueForKey:propertyName];
}
}
- (BOOL)isKindOfClass:(Class)aClass {
return [self.underlyingValue isKindOfClass:aClass] || RLMIsKindOfClass(object_getClass(self), aClass);
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
return [self.underlyingValue methodSignatureForSelector:sel];
}
- (void)forwardInvocation:(NSInvocation *)invocation {
[invocation invokeWithTarget:self.underlyingValue];
}
- (id)forwardingTargetForSelector:(__unused SEL)sel {
return self.underlyingValue;
}
- (BOOL)respondsToSelector:(SEL)aSelector {
if (id val = self.underlyingValue) {
return [val respondsToSelector:aSelector];
}
return NO;
}
- (void)doesNotRecognizeSelector:(SEL)aSelector {
[self.underlyingValue doesNotRecognizeSelector:aSelector];
}
@end

View File

@@ -0,0 +1,118 @@
////////////////////////////////////////////////////////////////////////////
//
// Copyright 2015 Realm Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#import "RLMPredicateUtil.hpp"
// NSConditionalExpressionType is new in OS X 10.11 and iOS 9.0
#if defined(__MAC_OS_X_VERSION_MIN_REQUIRED)
#define CONDITIONAL_EXPRESSION_DECLARED (__MAC_OS_X_VERSION_MIN_REQUIRED >= 101100)
#elif defined(__IPHONE_OS_VERSION_MIN_REQUIRED)
#define CONDITIONAL_EXPRESSION_DECLARED (__IPHONE_OS_VERSION_MIN_REQUIRED >= 90000)
#else
#define CONDITIONAL_EXPRESSION_DECLARED 0
#endif
#if !CONDITIONAL_EXPRESSION_DECLARED
#define NSConditionalExpressionType 20
@interface NSExpression (NewIn1011And90)
+ (NSExpression *)expressionForConditional:(NSPredicate *)predicate trueExpression:(NSExpression *)trueExpression falseExpression:(NSExpression *)falseExpression;
- (NSExpression *)trueExpression;
- (NSExpression *)falseExpression;
@end
#endif
namespace {
struct PredicateExpressionTransformer {
PredicateExpressionTransformer(ExpressionVisitor visitor) : m_visitor(visitor) { }
NSExpression *visit(NSExpression *expression) const;
NSPredicate *visit(NSPredicate *predicate) const;
ExpressionVisitor m_visitor;
};
NSExpression *PredicateExpressionTransformer::visit(NSExpression *expression) const {
expression = m_visitor(expression);
switch (expression.expressionType) {
case NSFunctionExpressionType: {
NSMutableArray *arguments = [NSMutableArray array];
for (NSExpression *argument in expression.arguments) {
[arguments addObject:visit(argument)];
}
if (expression.operand) {
return [NSExpression expressionForFunction:visit(expression.operand) selectorName:expression.function arguments:arguments];
} else {
return [NSExpression expressionForFunction:expression.function arguments:arguments];
}
}
case NSUnionSetExpressionType:
return [NSExpression expressionForUnionSet:visit(expression.leftExpression) with:visit(expression.rightExpression)];
case NSIntersectSetExpressionType:
return [NSExpression expressionForIntersectSet:visit(expression.leftExpression) with:visit(expression.rightExpression)];
case NSMinusSetExpressionType:
return [NSExpression expressionForMinusSet:visit(expression.leftExpression) with:visit(expression.rightExpression)];
case NSSubqueryExpressionType:
return [NSExpression expressionForSubquery:visit(expression.operand) usingIteratorVariable:expression.variable predicate:visit(expression.predicate)];
case NSAggregateExpressionType: {
NSMutableArray *subexpressions = [NSMutableArray array];
for (NSExpression *subexpression in expression.collection) {
[subexpressions addObject:visit(subexpression)];
}
return [NSExpression expressionForAggregate:subexpressions];
}
case NSConditionalExpressionType:
return [NSExpression expressionForConditional:visit(expression.predicate) trueExpression:visit(expression.trueExpression) falseExpression:visit(expression.falseExpression)];
default:
// The remaining expression types do not contain nested expressions or predicates.
return expression;
}
}
NSPredicate *PredicateExpressionTransformer::visit(NSPredicate *predicate) const {
if ([predicate isKindOfClass:[NSCompoundPredicate class]]) {
NSCompoundPredicate *compoundPredicate = (NSCompoundPredicate *)predicate;
NSMutableArray *subpredicates = [NSMutableArray array];
for (NSPredicate *subpredicate in compoundPredicate.subpredicates) {
[subpredicates addObject:visit(subpredicate)];
}
return [[NSCompoundPredicate alloc] initWithType:compoundPredicate.compoundPredicateType subpredicates:subpredicates];
}
if ([predicate isKindOfClass:[NSComparisonPredicate class]]) {
NSComparisonPredicate *comparisonPredicate = (NSComparisonPredicate *)predicate;
NSExpression *leftExpression = visit(comparisonPredicate.leftExpression);
NSExpression *rightExpression = visit(comparisonPredicate.rightExpression);
return [NSComparisonPredicate predicateWithLeftExpression:leftExpression rightExpression:rightExpression modifier:comparisonPredicate.comparisonPredicateModifier type:comparisonPredicate.predicateOperatorType options:comparisonPredicate.options];
}
return predicate;
}
} // anonymous namespace
NSPredicate *transformPredicate(NSPredicate *predicate, ExpressionVisitor visitor) {
PredicateExpressionTransformer transformer(visitor);
return transformer.visit(predicate);
}

434
Example/Pods/Realm/Realm/RLMProperty.mm generated Normal file
View File

@@ -0,0 +1,434 @@
////////////////////////////////////////////////////////////////////////////
//
// Copyright 2014 Realm Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////
#import "RLMProperty_Private.h"
#import "RLMArray.h"
#import "RLMListBase.h"
#import "RLMObject.h"
#import "RLMObject_Private.h"
#import "RLMOptionalBase.h"
#import "RLMSchema_Private.h"
#import "RLMSwiftSupport.h"
#import "RLMUtil.hpp"
BOOL RLMPropertyTypeIsNullable(RLMPropertyType propertyType) {
return propertyType != RLMPropertyTypeAny && propertyType != RLMPropertyTypeArray;
}
BOOL RLMPropertyTypeIsNumeric(RLMPropertyType propertyType) {
switch (propertyType) {
case RLMPropertyTypeInt:
case RLMPropertyTypeFloat:
case RLMPropertyTypeDouble:
return YES;
default:
return NO;
}
}
@implementation RLMProperty
- (instancetype)initWithName:(NSString *)name
type:(RLMPropertyType)type
objectClassName:(NSString *)objectClassName
indexed:(BOOL)indexed
optional:(BOOL)optional {
self = [super init];
if (self) {
_name = name;
_type = type;
_objectClassName = objectClassName;
_indexed = indexed;
_optional = optional;
[self setObjcCodeFromType];
[self updateAccessors];
}
return self;
}
-(void)updateAccessors {
// populate getter/setter names if generic
if (!_getterName) {
_getterName = _name;
}
if (!_setterName) {
// Objective-C setters only capitalize the first letter of the property name if it falls between 'a' and 'z'
int asciiCode = [_name characterAtIndex:0];
BOOL shouldUppercase = asciiCode >= 'a' && asciiCode <= 'z';
NSString *firstChar = [_name substringToIndex:1];
firstChar = shouldUppercase ? firstChar.uppercaseString : firstChar;
_setterName = [NSString stringWithFormat:@"set%@%@:", firstChar, [_name substringFromIndex:1]];
}
_getterSel = NSSelectorFromString(_getterName);
_setterSel = NSSelectorFromString(_setterName);
}
-(void)setObjcCodeFromType {
if (_optional) {
_objcType = '@';
}
switch (_type) {
case RLMPropertyTypeInt:
_objcType = 'q';
break;
case RLMPropertyTypeBool:
_objcType = 'c';
break;
case RLMPropertyTypeDouble:
_objcType = 'd';
break;
case RLMPropertyTypeFloat:
_objcType = 'f';
break;
case RLMPropertyTypeAny:
case RLMPropertyTypeArray:
case RLMPropertyTypeData:
case RLMPropertyTypeDate:
case RLMPropertyTypeObject:
case RLMPropertyTypeString:
_objcType = '@';
break;
}
}
// determine RLMPropertyType from objc code - returns true if valid type was found/set
- (BOOL)setTypeFromRawType {
const char *code = _objcRawType.UTF8String;
_objcType = *code; // first char of type attr
// map to RLMPropertyType
switch (self.objcType) {
case 's': // short
case 'i': // int
case 'l': // long
case 'q': // long long
_type = RLMPropertyTypeInt;
return YES;
case 'f':
_type = RLMPropertyTypeFloat;
return YES;
case 'd':
_type = RLMPropertyTypeDouble;
return YES;
case 'c': // BOOL is stored as char - since rlm has no char type this is ok
case 'B':
_type = RLMPropertyTypeBool;
return YES;
case '@': {
_optional = true;
static const char arrayPrefix[] = "@\"RLMArray<";
static const int arrayPrefixLen = sizeof(arrayPrefix) - 1;
static const char numberPrefix[] = "@\"NSNumber<";
static const int numberPrefixLen = sizeof(numberPrefix) - 1;
if (code[1] == '\0') {
// string is "@"
_type = RLMPropertyTypeAny;
_optional = false;
}
else if (strcmp(code, "@\"NSString\"") == 0) {
_type = RLMPropertyTypeString;
}
else if (strcmp(code, "@\"NSDate\"") == 0) {
_type = RLMPropertyTypeDate;
}
else if (strcmp(code, "@\"NSData\"") == 0) {
_type = RLMPropertyTypeData;
}
else if (strncmp(code, arrayPrefix, arrayPrefixLen) == 0) {
_optional = false;
// get object class from type string - @"RLMArray<objectClassName>"
_type = RLMPropertyTypeArray;
_objectClassName = [[NSString alloc] initWithBytes:code + arrayPrefixLen
length:strlen(code + arrayPrefixLen) - 2 // drop trailing >"
encoding:NSUTF8StringEncoding];
Class cls = [RLMSchema classForString:_objectClassName];
if (!cls) {
@throw RLMException(@"Property '%@' is of type 'RLMArray<%@>' which is not a supported RLMArray object type. "
@"RLMArrays can only contain instances of RLMObject subclasses. "
@"See https://realm.io/docs/objc/latest/#to-many for more information.", _name, _objectClassName);
}
}
else if (strncmp(code, numberPrefix, numberPrefixLen) == 0) {
// get number type from type string - @"NSNumber<objectClassName>"
NSString *numberType = [[NSString alloc] initWithBytes:code + numberPrefixLen
length:strlen(code + numberPrefixLen) - 2 // drop trailing >"
encoding:NSUTF8StringEncoding];
if ([numberType isEqualToString:@"RLMInt"]) {
_type = RLMPropertyTypeInt;
}
else if ([numberType isEqualToString:@"RLMFloat"]) {
_type = RLMPropertyTypeFloat;
}
else if ([numberType isEqualToString:@"RLMDouble"]) {
_type = RLMPropertyTypeDouble;
}
else if ([numberType isEqualToString:@"RLMBool"]) {
_type = RLMPropertyTypeBool;
}
else {
@throw RLMException(@"Property '%@' is of type 'NSNumber<%@>' which is not a supported NSNumber object type. "
@"NSNumbers can only be RLMInt, RLMFloat, RLMDouble, and RLMBool at the moment. "
@"See https://realm.io/docs/objc/latest for more information.", _name, numberType);
}
}
else if (strcmp(code, "@\"NSNumber\"") == 0) {
@throw RLMException(@"Property '%@' requires a protocol defining the contained type - example: NSNumber<RLMInt>.", _name);
}
else if (strcmp(code, "@\"RLMArray\"") == 0) {
@throw RLMException(@"Property '%@' requires a protocol defining the contained type - example: RLMArray<Person>.", _name);
}
else {
// for objects strip the quotes and @
NSString *className = [_objcRawType substringWithRange:NSMakeRange(2, _objcRawType.length-3)];
// verify type
Class cls = [RLMSchema classForString:className];
if (!cls) {
@throw RLMException(@"Property '%@' is declared as '%@', which is not a supported RLMObject property type. "
@"All properties must be primitives, NSString, NSDate, NSData, NSNumber, RLMArray, or subclasses of RLMObject. "
@"See https://realm.io/docs/objc/latest/api/Classes/RLMObject.html for more information.", _name, className);
}
_type = RLMPropertyTypeObject;
_optional = true;
_objectClassName = [cls className] ?: className;
}
return YES;
}
default:
return NO;
}
}
- (bool)parseObjcProperty:(objc_property_t)property {
unsigned int count;
objc_property_attribute_t *attrs = property_copyAttributeList(property, &count);
bool ignore = false;
for (size_t i = 0; i < count; ++i) {
switch (*attrs[i].name) {
case 'T':
_objcRawType = @(attrs[i].value);
break;
case 'R':
ignore = true;
break;
case 'N':
// nonatomic
break;
case 'D':
// dynamic
break;
case 'G':
_getterName = @(attrs[i].value);
break;
case 'S':
_setterName = @(attrs[i].value);
break;
default:
break;
}
}
free(attrs);
return ignore;
}
- (instancetype)initSwiftPropertyWithName:(NSString *)name
indexed:(BOOL)indexed
property:(objc_property_t)property
instance:(RLMObject *)obj {
self = [super init];
if (!self) {
return nil;
}
_name = name;
_indexed = indexed;
if ([self parseObjcProperty:property]) {
return nil;
}
id propertyValue = [obj valueForKey:_name];
// convert array types to objc variant
if ([_objcRawType isEqualToString:@"@\"RLMArray\""]) {
_objcRawType = [NSString stringWithFormat:@"@\"RLMArray<%@>\"", [propertyValue objectClassName]];
}
else if ([_objcRawType isEqualToString:@"@\"NSNumber\""]) {
const char *numberType = [propertyValue objCType];
switch (*numberType) {
case 'i':
case 'l':
case 'q':
_objcRawType = @"@\"NSNumber<RLMInt>\"";
break;
case 'f':
_objcRawType = @"@\"NSNumber<RLMFloat>\"";
break;
case 'd':
_objcRawType = @"@\"NSNumber<RLMDouble>\"";
break;
case 'B':
case 'c':
_objcRawType = @"@\"NSNumber<RLMBool>\"";
break;
default:
@throw RLMException(@"Can't persist NSNumber of type '%s': only integers, floats, doubles, and bools are currently supported.", numberType);
}
}
void (^throwForPropertyName)(NSString *) = ^(NSString *propertyName){
@throw RLMException(@"Can't persist property '%@' with incompatible type. "
"Add to Object.ignoredProperties() class method to ignore.", propertyName);
};
if (![self setTypeFromRawType]) {
throwForPropertyName(self.name);
}
if (_type == RLMPropertyTypeAny && propertyValue != nil) {
if ([propertyValue isKindOfClass:[NSString class]]) {
// NSStrings are parsed as Any.
_type = RLMPropertyTypeString;
} else if (![propertyValue isKindOfClass:[RLMListBase class]] && ![propertyValue isKindOfClass:[RLMOptionalBase class]]) {
// Don't throw if the property is a List/RealmOptional property because those types only
// get reported to ObjC with Swift 1.2 and not 2+.
throwForPropertyName(self.name);
}
} else if (_objcType == 'c') {
// Check if it's a BOOL or Int8 by trying to set it to 2 and seeing if
// it actually sets it to 1.
[obj setValue:@2 forKey:name];
NSNumber *value = [obj valueForKey:name];
_type = value.intValue == 2 ? RLMPropertyTypeInt : RLMPropertyTypeBool;
}
// update getter/setter names
[self updateAccessors];
return self;
}
- (instancetype)initWithName:(NSString *)name
indexed:(BOOL)indexed
property:(objc_property_t)property
{
self = [super init];
if (!self) {
return nil;
}
_name = name;
_indexed = indexed;
if ([self parseObjcProperty:property]) {
return nil;
}
if (![self setTypeFromRawType]) {
@throw RLMException(@"Can't persist property '%@' with incompatible type. "
"Add to ignoredPropertyNames: method to ignore.", self.name);
}
// update getter/setter names
[self updateAccessors];
return self;
}
- (instancetype)initSwiftListPropertyWithName:(NSString *)name
ivar:(Ivar)ivar
objectClassName:(NSString *)objectClassName {
self = [super init];
if (!self) {
return nil;
}
_name = name;
_type = RLMPropertyTypeArray;
_objectClassName = objectClassName;
_objcType = 't';
_swiftIvar = ivar;
// no obj-c property for generic lists, and thus no getter/setter names
return self;
}
- (instancetype)initSwiftOptionalPropertyWithName:(NSString *)name
indexed:(BOOL)indexed
ivar:(Ivar)ivar
propertyType:(RLMPropertyType)propertyType {
self = [super init];
if (!self) {
return nil;
}
_name = name;
_type = propertyType;
_indexed = indexed;
_objcType = '@';
_swiftIvar = ivar;
_optional = true;
// no obj-c property for generic optionals, and thus no getter/setter names
return self;
}
- (id)copyWithZone:(NSZone *)zone {
RLMProperty *prop = [[RLMProperty allocWithZone:zone] init];
prop->_name = _name;
prop->_type = _type;
prop->_objcType = _objcType;
prop->_objectClassName = _objectClassName;
prop->_indexed = _indexed;
prop->_getterName = _getterName;
prop->_setterName = _setterName;
prop->_getterSel = _getterSel;
prop->_setterSel = _setterSel;
prop->_isPrimary = _isPrimary;
prop->_swiftIvar = _swiftIvar;
prop->_optional = _optional;
prop->_declarationIndex = _declarationIndex;
return prop;
}
- (BOOL)isEqualToProperty:(RLMProperty *)property {
return _type == property->_type
&& _indexed == property->_indexed
&& _isPrimary == property->_isPrimary
&& _optional == property->_optional
&& [_name isEqualToString:property->_name]
&& (_objectClassName == property->_objectClassName || [_objectClassName isEqualToString:property->_objectClassName]);
}
- (NSString *)description {
return [NSString stringWithFormat:@"%@ {\n\ttype = %@;\n\tobjectClassName = %@;\n\tindexed = %@;\n\tisPrimary = %@;\n\toptional = %@;\n}", self.name, RLMTypeToString(self.type), self.objectClassName, self.indexed ? @"YES" : @"NO", self.isPrimary ? @"YES" : @"NO", self.optional ? @"YES" : @"NO"];
}
@end

1237
Example/Pods/Realm/Realm/RLMQueryUtil.mm generated Normal file

File diff suppressed because it is too large Load Diff

783
Example/Pods/Realm/Realm/RLMRealm.mm generated Normal file
View File

@@ -0,0 +1,783 @@
////////////////////////////////////////////////////////////////////////////
//
// Copyright 2014 Realm Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////
#import "RLMRealm_Private.hpp"
#import "RLMAnalytics.hpp"
#import "RLMArray_Private.hpp"
#import "RLMRealmConfiguration_Private.h"
#import "RLMMigration_Private.h"
#import "RLMObjectSchema_Private.hpp"
#import "RLMProperty_Private.h"
#import "RLMObjectStore.h"
#import "RLMObject_Private.h"
#import "RLMObject_Private.hpp"
#import "RLMObservation.hpp"
#import "RLMProperty.h"
#import "RLMQueryUtil.hpp"
#import "RLMRealmUtil.hpp"
#import "RLMSchema_Private.hpp"
#import "RLMUpdateChecker.hpp"
#import "RLMUtil.hpp"
#include "impl/realm_coordinator.hpp"
#include "object_store.hpp"
#include "schema.hpp"
#include "shared_realm.hpp"
#include <realm/commit_log.hpp>
#include <realm/disable_sync_to_disk.hpp>
#include <realm/version.hpp>
using namespace realm;
using util::File;
@interface RLMRealmConfiguration ()
- (realm::Realm::Config&)config;
@end
@interface RLMRealm ()
- (void)sendNotifications:(NSString *)notification;
@end
void RLMDisableSyncToDisk() {
realm::disable_sync_to_disk();
}
// Notification Token
@interface RLMRealmNotificationToken : RLMNotificationToken
@property (nonatomic, strong) RLMRealm *realm;
@property (nonatomic, copy) RLMNotificationBlock block;
@end
@implementation RLMRealmNotificationToken
- (void)stop {
[_realm removeNotification:self];
}
- (void)dealloc {
if (_realm || _block) {
NSLog(@"RLMNotificationToken released without unregistering a notification. You must hold "
@"on to the RLMNotificationToken returned from addNotificationBlock and call "
@"removeNotification: when you no longer wish to recieve RLMRealm notifications.");
}
}
@end
static bool shouldForciblyDisableEncryption() {
static bool disableEncryption = getenv("REALM_DISABLE_ENCRYPTION");
return disableEncryption;
}
NSData *RLMRealmValidatedEncryptionKey(NSData *key) {
if (shouldForciblyDisableEncryption()) {
return nil;
}
if (key) {
if (key.length != 64) {
@throw RLMException(@"Encryption key must be exactly 64 bytes long");
}
#if TARGET_OS_WATCH
@throw RLMException(@"Cannot open an encrypted Realm on watchOS.");
#endif
}
return key;
}
@implementation RLMRealm {
NSHashTable *_collectionEnumerators;
NSHashTable *_notificationHandlers;
}
+ (BOOL)isCoreDebug {
return realm::Version::has_feature(realm::feature_Debug);
}
+ (void)initialize {
static bool initialized;
if (initialized) {
return;
}
initialized = true;
RLMCheckForUpdates();
RLMInstallUncaughtExceptionHandler();
RLMSendAnalytics();
}
- (BOOL)isEmpty {
return realm::ObjectStore::is_empty(self.group);
}
- (void)verifyThread {
_realm->verify_thread();
}
- (BOOL)inWriteTransaction {
return _realm->is_in_transaction();
}
- (NSString *)path {
return @(_realm->config().path.c_str());
}
- (realm::Group *)group {
return _realm->read_group();
}
- (BOOL)isReadOnly {
return _realm->config().read_only;
}
-(BOOL)autorefresh {
return _realm->auto_refresh();
}
- (void)setAutorefresh:(BOOL)autorefresh {
_realm->set_auto_refresh(autorefresh);
}
+ (NSString *)writeableTemporaryPathForFile:(NSString *)fileName {
return [NSTemporaryDirectory() stringByAppendingPathComponent:fileName];
}
+ (instancetype)defaultRealm {
return [RLMRealm realmWithConfiguration:[RLMRealmConfiguration rawDefaultConfiguration] error:nil];
}
+ (instancetype)realmWithPath:(NSString *)path {
RLMRealmConfiguration *configuration = [RLMRealmConfiguration defaultConfiguration];
configuration.path = path;
return [RLMRealm realmWithConfiguration:configuration error:nil];
}
+ (instancetype)realmWithPath:(NSString *)path
key:(NSData *)key
readOnly:(BOOL)readonly
inMemory:(BOOL)inMemory
dynamic:(BOOL)dynamic
schema:(RLMSchema *)customSchema
error:(NSError **)outError
{
RLMRealmConfiguration *configuration = [[RLMRealmConfiguration alloc] init];
if (inMemory) {
configuration.inMemoryIdentifier = path.lastPathComponent;
}
else {
configuration.path = path;
}
configuration.encryptionKey = key;
configuration.readOnly = readonly;
configuration.dynamic = dynamic;
configuration.customSchema = customSchema;
return [RLMRealm realmWithConfiguration:configuration error:outError];
}
// ARC tries to eliminate calls to autorelease when the value is then immediately
// returned, but this results in significantly different semantics between debug
// and release builds for RLMRealm, so force it to always autorelease.
static id RLMAutorelease(id value) {
// +1 __bridge_retained, -1 CFAutorelease
return value ? (__bridge id)CFAutorelease((__bridge_retained CFTypeRef)value) : nil;
}
static void RLMCopyColumnMapping(RLMObjectSchema *targetSchema, const ObjectSchema &tableSchema) {
REALM_ASSERT_DEBUG(targetSchema.properties.count == tableSchema.properties.size());
// copy updated column mapping
for (auto const& prop : tableSchema.properties) {
RLMProperty *targetProp = targetSchema[@(prop.name.c_str())];
targetProp.column = prop.table_column;
}
// re-order properties
[targetSchema sortPropertiesByColumn];
}
static void RLMRealmSetSchemaAndAlign(RLMRealm *realm, RLMSchema *targetSchema) {
realm.schema = targetSchema;
for (auto const& aligned : *realm->_realm->config().schema) {
if (RLMObjectSchema *objectSchema = [targetSchema schemaForClassName:@(aligned.name.c_str())]) {
objectSchema.realm = realm;
RLMCopyColumnMapping(objectSchema, aligned);
}
}
}
+ (instancetype)realmWithSharedRealm:(SharedRealm)sharedRealm schema:(RLMSchema *)schema {
RLMRealm *realm = [RLMRealm new];
realm->_realm = sharedRealm;
realm->_dynamic = YES;
RLMRealmSetSchemaAndAlign(realm, schema);
return RLMAutorelease(realm);
}
void RLMRealmTranslateException(NSError **error) {
try {
throw;
}
catch (RealmFileException const& ex) {
switch (ex.kind()) {
case RealmFileException::Kind::PermissionDenied:
RLMSetErrorOrThrow(RLMMakeError(RLMErrorFilePermissionDenied, ex), error);
break;
case RealmFileException::Kind::IncompatibleLockFile: {
NSString *err = @"Realm file is currently open in another process "
"which cannot share access with this process. All "
"processes sharing a single file must be the same "
"architecture. For sharing files between the Realm "
"Browser and an iOS simulator, this means that you "
"must use a 64-bit simulator.";
RLMSetErrorOrThrow(RLMMakeError(RLMErrorIncompatibleLockFile,
File::PermissionDenied(err.UTF8String, ex.path())), error);
break;
}
case RealmFileException::Kind::NotFound:
RLMSetErrorOrThrow(RLMMakeError(RLMErrorFileNotFound, ex), error);
break;
case RealmFileException::Kind::Exists:
RLMSetErrorOrThrow(RLMMakeError(RLMErrorFileExists, ex), error);
break;
case RealmFileException::Kind::AccessError:
RLMSetErrorOrThrow(RLMMakeError(RLMErrorFileAccess, ex), error);
break;
case RealmFileException::Kind::FormatUpgradeRequired:
RLMSetErrorOrThrow(RLMMakeError(RLMErrorFileFormatUpgradeRequired, ex), error);
break;
default:
RLMSetErrorOrThrow(RLMMakeError(RLMErrorFail, ex), error);
break;
}
}
catch (std::system_error const& ex) {
RLMSetErrorOrThrow(RLMMakeError(ex), error);
}
catch (const std::exception &exp) {
RLMSetErrorOrThrow(RLMMakeError(RLMErrorFail, exp), error);
}
}
+ (SharedRealm)openSharedRealm:(Realm::Config const&)config error:(NSError **)outError {
try {
return Realm::get_shared_realm(config);
}
catch (...) {
RLMRealmTranslateException(outError);
}
return nullptr;
}
+ (instancetype)realmWithConfiguration:(RLMRealmConfiguration *)configuration error:(NSError **)error {
bool dynamic = configuration.dynamic;
bool readOnly = configuration.readOnly;
{
Realm::Config& config = configuration.config;
// try to reuse existing realm first
if (config.cache || dynamic) {
if (RLMRealm *realm = RLMGetThreadLocalCachedRealmForPath(config.path)) {
auto const& old_config = realm->_realm->config();
if (old_config.read_only != config.read_only) {
@throw RLMException(@"Realm at path '%s' already opened with different read permissions", config.path.c_str());
}
if (old_config.in_memory != config.in_memory) {
@throw RLMException(@"Realm at path '%s' already opened with different inMemory settings", config.path.c_str());
}
if (realm->_dynamic != dynamic) {
@throw RLMException(@"Realm at path '%s' already opened with different dynamic settings", config.path.c_str());
}
if (old_config.encryption_key != config.encryption_key) {
@throw RLMException(@"Realm at path '%s' already opened with different encryption key", config.path.c_str());
}
return RLMAutorelease(realm);
}
}
}
configuration = [configuration copy];
Realm::Config& config = configuration.config;
RLMRealm *realm = [RLMRealm new];
realm->_dynamic = dynamic;
auto migrationBlock = configuration.migrationBlock;
if (migrationBlock && config.schema_version > 0) {
auto customSchema = configuration.customSchema;
config.migration_function = [=](SharedRealm old_realm, SharedRealm realm) {
RLMSchema *oldSchema = [RLMSchema dynamicSchemaFromObjectStoreSchema:*old_realm->config().schema];
RLMRealm *oldRealm = [RLMRealm realmWithSharedRealm:old_realm schema:oldSchema];
// The destination RLMRealm can't just use the schema from the
// SharedRealm because it doesn't have information about whether or
// not a class was defined in Swift, which effects how new objects
// are created
RLMSchema *newSchema = [customSchema ?: RLMSchema.sharedSchema copy];
RLMRealm *newRealm = [RLMRealm realmWithSharedRealm:realm schema:newSchema];
[[[RLMMigration alloc] initWithRealm:newRealm oldRealm:oldRealm] execute:migrationBlock];
oldRealm->_realm = nullptr;
newRealm->_realm = nullptr;
};
}
else {
config.migration_function = [](SharedRealm, SharedRealm) { };
}
bool beganReadTransaction = false;
// protects the realm cache and accessors cache
static id initLock = [NSObject new];
@synchronized(initLock) {
realm->_realm = [self openSharedRealm:config error:error];
if (!realm->_realm) {
return nil;
}
// if we have a cached realm on another thread, copy without a transaction
if (RLMRealm *cachedRealm = RLMGetAnyCachedRealmForPath(config.path)) {
realm.schema = [cachedRealm.schema shallowCopy];
for (RLMObjectSchema *objectSchema in realm.schema.objectSchema) {
objectSchema.realm = realm;
}
}
else {
beganReadTransaction = !realm->_realm->is_in_read_transaction();
try {
// set/align schema or perform migration if needed
RLMSchema *schema = [configuration.customSchema copy];
if (!schema) {
if (dynamic) {
schema = [RLMSchema dynamicSchemaFromObjectStoreSchema:*realm->_realm->config().schema];
}
else {
schema = [RLMSchema.sharedSchema copy];
realm->_realm->update_schema(schema.objectStoreCopy, config.schema_version);
}
}
RLMRealmSetSchemaAndAlign(realm, schema);
} catch (std::exception const& exception) {
RLMSetErrorOrThrow(RLMMakeError(RLMException(exception)), error);
return nil;
}
if (!dynamic || configuration.customSchema) {
RLMRealmCreateAccessors(realm.schema);
}
}
if (config.cache) {
RLMCacheRealm(config.path, realm);
}
}
if (!readOnly) {
// initializing the schema started a read transaction, so end it
if (beganReadTransaction) {
[realm invalidate];
}
realm->_realm->m_binding_context = RLMCreateBindingContext(realm);
}
return RLMAutorelease(realm);
}
+ (void)resetRealmState {
RLMClearRealmCache();
realm::_impl::RealmCoordinator::clear_cache();
[RLMRealmConfiguration resetRealmConfigurationState];
}
static void CheckReadWrite(RLMRealm *realm, NSString *msg=@"Cannot write to a read-only Realm") {
if (realm.readOnly) {
@throw RLMException(@"%@", msg);
}
}
- (void)verifyNotificationsAreSupported {
[self verifyThread];
CheckReadWrite(self, @"Read-only Realms do not change and do not have change notifications");
if (!_realm->can_deliver_notifications()) {
@throw RLMException(@"Can only add notification blocks from within runloops.");
}
}
- (RLMNotificationToken *)addNotificationBlock:(RLMNotificationBlock)block {
if (!block) {
@throw RLMException(@"The notification block should not be nil");
}
[self verifyNotificationsAreSupported];
_realm->read_group();
if (!_notificationHandlers) {
_notificationHandlers = [NSHashTable hashTableWithOptions:NSPointerFunctionsWeakMemory];
}
RLMRealmNotificationToken *token = [[RLMRealmNotificationToken alloc] init];
token.realm = self;
token.block = block;
[_notificationHandlers addObject:token];
return token;
}
- (void)removeNotification:(RLMNotificationToken *)token {
[self verifyThread];
if (auto realmToken = RLMDynamicCast<RLMRealmNotificationToken>(token)) {
[_notificationHandlers removeObject:token];
realmToken.realm = nil;
realmToken.block = nil;
}
}
- (void)sendNotifications:(NSString *)notification {
NSAssert(!self.readOnly, @"Read-only realms do not have notifications");
// call this realms notification blocks
for (RLMRealmNotificationToken *token in [_notificationHandlers allObjects]) {
if (token.block) {
token.block(notification, self);
}
}
}
- (RLMRealmConfiguration *)configuration {
RLMRealmConfiguration *configuration = [[RLMRealmConfiguration alloc] init];
configuration.config = _realm->config();
configuration.dynamic = _dynamic;
configuration.customSchema = _schema;
return configuration;
}
- (void)beginWriteTransaction {
try {
_realm->begin_transaction();
}
catch (std::exception &ex) {
@throw RLMException(ex);
}
}
- (void)commitWriteTransaction {
[self commitWriteTransaction:nil];
}
- (BOOL)commitWriteTransaction:(NSError **)outError {
try {
_realm->commit_transaction();
return YES;
}
catch (File::AccessError const& ex) {
RLMSetErrorOrThrow(RLMMakeError(RLMErrorFail, ex), outError);
return NO;
}
catch (std::exception const& ex) {
RLMSetErrorOrThrow(RLMMakeError(RLMErrorFail, ex), outError);
return NO;
}
}
- (void)transactionWithBlock:(void(^)(void))block {
[self transactionWithBlock:block error:nil];
}
- (BOOL)transactionWithBlock:(void(^)(void))block error:(NSError **)outError {
[self beginWriteTransaction];
block();
if (_realm->is_in_transaction()) {
return [self commitWriteTransaction:outError];
}
return YES;
}
- (void)cancelWriteTransaction {
try {
_realm->cancel_transaction();
}
catch (std::exception &ex) {
@throw RLMException(ex);
}
}
- (void)invalidate {
if (_realm->is_in_transaction()) {
NSLog(@"WARNING: An RLMRealm instance was invalidated during a write "
"transaction and all pending changes have been rolled back.");
}
[self detachAllEnumerators];
for (RLMObjectSchema *objectSchema in _schema.objectSchema) {
for (RLMObservationInfo *info : objectSchema->_observedObjects) {
info->willChange(RLMInvalidatedKey);
}
}
_realm->invalidate();
for (RLMObjectSchema *objectSchema in _schema.objectSchema) {
for (RLMObservationInfo *info : objectSchema->_observedObjects) {
info->didChange(RLMInvalidatedKey);
}
objectSchema.table = nullptr;
}
}
/**
Replaces all string columns in this Realm with a string enumeration column and compacts the
database file.
Cannot be called from a write transaction.
Compaction will not occur if other `RLMRealm` instances exist.
While compaction is in progress, attempts by other threads or processes to open the database will
wait.
Be warned that resource requirements for compaction is proportional to the amount of live data in
the database.
Compaction works by writing the database contents to a temporary database file and then replacing
the database with the temporary one. The name of the temporary file is formed by appending
`.tmp_compaction_space` to the name of the database.
@return YES if the compaction succeeded.
*/
- (BOOL)compact {
// compact() automatically ends the read transaction, but we need to clean
// up cached state and send invalidated notifications when that happens, so
// explicitly end it first unless we're in a write transaction (in which
// case compact() will throw an exception)
if (!_realm->is_in_transaction()) {
[self invalidate];
}
try {
return _realm->compact();
}
catch (std::exception const& ex) {
@throw RLMException(ex);
}
}
- (void)dealloc {
if (_realm) {
if (_realm->is_in_transaction()) {
[self cancelWriteTransaction];
NSLog(@"WARNING: An RLMRealm instance was deallocated during a write transaction and all "
"pending changes have been rolled back. Make sure to retain a reference to the "
"RLMRealm for the duration of the write transaction.");
}
}
}
- (BOOL)refresh {
return _realm->refresh();
}
- (void)addObject:(__unsafe_unretained RLMObject *const)object {
RLMAddObjectToRealm(object, self, false);
}
- (void)addObjects:(id<NSFastEnumeration>)array {
for (RLMObject *obj in array) {
if (![obj isKindOfClass:[RLMObject class]]) {
@throw RLMException(@"Cannot insert objects of type %@ with addObjects:. Only RLMObjects are supported.",
NSStringFromClass(obj.class));
}
[self addObject:obj];
}
}
- (void)addOrUpdateObject:(RLMObject *)object {
// verify primary key
if (!object.objectSchema.primaryKeyProperty) {
@throw RLMException(@"'%@' does not have a primary key and can not be updated", object.objectSchema.className);
}
RLMAddObjectToRealm(object, self, true);
}
- (void)addOrUpdateObjectsFromArray:(id)array {
for (RLMObject *obj in array) {
[self addOrUpdateObject:obj];
}
}
- (void)deleteObject:(RLMObject *)object {
RLMDeleteObjectFromRealm(object, self);
}
- (void)deleteObjects:(id)array {
if ([array respondsToSelector:@selector(realm)] && [array respondsToSelector:@selector(deleteObjectsFromRealm)]) {
if (self != (RLMRealm *)[array realm]) {
@throw RLMException(@"Can only delete objects from the Realm they belong to.");
}
[array deleteObjectsFromRealm];
}
else if ([array conformsToProtocol:@protocol(NSFastEnumeration)]) {
for (id obj in array) {
if ([obj isKindOfClass:RLMObjectBase.class]) {
RLMDeleteObjectFromRealm(obj, self);
}
}
}
else {
@throw RLMException(@"Invalid array type - container must be an RLMArray, RLMArray, or NSArray of RLMObjects");
}
}
- (void)deleteAllObjects {
RLMDeleteAllObjectsFromRealm(self);
}
- (RLMResults *)allObjects:(NSString *)objectClassName {
return RLMGetObjects(self, objectClassName, nil);
}
- (RLMResults *)objects:(NSString *)objectClassName where:(NSString *)predicateFormat, ... {
va_list args;
va_start(args, predicateFormat);
RLMResults *results = [self objects:objectClassName where:predicateFormat args:args];
va_end(args);
return results;
}
- (RLMResults *)objects:(NSString *)objectClassName where:(NSString *)predicateFormat args:(va_list)args {
return [self objects:objectClassName withPredicate:[NSPredicate predicateWithFormat:predicateFormat arguments:args]];
}
- (RLMResults *)objects:(NSString *)objectClassName withPredicate:(NSPredicate *)predicate {
return RLMGetObjects(self, objectClassName, predicate);
}
- (RLMObject *)objectWithClassName:(NSString *)className forPrimaryKey:(id)primaryKey {
return RLMGetObject(self, className, primaryKey);
}
+ (uint64_t)schemaVersionAtPath:(NSString *)realmPath error:(NSError **)error {
return [RLMRealm schemaVersionAtPath:realmPath encryptionKey:nil error:error];
}
+ (uint64_t)schemaVersionAtPath:(NSString *)realmPath encryptionKey:(NSData *)key error:(NSError **)outError {
try {
RLMRealmConfiguration *config = [[RLMRealmConfiguration alloc] init];
config.path = realmPath;
config.encryptionKey = RLMRealmValidatedEncryptionKey(key);
uint64_t version = Realm::get_schema_version(config.config);
if (version == realm::ObjectStore::NotVersioned) {
RLMSetErrorOrThrow([NSError errorWithDomain:RLMErrorDomain code:RLMErrorFail userInfo:@{NSLocalizedDescriptionKey:@"Cannot open an uninitialized realm in read-only mode"}], outError);
}
return version;
}
catch (std::exception &exp) {
RLMSetErrorOrThrow(RLMMakeError(RLMErrorFail, exp), outError);
return RLMNotVersioned;
}
}
+ (NSError *)migrateRealm:(RLMRealmConfiguration *)configuration {
if (RLMGetAnyCachedRealmForPath(configuration.config.path)) {
@throw RLMException(@"Cannot migrate Realms that are already open.");
}
@autoreleasepool {
NSError *error;
[RLMRealm realmWithConfiguration:configuration error:&error];
return error;
}
}
- (RLMObject *)createObject:(NSString *)className withValue:(id)value {
return (RLMObject *)RLMCreateObjectInRealmWithValue(self, className, value, false);
}
- (BOOL)writeCopyToPath:(NSString *)path key:(NSData *)key error:(NSError **)error {
key = RLMRealmValidatedEncryptionKey(key);
try {
self.group->write(path.UTF8String, static_cast<const char *>(key.bytes));
return YES;
}
catch (File::PermissionDenied &ex) {
if (error) {
*error = RLMMakeError(RLMErrorFilePermissionDenied, ex);
}
}
catch (File::Exists &ex) {
if (error) {
*error = RLMMakeError(RLMErrorFileExists, ex);
}
}
catch (File::NotFound &ex) {
if (error) {
*error = RLMMakeError(RLMErrorFileNotFound, ex);
}
}
catch (File::AccessError &ex) {
if (error) {
*error = RLMMakeError(RLMErrorFileAccess, ex);
}
}
catch (std::exception &ex) {
if (error) {
*error = RLMMakeError(RLMErrorFail, ex);
}
}
return NO;
}
- (BOOL)writeCopyToPath:(NSString *)path error:(NSError **)error {
return [self writeCopyToPath:path key:nil error:error];
}
- (BOOL)writeCopyToPath:(NSString *)path encryptionKey:(NSData *)key error:(NSError **)error {
if (!key) {
@throw RLMException(@"Encryption key must not be nil");
}
return [self writeCopyToPath:path key:key error:error];
}
- (void)registerEnumerator:(RLMFastEnumerator *)enumerator {
if (!_collectionEnumerators) {
_collectionEnumerators = [NSHashTable hashTableWithOptions:NSPointerFunctionsWeakMemory];
}
[_collectionEnumerators addObject:enumerator];
}
- (void)unregisterEnumerator:(RLMFastEnumerator *)enumerator {
[_collectionEnumerators removeObject:enumerator];
}
- (void)detachAllEnumerators {
for (RLMFastEnumerator *enumerator in _collectionEnumerators) {
[enumerator detach];
}
_collectionEnumerators = nil;
}
@end

View File

@@ -0,0 +1,262 @@
////////////////////////////////////////////////////////////////////////////
//
// Copyright 2015 Realm Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////
#import "RLMRealmConfiguration_Private.h"
#import "RLMObjectSchema_Private.hpp"
#import "RLMRealm_Private.h"
#import "RLMSchema_Private.hpp"
#import "RLMUtil.hpp"
#import "schema.hpp"
#import "shared_realm.hpp"
static NSString *const c_RLMRealmConfigurationProperties[] = {
@"path",
@"inMemoryIdentifier",
@"encryptionKey",
@"readOnly",
@"schemaVersion",
@"migrationBlock",
@"dynamic",
@"customSchema",
};
static NSString *const c_defaultRealmFileName = @"default.realm";
RLMRealmConfiguration *s_defaultConfiguration;
NSString *RLMRealmPathForFileAndBundleIdentifier(NSString *fileName, NSString *bundleIdentifier) {
#if TARGET_OS_TV
(void)bundleIdentifier;
// tvOS prohibits writing to the Documents directory, so we use the Library/Caches directory instead.
NSString *path = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES)[0];
#elif TARGET_OS_IPHONE
(void)bundleIdentifier;
// On iOS the Documents directory isn't user-visible, so put files there
NSString *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
#else
// On OS X it is, so put files in Application Support. If we aren't running
// in a sandbox, put it in a subdirectory based on the bundle identifier
// to avoid accidentally sharing files between applications
NSString *path = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES)[0];
if (![[NSProcessInfo processInfo] environment][@"APP_SANDBOX_CONTAINER_ID"]) {
if (!bundleIdentifier) {
bundleIdentifier = [NSBundle mainBundle].bundleIdentifier;
}
if (!bundleIdentifier) {
bundleIdentifier = [NSBundle mainBundle].executablePath.lastPathComponent;
}
path = [path stringByAppendingPathComponent:bundleIdentifier];
// create directory
[[NSFileManager defaultManager] createDirectoryAtPath:path
withIntermediateDirectories:YES
attributes:nil
error:nil];
}
#endif
return [path stringByAppendingPathComponent:fileName];
}
NSString *RLMRealmPathForFile(NSString *fileName) {
return RLMRealmPathForFileAndBundleIdentifier(fileName, nil);
}
@implementation RLMRealmConfiguration {
realm::Realm::Config _config;
}
- (realm::Realm::Config&)config {
return _config;
}
+ (instancetype)defaultConfiguration {
return [[self rawDefaultConfiguration] copy];
}
+ (void)setDefaultConfiguration:(RLMRealmConfiguration *)configuration {
if (!configuration) {
@throw RLMException(@"Cannot set the default configuration to nil.");
}
@synchronized(c_defaultRealmFileName) {
s_defaultConfiguration = [configuration copy];
}
}
+ (RLMRealmConfiguration *)rawDefaultConfiguration {
@synchronized(c_defaultRealmFileName) {
if (!s_defaultConfiguration) {
s_defaultConfiguration = [[RLMRealmConfiguration alloc] init];
}
}
return s_defaultConfiguration;
}
+ (void)resetRealmConfigurationState {
@synchronized(c_defaultRealmFileName) {
s_defaultConfiguration = nil;
}
}
- (instancetype)init {
self = [super init];
if (self) {
static NSString *defaultRealmPath = RLMRealmPathForFile(c_defaultRealmFileName);
self.path = defaultRealmPath;
self.schemaVersion = 0;
}
return self;
}
- (instancetype)copyWithZone:(NSZone *)zone {
RLMRealmConfiguration *configuration = [[[self class] allocWithZone:zone] init];
configuration->_config = _config;
configuration->_dynamic = _dynamic;
configuration->_migrationBlock = _migrationBlock;
configuration->_customSchema = _customSchema;
return configuration;
}
- (NSString *)description {
NSMutableString *string = [NSMutableString stringWithFormat:@"%@ {\n", self.class];
for (NSString *key : c_RLMRealmConfigurationProperties) {
NSString *description = [[self valueForKey:key] description];
description = [description stringByReplacingOccurrencesOfString:@"\n" withString:@"\n\t"];
[string appendFormat:@"\t%@ = %@;\n", key, description];
}
return [string stringByAppendingString:@"}"];
}
- (NSString *)path {
return _config.in_memory ? nil :@(_config.path.c_str());
}
static void RLMNSStringToStdString(std::string &out, NSString *in) {
out.resize([in maximumLengthOfBytesUsingEncoding:NSUTF8StringEncoding]);
if (out.empty()) {
return;
}
NSUInteger size = out.size();
[in getBytes:&out[0]
maxLength:size
usedLength:&size
encoding:NSUTF8StringEncoding
options:0 range:{0, in.length} remainingRange:nullptr];
out.resize(size);
}
- (void)setPath:(NSString *)path {
if (path.length == 0) {
@throw RLMException(@"Realm path must not be empty");
}
RLMNSStringToStdString(_config.path, path);
_config.in_memory = false;
}
- (NSString *)inMemoryIdentifier {
if (!_config.in_memory) {
return nil;
}
return [@(_config.path.c_str()) lastPathComponent];
}
- (void)setInMemoryIdentifier:(NSString *)inMemoryIdentifier {
if (inMemoryIdentifier.length == 0) {
@throw RLMException(@"In-memory identifier must not be empty");
}
RLMNSStringToStdString(_config.path, [NSTemporaryDirectory() stringByAppendingPathComponent:inMemoryIdentifier]);
_config.in_memory = true;
}
- (NSData *)encryptionKey {
return _config.encryption_key.empty() ? nil : [NSData dataWithBytes:_config.encryption_key.data() length:_config.encryption_key.size()];
}
- (void)setEncryptionKey:(NSData * __nullable)encryptionKey {
if (NSData *key = RLMRealmValidatedEncryptionKey(encryptionKey)) {
auto bytes = static_cast<const char *>(key.bytes);
_config.encryption_key.assign(bytes, bytes + key.length);
}
else {
_config.encryption_key.clear();
}
}
- (BOOL)readOnly {
return _config.read_only;
}
- (void)setReadOnly:(BOOL)readOnly {
_config.read_only = readOnly;
}
- (uint64_t)schemaVersion {
return _config.schema_version;
}
- (void)setSchemaVersion:(uint64_t)schemaVersion {
if (schemaVersion == RLMNotVersioned) {
@throw RLMException(@"Cannot set schema version to %llu (RLMNotVersioned)", RLMNotVersioned);
}
_config.schema_version = schemaVersion;
}
- (NSArray *)objectClasses {
return [_customSchema.objectSchema valueForKeyPath:@"objectClass"];
}
- (void)setObjectClasses:(NSArray *)objectClasses {
self.customSchema = [RLMSchema schemaWithObjectClasses:objectClasses];
}
- (void)setDynamic:(bool)dynamic {
_dynamic = dynamic;
_config.cache = !dynamic;
}
- (bool)cache {
return _config.cache;
}
- (void)setCache:(bool)cache {
_config.cache = cache;
}
- (void)setCustomSchema:(RLMSchema *)customSchema {
_customSchema = customSchema;
_config.schema = [_customSchema objectStoreCopy];
}
- (void)setDisableFormatUpgrade:(bool)disableFormatUpgrade
{
_config.disable_format_upgrade = disableFormatUpgrade;
}
- (bool)disableFormatUpgrade
{
return _config.disable_format_upgrade;
}
@end

160
Example/Pods/Realm/Realm/RLMRealmUtil.mm generated Normal file
View File

@@ -0,0 +1,160 @@
////////////////////////////////////////////////////////////////////////////
//
// Copyright 2014 Realm Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////
#import "RLMRealmUtil.hpp"
#import "RLMObservation.hpp"
#import "RLMRealm_Private.h"
#import "RLMUtil.hpp"
#import <Realm/RLMConstants.h>
#import <Realm/RLMSchema.h>
#import "binding_context.hpp"
#import <map>
#import <mutex>
#import <sys/event.h>
#import <sys/stat.h>
#import <sys/time.h>
#import <unistd.h>
// Global realm state
static std::mutex s_realmCacheMutex;
static std::map<std::string, NSMapTable *> s_realmsPerPath;
void RLMCacheRealm(std::string const& path, RLMRealm *realm) {
std::lock_guard<std::mutex> lock(s_realmCacheMutex);
NSMapTable *realms = s_realmsPerPath[path];
if (!realms) {
s_realmsPerPath[path] = realms = [NSMapTable mapTableWithKeyOptions:NSPointerFunctionsObjectPersonality
valueOptions:NSPointerFunctionsWeakMemory];
}
[realms setObject:realm forKey:@(pthread_mach_thread_np(pthread_self()))];
}
RLMRealm *RLMGetAnyCachedRealmForPath(std::string const& path) {
std::lock_guard<std::mutex> lock(s_realmCacheMutex);
return [s_realmsPerPath[path] objectEnumerator].nextObject;
}
RLMRealm *RLMGetThreadLocalCachedRealmForPath(std::string const& path) {
mach_port_t threadID = pthread_mach_thread_np(pthread_self());
std::lock_guard<std::mutex> lock(s_realmCacheMutex);
return [s_realmsPerPath[path] objectForKey:@(threadID)];
}
void RLMClearRealmCache() {
std::lock_guard<std::mutex> lock(s_realmCacheMutex);
s_realmsPerPath.clear();
}
void RLMInstallUncaughtExceptionHandler() {
static auto previousHandler = NSGetUncaughtExceptionHandler();
NSSetUncaughtExceptionHandler([](NSException *exception) {
NSNumber *threadID = @(pthread_mach_thread_np(pthread_self()));
{
std::lock_guard<std::mutex> lock(s_realmCacheMutex);
for (auto const& realmsPerThread : s_realmsPerPath) {
if (RLMRealm *realm = [realmsPerThread.second objectForKey:threadID]) {
if (realm.inWriteTransaction) {
[realm cancelWriteTransaction];
}
}
}
}
if (previousHandler) {
previousHandler(exception);
}
});
}
namespace {
class RLMNotificationHelper : public realm::BindingContext {
public:
RLMNotificationHelper(RLMRealm *realm) : _realm(realm) { }
bool can_deliver_notifications() const noexcept override {
// The main thread may not be in a run loop yet if we're called from
// something like `applicationDidFinishLaunching:`, but it presumably will
// be in the future
if ([NSThread isMainThread]) {
return true;
}
// Current mode indicates why the current callout from the runloop was made,
// and is null if a runloop callout isn't currently being processed
if (auto mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent())) {
CFRelease(mode);
return true;
}
return false;
}
void changes_available() override {
@autoreleasepool {
auto realm = _realm;
if (realm && !realm.autorefresh) {
[realm sendNotifications:RLMRealmRefreshRequiredNotification];
}
}
}
std::vector<ObserverState> get_observed_rows() override {
@autoreleasepool {
auto realm = _realm;
[realm detachAllEnumerators];
return RLMGetObservedRows(realm.schema.objectSchema);
}
}
void will_change(std::vector<ObserverState> const& observed, std::vector<void*> const& invalidated) override {
@autoreleasepool {
RLMWillChange(observed, invalidated);
}
}
void did_change(std::vector<ObserverState> const& observed, std::vector<void*> const& invalidated) override {
try {
@autoreleasepool {
RLMDidChange(observed, invalidated);
[_realm sendNotifications:RLMRealmDidChangeNotification];
}
}
catch (...) {
// This can only be called during a write transaction if it was
// called due to the transaction beginning, so cancel it to ensure
// exceptions thrown here behave the same as exceptions thrown when
// actually beginning the write
if (_realm.inWriteTransaction) {
[_realm cancelWriteTransaction];
}
throw;
}
}
private:
// This is owned by the realm, so it needs to not retain the realm
__weak RLMRealm *const _realm;
};
} // anonymous namespace
std::unique_ptr<realm::BindingContext> RLMCreateBindingContext(RLMRealm *realm) {
return std::unique_ptr<realm::BindingContext>(new RLMNotificationHelper(realm));
}

570
Example/Pods/Realm/Realm/RLMResults.mm generated Normal file
View File

@@ -0,0 +1,570 @@
////////////////////////////////////////////////////////////////////////////
//
// Copyright 2014 Realm Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////
#import "RLMResults_Private.h"
#import "RLMArray_Private.hpp"
#import "RLMObjectSchema_Private.hpp"
#import "RLMObjectStore.h"
#import "RLMObject_Private.hpp"
#import "RLMObservation.hpp"
#import "RLMProperty_Private.h"
#import "RLMQueryUtil.hpp"
#import "RLMRealm_Private.hpp"
#import "RLMSchema_Private.h"
#import "RLMUtil.hpp"
#import "results.hpp"
#import "impl/external_commit_helper.hpp"
#import <objc/runtime.h>
#import <objc/message.h>
#import <realm/table_view.hpp>
using namespace realm;
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wincomplete-implementation"
@implementation RLMNotificationToken
@end
#pragma clang diagnostic pop
@interface RLMCancellationToken : RLMNotificationToken
@end
@implementation RLMCancellationToken {
realm::AsyncQueryCancelationToken _token;
}
- (instancetype)initWithToken:(realm::AsyncQueryCancelationToken)token {
self = [super init];
if (self) {
_token = std::move(token);
}
return self;
}
- (void)stop {
_token = {};
}
@end
static const int RLMEnumerationBufferSize = 16;
@implementation RLMFastEnumerator {
// The buffer supplied by fast enumeration does not retain the objects given
// to it, but because we create objects on-demand and don't want them
// autoreleased (a table can have more rows than the device has memory for
// accessor objects) we need a thing to retain them.
id _strongBuffer[RLMEnumerationBufferSize];
RLMRealm *_realm;
RLMObjectSchema *_objectSchema;
// Collection being enumerated. Only one of these two will be valid: when
// possible we enumerate the collection directly, but when in a write
// transaction we instead create a frozen TableView and enumerate that
// instead so that mutating the collection during enumeration works.
id<RLMFastEnumerable> _collection;
realm::TableView _tableView;
}
- (instancetype)initWithCollection:(id<RLMFastEnumerable>)collection objectSchema:(RLMObjectSchema *)objectSchema {
self = [super init];
if (self) {
_realm = collection.realm;
_objectSchema = objectSchema;
if (_realm.inWriteTransaction) {
_tableView = [collection tableView];
}
else {
_collection = collection;
[_realm registerEnumerator:self];
}
}
return self;
}
- (void)dealloc {
if (_collection) {
[_realm unregisterEnumerator:self];
}
}
- (void)detach {
_tableView = [_collection tableView];
_collection = nil;
}
- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state
count:(NSUInteger)len {
[_realm verifyThread];
if (!_tableView.is_attached() && !_collection) {
@throw RLMException(@"Collection is no longer valid");
}
// The fast enumeration buffer size is currently a hardcoded number in the
// compiler so this can't actually happen, but just in case it changes in
// the future...
if (len > RLMEnumerationBufferSize) {
len = RLMEnumerationBufferSize;
}
NSUInteger batchCount = 0, count = state->extra[1];
Class accessorClass = _objectSchema.accessorClass;
for (NSUInteger index = state->state; index < count && batchCount < len; ++index) {
RLMObject *accessor = [[accessorClass alloc] initWithRealm:_realm schema:_objectSchema];
if (_collection) {
accessor->_row = (*_objectSchema.table)[[_collection indexInSource:index]];
}
else if (_tableView.is_row_attached(index)) {
accessor->_row = (*_objectSchema.table)[_tableView.get_source_ndx(index)];
}
RLMInitializeSwiftAccessorGenerics(accessor);
_strongBuffer[batchCount] = accessor;
batchCount++;
}
for (NSUInteger i = batchCount; i < len; ++i) {
_strongBuffer[i] = nil;
}
if (batchCount == 0) {
// Release our data if we're done, as we're autoreleased and so may
// stick around for a while
_collection = nil;
if (_tableView.is_attached()) {
_tableView = TableView();
}
else {
[_realm unregisterEnumerator:self];
}
}
state->itemsPtr = (__unsafe_unretained id *)(void *)_strongBuffer;
state->state += batchCount;
state->mutationsPtr = state->extra+1;
return batchCount;
}
@end
//
// RLMResults implementation
//
@implementation RLMResults {
realm::Results _results;
RLMRealm *_realm;
}
- (instancetype)initPrivate {
self = [super init];
return self;
}
static void assertKeyPathIsNotNested(NSString *keyPath) {
if ([keyPath rangeOfString:@"."].location != NSNotFound) {
@throw RLMException(@"Nested key paths are not supported yet for KVC collection operators.");
}
}
[[gnu::noinline]]
[[noreturn]]
static void throwError(NSString *aggregateMethod) {
try {
throw;
}
catch (realm::InvalidTransactionException const&) {
@throw RLMException(@"Cannot modify Results outside of a write transaction");
}
catch (realm::IncorrectThreadException const&) {
@throw RLMException(@"Realm accessed from incorrect thread");
}
catch (realm::Results::InvalidatedException const&) {
@throw RLMException(@"RLMResults has been invalidated");
}
catch (realm::Results::DetatchedAccessorException const&) {
@throw RLMException(@"Object has been invalidated");
}
catch (realm::Results::IncorrectTableException const& e) {
@throw RLMException(@"Object type '%s' does not match RLMResults type '%s'.",
e.actual.data(), e.expected.data());
}
catch (realm::Results::OutOfBoundsIndexException const& e) {
@throw RLMException(@"Index %zu is out of bounds (must be less than %zu)",
e.requested, e.valid_count);
}
catch (realm::Results::UnsupportedColumnTypeException const& e) {
@throw RLMException(@"%@ is not supported for %@ property '%s'",
aggregateMethod,
RLMTypeToString((RLMPropertyType)e.column_type),
e.column_name.data());
}
}
template<typename Function>
static auto translateErrors(Function&& f, NSString *aggregateMethod=nil) {
try {
return f();
}
catch (...) {
throwError(aggregateMethod);
}
}
+ (instancetype)resultsWithObjectSchema:(RLMObjectSchema *)objectSchema
results:(realm::Results)results {
RLMResults *ar = [[self alloc] initPrivate];
ar->_results = std::move(results);
ar->_realm = objectSchema.realm;
ar->_objectSchema = objectSchema;
return ar;
}
static inline void RLMResultsValidateInWriteTransaction(__unsafe_unretained RLMResults *const ar) {
ar->_realm->_realm->verify_thread();
ar->_realm->_realm->verify_in_write();
}
- (NSUInteger)count {
return translateErrors([&] { return _results.size(); });
}
- (NSString *)objectClassName {
return RLMStringDataToNSString(_results.get_object_type());
}
- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state
objects:(__unused __unsafe_unretained id [])buffer
count:(NSUInteger)len {
__autoreleasing RLMFastEnumerator *enumerator;
if (state->state == 0) {
enumerator = [[RLMFastEnumerator alloc] initWithCollection:self objectSchema:_objectSchema];
state->extra[0] = (long)enumerator;
state->extra[1] = self.count;
}
else {
enumerator = (__bridge id)(void *)state->extra[0];
}
return [enumerator countByEnumeratingWithState:state count:len];
}
- (NSUInteger)indexOfObjectWhere:(NSString *)predicateFormat, ... {
va_list args;
va_start(args, predicateFormat);
NSUInteger index = [self indexOfObjectWhere:predicateFormat args:args];
va_end(args);
return index;
}
- (NSUInteger)indexOfObjectWhere:(NSString *)predicateFormat args:(va_list)args {
return [self indexOfObjectWithPredicate:[NSPredicate predicateWithFormat:predicateFormat
arguments:args]];
}
- (NSUInteger)indexOfObjectWithPredicate:(NSPredicate *)predicate {
if (_results.get_mode() == Results::Mode::Empty) {
return NSNotFound;
}
Query query = translateErrors([&] { return _results.get_query(); });
RLMUpdateQueryWithPredicate(&query, predicate, _realm.schema, _objectSchema);
TableView table_view;
if (const auto& sort = _results.get_sort()) {
// A sort order is specified so we need to return the first match given that ordering.
table_view = query.find_all();
table_view.sort(sort.columnIndices, sort.ascending);
} else {
// No sort order is specified so we only need to find a single match.
// FIXME: We're only looking for a single object so we'd like to be able to use `Query::find`
// for this, but as of core v0.97.1 it gives incorrect results if the query is restricted
// to a link view (<https://github.com/realm/realm-core/issues/1565>).
table_view = query.find_all(0, -1, 1);
}
if (!table_view.size()) {
return NSNotFound;
}
return _results.index_of(table_view.get_source_ndx(0));
}
- (id)objectAtIndex:(NSUInteger)index {
return translateErrors([&] {
return RLMCreateObjectAccessor(_realm, _objectSchema, _results.get(index));
});
}
- (id)firstObject {
auto row = translateErrors([&] { return _results.first(); });
return row ? RLMCreateObjectAccessor(_realm, _objectSchema, *row) : nil;
}
- (id)lastObject {
auto row = translateErrors([&] { return _results.last(); });
return row ? RLMCreateObjectAccessor(_realm, _objectSchema, *row) : nil;
}
- (NSUInteger)indexOfObject:(RLMObject *)object {
if (!object || (!object->_realm && !object.invalidated)) {
return NSNotFound;
}
return translateErrors([&] {
return RLMConvertNotFound(_results.index_of(object->_row));
});
}
- (id)valueForKeyPath:(NSString *)keyPath {
if ([keyPath characterAtIndex:0] == '@') {
if ([keyPath isEqualToString:@"@count"]) {
return @(self.count);
}
NSRange operatorRange = [keyPath rangeOfString:@"." options:NSLiteralSearch];
NSUInteger keyPathLength = keyPath.length;
NSUInteger separatorIndex = operatorRange.location != NSNotFound ? operatorRange.location : keyPathLength;
NSString *operatorName = [keyPath substringWithRange:NSMakeRange(1, separatorIndex - 1)];
SEL opSelector = NSSelectorFromString([NSString stringWithFormat:@"_%@ForKeyPath:", operatorName]);
BOOL isValidOperator = [self respondsToSelector:opSelector];
if (!isValidOperator) {
@throw RLMException(@"Unsupported KVC collection operator found in key path '%@'", keyPath);
}
else if (separatorIndex >= keyPathLength - 1) {
@throw RLMException(@"Missing key path for KVC collection operator %@ in key path '%@'", operatorName, keyPath);
}
NSString *operatorKeyPath = [keyPath substringFromIndex:separatorIndex + 1];
if (isValidOperator) {
return ((id(*)(id, SEL, id))objc_msgSend)(self, opSelector, operatorKeyPath);
}
}
return [super valueForKeyPath:keyPath];
}
- (id)valueForKey:(NSString *)key {
return translateErrors([&] {
return RLMCollectionValueForKey(self, key);
});
}
- (void)setValue:(id)value forKey:(NSString *)key {
translateErrors([&] { RLMResultsValidateInWriteTransaction(self); });
RLMCollectionSetValueForKey(self, key, value);
}
- (NSNumber *)_aggregateForKeyPath:(NSString *)keyPath method:(util::Optional<Mixed> (Results::*)(size_t))method methodName:(NSString *)methodName {
assertKeyPathIsNotNested(keyPath);
return [self aggregate:keyPath method:method methodName:methodName];
}
- (NSNumber *)_minForKeyPath:(NSString *)keyPath {
return [self _aggregateForKeyPath:keyPath method:&Results::min methodName:@"@min"];
}
- (NSNumber *)_maxForKeyPath:(NSString *)keyPath {
return [self _aggregateForKeyPath:keyPath method:&Results::max methodName:@"@max"];
}
- (NSNumber *)_sumForKeyPath:(NSString *)keyPath {
return [self _aggregateForKeyPath:keyPath method:&Results::sum methodName:@"@sum"];
}
- (NSNumber *)_avgForKeyPath:(NSString *)keyPath {
return [self _aggregateForKeyPath:keyPath method:&Results::average methodName:@"@avg"];
}
- (NSArray *)_unionOfObjectsForKeyPath:(NSString *)keyPath {
assertKeyPathIsNotNested(keyPath);
return translateErrors([&] {
return RLMCollectionValueForKey(self, keyPath);
});
}
- (NSArray *)_distinctUnionOfObjectsForKeyPath:(NSString *)keyPath {
return [NSSet setWithArray:[self _unionOfObjectsForKeyPath:keyPath]].allObjects;
}
- (NSArray *)_unionOfArraysForKeyPath:(NSString *)keyPath {
assertKeyPathIsNotNested(keyPath);
if ([keyPath isEqualToString:@"self"]) {
@throw RLMException(@"self is not a valid key-path for a KVC array collection operator as 'unionOfArrays'.");
}
return translateErrors([&] {
NSArray *nestedResults = RLMCollectionValueForKey(self, keyPath);
NSMutableArray *flatArray = [NSMutableArray arrayWithCapacity:nestedResults.count];
for (id<RLMFastEnumerable> array in nestedResults) {
NSArray *nsArray = RLMCollectionValueForKey(array, @"self");
[flatArray addObjectsFromArray:nsArray];
}
return flatArray;
});
}
- (NSArray *)_distinctUnionOfArraysForKeyPath:(__unused NSString *)keyPath {
return [NSSet setWithArray:[self _unionOfArraysForKeyPath:keyPath]].allObjects;
}
- (RLMResults *)objectsWhere:(NSString *)predicateFormat, ... {
va_list args;
va_start(args, predicateFormat);
RLMResults *results = [self objectsWhere:predicateFormat args:args];
va_end(args);
return results;
}
- (RLMResults *)objectsWhere:(NSString *)predicateFormat args:(va_list)args {
return [self objectsWithPredicate:[NSPredicate predicateWithFormat:predicateFormat arguments:args]];
}
- (RLMResults *)objectsWithPredicate:(NSPredicate *)predicate {
return translateErrors([&] {
if (_results.get_mode() == Results::Mode::Empty) {
return self;
}
auto query = _results.get_query();
RLMUpdateQueryWithPredicate(&query, predicate, _realm.schema, _objectSchema);
return [RLMResults resultsWithObjectSchema:_objectSchema
results:realm::Results(_realm->_realm, std::move(query), _results.get_sort())];
});
}
- (RLMResults *)sortedResultsUsingProperty:(NSString *)property ascending:(BOOL)ascending {
return [self sortedResultsUsingDescriptors:@[[RLMSortDescriptor sortDescriptorWithProperty:property ascending:ascending]]];
}
- (RLMResults *)sortedResultsUsingDescriptors:(NSArray *)properties {
return translateErrors([&] {
if (_results.get_mode() == Results::Mode::Empty) {
return self;
}
return [RLMResults resultsWithObjectSchema:_objectSchema
results:_results.sort(RLMSortOrderFromDescriptors(_objectSchema, properties))];
});
}
- (id)objectAtIndexedSubscript:(NSUInteger)index {
return [self objectAtIndex:index];
}
- (id)aggregate:(NSString *)property method:(util::Optional<Mixed> (Results::*)(size_t))method methodName:(NSString *)methodName {
size_t column = RLMValidatedProperty(_objectSchema, property).column;
auto value = translateErrors([&] { return (_results.*method)(column); }, methodName);
if (!value) {
return nil;
}
return RLMMixedToObjc(*value);
}
- (id)minOfProperty:(NSString *)property {
return [self aggregate:property method:&Results::min methodName:@"minOfProperty"];
}
- (id)maxOfProperty:(NSString *)property {
return [self aggregate:property method:&Results::max methodName:@"maxOfProperty"];
}
- (id)sumOfProperty:(NSString *)property {
return [self aggregate:property method:&Results::sum methodName:@"sumOfProperty"];
}
- (id)averageOfProperty:(NSString *)property {
return [self aggregate:property method:&Results::average methodName:@"averageOfProperty"];
}
- (void)deleteObjectsFromRealm {
return translateErrors([&] {
if (_results.get_mode() == Results::Mode::Table) {
RLMResultsValidateInWriteTransaction(self);
RLMClearTable(self.objectSchema);
}
else {
RLMTrackDeletions(_realm, ^{ _results.clear(); });
}
});
}
- (NSString *)description {
const NSUInteger maxObjects = 100;
NSMutableString *mString = [NSMutableString stringWithFormat:@"RLMResults <0x%lx> (\n", (long)self];
unsigned long index = 0, skipped = 0;
for (id obj in self) {
NSString *sub;
if ([obj respondsToSelector:@selector(descriptionWithMaxDepth:)]) {
sub = [obj descriptionWithMaxDepth:RLMDescriptionMaxDepth - 1];
}
else {
sub = [obj description];
}
// Indent child objects
NSString *objDescription = [sub stringByReplacingOccurrencesOfString:@"\n" withString:@"\n\t"];
[mString appendFormat:@"\t[%lu] %@,\n", index++, objDescription];
if (index >= maxObjects) {
skipped = self.count - maxObjects;
break;
}
}
// Remove last comma and newline characters
if(self.count > 0)
[mString deleteCharactersInRange:NSMakeRange(mString.length-2, 2)];
if (skipped) {
[mString appendFormat:@"\n\t... %lu objects skipped.", skipped];
}
[mString appendFormat:@"\n)"];
return [NSString stringWithString:mString];
}
- (NSUInteger)indexInSource:(NSUInteger)index {
return translateErrors([&] { return _results.get(index).get_index(); });
}
- (realm::TableView)tableView {
return translateErrors([&] { return _results.get_tableview(); });
}
// The compiler complains about the method's argument type not matching due to
// it not having the generic type attached, but it doesn't seem to be possible
// to actually include the generic type
// http://www.openradar.me/radar?id=6135653276319744
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wmismatched-parameter-types"
- (RLMNotificationToken *)addNotificationBlock:(void (^)(RLMResults *results, NSError *error))block {
[_realm verifyNotificationsAreSupported];
auto token = _results.async([self, block](std::exception_ptr err) {
if (err) {
try {
rethrow_exception(err);
}
catch (...) {
NSError *error;
RLMRealmTranslateException(&error);
block(nil, error);
}
}
else {
block(self, nil);
}
});
return [[RLMCancellationToken alloc] initWithToken:std::move(token)];
}
#pragma clang diagnostic pop
@end

336
Example/Pods/Realm/Realm/RLMSchema.mm generated Normal file
View File

@@ -0,0 +1,336 @@
////////////////////////////////////////////////////////////////////////////
//
// Copyright 2014 Realm Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////
#import "RLMSchema_Private.h"
#import "RLMAccessor.h"
#import "RLMObject_Private.hpp"
#import "RLMObjectSchema_Private.hpp"
#import "RLMProperty_Private.h"
#import "RLMRealm_Private.hpp"
#import "RLMSwiftSupport.h"
#import "RLMUtil.hpp"
#import "object_store.hpp"
#import "schema.hpp"
#import <realm/group.hpp>
#import <objc/runtime.h>
#include <mutex>
using namespace realm;
const uint64_t RLMNotVersioned = realm::ObjectStore::NotVersioned;
// RLMSchema private properties
@interface RLMSchema ()
@property (nonatomic, readwrite) NSMutableDictionary *objectSchemaByName;
@end
static RLMSchema *s_sharedSchema = [[RLMSchema alloc] init];
static NSMutableDictionary *s_localNameToClass = [[NSMutableDictionary alloc] init];
static NSMutableDictionary *s_privateObjectSubclasses = [[NSMutableDictionary alloc] init];
static enum class SharedSchemaState {
Uninitialized,
Initializing,
Initialized
} s_sharedSchemaState = SharedSchemaState::Uninitialized;
// Caller must @synchronize on s_localNameToClass
static RLMObjectSchema *RLMRegisterClass(Class cls) {
if (RLMObjectSchema *schema = s_privateObjectSubclasses[[cls className]]) {
return schema;
}
auto prevState = s_sharedSchemaState;
s_sharedSchemaState = SharedSchemaState::Initializing;
RLMObjectSchema *schema = [RLMObjectSchema schemaForObjectClass:cls];
s_sharedSchemaState = prevState;
// set standalone class on shared shema for standalone object creation
schema.standaloneClass = RLMStandaloneAccessorClassForObjectClass(schema.objectClass, schema);
// override sharedSchema class methods for performance
RLMReplaceSharedSchemaMethod(cls, schema);
s_privateObjectSubclasses[schema.className] = schema;
if ([cls shouldIncludeInDefaultSchema]) {
s_sharedSchema.objectSchemaByName[schema.className] = schema;
}
return schema;
}
// Caller must @synchronize on s_localNameToClass
static void RLMRegisterClassLocalNames(Class *classes, NSUInteger count) {
for (NSUInteger i = 0; i < count; i++) {
Class cls = classes[i];
if (!RLMIsObjectSubclass(cls) || RLMIsGeneratedClass(cls)) {
continue;
}
NSString *className = NSStringFromClass(cls);
if ([RLMSwiftSupport isSwiftClassName:className]) {
className = [RLMSwiftSupport demangleClassName:className];
}
// NSStringFromClass demangles the names for top-level Swift classes
// but not for nested classes. _T indicates it's a Swift symbol, t
// indicates it's a type, and C indicates it's a class.
else if ([className hasPrefix:@"_TtC"]) {
@throw RLMException(@"RLMObject subclasses cannot be nested within other declarations. Please move %@ to global scope.", className);
}
if (Class existingClass = s_localNameToClass[className]) {
if (existingClass != cls) {
@throw RLMException(@"RLMObject subclasses with the same name cannot be included twice in the same target. "
@"Please make sure '%@' is only linked once to your current target.", className);
}
continue;
}
s_localNameToClass[className] = cls;
RLMReplaceClassNameMethod(cls, className);
}
}
@implementation RLMSchema {
NSArray *_objectSchema;
}
- (instancetype)init {
self = [super init];
if (self) {
_objectSchemaByName = [[NSMutableDictionary alloc] init];
}
return self;
}
- (NSArray *)objectSchema {
if (!_objectSchema) {
_objectSchema = [_objectSchemaByName allValues];
}
return _objectSchema;
}
- (void)setObjectSchema:(NSArray *)objectSchema {
_objectSchema = objectSchema;
_objectSchemaByName = [NSMutableDictionary dictionaryWithCapacity:objectSchema.count];
for (RLMObjectSchema *object in objectSchema) {
[_objectSchemaByName setObject:object forKey:object.className];
}
}
- (RLMObjectSchema *)schemaForClassName:(NSString *)className {
return _objectSchemaByName[className];
}
- (RLMObjectSchema *)objectForKeyedSubscript:(__unsafe_unretained id<NSCopying> const)className {
RLMObjectSchema *schema = _objectSchemaByName[className];
if (!schema) {
@throw RLMException(@"Object type '%@' not persisted in Realm", className);
}
return schema;
}
+ (instancetype)schemaWithObjectClasses:(NSArray *)classes {
NSUInteger count = classes.count;
auto classArray = std::make_unique<__unsafe_unretained Class[]>(count);
[classes getObjects:classArray.get() range:NSMakeRange(0, count)];
RLMSchema *schema = [[self alloc] init];
@synchronized(s_localNameToClass) {
RLMRegisterClassLocalNames(classArray.get(), count);
schema->_objectSchemaByName = [NSMutableDictionary dictionaryWithCapacity:count];
for (Class cls in classes) {
if (!RLMIsObjectSubclass(cls)) {
@throw RLMException(@"Can't add non-Object type '%@' to a schema.", cls);
}
schema->_objectSchemaByName[[cls className]] = RLMRegisterClass(cls);
}
}
NSMutableArray *errors = [NSMutableArray new];
// Verify that all of the targets of links are included in the class list
[schema->_objectSchemaByName enumerateKeysAndObjectsUsingBlock:^(id, RLMObjectSchema *objectSchema, BOOL *) {
for (RLMProperty *prop in objectSchema.properties) {
if (prop.type != RLMPropertyTypeObject && prop.type != RLMPropertyTypeArray) {
continue;
}
if (!schema->_objectSchemaByName[prop.objectClassName]) {
[errors addObject:[NSString stringWithFormat:@"- '%@.%@' links to class '%@', which is missing from the list of classes to persist", objectSchema.className, prop.name, prop.objectClassName]];
}
}
}];
if (errors.count) {
@throw RLMException(@"Invalid class subset list:\n%@", [errors componentsJoinedByString:@"\n"]);
}
return schema;
}
+ (RLMObjectSchema *)sharedSchemaForClass:(Class)cls {
@synchronized(s_localNameToClass) {
// We create instances of Swift objects during schema init, and they
// obviously need to not also try to initialize the schema
if (s_sharedSchemaState == SharedSchemaState::Initializing) {
return nil;
}
RLMRegisterClassLocalNames(&cls, 1);
return RLMRegisterClass(cls);
}
}
+ (instancetype)partialSharedSchema {
return s_sharedSchema;
}
// schema based on runtime objects
+ (instancetype)sharedSchema {
@synchronized(s_localNameToClass) {
// We replace this method with one which just returns s_sharedSchema
// once initialization is complete, but we still need to check if it's
// already complete because it may have been done by another thread
// while we were waiting for the lock
if (s_sharedSchemaState == SharedSchemaState::Initialized) {
return s_sharedSchema;
}
if (s_sharedSchemaState == SharedSchemaState::Initializing) {
@throw RLMException(@"Illegal recursive call of +[%@ %@]. Note: Properties of Swift `Object` classes must not be prepopulated with queried results from a Realm.", self, NSStringFromSelector(_cmd));
}
s_sharedSchemaState = SharedSchemaState::Initializing;
try {
// Make sure we've discovered all classes
{
unsigned int numClasses;
using malloc_ptr = std::unique_ptr<__unsafe_unretained Class[], decltype(&free)>;
malloc_ptr classes(objc_copyClassList(&numClasses), &free);
RLMRegisterClassLocalNames(classes.get(), numClasses);
}
[s_localNameToClass enumerateKeysAndObjectsUsingBlock:^(NSString *, Class cls, BOOL *) {
RLMRegisterClass(cls);
}];
}
catch (...) {
s_sharedSchemaState = SharedSchemaState::Uninitialized;
throw;
}
// Replace this method with one that doesn't need to acquire a lock
Class metaClass = objc_getMetaClass(class_getName(self));
IMP imp = imp_implementationWithBlock(^{ return s_sharedSchema; });
class_replaceMethod(metaClass, @selector(sharedSchema), imp, "@@:");
s_sharedSchemaState = SharedSchemaState::Initialized;
}
return s_sharedSchema;
}
// schema based on tables in a realm
+ (instancetype)dynamicSchemaFromObjectStoreSchema:(Schema &)objectStoreSchema {
// cache descriptors for all subclasses of RLMObject
NSMutableArray *schemaArray = [NSMutableArray arrayWithCapacity:objectStoreSchema.size()];
for (auto &objectSchema : objectStoreSchema) {
RLMObjectSchema *schema = [RLMObjectSchema objectSchemaForObjectStoreSchema:objectSchema];
[schemaArray addObject:schema];
}
// set class array and mapping
RLMSchema *schema = [RLMSchema new];
schema.objectSchema = schemaArray;
return schema;
}
+ (Class)classForString:(NSString *)className {
if (Class cls = s_localNameToClass[className]) {
return cls;
}
if (Class cls = NSClassFromString(className)) {
return RLMIsObjectSubclass(cls) ? cls : nil;
}
// className might be the local name of a Swift class we haven't registered
// yet, so scan them all then recheck
{
unsigned int numClasses;
std::unique_ptr<__unsafe_unretained Class[], decltype(&free)> classes(objc_copyClassList(&numClasses), &free);
RLMRegisterClassLocalNames(classes.get(), numClasses);
}
return s_localNameToClass[className];
}
- (id)copyWithZone:(NSZone *)zone {
RLMSchema *schema = [[RLMSchema allocWithZone:zone] init];
schema->_objectSchemaByName = [[NSMutableDictionary allocWithZone:zone]
initWithDictionary:_objectSchemaByName copyItems:YES];
return schema;
}
- (instancetype)shallowCopy {
RLMSchema *schema = [[RLMSchema alloc] init];
schema->_objectSchemaByName = [[NSMutableDictionary alloc] initWithCapacity:_objectSchemaByName.count];
[_objectSchemaByName enumerateKeysAndObjectsUsingBlock:^(NSString *name, RLMObjectSchema *objectSchema, BOOL *) {
schema->_objectSchemaByName[name] = [objectSchema shallowCopy];
}];
return schema;
}
- (BOOL)isEqualToSchema:(RLMSchema *)schema {
if (_objectSchemaByName.count != schema->_objectSchemaByName.count) {
return NO;
}
__block BOOL matches = YES;
[_objectSchemaByName enumerateKeysAndObjectsUsingBlock:^(NSString *name, RLMObjectSchema *objectSchema, BOOL *stop) {
if (![schema->_objectSchemaByName[name] isEqualToObjectSchema:objectSchema]) {
*stop = YES;
matches = NO;
}
}];
return matches;
}
- (NSString *)description {
NSMutableString *objectSchemaString = [NSMutableString string];
NSArray *sort = @[[NSSortDescriptor sortDescriptorWithKey:@"className" ascending:YES]];
for (RLMObjectSchema *objectSchema in [self.objectSchema sortedArrayUsingDescriptors:sort]) {
[objectSchemaString appendFormat:@"\t%@\n",
[objectSchema.description stringByReplacingOccurrencesOfString:@"\n" withString:@"\n\t"]];
}
return [NSString stringWithFormat:@"Schema {\n%@}", objectSchemaString];
}
- (std::unique_ptr<Schema>)objectStoreCopy {
std::vector<realm::ObjectSchema> schema;
schema.reserve(_objectSchemaByName.count);
[_objectSchemaByName enumerateKeysAndObjectsUsingBlock:[&](NSString *, RLMObjectSchema *objectSchema, BOOL *) {
schema.push_back(objectSchema.objectStoreCopy);
}];
return std::make_unique<realm::Schema>(std::move(schema));
}
@end

View File

@@ -0,0 +1,31 @@
////////////////////////////////////////////////////////////////////////////
//
// Copyright 2014 Realm Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////
#import "RLMSwiftSupport.h"
@implementation RLMSwiftSupport
+ (BOOL)isSwiftClassName:(NSString *)className {
return [className rangeOfString:@"."].location != NSNotFound;
}
+ (NSString *)demangleClassName:(NSString *)className {
return [className substringFromIndex:[className rangeOfString:@"."].location + 1];
}
@end

View File

@@ -0,0 +1,48 @@
////////////////////////////////////////////////////////////////////////////
//
// Copyright 2014 Realm Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////
#import "RLMUpdateChecker.hpp"
#import "RLMRealm.h"
#import "RLMUtil.hpp"
#if TARGET_IPHONE_SIMULATOR && !defined(REALM_COCOA_VERSION)
#import "RLMVersion.h"
#endif
void RLMCheckForUpdates() {
#if TARGET_IPHONE_SIMULATOR
if (getenv("REALM_DISABLE_UPDATE_CHECKER") || RLMIsRunningInPlayground()) {
return;
}
auto handler = ^(NSData *data, NSURLResponse *response, NSError *error) {
if (error || ((NSHTTPURLResponse *)response).statusCode != 200) {
return;
}
NSString *latestVersion = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
if (![REALM_COCOA_VERSION isEqualToString:latestVersion]) {
NSLog(@"Version %@ of Realm is now available: https://github.com/realm/realm-cocoa/blob/v%@/CHANGELOG.md", latestVersion, latestVersion);
}
};
NSString *url = [NSString stringWithFormat:@"https://static.realm.io/update/cocoa?%@", REALM_COCOA_VERSION];
[[NSURLSession.sharedSession dataTaskWithURL:[NSURL URLWithString:url] completionHandler:handler] resume];
#endif
}

374
Example/Pods/Realm/Realm/RLMUtil.mm generated Normal file
View File

@@ -0,0 +1,374 @@
////////////////////////////////////////////////////////////////////////////
//
// Copyright 2014 Realm Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////
#import "RLMUtil.hpp"
#import "RLMArray_Private.hpp"
#import "RLMListBase.h"
#import "RLMObjectSchema_Private.hpp"
#import "RLMObjectStore.h"
#import "RLMObject_Private.hpp"
#import "RLMProperty_Private.h"
#import "RLMSchema_Private.h"
#import "RLMSwiftSupport.h"
#import <realm/mixed.hpp>
#import <realm/table_view.hpp>
#include <sys/sysctl.h>
#include <sys/types.h>
#if !defined(REALM_COCOA_VERSION)
#import "RLMVersion.h"
#endif
static inline bool nsnumber_is_like_integer(__unsafe_unretained NSNumber *const obj)
{
char data_type = [obj objCType][0];
return data_type == *@encode(bool) ||
data_type == *@encode(char) ||
data_type == *@encode(short) ||
data_type == *@encode(int) ||
data_type == *@encode(long) ||
data_type == *@encode(long long) ||
data_type == *@encode(unsigned short) ||
data_type == *@encode(unsigned int) ||
data_type == *@encode(unsigned long) ||
data_type == *@encode(unsigned long long);
}
static inline bool nsnumber_is_like_bool(__unsafe_unretained NSNumber *const obj)
{
// @encode(BOOL) is 'B' on iOS 64 and 'c'
// objcType is always 'c'. Therefore compare to "c".
if ([obj objCType][0] == 'c') {
return true;
}
if (nsnumber_is_like_integer(obj)) {
int value = [obj intValue];
return value == 0 || value == 1;
}
return false;
}
static inline bool nsnumber_is_like_float(__unsafe_unretained NSNumber *const obj)
{
char data_type = [obj objCType][0];
return data_type == *@encode(float) ||
data_type == *@encode(short) ||
data_type == *@encode(int) ||
data_type == *@encode(long) ||
data_type == *@encode(long long) ||
data_type == *@encode(unsigned short) ||
data_type == *@encode(unsigned int) ||
data_type == *@encode(unsigned long) ||
data_type == *@encode(unsigned long long) ||
// A double is like float if it fits within float bounds
(data_type == *@encode(double) && ABS([obj doubleValue]) <= FLT_MAX);
}
static inline bool nsnumber_is_like_double(__unsafe_unretained NSNumber *const obj)
{
char data_type = [obj objCType][0];
return data_type == *@encode(double) ||
data_type == *@encode(float) ||
data_type == *@encode(short) ||
data_type == *@encode(int) ||
data_type == *@encode(long) ||
data_type == *@encode(long long) ||
data_type == *@encode(unsigned short) ||
data_type == *@encode(unsigned int) ||
data_type == *@encode(unsigned long) ||
data_type == *@encode(unsigned long long);
}
static inline bool object_has_valid_type(__unsafe_unretained id const obj)
{
return ([obj isKindOfClass:[NSString class]] ||
[obj isKindOfClass:[NSNumber class]] ||
[obj isKindOfClass:[NSDate class]] ||
[obj isKindOfClass:[NSData class]]);
}
BOOL RLMIsObjectValidForProperty(__unsafe_unretained id const obj,
__unsafe_unretained RLMProperty *const property) {
if (property.optional && !RLMCoerceToNil(obj)) {
return YES;
}
switch (property.type) {
case RLMPropertyTypeString:
return [obj isKindOfClass:[NSString class]];
case RLMPropertyTypeBool:
if ([obj isKindOfClass:[NSNumber class]]) {
return nsnumber_is_like_bool(obj);
}
return NO;
case RLMPropertyTypeDate:
return [obj isKindOfClass:[NSDate class]];
case RLMPropertyTypeInt:
if (NSNumber *number = RLMDynamicCast<NSNumber>(obj)) {
return nsnumber_is_like_integer(number);
}
return NO;
case RLMPropertyTypeFloat:
if (NSNumber *number = RLMDynamicCast<NSNumber>(obj)) {
return nsnumber_is_like_float(number);
}
return NO;
case RLMPropertyTypeDouble:
if (NSNumber *number = RLMDynamicCast<NSNumber>(obj)) {
return nsnumber_is_like_double(number);
}
return NO;
case RLMPropertyTypeData:
return [obj isKindOfClass:[NSData class]];
case RLMPropertyTypeAny:
return object_has_valid_type(obj);
case RLMPropertyTypeObject: {
// only NSNull, nil, or objects which derive from RLMObject and match the given
// object class are valid
RLMObjectBase *objBase = RLMDynamicCast<RLMObjectBase>(obj);
return objBase && [objBase->_objectSchema.className isEqualToString:property.objectClassName];
}
case RLMPropertyTypeArray: {
if (RLMArray *array = RLMDynamicCast<RLMArray>(obj)) {
return [array.objectClassName isEqualToString:property.objectClassName];
}
if (RLMListBase *list = RLMDynamicCast<RLMListBase>(obj)) {
return [list._rlmArray.objectClassName isEqualToString:property.objectClassName];
}
if ([obj conformsToProtocol:@protocol(NSFastEnumeration)]) {
// check each element for compliance
for (id el in (id<NSFastEnumeration>)obj) {
RLMObjectBase *obj = RLMDynamicCast<RLMObjectBase>(el);
if (!obj || ![obj->_objectSchema.className isEqualToString:property.objectClassName]) {
return NO;
}
}
return YES;
}
if (!obj || obj == NSNull.null) {
return YES;
}
return NO;
}
}
@throw RLMException(@"Invalid RLMPropertyType specified");
}
NSDictionary *RLMDefaultValuesForObjectSchema(__unsafe_unretained RLMObjectSchema *const objectSchema) {
if (!objectSchema.isSwiftClass) {
return [objectSchema.objectClass defaultPropertyValues];
}
NSMutableDictionary *defaults = nil;
if ([objectSchema.objectClass isSubclassOfClass:RLMObject.class]) {
defaults = [NSMutableDictionary dictionaryWithDictionary:[objectSchema.objectClass defaultPropertyValues]];
}
else {
defaults = [NSMutableDictionary dictionary];
}
RLMObject *defaultObject = [[objectSchema.objectClass alloc] init];
for (RLMProperty *prop in objectSchema.properties) {
if (!defaults[prop.name] && defaultObject[prop.name]) {
defaults[prop.name] = defaultObject[prop.name];
}
}
return defaults;
}
NSArray *RLMCollectionValueForKey(id<RLMFastEnumerable> collection, NSString *key) {
size_t count = collection.count;
if (count == 0) {
return @[];
}
RLMRealm *realm = collection.realm;
RLMObjectSchema *objectSchema = collection.objectSchema;
NSMutableArray *results = [NSMutableArray arrayWithCapacity:count];
if ([key isEqualToString:@"self"]) {
for (size_t i = 0; i < count; i++) {
size_t rowIndex = [collection indexInSource:i];
[results addObject:RLMCreateObjectAccessor(realm, objectSchema, rowIndex) ?: NSNull.null];
}
return results;
}
RLMObjectBase *accessor = [[objectSchema.accessorClass alloc] initWithRealm:realm schema:objectSchema];
realm::Table *table = objectSchema.table;
for (size_t i = 0; i < count; i++) {
size_t rowIndex = [collection indexInSource:i];
accessor->_row = (*table)[rowIndex];
RLMInitializeSwiftAccessorGenerics(accessor);
[results addObject:[accessor valueForKey:key] ?: NSNull.null];
}
return results;
}
void RLMCollectionSetValueForKey(id<RLMFastEnumerable> collection, NSString *key, id value) {
realm::TableView tv = [collection tableView];
if (tv.size() == 0) {
return;
}
RLMRealm *realm = collection.realm;
RLMObjectSchema *objectSchema = collection.objectSchema;
RLMObjectBase *accessor = [[objectSchema.accessorClass alloc] initWithRealm:realm schema:objectSchema];
for (size_t i = 0; i < tv.size(); i++) {
accessor->_row = tv[i];
RLMInitializeSwiftAccessorGenerics(accessor);
[accessor setValue:value forKey:key];
}
}
static NSException *RLMException(NSString *reason, NSDictionary *additionalUserInfo) {
NSMutableDictionary *userInfo = @{RLMRealmVersionKey: REALM_COCOA_VERSION,
RLMRealmCoreVersionKey: @REALM_VERSION}.mutableCopy;
if (additionalUserInfo != nil) {
[userInfo addEntriesFromDictionary:additionalUserInfo];
}
NSException *e = [NSException exceptionWithName:RLMExceptionName
reason:reason
userInfo:userInfo];
return e;
}
NSException *RLMException(NSString *fmt, ...) {
va_list args;
va_start(args, fmt);
NSException *e = RLMException([[NSString alloc] initWithFormat:fmt arguments:args], @{});
va_end(args);
return e;
}
NSException *RLMException(std::exception const& exception) {
return RLMException(@"%@", @(exception.what()));
}
NSError *RLMMakeError(RLMError code, std::exception const& exception) {
return [NSError errorWithDomain:RLMErrorDomain
code:code
userInfo:@{NSLocalizedDescriptionKey: @(exception.what()),
@"Error Code": @(code)}];
}
NSError *RLMMakeError(RLMError code, const realm::util::File::AccessError& exception) {
return [NSError errorWithDomain:RLMErrorDomain
code:code
userInfo:@{NSLocalizedDescriptionKey: @(exception.what()),
NSFilePathErrorKey: @(exception.get_path().c_str()),
@"Error Code": @(code)}];
}
NSError *RLMMakeError(RLMError code, const realm::RealmFileException& exception) {
return [NSError errorWithDomain:RLMErrorDomain
code:code
userInfo:@{NSLocalizedDescriptionKey: @(exception.what()),
NSFilePathErrorKey: @(exception.path().c_str()),
@"Error Code": @(code)}];
}
NSError *RLMMakeError(std::system_error const& exception) {
return [NSError errorWithDomain:RLMErrorDomain
code:exception.code().value()
userInfo:@{NSLocalizedDescriptionKey: @(exception.what()),
@"Error Code": @(exception.code().value())}];
}
NSError *RLMMakeError(NSException *exception) {
return [NSError errorWithDomain:RLMErrorDomain
code:0
userInfo:@{NSLocalizedDescriptionKey: exception.reason}];
}
void RLMSetErrorOrThrow(NSError *error, NSError **outError) {
if (outError) {
*outError = error;
}
else {
NSString *msg = error.localizedDescription;
if (error.userInfo[NSFilePathErrorKey]) {
msg = [NSString stringWithFormat:@"%@: %@", error.userInfo[NSFilePathErrorKey], error.localizedDescription];
}
@throw RLMException(msg, @{NSUnderlyingErrorKey: error});
}
}
// Determines if class1 descends from class2
static inline BOOL RLMIsSubclass(Class class1, Class class2) {
class1 = class_getSuperclass(class1);
return RLMIsKindOfClass(class1, class2);
}
BOOL RLMIsObjectSubclass(Class klass) {
return RLMIsSubclass(class_getSuperclass(klass), RLMObjectBase.class);
}
BOOL RLMIsDebuggerAttached()
{
int name[] = {
CTL_KERN,
KERN_PROC,
KERN_PROC_PID,
getpid()
};
struct kinfo_proc info;
size_t info_size = sizeof(info);
if (sysctl(name, sizeof(name)/sizeof(name[0]), &info, &info_size, NULL, 0) == -1) {
NSLog(@"sysctl() failed: %s", strerror(errno));
return false;
}
return (info.kp_proc.p_flag & P_TRACED) != 0;
}
BOOL RLMIsRunningInPlayground() {
return [[NSBundle mainBundle].bundleIdentifier hasPrefix:@"com.apple.dt.playground."];
}
id RLMMixedToObjc(realm::Mixed const& mixed) {
switch (mixed.get_type()) {
case realm::type_String:
return RLMStringDataToNSString(mixed.get_string());
case realm::type_Int: {
return @(mixed.get_int());
case realm::type_Float:
return @(mixed.get_float());
case realm::type_Double:
return @(mixed.get_double());
case realm::type_Bool:
return @(mixed.get_bool());
case realm::type_DateTime:
return RLMDateTimeToNSDate(mixed.get_datetime());
case realm::type_Binary: {
return RLMBinaryDataToNSData(mixed.get_binary());
}
case realm::type_Link:
case realm::type_LinkList:
default:
@throw RLMException(@"Invalid data type for RLMPropertyTypeAny property.");
}
}
}

View File

@@ -0,0 +1,27 @@
framework module Realm {
umbrella header "Realm.h"
export *
module * { export * }
explicit module Private {
header "RLMAccessor.h"
header "RLMArray_Private.h"
header "RLMListBase.h"
header "RLMMigration_Private.h"
header "RLMObjectSchema_Private.h"
header "RLMObjectStore.h"
header "RLMObject_Private.h"
header "RLMOptionalBase.h"
header "RLMProperty_Private.h"
header "RLMRealmConfiguration_Private.h"
header "RLMRealm_Private.h"
header "RLMResults_Private.h"
header "RLMSchema_Private.h"
}
explicit module Dynamic {
header "RLMRealm_Dynamic.h"
header "RLMObjectBase_Dynamic.h"
}
}

1300
Example/Pods/Realm/build.sh generated Executable file

File diff suppressed because it is too large Load Diff

BIN
Example/Pods/Realm/core/librealm-ios.a generated Normal file

Binary file not shown.

View File

@@ -0,0 +1,55 @@
////////////////////////////////////////////////////////////////////////////
//
// Copyright 2015 Realm Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////
// Asynchronously submits build information to Realm if running in an iOS
// simulator or on OS X if a debugger is attached. Does nothing if running on an
// iOS / watchOS device or if a debugger is *not* attached.
//
// To be clear: this does *not* run when your app is in production or on
// your end-users devices; it will only run in the simulator or when a debugger
// is attached.
//
// Why are we doing this? In short, because it helps us build a better product
// for you. None of the data personally identifies you, your employer or your
// app, but it *will* help us understand what language you use, what iOS
// versions you target, etc. Having this info will help prioritizing our time,
// adding new features and deprecating old features. Collecting an anonymized
// bundle & anonymized MAC is the only way for us to count actual usage of the
// other metrics accurately. If we dont have a way to deduplicate the info
// reported, it will be useless, as a single developer building their Swift app
// 10 times would report 10 times more than a single Objective-C developer that
// only builds once, making the data all but useless.
// No one likes sharing data unless its necessary, we get it, and weve
// debated adding this for a long long time. Since Realm is a free product
// without an email signup, we feel this is a necessary step so we can collect
// relevant data to build a better product for you. If you truly, absolutely
// feel compelled to not send this data back to Realm, then you can set an env
// variable named REALM_DISABLE_ANALYTICS. Since Realm is free we believe
// letting these analytics run is a small price to pay for the product & support
// we give you.
//
// Currently the following information is reported:
// - What version of Realm is being used, and from which language (obj-c or Swift).
// - What version of OS X it's running on (in case Xcode aggressively drops
// support for older versions again, we need to know what we need to support).
// - The minimum iOS/OS X version that the application is targeting (again, to
// help us decide what versions we need to support).
// - An anonymous MAC address and bundle ID to aggregate the other information on.
// - What version of Swift is being used (if applicable).
void RLMSendAnalytics();

View File

@@ -0,0 +1,105 @@
////////////////////////////////////////////////////////////////////////////
//
// Copyright 2014 Realm Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////
#import "RLMArray_Private.h"
#import <Realm/RLMResults.h>
#import <memory>
#import <vector>
namespace realm {
class LinkView;
class Results;
class TableView;
struct SortOrder;
namespace util {
template<typename T> class bind_ptr;
}
typedef util::bind_ptr<LinkView> LinkViewRef;
}
@class RLMObjectBase;
@class RLMObjectSchema;
class RLMObservationInfo;
@protocol RLMFastEnumerable
@property (nonatomic, readonly) RLMRealm *realm;
@property (nonatomic, readonly) RLMObjectSchema *objectSchema;
@property (nonatomic, readonly) NSUInteger count;
- (NSUInteger)indexInSource:(NSUInteger)index;
- (realm::TableView)tableView;
@end
@interface RLMArray () {
@protected
NSString *_objectClassName;
@public
// The name of the property which this RLMArray represents
NSString *_key;
__weak RLMObjectBase *_parentObject;
}
@end
//
// LinkView backed RLMArray subclass
//
@interface RLMArrayLinkView : RLMArray <RLMFastEnumerable>
@property (nonatomic, unsafe_unretained) RLMObjectSchema *objectSchema;
+ (RLMArrayLinkView *)arrayWithObjectClassName:(NSString *)objectClassName
view:(realm::LinkViewRef)view
realm:(RLMRealm *)realm
key:(NSString *)key
parentSchema:(RLMObjectSchema *)parentSchema;
// deletes all objects in the RLMArray from their containing realms
- (void)deleteObjectsFromRealm;
@end
void RLMValidateArrayObservationKey(NSString *keyPath, RLMArray *array);
// Initialize the observation info for an array if needed
void RLMEnsureArrayObservationInfo(std::unique_ptr<RLMObservationInfo>& info, NSString *keyPath, RLMArray *array, id observed);
//
// RLMResults private methods
//
@interface RLMResults () <RLMFastEnumerable>
+ (instancetype)resultsWithObjectSchema:(RLMObjectSchema *)objectSchema
results:(realm::Results)results;
- (void)deleteObjectsFromRealm;
@end
// An object which encapulates the shared logic for fast-enumerating RLMArray
// and RLMResults, and has a buffer to store strong references to the current
// set of enumerated items
@interface RLMFastEnumerator : NSObject
- (instancetype)initWithCollection:(id<RLMFastEnumerable>)collection objectSchema:(RLMObjectSchema *)objectSchema;
// Detach this enumerator from the source collection. Must be called before the
// source collection is changed.
- (void)detach;
- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state
count:(NSUInteger)len;
@end

View File

@@ -0,0 +1,49 @@
////////////////////////////////////////////////////////////////////////////
//
// Copyright 2014 Realm Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////
#import "RLMObjectSchema_Private.h"
#import "object_schema.hpp"
#import "RLMObject_Private.hpp"
#import <realm/row.hpp>
#import <vector>
namespace realm {
class Table;
}
class RLMObservationInfo;
// RLMObjectSchema private
@interface RLMObjectSchema () {
@public
std::vector<RLMObservationInfo *> _observedObjects;
}
@property (nonatomic) realm::Table *table;
// shallow copy reusing properties and property map
- (instancetype)shallowCopy;
// create realm::ObjectSchema copy
- (realm::ObjectSchema)objectStoreCopy;
// initialize with realm::ObjectSchema
+ (instancetype)objectSchemaForObjectStoreSchema:(realm::ObjectSchema &)objectSchema;
@end

View File

@@ -0,0 +1,52 @@
////////////////////////////////////////////////////////////////////////////
//
// Copyright 2014 Realm Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////
#import "RLMObject_Private.h"
#import "RLMRealm_Private.hpp"
#import <realm/link_view.hpp> // required by row.hpp
#import <realm/row.hpp>
class RLMObservationInfo;
// RLMObject accessor and read/write realm
@interface RLMObjectBase () {
@public
realm::Row _row;
RLMObservationInfo *_observationInfo;
}
@end
// throw an exception if the object is invalidated or on the wrong thread
static inline void RLMVerifyAttached(__unsafe_unretained RLMObjectBase *const obj) {
if (!obj->_row.is_attached()) {
@throw RLMException(@"Object has been deleted or invalidated.");
}
[obj->_realm verifyThread];
}
// throw an exception if the object can't be modified for any reason
static inline void RLMVerifyInWriteTransaction(__unsafe_unretained RLMObjectBase *const obj) {
// first verify is attached
RLMVerifyAttached(obj);
if (!obj->_realm.inWriteTransaction) {
@throw RLMException(@"Attempting to modify object outside of a write transaction - call beginWriteTransaction on an RLMRealm instance first.");
}
}

View File

@@ -0,0 +1,144 @@
////////////////////////////////////////////////////////////////////////////
//
// Copyright 2015 Realm Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////
#import <Foundation/Foundation.h>
#import "binding_context.hpp"
#import <Realm/RLMDefines.h>
#import <realm/link_view.hpp> // required by row.hpp
#import <realm/row.hpp>
@class RLMObjectSchema, RLMObjectBase, RLMRealm, RLMSchema, RLMProperty;
namespace realm {
class History;
class SharedGroup;
}
// RLMObservationInfo stores all of the KVO-related data for RLMObjectBase and
// RLMArray. There is a one-to-one relationship between observed objects and
// RLMObservationInfo instances, so it could be folded into RLMObjectBase, and
// is a separate class mostly to avoid making all accessor objects far larger.
//
// RLMObjectSchema stores a vector of pointers to the first observation info
// created for each row. If there are multiple observation infos for a single
// row (such as if there are multiple observed objects backed by a single row,
// or if both an object and an array property of that object are observed),
// they're stored in an intrusive doubly-linked-list in the `next` and `prev`
// members. This is done primarily to make it simpler and faster to loop over
// all of the observed objects for a single row, as that needs to be done for
// every change.
class RLMObservationInfo {
public:
RLMObservationInfo(id object);
RLMObservationInfo(RLMObjectSchema *objectSchema, std::size_t row, id object);
~RLMObservationInfo();
realm::Row const& getRow() const {
return row;
}
RLMObjectSchema *getObjectSchema() const {
return objectSchema;
}
// Send willChange/didChange notifications to all observers for this object/row
// Sends the array versions if indexes is non-nil, normal versions otherwise
void willChange(NSString *key, NSKeyValueChange kind=NSKeyValueChangeSetting, NSIndexSet *indexes=nil) const;
void didChange(NSString *key, NSKeyValueChange kind=NSKeyValueChangeSetting, NSIndexSet *indexes=nil) const;
bool isForRow(size_t ndx) const {
return row && row.get_index() == ndx;
}
void recordObserver(realm::Row& row, RLMObjectSchema *objectSchema, NSString *keyPath);
void removeObserver();
bool hasObservers() const { return observerCount > 0; }
// valueForKey: on observed object and array properties needs to return the
// same object each time for KVO to work at all. Doing this all the time
// requires some odd semantics to avoid reference cycles, so instead we do
// it only to the extent specifically required by KVO. In addition, we
// need to continue to return the same object even if this row is deleted,
// or deleting an object with active observers will explode horribly.
// Once prepareForInvalidation() is called, valueForKey() will always return
// the cached value for object and array properties without checking the
// backing row to verify it's up-to-date.
//
// prepareForInvalidation() must be called on the head of the linked list
// (i.e. on the object pointed to directly by the object schema)
id valueForKey(NSString *key);
void prepareForInvalidation();
private:
// Doubly-linked-list of observed objects for the same row as this
RLMObservationInfo *next = nullptr;
RLMObservationInfo *prev = nullptr;
// Row being observed
realm::Row row;
RLMObjectSchema *objectSchema;
// Object doing the observing
__unsafe_unretained id object;
// valueForKey: hack
bool invalidated = false;
size_t observerCount = 0;
NSString *lastKey = nil;
__unsafe_unretained RLMProperty *lastProp = nil;
// objects returned from valueForKey() to keep them alive in case observers
// are added and so that they can still be accessed after row is detached
NSMutableDictionary *cachedObjects;
void setRow(realm::Table &table, size_t newRow);
template<typename F>
void forEach(F&& f) const {
for (auto info = prev; info; info = info->prev)
f(info->object);
for (auto info = this; info; info = info->next)
f(info->object);
}
// Default move/copy constructors don't work due to the intrusive linked
// list and we don't need them
RLMObservationInfo(RLMObservationInfo const&) = delete;
RLMObservationInfo(RLMObservationInfo&&) = delete;
RLMObservationInfo& operator=(RLMObservationInfo const&) = delete;
RLMObservationInfo& operator=(RLMObservationInfo&&) = delete;
};
// Get the the observation info chain for the given row
// Will simply return info if it's non-null, and will search ojectSchema's array
// for a matching one otherwise, and return null if there are none
RLMObservationInfo *RLMGetObservationInfo(RLMObservationInfo *info, size_t row, RLMObjectSchema *objectSchema);
// delete all objects from a single table with change notifications
void RLMClearTable(RLMObjectSchema *realm);
// invoke the block, sending notifications for cascading deletes/link nullifications
void RLMTrackDeletions(RLMRealm *realm, dispatch_block_t block);
std::vector<realm::BindingContext::ObserverState> RLMGetObservedRows(NSArray RLM_GENERIC(RLMObjectSchema *) *schema);
void RLMWillChange(std::vector<realm::BindingContext::ObserverState> const& observed, std::vector<void *> const& invalidated);
void RLMDidChange(std::vector<realm::BindingContext::ObserverState> const& observed, std::vector<void *> const& invalidated);

View File

@@ -0,0 +1,21 @@
////////////////////////////////////////////////////////////////////////////
//
// Copyright 2015 Realm Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#import <Foundation/Foundation.h>
using ExpressionVisitor = NSExpression *(*)(NSExpression *);
NSPredicate *transformPredicate(NSPredicate *, ExpressionVisitor);

View File

@@ -0,0 +1,44 @@
////////////////////////////////////////////////////////////////////////////
//
// Copyright 2014 Realm Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////
#import <Foundation/Foundation.h>
#import <vector>
namespace realm {
class Query;
struct SortOrder;
class Table;
class TableView;
}
@class RLMObjectSchema;
@class RLMProperty;
@class RLMSchema;
extern NSString * const RLMPropertiesComparisonTypeMismatchException;
extern NSString * const RLMUnsupportedTypesFoundInPropertyComparisonException;
// apply the given predicate to the passed in query, returning the updated query
void RLMUpdateQueryWithPredicate(realm::Query *query, NSPredicate *predicate, RLMSchema *schema,
RLMObjectSchema *objectSchema);
// return property - throw for invalid column name
RLMProperty *RLMValidatedProperty(RLMObjectSchema *objectSchema, NSString *columnName);
// validate the array of RLMSortDescriptors and convert it to a realm::SortOrder
realm::SortOrder RLMSortOrderFromDescriptors(RLMObjectSchema *objectSchema, NSArray *descriptors);

View File

@@ -0,0 +1,42 @@
////////////////////////////////////////////////////////////////////////////
//
// Copyright 2014 Realm Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////
#import <Foundation/Foundation.h>
#import <memory>
#import <string>
@class RLMRealm;
namespace realm {
class BindingContext;
}
// Add a Realm to the weak cache
void RLMCacheRealm(std::string const& path, RLMRealm *realm);
// Get a Realm for the given path which can be used on the current thread
RLMRealm *RLMGetThreadLocalCachedRealmForPath(std::string const& path);
// Get a Realm for the given path
RLMRealm *RLMGetAnyCachedRealmForPath(std::string const& path);
// Clear the weak cache of Realms
void RLMClearRealmCache();
// Install an uncaught exception handler that cancels write transactions
// for all cached realms on the current thread
void RLMInstallUncaughtExceptionHandler();
std::unique_ptr<realm::BindingContext> RLMCreateBindingContext(RLMRealm *realm);

View File

@@ -0,0 +1,38 @@
////////////////////////////////////////////////////////////////////////////
//
// Copyright 2014 Realm Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////
#import "RLMRealm_Private.h"
#import "RLMUtil.hpp"
#import "shared_realm.hpp"
#import <realm/group.hpp>
namespace realm {
class Group;
class Realm;
typedef std::shared_ptr<realm::Realm> SharedRealm;
}
@interface RLMRealm () {
@public
realm::SharedRealm _realm;
}
// FIXME - group should not be exposed
@property (nonatomic, readonly) realm::Group *group;
@end

View File

@@ -0,0 +1,31 @@
////////////////////////////////////////////////////////////////////////////
//
// Copyright 2015 Realm Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////
#import "RLMSchema_Private.h"
#import <memory>
namespace realm {
class Schema;
class ObjectSchema;
}
@interface RLMSchema ()
+ (instancetype)dynamicSchemaFromObjectStoreSchema:(realm::Schema &)objectStoreSchema;
- (std::unique_ptr<realm::Schema>)objectStoreCopy;
@end

View File

@@ -0,0 +1,20 @@
////////////////////////////////////////////////////////////////////////////
//
// Copyright 2014 Realm Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////
// Asynchronously check for updates to Realm if running on a simulator
void RLMCheckForUpdates();

174
Example/Pods/Realm/include/RLMUtil.hpp generated Normal file
View File

@@ -0,0 +1,174 @@
////////////////////////////////////////////////////////////////////////////
//
// Copyright 2014 Realm Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////
#import <Realm/RLMConstants.h>
#import <Realm/RLMOptionalBase.h>
#import <objc/runtime.h>
#import <realm/array.hpp>
#import <realm/binary_data.hpp>
#import <realm/datetime.hpp>
#import <realm/string_data.hpp>
#import <realm/util/file.hpp>
namespace realm {
class Mixed;
}
@class RLMObjectSchema;
@class RLMProperty;
@protocol RLMFastEnumerable;
namespace realm {
class RealmFileException;
}
__attribute__((format(NSString, 1, 2)))
NSException *RLMException(NSString *fmt, ...);
NSException *RLMException(std::exception const& exception);
NSError *RLMMakeError(RLMError code, std::exception const& exception);
NSError *RLMMakeError(RLMError code, const realm::util::File::AccessError&);
NSError *RLMMakeError(RLMError code, const realm::RealmFileException&);
NSError *RLMMakeError(std::system_error const& exception);
NSError *RLMMakeError(NSException *exception);
void RLMSetErrorOrThrow(NSError *error, NSError **outError);
// returns if the object can be inserted as the given type
BOOL RLMIsObjectValidForProperty(id obj, RLMProperty *prop);
// gets default values for the given schema (+defaultPropertyValues)
// merges with native property defaults if Swift class
NSDictionary *RLMDefaultValuesForObjectSchema(RLMObjectSchema *objectSchema);
NSArray *RLMCollectionValueForKey(id<RLMFastEnumerable> collection, NSString *key);
void RLMCollectionSetValueForKey(id<RLMFastEnumerable> collection, NSString *key, id value);
BOOL RLMIsDebuggerAttached();
BOOL RLMIsRunningInPlayground();
// C version of isKindOfClass
static inline BOOL RLMIsKindOfClass(Class class1, Class class2) {
while (class1) {
if (class1 == class2) return YES;
class1 = class_getSuperclass(class1);
}
return NO;
}
// Returns whether the class is an indirect descendant of RLMObjectBase
BOOL RLMIsObjectSubclass(Class klass);
template<typename T>
static inline T *RLMDynamicCast(__unsafe_unretained id obj) {
if ([obj isKindOfClass:[T class]]) {
return obj;
}
return nil;
}
template<typename T>
static inline T RLMCoerceToNil(__unsafe_unretained T obj) {
if (static_cast<id>(obj) == NSNull.null) {
return nil;
}
else if (__unsafe_unretained auto optional = RLMDynamicCast<RLMOptionalBase>(obj)) {
return RLMCoerceToNil(optional.underlyingValue);
}
return obj;
}
// Translate an rlmtype to a string representation
static inline NSString *RLMTypeToString(RLMPropertyType type) {
switch (type) {
case RLMPropertyTypeString:
return @"string";
case RLMPropertyTypeInt:
return @"int";
case RLMPropertyTypeBool:
return @"bool";
case RLMPropertyTypeDate:
return @"date";
case RLMPropertyTypeData:
return @"data";
case RLMPropertyTypeDouble:
return @"double";
case RLMPropertyTypeFloat:
return @"float";
case RLMPropertyTypeAny:
return @"any";
case RLMPropertyTypeObject:
return @"object";
case RLMPropertyTypeArray:
return @"array";
}
return @"Unknown";
}
// String conversion utilities
static inline NSString * RLMStringDataToNSString(realm::StringData stringData) {
static_assert(sizeof(NSUInteger) >= sizeof(size_t),
"Need runtime overflow check for size_t to NSUInteger conversion");
if (stringData.is_null()) {
return nil;
}
else {
return [[NSString alloc] initWithBytes:stringData.data()
length:stringData.size()
encoding:NSUTF8StringEncoding];
}
}
static inline realm::StringData RLMStringDataWithNSString(__unsafe_unretained NSString *const string) {
static_assert(sizeof(size_t) >= sizeof(NSUInteger),
"Need runtime overflow check for NSUInteger to size_t conversion");
return realm::StringData(string.UTF8String,
[string lengthOfBytesUsingEncoding:NSUTF8StringEncoding]);
}
// Binary convertion utilities
static inline NSData *RLMBinaryDataToNSData(realm::BinaryData binaryData) {
return binaryData ? [NSData dataWithBytes:binaryData.data() length:binaryData.size()] : nil;
}
static inline realm::BinaryData RLMBinaryDataForNSData(__unsafe_unretained NSData *const data) {
// this is necessary to ensure that the empty NSData isn't treated by core as the null realm::BinaryData
// because data.bytes == 0 when data.length == 0
// the casting bit ensures that we create a data with a non-null pointer
auto bytes = static_cast<const char *>(data.bytes) ?: static_cast<char *>((__bridge void *)data);
return realm::BinaryData(bytes, data.length);
}
// Date convertion utilities
static inline NSDate *RLMDateTimeToNSDate(realm::DateTime dateTime) {
auto timeInterval = static_cast<NSTimeInterval>(dateTime.get_datetime());
return [NSDate dateWithTimeIntervalSince1970:timeInterval];
}
static inline realm::DateTime RLMDateTimeForNSDate(__unsafe_unretained NSDate *const date) {
auto time = static_cast<int64_t>(date.timeIntervalSince1970);
return realm::DateTime(time);
}
static inline NSUInteger RLMConvertNotFound(size_t index) {
return index == realm::not_found ? NSNotFound : index;
}
id RLMMixedToObjc(realm::Mixed const& value);

View File

@@ -0,0 +1,64 @@
////////////////////////////////////////////////////////////////////////////
//
// Copyright 2014 Realm Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////
#import <Foundation/Foundation.h>
#import <Realm/RLMDefines.h>
@class RLMObjectSchema, RLMProperty, RLMObjectBase, RLMProperty;
#ifdef __cplusplus
typedef NSUInteger RLMCreationOptions;
#else
typedef NS_OPTIONS(NSUInteger, RLMCreationOptions);
#endif
RLM_ASSUME_NONNULL_BEGIN
//
// Accessors Class Creation/Caching
//
// get accessor classes for an object class - generates classes if not cached
Class RLMAccessorClassForObjectClass(Class objectClass, RLMObjectSchema *schema, NSString *prefix);
Class RLMStandaloneAccessorClassForObjectClass(Class objectClass, RLMObjectSchema *schema);
// Check if a given class is a generated accessor class
bool RLMIsGeneratedClass(Class cls);
//
// Dynamic getters/setters
//
FOUNDATION_EXTERN void RLMDynamicValidatedSet(RLMObjectBase *obj, NSString *propName, id __nullable val);
FOUNDATION_EXTERN RLMProperty *RLMValidatedGetProperty(RLMObjectBase *obj, NSString *propName);
FOUNDATION_EXTERN id __nullable RLMDynamicGet(RLMObjectBase *obj, RLMProperty *prop);
// by property/column
FOUNDATION_EXTERN void RLMDynamicSet(RLMObjectBase *obj, RLMProperty *prop, id val, RLMCreationOptions options);
//
// Class modification
//
// Replace className method for the given class
void RLMReplaceClassNameMethod(Class accessorClass, NSString *className);
// Replace sharedSchema method for the given class
void RLMReplaceSharedSchemaMethod(Class accessorClass, RLMObjectSchema * __nullable schema);
RLM_ASSUME_NONNULL_END

View File

@@ -0,0 +1,364 @@
////////////////////////////////////////////////////////////////////////////
//
// Copyright 2014 Realm Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////
#import <Foundation/Foundation.h>
#import <Realm/RLMCollection.h>
#import <Realm/RLMDefines.h>
RLM_ASSUME_NONNULL_BEGIN
@class RLMObject, RLMRealm, RLMResults RLM_GENERIC_COLLECTION, RLMNotificationToken;
/**
RLMArray is the container type in Realm used to define to-many relationships.
Unlike an NSArray, RLMArrays hold a single type, specified by the `objectClassName` property.
This is referred to in these docs as the “type” of the array.
When declaring an RLMArray property, the type must be marked as conforming to a
protocol by the same name as the objects it should contain (see the
`RLM_ARRAY_TYPE` macro). RLMArray properties can also use Objective-C generics
if available. For example:
RLM_ARRAY_TYPE(ObjectType)
...
@property RLMArray<ObjectType *><ObjectType> *arrayOfObjectTypes;
RLMArrays can be queried with the same predicates as RLMObject and RLMResults.
RLMArrays cannot be created directly. RLMArray properties on RLMObjects are
lazily created when accessed, or can be obtained by querying a Realm.
### Key-Value Observing
RLMArray supports array key-value observing on RLMArray properties on RLMObject
subclasses, and the `invalidated` property on RLMArray instances themselves is
key-value observing compliant when the RLMArray is attached to a persisted
RLMObject (RLMArrays on standalone RLMObjects will never become invalidated).
Because RLMArrays are attached to the object which they are a property of, they
do not require using the mutable collection proxy objects from
`-mutableArrayValueForKey:` or KVC-compatible mutation methods on the containing
object. Instead, you can call the mutation methods on the RLMArray directly.
*/
@interface RLMArray RLM_GENERIC_COLLECTION : NSObject<RLMCollection, NSFastEnumeration>
#pragma mark - Properties
/**
Number of objects in the array.
*/
@property (nonatomic, readonly, assign) NSUInteger count;
/**
The class name (i.e. type) of the RLMObjects contained in this RLMArray.
*/
@property (nonatomic, readonly, copy) NSString *objectClassName;
/**
The Realm in which this array is persisted. Returns nil for standalone arrays.
*/
@property (nonatomic, readonly, nullable) RLMRealm *realm;
/**
Indicates if an array can no longer be accessed.
*/
@property (nonatomic, readonly, getter = isInvalidated) BOOL invalidated;
#pragma mark - Accessing Objects from an Array
/**
Returns the object at the index specified.
@param index The index to look up.
@return An RLMObject of the type contained in this RLMArray.
*/
- (RLMObjectType)objectAtIndex:(NSUInteger)index;
/**
Returns the first object in the array.
Returns `nil` if called on an empty RLMArray.
@return An RLMObject of the type contained in this RLMArray.
*/
- (nullable RLMObjectType)firstObject;
/**
Returns the last object in the array.
Returns `nil` if called on an empty RLMArray.
@return An RLMObject of the type contained in this RLMArray.
*/
- (nullable RLMObjectType)lastObject;
#pragma mark - Adding, Removing, and Replacing Objects in an Array
/**
Adds an object to the end of the array.
@warning This method can only be called during a write transaction.
@param object An RLMObject of the type contained in this RLMArray.
*/
- (void)addObject:(RLMObjectArgument)object;
/**
Adds an array of objects at the end of the array.
@warning This method can only be called during a write transaction.
@param objects An enumerable object such as NSArray or RLMResults which contains objects of the
same class as this RLMArray.
*/
- (void)addObjects:(id<NSFastEnumeration>)objects;
/**
Inserts an object at the given index.
Throws an exception when the index exceeds the bounds of this RLMArray.
@warning This method can only be called during a write transaction.
@param anObject An RLMObject of the type contained in this RLMArray.
@param index The array index at which the object is inserted.
*/
- (void)insertObject:(RLMObjectArgument)anObject atIndex:(NSUInteger)index;
/**
Removes an object at a given index.
Throws an exception when the index exceeds the bounds of this RLMArray.
@warning This method can only be called during a write transaction.
@param index The array index identifying the object to be removed.
*/
- (void)removeObjectAtIndex:(NSUInteger)index;
/**
Removes the last object in an RLMArray.
@warning This method can only be called during a write transaction.
*/
- (void)removeLastObject;
/**
Removes all objects from an RLMArray.
@warning This method can only be called during a write transaction.
*/
- (void)removeAllObjects;
/**
Replaces an object at the given index with a new object.
Throws an exception when the index exceeds the bounds of this RLMArray.
@warning This method can only be called during a write transaction.
@param index The array index of the object to be replaced.
@param anObject An object (of the same type as returned from the objectClassName selector).
*/
- (void)replaceObjectAtIndex:(NSUInteger)index withObject:(RLMObjectArgument)anObject;
/**
Moves the object at the given source index to the given destination index.
Throws an exception when the index exceeds the bounds of this RLMArray.
@warning This method can only be called during a write transaction.
@param sourceIndex The index of the object to be moved.
@param destinationIndex The index to which the object at `sourceIndex` should be moved.
*/
- (void)moveObjectAtIndex:(NSUInteger)sourceIndex toIndex:(NSUInteger)destinationIndex;
/**
Exchanges the objects in the array at given indexes.
Throws an exception when either index exceeds the bounds of this RLMArray.
@warning This method can only be called during a write transaction.
@param index1 The index of the object with which to replace the object at index `index2`.
@param index2 The index of the object with which to replace the object at index `index1`.
*/
- (void)exchangeObjectAtIndex:(NSUInteger)index1 withObjectAtIndex:(NSUInteger)index2;
#pragma mark - Querying an Array
/**
Gets the index of an object.
Returns NSNotFound if the object is not found in this RLMArray.
@param object An object (of the same type as returned from the objectClassName selector).
*/
- (NSUInteger)indexOfObject:(RLMObjectArgument)object;
/**
Gets the index of the first object matching the predicate.
@param predicateFormat The predicate format string which can accept variable arguments.
@return Index of object or NSNotFound if the object is not found in this RLMArray.
*/
- (NSUInteger)indexOfObjectWhere:(NSString *)predicateFormat, ...;
/// :nodoc:
- (NSUInteger)indexOfObjectWhere:(NSString *)predicateFormat args:(va_list)args;
/**
Gets the index of the first object matching the predicate.
@param predicate The predicate to filter the objects.
@return Index of object or NSNotFound if the object is not found in this RLMArray.
*/
- (NSUInteger)indexOfObjectWithPredicate:(NSPredicate *)predicate;
/**
Get objects matching the given predicate in the RLMArray.
@param predicateFormat The predicate format string which can accept variable arguments.
@return An RLMResults of objects that match the given predicate
*/
- (RLMResults RLM_GENERIC_RETURN*)objectsWhere:(NSString *)predicateFormat, ...;
/// :nodoc:
- (RLMResults RLM_GENERIC_RETURN*)objectsWhere:(NSString *)predicateFormat args:(va_list)args;
/**
Get objects matching the given predicate in the RLMArray.
@param predicate The predicate to filter the objects.
@return An RLMResults of objects that match the given predicate
*/
- (RLMResults RLM_GENERIC_RETURN*)objectsWithPredicate:(NSPredicate *)predicate;
/**
Get a sorted RLMResults from an RLMArray
@param property The property name to sort by.
@param ascending The direction to sort by.
@return An RLMResults sorted by the specified property.
*/
- (RLMResults RLM_GENERIC_RETURN*)sortedResultsUsingProperty:(NSString *)property ascending:(BOOL)ascending;
/**
Get a sorted RLMResults from an RLMArray
@param properties An array of `RLMSortDescriptor`s to sort by.
@return An RLMResults sorted by the specified properties.
*/
- (RLMResults RLM_GENERIC_RETURN*)sortedResultsUsingDescriptors:(NSArray *)properties;
/// :nodoc:
- (RLMObjectType)objectAtIndexedSubscript:(NSUInteger)index;
/// :nodoc:
- (void)setObject:(RLMObjectType)newValue atIndexedSubscript:(NSUInteger)index;
#pragma mark - Notifications
/**
Register a block to be called each time the RLMArray changes.
The block will be asynchronously called with the initial array, and then
called again after each write transaction which changes the array or any
items contained in the array. You must retain the returned token for as long as
you want the block to continue to be called. To stop receiving updates, call
`-stop` on the token.
The error parameter will always be `nil`, and is present only for compatiblity
with the RLMResults version of this method, which can potentially fail.
@param block The block to be called each time the array changes.
@return A token which must be held for as long as you want notifications to be delivered.
*/
- (RLMNotificationToken *)addNotificationBlock:(void (^)(RLMArray RLM_GENERIC_RETURN *array, NSError *))block RLM_WARN_UNUSED_RESULT;
#pragma mark - Unavailable Methods
/**
-[RLMArray init] is not available because RLMArrays cannot be created directly.
RLMArray properties on RLMObjects are lazily created when accessed, or can be obtained by querying a Realm.
*/
- (instancetype)init __attribute__((unavailable("RLMArrays cannot be created directly")));
/**
+[RLMArray new] is not available because RLMArrays cannot be created directly.
RLMArray properties on RLMObjects are lazily created when accessed, or can be obtained by querying a Realm.
*/
+ (instancetype)new __attribute__((unavailable("RLMArrays cannot be created directly")));
@end
/**
An RLMSortDescriptor stores a property name and a sort order for use with
`sortedResultsUsingDescriptors:`. It is similar to NSSortDescriptor, but supports
only the subset of functionality which can be efficiently run by the query
engine. RLMSortDescriptor instances are immutable.
*/
@interface RLMSortDescriptor : NSObject
#pragma mark - Properties
/**
The name of the property which this sort descriptor orders results by.
*/
@property (nonatomic, readonly) NSString *property;
/**
Whether this descriptor sorts in ascending or descending order.
*/
@property (nonatomic, readonly) BOOL ascending;
#pragma mark - Methods
/**
Returns a new sort descriptor for the given property name and order.
*/
+ (instancetype)sortDescriptorWithProperty:(NSString *)propertyName ascending:(BOOL)ascending;
/**
Returns a copy of the receiver with the sort order reversed.
*/
- (instancetype)reversedSortDescriptor;
@end
/// :nodoc:
@interface RLMArray (Swift)
// for use only in Swift class definitions
- (instancetype)initWithObjectClassName:(NSString *)objectClassName;
@end
RLM_ASSUME_NONNULL_END

View File

@@ -0,0 +1,24 @@
////////////////////////////////////////////////////////////////////////////
//
// Copyright 2014 Realm Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////
#import <Realm/RLMArray.h>
@interface RLMArray ()
- (instancetype)initWithObjectClassName:(NSString *)objectClassName;
- (NSString *)descriptionWithMaxDepth:(NSUInteger)depth;
@end

View File

@@ -0,0 +1,184 @@
////////////////////////////////////////////////////////////////////////////
//
// Copyright 2014 Realm Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////
#import <Realm/RLMDefines.h>
RLM_ASSUME_NONNULL_BEGIN
@class RLMRealm, RLMResults, RLMObject, RLMSortDescriptor, RLMNotificationToken;
/**
A homogenous collection of `RLMObject`s like `RLMArray` or `RLMResults`.
*/
@protocol RLMCollection <NSFastEnumeration>
@required
#pragma mark - Properties
/**
Number of objects in the collection.
*/
@property (nonatomic, readonly, assign) NSUInteger count;
/**
The class name (i.e. type) of the RLMObjects contained in this RLMCollection.
*/
@property (nonatomic, readonly, copy) NSString *objectClassName;
/**
The Realm in which this collection is persisted. Returns nil for standalone collections.
*/
@property (nonatomic, readonly) RLMRealm *realm;
#pragma mark - Accessing Objects from a Collection
/**
Returns the object at the index specified.
@param index The index to look up.
@return An RLMObject of the type contained in this RLMCollection.
*/
- (id)objectAtIndex:(NSUInteger)index;
/**
Returns the first object in the collection.
Returns `nil` if called on an empty RLMCollection.
@return An RLMObject of the type contained in this RLMCollection.
*/
- (nullable id)firstObject;
/**
Returns the last object in the collection.
Returns `nil` if called on an empty RLMCollection.
@return An RLMObject of the type contained in this RLMCollection.
*/
- (nullable id)lastObject;
#pragma mark - Querying a Collection
/**
Gets the index of an object.
Returns NSNotFound if the object is not found in this RLMCollection.
@param object An object (of the same type as returned from the objectClassName selector).
*/
- (NSUInteger)indexOfObject:(RLMObject *)object;
/**
Gets the index of the first object matching the predicate.
@param predicateFormat The predicate format string which can accept variable arguments.
@return Index of object or NSNotFound if the object is not found in this RLMCollection.
*/
- (NSUInteger)indexOfObjectWhere:(NSString *)predicateFormat, ...;
/// :nodoc:
- (NSUInteger)indexOfObjectWhere:(NSString *)predicateFormat args:(va_list)args;
/**
Gets the index of the first object matching the predicate.
@param predicate The predicate to filter the objects.
@return Index of object or NSNotFound if the object is not found in this RLMCollection.
*/
- (NSUInteger)indexOfObjectWithPredicate:(NSPredicate *)predicate;
/**
Get objects matching the given predicate in the RLMCollection.
@param predicateFormat The predicate format string which can accept variable arguments.
@return An RLMResults of objects that match the given predicate
*/
- (RLMResults *)objectsWhere:(NSString *)predicateFormat, ...;
/// :nodoc:
- (RLMResults *)objectsWhere:(NSString *)predicateFormat args:(va_list)args;
/**
Get objects matching the given predicate in the RLMCollection.
@param predicate The predicate to filter the objects.
@return An RLMResults of objects that match the given predicate
*/
- (RLMResults *)objectsWithPredicate:(NSPredicate *)predicate;
/**
Get a sorted RLMResults from an RLMCollection.
@param property The property name to sort by.
@param ascending The direction to sort by.
@return An RLMResults sorted by the specified property.
*/
- (RLMResults *)sortedResultsUsingProperty:(NSString *)property ascending:(BOOL)ascending;
/**
Get a sorted RLMResults from an RLMCollection.
@param properties An array of `RLMSortDescriptor`s to sort by.
@return An RLMResults sorted by the specified properties.
*/
- (RLMResults *)sortedResultsUsingDescriptors:(NSArray RLM_GENERIC(RLMSortDescriptor *) *)properties;
/// :nodoc:
- (id)objectAtIndexedSubscript:(NSUInteger)index;
/**
Returns an NSArray containing the results of invoking `valueForKey:` using key on each of the collection's objects.
@param key The name of the property.
@return NSArray containing the results of invoking `valueForKey:` using key on each of the collection's objects.
*/
- (nullable id)valueForKey:(NSString *)key;
/**
Invokes `setValue:forKey:` on each of the collection's objects using the specified value and key.
@warning This method can only be called during a write transaction.
@param value The object value.
@param key The name of the property.
*/
- (void)setValue:(nullable id)value forKey:(NSString *)key;
#pragma mark - Notifications
/**
Register a block to be called each time the collection changes.
@param block The block to be called each time the collection changes.
@return A token which must be held for as long as you want notifications to be delivered.
*/
- (RLMNotificationToken *)addNotificationBlock:(void (^)(id<RLMCollection> collection))block RLM_WARN_UNUSED_RESULT;
@end
RLM_ASSUME_NONNULL_END

View File

@@ -0,0 +1,131 @@
////////////////////////////////////////////////////////////////////////////
//
// Copyright 2014 Realm Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////
#import <Foundation/Foundation.h>
#pragma mark - Enums
/**
Property types supported in Realm models.
See [Realm Models](https://realm.io/docs/objc/latest/#models)
*/
// Make sure numbers match those in <realm/data_type.hpp>
typedef NS_ENUM(int32_t, RLMPropertyType) {
#pragma mark - Primitive types
/** Integer type: NSInteger, int, long, Int (Swift) */
RLMPropertyTypeInt = 0,
/** Boolean type: BOOL, bool, Bool (Swift) */
RLMPropertyTypeBool = 1,
/** Float type: float, Float (Swift) */
RLMPropertyTypeFloat = 9,
/** Double type: double, Double (Swift) */
RLMPropertyTypeDouble = 10,
#pragma mark - Object types
/** String type: NSString, String (Swift) */
RLMPropertyTypeString = 2,
/** Data type: NSData */
RLMPropertyTypeData = 4,
/** Any type: id, **not supported in Swift** */
RLMPropertyTypeAny = 6,
/** Date type: NSDate */
RLMPropertyTypeDate = 7,
#pragma mark - Array/Linked object types
/** Object type. See [Realm Models](https://realm.io/docs/objc/latest/#models) */
RLMPropertyTypeObject = 12,
/** Array type. See [Realm Models](http://realms.io/docs/objc/latest/#models) */
RLMPropertyTypeArray = 13,
};
/**
Enum representing all recoverable errors in Realm.
*/
typedef NS_ENUM(NSInteger, RLMError) {
/** Returned by RLMRealm if no other specific error is returned when a realm is opened. */
RLMErrorFail = 1,
/** Returned by RLMRealm for any I/O related exception scenarios when a realm is opened. */
RLMErrorFileAccess = 2,
/** Returned by RLMRealm if the user does not have permission to open or create
the specified file in the specified access mode when the realm is opened. */
RLMErrorFilePermissionDenied = 3,
/** Returned by RLMRealm if the file already exists when a copy should be written. */
RLMErrorFileExists = 4,
/** Returned by RLMRealm if no file was found when a realm was opened as
read-only or if the directory part of the specified path was not
found when a copy should be written. */
RLMErrorFileNotFound = 5,
/** Returned by RLMRealm if a file format upgrade is required to open the file, but upgrades were explicilty disabled. */
RLMErrorFileFormatUpgradeRequired = 6,
/** Returned by RLMRealm if the database file is currently open in another
process which cannot share with the current process due to an
architecture mismatch. */
RLMErrorIncompatibleLockFile = 8,
};
#pragma mark - Constants
#pragma mark - Notification Constants
/**
Posted by RLMRealm when the data in the realm has changed.
DidChange are posted after a realm has been refreshed to reflect a write
transaction, i.e. when an autorefresh occurs, `[RLMRealm refresh]` is
called, after an implicit refresh from `[RLMRealm beginWriteTransaction]`,
and after a local write transaction is committed.
*/
extern NSString * const RLMRealmRefreshRequiredNotification;
/**
Posted by RLMRealm when a write transaction has been committed to an RLMRealm on
a different thread for the same file. This is not posted if
`[RLMRealm autorefresh]` is enabled or if the RLMRealm is
refreshed before the notifcation has a chance to run.
Realms with autorefresh disabled should normally have a handler for this
notification which calls `[RLMRealm refresh]` after doing some work.
While not refreshing is allowed, it may lead to large Realm files as Realm has
to keep an extra copy of the data for the un-refreshed RLMRealm.
*/
extern NSString * const RLMRealmDidChangeNotification;
#pragma mark - Other Constants
/** Schema version used for uninitialized Realms */
extern const uint64_t RLMNotVersioned;
/** Error domain used in Realm. */
extern NSString * const RLMErrorDomain;
/** Key for name of Realm exceptions. */
extern NSString * const RLMExceptionName;
/** Key for Realm file version. */
extern NSString * const RLMRealmVersionKey;
/** Key for Realm core version. */
extern NSString * const RLMRealmCoreVersionKey;
/** Key for Realm invalidated property name. */
extern NSString * const RLMInvalidatedKey;

View File

@@ -0,0 +1,95 @@
////////////////////////////////////////////////////////////////////////////
//
// Copyright 2015 Realm Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////
#import <Foundation/Foundation.h>
@class RLMObject;
#ifndef __has_feature
#define __has_feature(x) 0
#endif
#pragma mark - Generics
#if __has_extension(objc_generics)
#define RLM_GENERIC(...) <__VA_ARGS__>
#define RLM_GENERIC_COLLECTION <RLMObjectType: RLMObject *>
#define RLM_GENERIC_RETURN <RLMObjectType>
#define RLMObjectArgument RLMObjectType
#else
#define RLM_GENERIC(...)
#define RLM_GENERIC_COLLECTION
#define RLM_GENERIC_RETURN
typedef id RLMObjectType;
typedef RLMObject * RLMObjectArgument;
#endif
#pragma mark - Nullability
#if !__has_feature(nullability)
#ifndef __nullable
#define __nullable
#endif
#ifndef __nonnull
#define __nonnull
#endif
#ifndef __null_unspecified
#define __null_unspecified
#endif
#ifndef nullable
#define nullable
#endif
#ifndef nonnull
#define nonnull
#endif
#ifndef null_unspecified
#define null_unspecified
#endif
#endif
#if defined(NS_ASSUME_NONNULL_BEGIN) && defined(NS_ASSUME_NONNULL_END)
#define RLM_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_BEGIN
#define RLM_ASSUME_NONNULL_END NS_ASSUME_NONNULL_END
#else
#define RLM_ASSUME_NONNULL_BEGIN
#define RLM_ASSUME_NONNULL_END
#endif
#pragma mark - Escaping
#if __has_attribute(noescape)
# define RLM_NOESCAPE __attribute__((noescape))
#else
# define RLM_NOESCAPE
#endif
#pragma mark - Unused Result
#if __has_attribute(warn_unused_result)
# define RLM_WARN_UNUSED_RESULT __attribute__((warn_unused_result))
#else
# define RLM_WARN_UNUSED_RESULT
#endif
#pragma mark - Swift Availability
#if defined(NS_SWIFT_UNAVAILABLE)
# define RLM_SWIFT_UNAVAILABLE(msg) NS_SWIFT_UNAVAILABLE(msg)
#else
# define RLM_SWIFT_UNAVAILABLE(msg)
#endif

View File

@@ -0,0 +1,29 @@
////////////////////////////////////////////////////////////////////////////
//
// Copyright 2014 Realm Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////
#import <Foundation/Foundation.h>
@class RLMArray;
// A base class for Swift generic Lists to make it possible to interact with
// them from obj-c
@interface RLMListBase : NSObject <NSFastEnumeration>
@property (nonatomic, strong) RLMArray *_rlmArray;
- (instancetype)initWithArray:(RLMArray *)array;
@end

View File

@@ -0,0 +1,107 @@
////////////////////////////////////////////////////////////////////////////
//
// Copyright 2014 Realm Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////
#import <Foundation/Foundation.h>
#import <Realm/RLMDefines.h>
RLM_ASSUME_NONNULL_BEGIN
@class RLMSchema;
@class RLMArray;
@class RLMObject;
/**
Provides both the old and new versions of an object in this Realm. Object properties can only be
accessed using keyed subscripting.
@param oldObject Object in original RLMRealm (read-only).
@param newObject Object in migrated RLMRealm (read-write).
*/
typedef void (^RLMObjectMigrationBlock)(RLMObject * __nullable oldObject, RLMObject * __nullable newObject);
/**
RLMMigration is the object passed into a user defined RLMMigrationBlock when updating the version
of an RLMRealm instance.
This object provides access to the RLMSchema current to this migration.
*/
@interface RLMMigration : NSObject
#pragma mark - Properties
/**
Get the old RLMSchema for the migration. This is the schema which describes the RLMRealm before the
migration is applied.
*/
@property (nonatomic, readonly) RLMSchema *oldSchema;
/**
Get the new RLMSchema for the migration. This is the schema which describes the RLMRealm after applying
a migration.
*/
@property (nonatomic, readonly) RLMSchema *newSchema;
#pragma mark - Altering Objects during a Migration
/**
Enumerates objects of a given type in this Realm, providing both the old and new versions of each object.
Objects properties can be accessed using keyed subscripting.
@param className The name of the RLMObject class to enumerate.
@warning All objects returned are of a type specific to the current migration and should not be casted
to className. Instead you should access them as RLMObjects and use keyed subscripting to access
properties.
*/
- (void)enumerateObjects:(NSString *)className block:(RLMObjectMigrationBlock)block;
/**
Create an RLMObject of type `className` in the Realm being migrated.
@param className The name of the RLMObject class to create.
@param value The value used to populate the created object. This can be any key/value coding compliant
object, or a JSON object such as those returned from the methods in NSJSONSerialization, or
an NSArray with one object for each persisted property. An exception will be
thrown if any required properties are not present and no default is set.
When passing in an NSArray, all properties must be present, valid and in the same order as the properties defined in the model.
*/
-(RLMObject *)createObject:(NSString *)className withValue:(id)value;
/**
Delete an object from a Realm during a migration. This can be called within `enumerateObjects:block:`.
@param object Object to be deleted from the Realm being migrated.
*/
- (void)deleteObject:(RLMObject *)object;
/**
Deletes the data for the class with the given name.
This deletes all objects of the given class, and if the RLMObject subclass no longer exists in your program,
cleans up any remaining metadata for the class in the Realm file.
@param name The name of the RLMObject class to delete.
@return whether there was any data to delete.
*/
- (BOOL)deleteDataForClassName:(NSString *)name;
@end
RLM_ASSUME_NONNULL_END

View File

@@ -0,0 +1,34 @@
////////////////////////////////////////////////////////////////////////////
//
// Copyright 2014 Realm Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////
#import <Realm/RLMMigration.h>
#import <Realm/RLMObjectBase.h>
#import <Realm/RLMRealm.h>
typedef void (^RLMObjectBaseMigrationBlock)(RLMObjectBase *oldObject, RLMObjectBase *newObject);
@interface RLMMigration ()
@property (nonatomic, strong) RLMRealm *oldRealm;
@property (nonatomic, strong) RLMRealm *realm;
- (instancetype)initWithRealm:(RLMRealm *)realm oldRealm:(RLMRealm *)oldRealm;
- (void)execute:(RLMMigrationBlock)block;
@end

View File

@@ -0,0 +1,425 @@
////////////////////////////////////////////////////////////////////////////
//
// Copyright 2014 Realm Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////
#import <Foundation/Foundation.h>
#import <Realm/RLMObjectBase.h>
RLM_ASSUME_NONNULL_BEGIN
@class RLMRealm;
@class RLMResults;
@class RLMObjectSchema;
/**
In Realm you define your model classes by subclassing `RLMObject` and adding properties to be persisted.
You then instantiate and use your custom subclasses instead of using the `RLMObject` class directly.
// Dog.h
@interface Dog : RLMObject
@property NSString *name;
@property BOOL adopted;
@end
// Dog.m
@implementation Dog
@end //none needed
### Supported property types
- `NSString`
- `NSInteger`, `int`, `long`, `float`, and `double`
- `BOOL` or `bool`
- `NSDate`
- `NSData`
- `NSNumber<X>`, where X is one of RLMInt, RLMFloat, RLMDouble or RLMBool, for optional number properties
- `RLMObject` subclasses, so you can have many-to-one relationships.
- `RLMArray<X>`, where X is an `RLMObject` subclass, so you can have many-to-many relationships.
### Querying
You can query an object directly via the class methods: `allObjects`, `objectsWhere:`, and `objectsWithPredicate:`.
These methods allow you to easily query a custom subclass for instances of this class in the
default Realm. To search in a Realm other than the default Realm use the interface on an RLMRealm instance.
### Relationships
See our [Cocoa guide](https://realm.io/docs/objc/latest#relationships) for more details.
### Key-Value Observing
All `RLMObject` properties (including properties you create in subclasses) are
[Key-Value Observing compliant](https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/KeyValueObserving/KeyValueObserving.html),
except for `realm` and `objectSchema`. There are several Realm-specific things
to keep in mind when observing Realm objects:
1. Unlike `NSMutableArray` properties, `RLMArray` properties do not require
using the proxy object returned from `-mutableArrayValueForKey:`, or defining
KVC mutation methods on the containing class. You can simply call methods on
the RLMArray directly and the changes will be observed by the containing
object.
2. Standalone `RLMObjects` cannot be added to a Realm while they have any
observed properties.
3. Modifying persisted `RLMObjects` in `-observeValueForKeyPath:ofObject:change:context:`
is problematic. Properties may change when the Realm is not in a write
transaction (for example, when `-[RLMRealm refresh]` is called after changes
are made on a different thread), and notifications sent prior to the change
being applied (when `NSKeyValueObservingOptionPrior` is used) may be sent at
times when you *cannot* begin a write transaction.
*/
@interface RLMObject : RLMObjectBase
#pragma mark - Creating & Initializing Objects
/**
Initialize a standalone RLMObject
Initialize an unpersisted instance of this object.
Call addObject: on an RLMRealm to add standalone object to a realm.
@see [RLMRealm addObject:]:
*/
- (instancetype)init NS_DESIGNATED_INITIALIZER;
/**
Initialize a standalone RLMObject with values from an NSArray or NSDictionary
Initialize an unpersisted instance of this object.
Call addObject: on an RLMRealm to add standalone object to a realm.
@see [RLMRealm addObject:]:
*/
- (instancetype)initWithValue:(id)value NS_DESIGNATED_INITIALIZER;
/**
Helper to return the class name for an RLMObject subclass.
@warning Do not override. Realm relies on this method returning the exact class
name.
@return The class name for the model class.
*/
+ (NSString *)className;
/**
Create an RLMObject in the default Realm with a given value.
Creates an instance of this object and adds it to the default Realm populating
the object with the given value.
If nested objects are included in the argument, `createInDefaultRealmWithValue:` will be called
on them.
@param value The value used to populate the object. This can be any key/value coding compliant
object, or a JSON object such as those returned from the methods in NSJSONSerialization, or
an NSArray with one object for each persisted property. An exception will be
thrown if any required properties are not present and no default is set.
When passing in an NSArray, all properties must be present, valid and in the same order as the properties defined in the model.
@see defaultPropertyValues
*/
+ (instancetype)createInDefaultRealmWithValue:(id)value;
/**
Create an RLMObject in a Realm with a given object.
Creates an instance of this object and adds it to the given Realm populating
the object with the given object.
If nested objects are included in the argument, `createInRealm:withValue:` will be called
on them.
@param realm The Realm in which this object is persisted.
@param value The value used to populate the object. This can be any key/value coding compliant
object, or a JSON object such as those returned from the methods in NSJSONSerialization, or
an NSArray with one object for each persisted property. An exception will be
thrown if any required properties are not present and no default is set.
When passing in an NSArray, all properties must be present, valid and in the same order as the properties defined in the model.
@see defaultPropertyValues
*/
+ (instancetype)createInRealm:(RLMRealm *)realm withValue:(id)value;
/**
Create or update an RLMObject in the default Realm with a given object.
This method can only be called on object types with a primary key defined. If there is already
an object with the same primary key value in the default RLMRealm its values are updated and the object
is returned. Otherwise this creates and populates a new instance of this object in the default Realm.
If nested objects are included in the argument, `createOrUpdateInDefaultRealmWithValue:` will be
called on them if have a primary key (`createInDefaultRealmWithValue:` otherwise).
This is a no-op if the argument is an RLMObject of the same type already backed by the target realm.
@param value The value used to populate the object. This can be any key/value coding compliant
object, or a JSON object such as those returned from the methods in NSJSONSerialization, or
an NSArray with one object for each persisted property. An exception will be
thrown if any required properties are not present and no default is set.
When passing in an NSArray, all properties must be present, valid and in the same order as the properties defined in the model.
@see defaultPropertyValues, primaryKey
*/
+ (instancetype)createOrUpdateInDefaultRealmWithValue:(id)value;
/**
Create or update an RLMObject with a given object.
This method can only be called on object types with a primary key defined. If there is already
an object with the same primary key value in the provided RLMRealm its values are updated and the object
is returned. Otherwise this creates and populates a new instance of this object in the provided Realm.
If nested objects are included in the argument, `createOrUpdateInRealm:withValue:` will be
called on them if have a primary key (`createInRealm:withValue:` otherwise).
This is a no-op if the argument is an RLMObject of the same type already backed by the target realm.
@param realm The Realm in which this object is persisted.
@param value The value used to populate the object. This can be any key/value coding compliant
object, or a JSON object such as those returned from the methods in NSJSONSerialization, or
an NSArray with one object for each persisted property. An exception will be
thrown if any required properties are not present and no default is set.
When passing in an NSArray, all properties must be present, valid and in the same order as the properties defined in the model.
@see defaultPropertyValues, primaryKey
*/
+ (instancetype)createOrUpdateInRealm:(RLMRealm *)realm withValue:(id)value;
#pragma mark - Properties
/**
The Realm in which this object is persisted. Returns nil for standalone objects.
*/
@property (nonatomic, readonly, nullable) RLMRealm *realm;
/**
The ObjectSchema which lists the persisted properties for this object.
*/
@property (nonatomic, readonly) RLMObjectSchema *objectSchema;
/**
Indicates if an object can no longer be accessed.
An object can no longer be accessed if the object has been deleted from the containing `realm` or
if `invalidate` is called on the containing `realm`.
*/
@property (nonatomic, readonly, getter = isInvalidated) BOOL invalidated;
#pragma mark - Customizing your Objects
/**
Return an array of property names for properties which should be indexed. Only supported
for strings, integers, booleans and NSDate properties.
@return NSArray of property names.
*/
+ (NSArray RLM_GENERIC(NSString *) *)indexedProperties;
/**
Implement to indicate the default values to be used for each property.
@return NSDictionary mapping property names to their default values.
*/
+ (nullable NSDictionary *)defaultPropertyValues;
/**
Implement to designate a property as the primary key for an RLMObject subclass. Only properties of
type RLMPropertyTypeString and RLMPropertyTypeInt can be designated as the primary key. Primary key
properties enforce uniqueness for each value whenever the property is set which incurs some overhead.
Indexes are created automatically for primary key properties.
@return Name of the property designated as the primary key.
*/
+ (nullable NSString *)primaryKey;
/**
Implement to return an array of property names to ignore. These properties will not be persisted
and are treated as transient.
@return NSArray of property names to ignore.
*/
+ (nullable NSArray RLM_GENERIC(NSString *) *)ignoredProperties;
/**
Implement to return an array of property names that should not allow storing nil.
By default, all properties of a type that support storing nil are considered optional properties.
To require that an object in a Realm always have a non-nil value for a property,
add the name of the property to the array returned from this method.
Currently Object properties cannot be required. Array and NSNumber properties
can, but it makes little sense to do so: arrays do not support storing nil, and
if you want a non-optional number you should instead use the primitive type.
@return NSArray of property names that are required.
*/
+ (NSArray RLM_GENERIC(NSString *) *)requiredProperties;
#pragma mark - Getting & Querying Objects from the Default Realm
/**
Get all objects of this type from the default Realm.
@return An RLMResults of all objects of this type in the default Realm.
*/
+ (RLMResults *)allObjects;
/**
Get objects matching the given predicate for this type from the default Realm.
@param predicateFormat The predicate format string which can accept variable arguments.
@return An RLMResults of objects of the subclass type in the default Realm that match the given predicate
*/
+ (RLMResults *)objectsWhere:(NSString *)predicateFormat, ...;
/// :nodoc:
+ (RLMResults *)objectsWhere:(NSString *)predicateFormat args:(va_list)args;
/**
Get objects matching the given predicate for this type from the default Realm.
@param predicate The predicate to filter the objects.
@return An RLMResults of objects of the subclass type in the default Realm that match the given predicate
*/
+ (RLMResults *)objectsWithPredicate:(nullable NSPredicate *)predicate;
/**
Get the single object with the given primary key from the default Realm.
Returns the object from the default Realm which has the given primary key, or
`nil` if the object does not exist. This is slightly faster than the otherwise
equivalent `[[SubclassName objectsWhere:@"primaryKeyPropertyName = %@", key] firstObject]`.
This method requires that `primaryKey` be overridden on the receiving subclass.
@return An object of the subclass type or nil if an object with the given primary key does not exist.
@see -primaryKey
*/
+ (nullable instancetype)objectForPrimaryKey:(nullable id)primaryKey;
#pragma mark - Querying Specific Realms
/**
Get all objects of this type from the specified Realm.
@param realm The Realm instance to query.
@return An RLMResults of all objects of this type in the specified Realm.
*/
+ (RLMResults *)allObjectsInRealm:(RLMRealm *)realm;
/**
Get objects matching the given predicate for this type from the specified Realm.
@param predicateFormat The predicate format string which can accept variable arguments.
@param realm The Realm instance to query.
@return An RLMResults of objects of the subclass type in the specified Realm that match the given predicate
*/
+ (RLMResults *)objectsInRealm:(RLMRealm *)realm where:(NSString *)predicateFormat, ...;
/// :nodoc:
+ (RLMResults *)objectsInRealm:(RLMRealm *)realm where:(NSString *)predicateFormat args:(va_list)args;
/**
Get objects matching the given predicate for this type from the specified Realm.
@param predicate The predicate to filter the objects.
@param realm The Realm instance to query.
@return An RLMResults of objects of the subclass type in the specified Realm that match the given predicate
*/
+ (RLMResults *)objectsInRealm:(RLMRealm *)realm withPredicate:(nullable NSPredicate *)predicate;
/**
Get the single object with the given primary key from the specified Realm.
Returns the object from the specified Realm which has the given primary key, or
`nil` if the object does not exist. This is slightly faster than the otherwise
equivalent `[[SubclassName objectsInRealm:realm where:@"primaryKeyPropertyName = %@", key] firstObject]`.
This method requires that `primaryKey` be overridden on the receiving subclass.
@return An object of the subclass type or nil if an object with the given primary key does not exist.
@see -primaryKey
*/
+ (nullable instancetype)objectInRealm:(RLMRealm *)realm forPrimaryKey:(nullable id)primaryKey;
#pragma mark - Other Instance Methods
/**
Get an `NSArray` of objects of type `className` which have this object as the given property value. This can
be used to get the inverse relationship value for `RLMObject` and `RLMArray` properties.
@param className The type of object on which the relationship to query is defined.
@param property The name of the property which defines the relationship.
@return An NSArray of objects of type `className` which have this object as their value for the `property` property.
*/
- (NSArray *)linkingObjectsOfClass:(NSString *)className forProperty:(NSString *)property;
/**
Returns YES if another RLMObject points to the same object in an RLMRealm. For RLMObject types
with a primary, key, `isEqual:` is overridden to use this method (along with a corresponding
implementation for `hash`.
@param object The object to compare to.
@return YES if the object represents the same object in the same RLMRealm.
*/
- (BOOL)isEqualToObject:(RLMObject *)object;
#pragma mark - Dynamic Accessors
/// :nodoc:
- (nullable id)objectForKeyedSubscript:(NSString *)key;
/// :nodoc:
- (void)setObject:(nullable id)obj forKeyedSubscript:(NSString *)key;
@end
#pragma mark - RLMArray Property Declaration
/**
Properties on RLMObjects of type RLMArray must have an associated type. A type is associated
with an RLMArray property by defining a protocol for the object type which the RLMArray will
hold. To define the protocol for an object you can use the macro RLM_ARRAY_TYPE:
RLM_ARRAY_TYPE(ObjectType)
...
@property RLMArray<ObjectType *><ObjectType> *arrayOfObjectTypes;
*/
#define RLM_ARRAY_TYPE(RLM_OBJECT_SUBCLASS)\
@protocol RLM_OBJECT_SUBCLASS <NSObject> \
@end
RLM_ASSUME_NONNULL_END

View File

@@ -0,0 +1,42 @@
////////////////////////////////////////////////////////////////////////////
//
// Copyright 2014 Realm Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////
#import <Foundation/Foundation.h>
#import <Realm/RLMDefines.h>
RLM_ASSUME_NONNULL_BEGIN
@class RLMRealm;
@class RLMSchema;
@class RLMObjectSchema;
/// :nodoc:
@interface RLMObjectBase : NSObject
@property (nonatomic, readonly, getter = isInvalidated) BOOL invalidated;
- (instancetype)init NS_DESIGNATED_INITIALIZER;
+ (NSString *)className;
// Returns whether the class is included in the default set of classes persisted in a Realm.
+ (BOOL)shouldIncludeInDefaultSchema;
@end
RLM_ASSUME_NONNULL_END

View File

@@ -0,0 +1,84 @@
////////////////////////////////////////////////////////////////////////////
//
// Copyright 2014 Realm Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////
#import <Realm/RLMObject.h>
@class RLMObjectSchema, RLMRealm;
/**
This function is useful only in specialized circumstances, for example, when building components
that integrate with Realm. If you are simply building an app on Realm, it is
recommended to retrieve `realm` via `RLMObject`.
@param object an RLMObjectBase obtained via a Swift Object or RLMObject
@return The Realm in which this object is persisted. Returns nil for standalone objects.
*/
FOUNDATION_EXTERN RLMRealm *RLMObjectBaseRealm(RLMObjectBase *object);
/**
This function is useful only in specialized circumstances, for example, when building components
that integrate with Realm. If you are simply building an app on Realm, it is
recommended to retrieve `objectSchema` via `RLMObject`.
@param object an RLMObjectBase obtained via a Swift Object or RLMObject
@return The ObjectSchema which lists the persisted properties for this object.
*/
FOUNDATION_EXTERN RLMObjectSchema *RLMObjectBaseObjectSchema(RLMObjectBase *object);
/**
This function is useful only in specialized circumstances, for example, when building components
that integrate with Realm. If you are simply building an app on Realm, it is
recommended to retrieve the linking objects via `RLMObject`.
@param object an RLMObjectBase obtained via a Swift Object or RLMObject
@param className The type of object on which the relationship to query is defined.
@param property The name of the property which defines the relationship.
@return An NSArray of objects of type `className` which have this object as their value for the `property` property.
*/
FOUNDATION_EXTERN NSArray *RLMObjectBaseLinkingObjectsOfClass(RLMObjectBase *object, NSString *className, NSString *property);
/**
This function is useful only in specialized circumstances, for example, when building components
that integrate with Realm. If you are simply building an app on Realm, it is
recommended to retrieve key values via `RLMObject`.
@warning Will throw `NSUndefinedKeyException` if key is not present on the object
@param object an RLMObjectBase obtained via a Swift Object or RLMObject
@param key The name of the property
@return the object for the property requested
*/
FOUNDATION_EXTERN id RLMObjectBaseObjectForKeyedSubscript(RLMObjectBase *object, NSString *key);
/**
This function is useful only in specialized circumstances, for example, when building components
that integrate with Realm. If you are simply building an app on Realm, it is
recommended to set key values via `RLMObject`.
@warning Will throw `NSUndefinedKeyException` if key is not present on the object
@param object an RLMObjectBase obtained via a Swift Object or RLMObject
@param key The name of the property
@param obj The object to set as the value of the key
*/
FOUNDATION_EXTERN void RLMObjectBaseSetObjectForKeyedSubscript(RLMObjectBase *object, NSString *key, id obj);

View File

@@ -0,0 +1,73 @@
////////////////////////////////////////////////////////////////////////////
//
// Copyright 2014 Realm Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////
#import <Foundation/Foundation.h>
#import <Realm/RLMDefines.h>
RLM_ASSUME_NONNULL_BEGIN
@class RLMProperty;
/**
This class represents Realm model object schemas.
When using Realm, RLMObjectSchema objects allow performing migrations and
introspecting the database's schema.
Object schemas map to tables in the core database.
*/
@interface RLMObjectSchema : NSObject<NSCopying>
#pragma mark - Properties
/**
Array of persisted RLMProperty objects for an object.
@see RLMProperty
*/
@property (nonatomic, readonly, copy) NSArray RLM_GENERIC(RLMProperty *) *properties;
/**
The name of the class this schema describes.
*/
@property (nonatomic, readonly) NSString *className;
/**
The property which is the primary key for this object (if any).
*/
@property (nonatomic, readonly, nullable) RLMProperty *primaryKeyProperty;
#pragma mark - Methods
/**
Retrieve an RLMProperty object by name.
@param propertyName The property's name.
@return RLMProperty object or nil if there is no property with the given name.
*/
- (nullable RLMProperty *)objectForKeyedSubscript:(id <NSCopying>)propertyName;
/**
Returns YES if equal to objectSchema
*/
- (BOOL)isEqualToObjectSchema:(RLMObjectSchema *)objectSchema;
@end
RLM_ASSUME_NONNULL_END

Some files were not shown because too many files have changed in this diff Show More