FIRDatabaseQuery.m 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677
  1. /*
  2. * Copyright 2017 Google
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. #import "FirebaseDatabase/Sources/Public/FirebaseDatabase/FIRDatabaseQuery.h"
  17. #import "FirebaseDatabase/Sources/Api/Private/FIRDatabaseQuery_Private.h"
  18. #import "FirebaseDatabase/Sources/Constants/FConstants.h"
  19. #import "FirebaseDatabase/Sources/Core/FQueryParams.h"
  20. #import "FirebaseDatabase/Sources/Core/FQuerySpec.h"
  21. #import "FirebaseDatabase/Sources/Core/Utilities/FPath.h"
  22. #import "FirebaseDatabase/Sources/Core/View/FChildEventRegistration.h"
  23. #import "FirebaseDatabase/Sources/Core/View/FValueEventRegistration.h"
  24. #import "FirebaseDatabase/Sources/FKeyIndex.h"
  25. #import "FirebaseDatabase/Sources/FPathIndex.h"
  26. #import "FirebaseDatabase/Sources/FPriorityIndex.h"
  27. #import "FirebaseDatabase/Sources/FValueIndex.h"
  28. #import "FirebaseDatabase/Sources/Snapshot/FLeafNode.h"
  29. #import "FirebaseDatabase/Sources/Snapshot/FSnapshotUtilities.h"
  30. #import "FirebaseDatabase/Sources/Utilities/FValidation.h"
  31. @implementation FIRDatabaseQuery
  32. @synthesize repo;
  33. @synthesize path;
  34. @synthesize queryParams;
  35. #define INVALID_QUERY_PARAM_ERROR @"InvalidQueryParameter"
  36. + (dispatch_queue_t)sharedQueue {
  37. // We use this shared queue across all of the FQueries so things happen FIFO
  38. // (as opposed to dispatch_get_global_queue(0, 0) which is concurrent)
  39. static dispatch_once_t pred;
  40. static dispatch_queue_t sharedDispatchQueue;
  41. dispatch_once(&pred, ^{
  42. sharedDispatchQueue = dispatch_queue_create("FirebaseWorker", NULL);
  43. });
  44. return sharedDispatchQueue;
  45. }
  46. - (id)initWithRepo:(FRepo *)theRepo path:(FPath *)thePath {
  47. return [self initWithRepo:theRepo
  48. path:thePath
  49. params:nil
  50. orderByCalled:NO
  51. priorityMethodCalled:NO];
  52. }
  53. - (id)initWithRepo:(FRepo *)theRepo
  54. path:(FPath *)thePath
  55. params:(FQueryParams *)theParams
  56. orderByCalled:(BOOL)orderByCalled
  57. priorityMethodCalled:(BOOL)priorityMethodCalled {
  58. self = [super init];
  59. if (self) {
  60. self.repo = theRepo;
  61. self.path = thePath;
  62. if (!theParams) {
  63. theParams = [FQueryParams defaultInstance];
  64. }
  65. if (![theParams isValid]) {
  66. @throw [[NSException alloc]
  67. initWithName:@"InvalidArgumentError"
  68. reason:@"Queries are limited to two constraints"
  69. userInfo:nil];
  70. }
  71. self.queryParams = theParams;
  72. self.orderByCalled = orderByCalled;
  73. self.priorityMethodCalled = priorityMethodCalled;
  74. }
  75. return self;
  76. }
  77. - (FQuerySpec *)querySpec {
  78. return [[FQuerySpec alloc] initWithPath:self.path params:self.queryParams];
  79. }
  80. - (void)validateQueryEndpointsForParams:(FQueryParams *)params {
  81. if ([params.index isEqual:[FKeyIndex keyIndex]]) {
  82. if ([params hasStart]) {
  83. if (params.indexStartKey != [FUtilities minName]) {
  84. [NSException raise:INVALID_QUERY_PARAM_ERROR
  85. format:@"Can't use queryStartingAtValue:childKey: "
  86. @"or queryEqualTo:andChildKey: in "
  87. @"combination with queryOrderedByKey"];
  88. }
  89. if (![params.indexStartValue.val isKindOfClass:[NSString class]]) {
  90. [NSException
  91. raise:INVALID_QUERY_PARAM_ERROR
  92. format:
  93. @"Can't use queryStartingAtValue: with other types "
  94. @"than string in combination with queryOrderedByKey"];
  95. }
  96. }
  97. if ([params hasEnd]) {
  98. if (params.indexEndKey != [FUtilities maxName]) {
  99. [NSException raise:INVALID_QUERY_PARAM_ERROR
  100. format:@"Can't use queryEndingAtValue:childKey: or "
  101. @"queryEqualToValue:childKey: in "
  102. @"combination with queryOrderedByKey"];
  103. }
  104. if (![params.indexEndValue.val isKindOfClass:[NSString class]]) {
  105. [NSException
  106. raise:INVALID_QUERY_PARAM_ERROR
  107. format:
  108. @"Can't use queryEndingAtValue: with other types than "
  109. @"string in combination with queryOrderedByKey"];
  110. }
  111. }
  112. } else if ([params.index isEqual:[FPriorityIndex priorityIndex]]) {
  113. if (([params hasStart] &&
  114. ![FValidation validatePriorityValue:params.indexStartValue.val]) ||
  115. ([params hasEnd] &&
  116. ![FValidation validatePriorityValue:params.indexEndValue.val])) {
  117. [NSException
  118. raise:INVALID_QUERY_PARAM_ERROR
  119. format:@"When using queryOrderedByPriority, values provided to "
  120. @"queryStartingAtValue:, queryEndingAtValue:, or "
  121. @"queryEqualToValue: must be valid priorities."];
  122. }
  123. }
  124. }
  125. - (void)validateEqualToCall {
  126. if ([self.queryParams hasStart]) {
  127. [NSException
  128. raise:INVALID_QUERY_PARAM_ERROR
  129. format:
  130. @"Cannot combine queryEqualToValue: and queryStartingAtValue:"];
  131. }
  132. if ([self.queryParams hasEnd]) {
  133. [NSException
  134. raise:INVALID_QUERY_PARAM_ERROR
  135. format:
  136. @"Cannot combine queryEqualToValue: and queryEndingAtValue:"];
  137. }
  138. }
  139. - (void)validateNoPreviousOrderByCalled {
  140. if (self.orderByCalled) {
  141. [NSException raise:INVALID_QUERY_PARAM_ERROR
  142. format:@"Cannot use multiple queryOrderedBy calls!"];
  143. }
  144. }
  145. - (void)validateIndexValueType:(id)type fromMethod:(NSString *)method {
  146. if (type != nil && ![type isKindOfClass:[NSNumber class]] &&
  147. ![type isKindOfClass:[NSString class]] &&
  148. ![type isKindOfClass:[NSNull class]]) {
  149. [NSException raise:INVALID_QUERY_PARAM_ERROR
  150. format:@"You can only pass nil, NSString or NSNumber to %@",
  151. method];
  152. }
  153. }
  154. - (FIRDatabaseQuery *)queryStartingAtValue:(id)startValue {
  155. return [self queryStartingAtInternal:startValue
  156. childKey:nil
  157. from:@"queryStartingAtValue:"
  158. priorityMethod:NO];
  159. }
  160. - (FIRDatabaseQuery *)queryStartingAtValue:(id)startValue
  161. childKey:(NSString *)childKey {
  162. if ([self.queryParams.index isEqual:[FKeyIndex keyIndex]]) {
  163. @throw [[NSException alloc]
  164. initWithName:INVALID_QUERY_PARAM_ERROR
  165. reason:@"You must use queryStartingAtValue: instead of "
  166. @"queryStartingAtValue:childKey: when using "
  167. @"queryOrderedByKey:"
  168. userInfo:nil];
  169. }
  170. return [self queryStartingAtInternal:startValue
  171. childKey:childKey
  172. from:@"queryStartingAtValue:childKey:"
  173. priorityMethod:NO];
  174. }
  175. - (FIRDatabaseQuery *)queryStartingAtInternal:(id<FNode>)startValue
  176. childKey:(NSString *)childKey
  177. from:(NSString *)methodName
  178. priorityMethod:(BOOL)priorityMethod {
  179. [self validateIndexValueType:startValue fromMethod:methodName];
  180. if (childKey != nil) {
  181. [FValidation validateFrom:methodName validKey:childKey];
  182. }
  183. if ([self.queryParams hasStart]) {
  184. [NSException raise:INVALID_QUERY_PARAM_ERROR
  185. format:@"Can't call %@ after queryStartingAtValue or "
  186. @"queryEqualToValue was previously called",
  187. methodName];
  188. }
  189. id<FNode> startNode = [FSnapshotUtilities nodeFrom:startValue];
  190. FQueryParams *params = [self.queryParams startAt:startNode
  191. childKey:childKey];
  192. [self validateQueryEndpointsForParams:params];
  193. return [[FIRDatabaseQuery alloc]
  194. initWithRepo:self.repo
  195. path:self.path
  196. params:params
  197. orderByCalled:self.orderByCalled
  198. priorityMethodCalled:priorityMethod || self.priorityMethodCalled];
  199. }
  200. - (FIRDatabaseQuery *)queryEndingAtValue:(id)endValue {
  201. return [self queryEndingAtInternal:endValue
  202. childKey:nil
  203. from:@"queryEndingAtValue:"
  204. priorityMethod:NO];
  205. }
  206. - (FIRDatabaseQuery *)queryEndingAtValue:(id)endValue
  207. childKey:(NSString *)childKey {
  208. if ([self.queryParams.index isEqual:[FKeyIndex keyIndex]]) {
  209. @throw [[NSException alloc]
  210. initWithName:INVALID_QUERY_PARAM_ERROR
  211. reason:@"You must use queryEndingAtValue: instead of "
  212. @"queryEndingAtValue:childKey: when using "
  213. @"queryOrderedByKey:"
  214. userInfo:nil];
  215. }
  216. return [self queryEndingAtInternal:endValue
  217. childKey:childKey
  218. from:@"queryEndingAtValue:childKey:"
  219. priorityMethod:NO];
  220. }
  221. - (FIRDatabaseQuery *)queryEndingAtInternal:(id)endValue
  222. childKey:(NSString *)childKey
  223. from:(NSString *)methodName
  224. priorityMethod:(BOOL)priorityMethod {
  225. [self validateIndexValueType:endValue fromMethod:methodName];
  226. if (childKey != nil) {
  227. [FValidation validateFrom:methodName validKey:childKey];
  228. }
  229. if ([self.queryParams hasEnd]) {
  230. [NSException raise:INVALID_QUERY_PARAM_ERROR
  231. format:@"Can't call %@ after queryEndingAtValue or "
  232. @"queryEqualToValue was previously called",
  233. methodName];
  234. }
  235. id<FNode> endNode = [FSnapshotUtilities nodeFrom:endValue];
  236. FQueryParams *params = [self.queryParams endAt:endNode childKey:childKey];
  237. [self validateQueryEndpointsForParams:params];
  238. return [[FIRDatabaseQuery alloc]
  239. initWithRepo:self.repo
  240. path:self.path
  241. params:params
  242. orderByCalled:self.orderByCalled
  243. priorityMethodCalled:priorityMethod || self.priorityMethodCalled];
  244. }
  245. - (FIRDatabaseQuery *)queryEqualToValue:(id)value {
  246. return [self queryEqualToInternal:value
  247. childKey:nil
  248. from:@"queryEqualToValue:"
  249. priorityMethod:NO];
  250. }
  251. - (FIRDatabaseQuery *)queryEqualToValue:(id)value
  252. childKey:(NSString *)childKey {
  253. if ([self.queryParams.index isEqual:[FKeyIndex keyIndex]]) {
  254. @throw [[NSException alloc]
  255. initWithName:INVALID_QUERY_PARAM_ERROR
  256. reason:@"You must use queryEqualToValue: instead of "
  257. @"queryEqualTo:childKey: when using queryOrderedByKey:"
  258. userInfo:nil];
  259. }
  260. return [self queryEqualToInternal:value
  261. childKey:childKey
  262. from:@"queryEqualToValue:childKey:"
  263. priorityMethod:NO];
  264. }
  265. - (FIRDatabaseQuery *)queryEqualToInternal:(id)value
  266. childKey:(NSString *)childKey
  267. from:(NSString *)methodName
  268. priorityMethod:(BOOL)priorityMethod {
  269. [self validateIndexValueType:value fromMethod:methodName];
  270. if (childKey != nil) {
  271. [FValidation validateFrom:methodName validKey:childKey];
  272. }
  273. if ([self.queryParams hasEnd] || [self.queryParams hasStart]) {
  274. [NSException
  275. raise:INVALID_QUERY_PARAM_ERROR
  276. format:
  277. @"Can't call %@ after queryStartingAtValue, queryEndingAtValue "
  278. @"or queryEqualToValue was previously called",
  279. methodName];
  280. }
  281. id<FNode> node = [FSnapshotUtilities nodeFrom:value];
  282. FQueryParams *params = [[self.queryParams startAt:node
  283. childKey:childKey] endAt:node
  284. childKey:childKey];
  285. [self validateQueryEndpointsForParams:params];
  286. return [[FIRDatabaseQuery alloc]
  287. initWithRepo:self.repo
  288. path:self.path
  289. params:params
  290. orderByCalled:self.orderByCalled
  291. priorityMethodCalled:priorityMethod || self.priorityMethodCalled];
  292. }
  293. - (void)validateLimitRange:(NSUInteger)limit {
  294. // No need to check for negative ranges, since limit is unsigned
  295. if (limit == 0) {
  296. [NSException raise:INVALID_QUERY_PARAM_ERROR
  297. format:@"Limit can't be zero"];
  298. }
  299. if (limit >= 1ul << 31) {
  300. [NSException raise:INVALID_QUERY_PARAM_ERROR
  301. format:@"Limit must be less than 2,147,483,648"];
  302. }
  303. }
  304. - (FIRDatabaseQuery *)queryLimitedToFirst:(NSUInteger)limit {
  305. if (self.queryParams.limitSet) {
  306. [NSException raise:INVALID_QUERY_PARAM_ERROR
  307. format:@"Can't call queryLimitedToFirst: if a limit was "
  308. @"previously set"];
  309. }
  310. [self validateLimitRange:limit];
  311. FQueryParams *params = [self.queryParams limitToFirst:limit];
  312. return [[FIRDatabaseQuery alloc] initWithRepo:self.repo
  313. path:self.path
  314. params:params
  315. orderByCalled:self.orderByCalled
  316. priorityMethodCalled:self.priorityMethodCalled];
  317. }
  318. - (FIRDatabaseQuery *)queryLimitedToLast:(NSUInteger)limit {
  319. if (self.queryParams.limitSet) {
  320. [NSException raise:INVALID_QUERY_PARAM_ERROR
  321. format:@"Can't call queryLimitedToLast: if a limit was "
  322. @"previously set"];
  323. }
  324. [self validateLimitRange:limit];
  325. FQueryParams *params = [self.queryParams limitToLast:limit];
  326. return [[FIRDatabaseQuery alloc] initWithRepo:self.repo
  327. path:self.path
  328. params:params
  329. orderByCalled:self.orderByCalled
  330. priorityMethodCalled:self.priorityMethodCalled];
  331. }
  332. - (FIRDatabaseQuery *)queryOrderedByChild:(NSString *)indexPathString {
  333. if ([indexPathString isEqualToString:@"$key"] ||
  334. [indexPathString isEqualToString:@".key"]) {
  335. @throw [[NSException alloc]
  336. initWithName:INVALID_QUERY_PARAM_ERROR
  337. reason:[NSString stringWithFormat:
  338. @"(queryOrderedByChild:) %@ is invalid. "
  339. @" Use queryOrderedByKey: instead.",
  340. indexPathString]
  341. userInfo:nil];
  342. } else if ([indexPathString isEqualToString:@"$priority"] ||
  343. [indexPathString isEqualToString:@".priority"]) {
  344. @throw [[NSException alloc]
  345. initWithName:INVALID_QUERY_PARAM_ERROR
  346. reason:[NSString stringWithFormat:
  347. @"(queryOrderedByChild:) %@ is invalid. "
  348. @" Use queryOrderedByPriority: instead.",
  349. indexPathString]
  350. userInfo:nil];
  351. } else if ([indexPathString isEqualToString:@"$value"] ||
  352. [indexPathString isEqualToString:@".value"]) {
  353. @throw [[NSException alloc]
  354. initWithName:INVALID_QUERY_PARAM_ERROR
  355. reason:[NSString stringWithFormat:
  356. @"(queryOrderedByChild:) %@ is invalid. "
  357. @" Use queryOrderedByValue: instead.",
  358. indexPathString]
  359. userInfo:nil];
  360. }
  361. [self validateNoPreviousOrderByCalled];
  362. [FValidation validateFrom:@"queryOrderedByChild:"
  363. validPathString:indexPathString];
  364. FPath *indexPath = [FPath pathWithString:indexPathString];
  365. if (indexPath.isEmpty) {
  366. @throw [[NSException alloc]
  367. initWithName:INVALID_QUERY_PARAM_ERROR
  368. reason:[NSString
  369. stringWithFormat:@"(queryOrderedByChild:) with an "
  370. @"empty path is invalid. Use "
  371. @"queryOrderedByValue: instead."]
  372. userInfo:nil];
  373. }
  374. id<FIndex> index = [[FPathIndex alloc] initWithPath:indexPath];
  375. FQueryParams *params = [self.queryParams orderBy:index];
  376. [self validateQueryEndpointsForParams:params];
  377. return [[FIRDatabaseQuery alloc] initWithRepo:self.repo
  378. path:self.path
  379. params:params
  380. orderByCalled:YES
  381. priorityMethodCalled:self.priorityMethodCalled];
  382. }
  383. - (FIRDatabaseQuery *)queryOrderedByKey {
  384. [self validateNoPreviousOrderByCalled];
  385. FQueryParams *params = [self.queryParams orderBy:[FKeyIndex keyIndex]];
  386. [self validateQueryEndpointsForParams:params];
  387. return [[FIRDatabaseQuery alloc] initWithRepo:self.repo
  388. path:self.path
  389. params:params
  390. orderByCalled:YES
  391. priorityMethodCalled:self.priorityMethodCalled];
  392. }
  393. - (FIRDatabaseQuery *)queryOrderedByValue {
  394. [self validateNoPreviousOrderByCalled];
  395. FQueryParams *params = [self.queryParams orderBy:[FValueIndex valueIndex]];
  396. return [[FIRDatabaseQuery alloc] initWithRepo:self.repo
  397. path:self.path
  398. params:params
  399. orderByCalled:YES
  400. priorityMethodCalled:self.priorityMethodCalled];
  401. }
  402. - (FIRDatabaseQuery *)queryOrderedByPriority {
  403. [self validateNoPreviousOrderByCalled];
  404. FQueryParams *params =
  405. [self.queryParams orderBy:[FPriorityIndex priorityIndex]];
  406. return [[FIRDatabaseQuery alloc] initWithRepo:self.repo
  407. path:self.path
  408. params:params
  409. orderByCalled:YES
  410. priorityMethodCalled:self.priorityMethodCalled];
  411. }
  412. - (FIRDatabaseHandle)observeEventType:(FIRDataEventType)eventType
  413. withBlock:(void (^)(FIRDataSnapshot *))block {
  414. [FValidation validateFrom:@"observeEventType:withBlock:"
  415. knownEventType:eventType];
  416. return [self observeEventType:eventType
  417. withBlock:block
  418. withCancelBlock:nil];
  419. }
  420. - (FIRDatabaseHandle)observeEventType:(FIRDataEventType)eventType
  421. andPreviousSiblingKeyWithBlock:(fbt_void_datasnapshot_nsstring)block {
  422. [FValidation
  423. validateFrom:@"observeEventType:andPreviousSiblingKeyWithBlock:"
  424. knownEventType:eventType];
  425. return [self observeEventType:eventType
  426. andPreviousSiblingKeyWithBlock:block
  427. withCancelBlock:nil];
  428. }
  429. - (FIRDatabaseHandle)observeEventType:(FIRDataEventType)eventType
  430. withBlock:(fbt_void_datasnapshot)block
  431. withCancelBlock:(fbt_void_nserror)cancelBlock {
  432. [FValidation validateFrom:@"observeEventType:withBlock:withCancelBlock:"
  433. knownEventType:eventType];
  434. if (eventType == FIRDataEventTypeValue) {
  435. // Handle FIRDataEventTypeValue specially because they shouldn't have
  436. // prevName callbacks
  437. NSUInteger handle = [[FUtilities LUIDGenerator] integerValue];
  438. [self observeValueEventWithHandle:handle
  439. withBlock:block
  440. cancelCallback:cancelBlock];
  441. return handle;
  442. } else {
  443. // Wrap up the userCallback so we can treat everything as a callback
  444. // that has a prevName
  445. fbt_void_datasnapshot userCallback = [block copy];
  446. return [self observeEventType:eventType
  447. andPreviousSiblingKeyWithBlock:^(FIRDataSnapshot *snapshot,
  448. NSString *prevName) {
  449. if (userCallback != nil) {
  450. userCallback(snapshot);
  451. }
  452. }
  453. withCancelBlock:cancelBlock];
  454. }
  455. }
  456. - (FIRDatabaseHandle)observeEventType:(FIRDataEventType)eventType
  457. andPreviousSiblingKeyWithBlock:(fbt_void_datasnapshot_nsstring)block
  458. withCancelBlock:(fbt_void_nserror)cancelBlock {
  459. [FValidation validateFrom:@"observeEventType:"
  460. @"andPreviousSiblingKeyWithBlock:withCancelBlock:"
  461. knownEventType:eventType];
  462. if (eventType == FIRDataEventTypeValue) {
  463. // TODO: This gets hit by observeSingleEventOfType. Need to fix.
  464. /*
  465. @throw [[NSException alloc] initWithName:@"InvalidEventTypeForObserver"
  466. reason:@"(observeEventType:andPreviousSiblingKeyWithBlock:withCancelBlock:)
  467. Cannot use
  468. observeEventType:andPreviousSiblingKeyWithBlock:withCancelBlock: with
  469. FIRDataEventTypeValue. Use observeEventType:withBlock:withCancelBlock:
  470. instead." userInfo:nil];
  471. */
  472. }
  473. NSUInteger handle = [[FUtilities LUIDGenerator] integerValue];
  474. NSDictionary *callbacks =
  475. @{[NSNumber numberWithInteger:eventType] : [block copy]};
  476. [self observeChildEventWithHandle:handle
  477. withCallbacks:callbacks
  478. cancelCallback:cancelBlock];
  479. return handle;
  480. }
  481. // If we want to distinguish between value event listeners and child event
  482. // listeners, like in the Java client, we can consider exporting this. If we do,
  483. // add argument validation. Otherwise, arguments are validated in the
  484. // public-facing portions of the API. Also, move the FIRDatabaseHandle logic.
  485. - (void)observeValueEventWithHandle:(FIRDatabaseHandle)handle
  486. withBlock:(fbt_void_datasnapshot)block
  487. cancelCallback:(fbt_void_nserror)cancelBlock {
  488. // Note that we don't need to copy the callbacks here, FEventRegistration
  489. // callback properties set to copy
  490. FValueEventRegistration *registration =
  491. [[FValueEventRegistration alloc] initWithRepo:self.repo
  492. handle:handle
  493. callback:block
  494. cancelCallback:cancelBlock];
  495. dispatch_async([FIRDatabaseQuery sharedQueue], ^{
  496. [self.repo addEventRegistration:registration forQuery:self.querySpec];
  497. });
  498. }
  499. // Note: as with the above method, we may wish to expose this at some point.
  500. - (void)observeChildEventWithHandle:(FIRDatabaseHandle)handle
  501. withCallbacks:(NSDictionary *)callbacks
  502. cancelCallback:(fbt_void_nserror)cancelBlock {
  503. // Note that we don't need to copy the callbacks here, FEventRegistration
  504. // callback properties set to copy
  505. FChildEventRegistration *registration =
  506. [[FChildEventRegistration alloc] initWithRepo:self.repo
  507. handle:handle
  508. callbacks:callbacks
  509. cancelCallback:cancelBlock];
  510. dispatch_async([FIRDatabaseQuery sharedQueue], ^{
  511. [self.repo addEventRegistration:registration forQuery:self.querySpec];
  512. });
  513. }
  514. - (void)removeObserverWithHandle:(FIRDatabaseHandle)handle {
  515. FValueEventRegistration *event =
  516. [[FValueEventRegistration alloc] initWithRepo:self.repo
  517. handle:handle
  518. callback:nil
  519. cancelCallback:nil];
  520. dispatch_async([FIRDatabaseQuery sharedQueue], ^{
  521. [self.repo removeEventRegistration:event forQuery:self.querySpec];
  522. });
  523. }
  524. - (void)removeAllObservers {
  525. [self removeObserverWithHandle:NSNotFound];
  526. }
  527. - (void)keepSynced:(BOOL)keepSynced {
  528. if ([self.path.getFront isEqualToString:kDotInfoPrefix]) {
  529. [NSException raise:NSInvalidArgumentException
  530. format:@"Can't keep query on .info tree synced (this "
  531. @"already is the case)."];
  532. }
  533. dispatch_async([FIRDatabaseQuery sharedQueue], ^{
  534. [self.repo keepQuery:self.querySpec synced:keepSynced];
  535. });
  536. }
  537. - (void)observeSingleEventOfType:(FIRDataEventType)eventType
  538. withBlock:(fbt_void_datasnapshot)block {
  539. [self observeSingleEventOfType:eventType
  540. withBlock:block
  541. withCancelBlock:nil];
  542. }
  543. - (void)observeSingleEventOfType:(FIRDataEventType)eventType
  544. andPreviousSiblingKeyWithBlock:(fbt_void_datasnapshot_nsstring)block {
  545. [self observeSingleEventOfType:eventType
  546. andPreviousSiblingKeyWithBlock:block
  547. withCancelBlock:nil];
  548. }
  549. - (void)observeSingleEventOfType:(FIRDataEventType)eventType
  550. withBlock:(fbt_void_datasnapshot)block
  551. withCancelBlock:(fbt_void_nserror)cancelBlock {
  552. // XXX: user reported memory leak in method
  553. // "When you copy a block, any references to other blocks from within that
  554. // block are copied if necessary—an entire tree may be copied (from the
  555. // top). If you have block variables and you reference a block from within
  556. // the block, that block will be copied."
  557. // http://developer.apple.com/library/ios/#documentation/cocoa/Conceptual/Blocks/Articles/bxVariables.html#//apple_ref/doc/uid/TP40007502-CH6-SW1
  558. // So... we don't need to do this since inside the on: we copy this block
  559. // off the stack to the heap.
  560. // __block fbt_void_datasnapshot userCallback = [callback copy];
  561. [self observeSingleEventOfType:eventType
  562. andPreviousSiblingKeyWithBlock:^(FIRDataSnapshot *snapshot,
  563. NSString *prevName) {
  564. if (block != nil) {
  565. block(snapshot);
  566. }
  567. }
  568. withCancelBlock:cancelBlock];
  569. }
  570. /**
  571. * Attaches a listener, waits for the first event, and then removes the listener
  572. */
  573. - (void)observeSingleEventOfType:(FIRDataEventType)eventType
  574. andPreviousSiblingKeyWithBlock:(fbt_void_datasnapshot_nsstring)block
  575. withCancelBlock:(fbt_void_nserror)cancelBlock {
  576. // XXX: user reported memory leak in method
  577. // "When you copy a block, any references to other blocks from within that
  578. // block are copied if necessary—an entire tree may be copied (from the
  579. // top). If you have block variables and you reference a block from within
  580. // the block, that block will be copied."
  581. // http://developer.apple.com/library/ios/#documentation/cocoa/Conceptual/Blocks/Articles/bxVariables.html#//apple_ref/doc/uid/TP40007502-CH6-SW1
  582. // So... we don't need to do this since inside the on: we copy this block
  583. // off the stack to the heap.
  584. // __block fbt_void_datasnapshot userCallback = [callback copy];
  585. __block FIRDatabaseHandle handle;
  586. __block BOOL firstCall = YES;
  587. fbt_void_datasnapshot_nsstring callback = [block copy];
  588. fbt_void_datasnapshot_nsstring wrappedCallback =
  589. ^(FIRDataSnapshot *snap, NSString *prevName) {
  590. if (firstCall) {
  591. firstCall = NO;
  592. [self removeObserverWithHandle:handle];
  593. callback(snap, prevName);
  594. }
  595. };
  596. fbt_void_nserror cancelCallback = [cancelBlock copy];
  597. handle = [self observeEventType:eventType
  598. andPreviousSiblingKeyWithBlock:wrappedCallback
  599. withCancelBlock:^(NSError *error) {
  600. [self removeObserverWithHandle:handle];
  601. if (cancelCallback) {
  602. cancelCallback(error);
  603. }
  604. }];
  605. }
  606. - (NSString *)description {
  607. return [NSString
  608. stringWithFormat:@"(%@ %@)", self.path, self.queryParams.description];
  609. }
  610. - (FIRDatabaseReference *)ref {
  611. return [[FIRDatabaseReference alloc] initWithRepo:self.repo path:self.path];
  612. }
  613. @end