CLBeaconRegion *region;
NSMutableDictionary *beacons;
....
beacons[region] = beacons;
Is the code above setting beacons key as region to the value of beacons? I thought the key has to be a string in a NSDictionary/NSMutableDictionary?
When you say:
beacons[region] = beacons;
That's the same as:
[beacons setObject:beacons forKey:region];
The first is just syntactical shortcut for second. Either way, it's probably not what you want, since it doesn't usually make sense to set a dictionary key/value pair that points back to the dictionary itself. What are you actually trying to do?
The key in a dictionary doesn't have to be a string, but it does have to conform to the NSCopying protocol, see the definition:
- (void)setObject:(id)anObject
forKey:(id<NSCopying>)aKey
beacons[region] = beacons; seems to be incorrect as you are adding the beacons dictionary to itself under the key region.
Note that when using key-value coding the key must be a string see Key-Value Coding Fundamentals.
Related
According to the Swift 2.0 documentation for CLBeaconRegion, it should still be possible to pass the output of the peripheralDataWithMeasuredPower: method to the startAdvertising: method of CLPeripheralManager.
Getting Beacon Advertisement Data
- peripheralDataWithMeasuredPower:
Retrieves data that can be used to advertise the current device as a beacon.
Declaration
SWIFT
func peripheralDataWithMeasuredPower(_measuredPower: NSNumber?) -> NSMutableDictionary
OBJECTIVE-C
- (NSMutableDictionary<NSString *,id> * _Nonnull)peripheralDataWithMeasuredPower:(NSNumber * _Nullable)measuredPower
Parameters
measuredPower The received signal strength indicator (RSSI) value (measured in decibels) for the device.
This value represents the measured strength of the beacon from one
meter away and is used during ranging. Specify nil to use the default
value for the device.
Return Value
A dictionary of data that you can
use in conjunction with a CBPeripheralManager to advertise the current
device as a beacon.
Discussion
The returned dictionary encodes the beacon’s identifying
information along with other information needed to advertise the
beacon. You should not need to access the dictionary contents
directly. Pass the dictionary to the startAdvertising: method of a
CBPeripheralManager to begin advertising the beacon.
Availability
Available in iOS 7.0 and later.
However, peripheralDataWithMeasuredPower: returns an NSMutableDictionary whereas the startAdvertising: method of CLPeripheralManager accepts a Swift Dictionary of [String : AnyObject]?, although the documentation contends that it accepts an NSDictionary. The following code that worked in Swift 1.0:
// Set up a beacon region with the UUID, Major and Minor values
let region = CLBeaconRegion(proximityUUID:beaconUUID!, major:withMajor.unsignedShortValue, minor:withMinor.unsignedShortValue, identifier:"com.example.beacon")
// Attempt to set up a peripheral with the measured power
let peripheralData = region.peripheralDataWithMeasuredPower(withPower)
_peripheralManager!.startAdvertising(peripheralData)
In Swift 2.0 the same code fails to compile with a warning:
fails to compile, with a warning:
NSDictionary is not convertible to [String : AnyObject]; did you
mean to use as! to force downcast?
However, forcing a downcast always fails.
Is this a documentation bug, a bug in Swift 2.0, or am I missing something?
The problem seems to be that NSMutableDictionary is not easily convertible to Swift's Dictionary, but NSDictinoary is. So I ended up converting the NSMutableDictionary to a NSDictinoary first:
let pd = NSDictionary(dictionary: region.peripheralDataWithMeasuredPower(nil))
as! [String: AnyObject]
peripheralManager.startAdvertising(pd)
And it works!
I am getting a initWithObjects:count:]: attempt to insert nil object from objects[0] at the following line:
[contentsOfCocktails setObject:[NSMutableArray arrayWithObject: recipeTitleA] forKey:cocktailsTitleA];
recipeTitleA is the string I'm creating from the cocktails.recipeID class property that equals A. However, I am getting a recipeTitleA equals nil in the debug window.
Here is where I set cocktails.recipeID equal to recipeTitleA:
if ([cocktails.recipeID isEqualToString:#"A"]){
recipeTitleA = cocktails.recipeID;
}
Is this the correct way to set a string equal to another string in order to use it as a key in a NSMutableDictionary?
Long story short: I am trying to extract the recipeIDs that equal A and set them as a key in a dictionary. I will be doing this with other letters as keys as well. I was then going to store them in an array in which I could create sections in the tableview. Data is brought in with FMDB.
I'm new to obj-c and new to data formatting with arrays and dictionaries. Any help would be greatly appreciated!
It looks like you are doing the string comparison correctly. Set a breakpoint at if ([cocktails.recipeID isEqualToString:#"A"]){ and see what you are getting for cocktails.recipeID, if it's nil go back into your cocktails.recipeID property and look if its being set correctly.
I am trying to save a NSDictionary to NSUserDefaults, and am using MD5 hash to check for integrity, using this helpder class: Secure-NSUserDefaults.
The code to set the Dictionary:
#import "NSUserDefaults+MPSecureUserDefaults.h"
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setSecureObject:aDictionary forKey:aKey];
[defaults synchronize];
The code to retrieve it:
BOOL valid = NO;
NSDictionary * aDictionary = [defaults secureDictionaryForKey:aKey valid:&valid];
if (!valid) {
//... hash doesn't match
} else {
//... hash matches
}
This works great as long as the app is running (testing in the simulator right now), but when I exit the simulator and restart the app, the hash value is different than before.
It's as if exiting the app changes the dictionary value (when it's saved to disk perhaps?) in some way. It's not adding visible characters, though, because it looks exactly the same in the debugger.
Would appreciate any ideas from more experienced programmers!
EDIT:
So this seems to work for me. Thoughts?
Change NSUserDefaults+MPSecureUserDefaults.m like so:
- (NSString *)_hashObject:(id)object
{
if (_secretData == nil) {
// Use if statement in case asserts are disabled
NSAssert(NO, #"Provide a secret before using any secure writing or reading methods!");
return nil;
}
// Copy object to make sure it is immutable (thanks Stephen)
object = [object copy];
//added check for array or dictionary
if ([NSJSONSerialization isValidJSONObject:object]) {
NSMutableData *archivedData = [[NSJSONSerialization dataWithJSONObject:object options:0 error:nil] mutableCopy];
[archivedData appendData:_secretData];
if (_deviceIdentifierData != nil) {
[archivedData appendData:_deviceIdentifierData];
}
NSString *hash = [self _hashData:archivedData];
return hash;
}
// Archive & hash
NSMutableData *archivedData = [[NSKeyedArchiver archivedDataWithRootObject:object] mutableCopy];
[archivedData appendData:_secretData];
if (_deviceIdentifierData != nil) {
[archivedData appendData:_deviceIdentifierData];
}
NSString *hash = [self _hashData:archivedData];
////[archivedData release];
return hash;
}
The code you are using, Secure-NSUserDefaults, is incorrect.
The code makes assumption about NSKeyedArchiver's archivedDataWithRootObject: which are invalid - namely that if two dictionaries are the same then the archived version of them is the same. The internal ordering of key/value pairs in a dictionary is not defined, two dictionaries can be semantically the same while being structurally different - and if they are structurally different their archived version of them may be also.
Either write your own or fix the library you have used. You need to deal with dictionaries as an ordered collection of key/values pairs - say by sorting based on the key as NSLog does when printing them.
HTH
Addendum: After question edit
NSJSONSerialization suffers from the same problem (for this usage) as NSKeyedArchiver, as the simple test I posted on GitHub will show.
It seems you may be missing the core problem here. A dictionary is an unordered collection of key/value pairs. The code you are using is attempting to generate a sequence of bytes which is identical (or at least produces the same hash value) for different dictionaries which contain the same key/value pairs in any order. The issue is compounded as dictionaries/arrays can contain other arrays/dictionaries to any nesting depth.
The obvious way to do generate a byte sequence independent of the (internal) ordering is to order the key/values pairs when producing the byte sequence. However dictionary keys are not required to have an ordering, only an equality, relation.
As there is no ordering requirement on keys the NSKeyedArchiver and NSJSONSerialization cannot assume one exists and so do not guarantee to produce the same byte sequence for dictionaries with the same key/value pairs which are ordered (internally to the type) differently. Furthermore NSKeyedArchiver is preserving the object graph, including any sharing, see Object Graphs, which could also contribute to the differences you observe.
However you are writing property lists and for a dictionary to be valid for inclusion in a property list the keys must be strings (see Apple's About Property Lists). Now strings do have an ordering, e.g. NSString's compare: method, so in this particular case you can order the key/value pairs. So you can either write your own code, or find pre-written code, which produces a byte stream for property list types and orders the dictionary key/value pairs while doing so; then you can use this code in the library you are trying to adopt.
Just an idea how this class may be fixed:
NSDictionary should be archived with NSKeyedArchiver not only to calculate hash over it, but also to be saved like that (archived) in the NSUserDefaults (in opposite to the direct storing as it is done now).
In the get method, upon the hash validation, it will be needed additionally to unarchive it with NSKeyedUnarchiver to get back original value.
Thanks.
I am currently programming the peripheral side of an app. I want to advertise the tx power level, but all i have found as far as tx documentation is:
CB_EXTERN NSString * const CBAdvertisementDataTxPowerLevelKey; // A NSNumber
I have tried to implement this in the following way:
/** Start advertising
*/
- (IBAction)switchChanged:(id)sender
{
[self.peripheralManager startAdvertising:#{ CBAdvertisementDataServiceUUIDsKey : #[[CBUUID UUIDWithString:TRANSFER_SERVICE_UUID]] }];
[self.peripheralManager startAdvertising: CBAdvertisementDataTxPowerLevelKey];
}
#end
I keep getting a warning on my last line of code saying "Incompatible pointer types sending 'NSString*' to parameter of type 'NSDictionary*'. I understand that my TxPowerLevelKey is a NSString, but what is NSDictionary referring to?
Other answers have addressed how your dictionary is defined, however, you were looking for a higher level issue; How to transmit the txPower level from an iOS device.
The answer is that currently you can't. After you fixed your code, it compiled and ran, but CoreBluetooth is simply ignoring that key.
As stated by the documentation:
An optional dictionary containing the data you want to advertise. The
possible keys of an advertisementData dictionary are detailed in
CBCentralManagerDelegate Protocol Reference. That said, only two of
the keys are supported for peripheral manager objects:
CBAdvertisementDataLocalNameKey and
CBAdvertisementDataServiceUUIDsKey.
Hope that helps
In Objective-C, #{} is shorthand for [NSDictionary dictionaryWithObjectsAndKeys:(id), ..., nil]. The warning indicates the -[PeripheralManager startAdvertising] method is expecting an NSDictionary. Try wrapping the key in a dictionary with a Boolean True value (represented as an NSNumber object with #(YES)):
[self.peripheralManager startAdvertising:#{ CBAdvertisementDataTxPowerLevelKey : #(YES)}];
As you don't seem to know what an NSDictionary* object is please see the Apple Documentation for NSDictionary.
But to answer your question the warning
Incompatible pointer types sending 'NSString*' to parameter of type 'NSDictionary*
which is referring to
[self.peripheralManager startAdvertising: CBAdvertisementDataTxPowerLevelKey];
Is because startAdvertising: will be declared something like
- (void)startAdvertising:(NSDictionary *)start;
So it is expecting you to pass in an NSDictionary* object whereas at the moment you are passing in an NSString* object.
You can resolve this in one of two ways. The first way would be to use the short hand way like you have already done here
[self.peripheralManager startAdvertising:#{ CBAdvertisementDataServiceUUIDsKey : #[[CBUUID UUIDWithString:TRANSFER_SERVICE_UUID]] }];
Notice that the shorthand version of an NSDictionary* object starts at #{ and ends at } so the to declare an NSDictionary* object this way it would be something like #{ Key : Object } so for you it would be #{ CBAdvertisementDataTxPowerLevelKey : #(YES) }
The second way of declaring this would be to do it as I would think of the normal way like :
[NSDictionary dictionaryWithObjectsAndKeys:#(YES), CBAdvertisementDataTxPowerLevelKey, nil]
if you have any questions please just ask.
When debugging in XCode, the debugger is telling me that the NSDictionary object contains 1 key/value pair. When the debug console prints the description of the key/value pair is shows:
Printing description of testdictionary:
{
"Unknown (<1809>)" = <000000ff>;
}
I want to extract both the <1809> and the <000000ff>. I have tried both the valueForKey and objectforKey methods as described elsewhere on this site. But I think I am having difficulty understanding what is the key and what is the value here.
For example, is "Unknown (<1809>)" the key? Or is "<1809>" the key? Or is 1809 the key?
Thanks Tim for the reply.
The NSDictionary comes from the CoreBluetoothFramework the didDiscoverPeripheral: method is called and passes advertising data into an NSDictionary called "advertisementData".
This dictionary contains all sorts of stuff like the advertising channel and device name. However, I am trying to extract just the advertising data from "advertisementData". I used the key provided by corebluetooth "CBAdvertisementDataServiceDataKey" like this:
NSData* information;
information = [advertisementData objectForKey:CBAdvertisementDataServiceDataKey];
I was declaring "information" as an NSDictionary* object before. But changed it to NSData* after some more reading on Apples documentation. The result is the same. The debugger says that it contains a key/value pair as follows:
"Unknown (<1809>)" = <000000ff>;
Thanks again.
Nik
When you do not know the keys that are present in the dictionary, for example, because the key-value pairs come from an external source, you can use enumerateKeysAndObjectsUsingBlock: method to go through all key-value pairs present in the dictionary:
[testdictionary enumerateKeysAndObjectsUsingBlock::^(id key, id object, BOOL *stop) {
NSLog(#"The key is %#", key);
NSLog(#"The value is %#", object);
}];
I've never seen this before so this is nothing more than an educated guess:
The dictionary may have been casted from CFDictionaryRef, in which case both the key and value are const void * (instead of NSObject). The key might have been some Core Foundation type holding a file descriptor (hence 1809). The value could be a pointer (or an integer casted to a "pointer": (void *)32).
You should try and find out where the dictionary originates from, because it's the only thing that's going to give you any valuable information.
Update: the docs state that the value of CBAdvertisementDataServiceDataKey is a dictionary. The keys are CBUUID objects, representing CBService UUIDs and the values are NSData objects. (1)