2

I am working on changing the code created in objective c to swift3.
I want to change the code below to the swift3 code created with objective c.

Objective c NSDate to NSData code :

NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
NSDateComponents *components = [calendar components:NSDayCalendarUnit |NSMonthCalendarUnit | NSYearCalendarUnit | NSHourCalendarUnit | NSMinuteCalendarUnit | NSSecondCalendarUnit fromDate:[NSDate date]];

NSInteger year = components.year;
NSMutableData *yearData = [[NSMutableData alloc] initWithBytes:&year length:sizeof(year)];
int year1 = *(int *)[[yearData subdataWithRange:NSMakeRange(0, 1)] bytes];
int year2 = *(int *)[[yearData subdataWithRange:NSMakeRange(1, 1)] bytes];
int month = components.month;
int day = components.day;
int hour = components.hour;
int min = components.minute;
int second = components.second;

char bytes[7];
bytes[0] = year1;
bytes[1] = year2;
bytes[2] = month;
bytes[3] = day;
bytes[4] = hour;
bytes[5] = min;
bytes[6] = second;
NSData *data = [[NSData alloc] initWithBytes:&bytes length:sizeof(bytes)];

Objective c NSData to NSDate code :

NSData *date = [[NSData alloc] initWithData:characteristic.value];
int year = *(int *)[[date subdataWithRange:NSMakeRange(0, 2)] bytes];
int month = *(int *)[[date subdataWithRange:NSMakeRange(2, 1)] bytes];
int day = *(int *)[[date subdataWithRange:NSMakeRange(3, 1)] bytes];
int hour = *(int *)[[date subdataWithRange:NSMakeRange(4, 1)] bytes];
int minutes = *(int *)[[date subdataWithRange:NSMakeRange(5, 1)] bytes];
int seconds = *(int *)[[date subdataWithRange:NSMakeRange(6, 1)] bytes];

NSLog(@"year %d month %d day %d hour %d minutes %d second %d", year, month, day, hour, minutes, seconds); //year 2017 month 7 day 13 hour 16 minutes 8 second 2

NSDateComponents *components = [[NSDateComponents alloc] init];
[components setYear:year];
[components setMonth:month];
[components setDay:day];
[components setHour:hour];
[components setMinute:minutes];
[components setSecond:seconds];
NSCalendar *calendar = [NSCalendar currentCalendar];
self.time = [calendar dateFromComponents:components];

Swift Date to Data code :

let cal = Calendar(identifier: .gregorian)
var comp = cal.dateComponents([.day,.month,.year,.hour,.minute,.second], from: Date())
var year = comp.year
let yearData:Data = Data(bytes: &year, count: MemoryLayout.size(ofValue: year))
let year1:Data = yearData.subdata(in: 0..<1)
let year2:Data = yearData.subdata(in: 1..<2)
let settingArray = [UInt8]([
      UInt8(year1[0])
    , UInt8(year2[0])
    , UInt8(comp.month!)
    , UInt8(comp.day!)
    , UInt8(comp.hour!)
    , UInt8(comp.minute!)
    , UInt8(comp.second!)
])
let settingData:Data = Data(bytes: settingArray, count: MemoryLayout.size(ofValue: settingArray))

Swift Data to Date code :

var yearVal:UInt8 = 0
let year = characteristic.value?.subdata(in: 0..<2)
year?.copyBytes(to: &yearVal, count: MemoryLayout.size(ofValue: year))
var month = characteristic.value?.subdata(in: 2..<3)
var day = characteristic.value?.subdata(in: 3..<4)
var hour = characteristic.value?.subdata(in: 4..<5)
var minutes = characteristic.value?.subdata(in: 5..<6)
var seconds = characteristic.value?.subdata(in: 6..<7)
print("year = \(yearVal), month = \(Int((month?[0])!)), day = \(Int((day?[0])!)), hour = \(Int((hour?[0])!)), minutes = \(Int((minutes?[0])!)), seconds = \(Int((seconds?[0])!))") // year = 225, month = 7, day = 13, hour = 15, minutes = 56, seconds = 56

When I modify the let year = characteristic.value?.subdata(in: 0..<2) part, the conversion value should be 2017. However, only 225 values are output. I do not know how to solve this part.

Please help me.

bongpro
  • 23
  • 1
  • 3
  • What are you trying to achieve by converting `Date` to `Data`? – mag_zbc Jul 13 '17 at 10:00
  • @mag_zbc BLE The purpose is to set the time when pairing to a BLE device. In addition, I am worried about how to implement this part in swift `*(int *)[[date subdataWithRange:NSMakeRange(0, 2)] bytes]` in Objective c code. – bongpro Jul 13 '17 at 10:17
  • Why you don't convert your date into NSString and then NSData , and your NSData ----> NSString ----> NSDate. – Parvendra Singh Jul 13 '17 at 10:27

2 Answers2

1

You are very lucky your Objective-C code works as you are reading unassigned memory and ignoring endian issues.

Consider the line:

int month = *(int *)[[date subdataWithRange:NSMakeRange(2, 1)] bytes];

Here you are taking a pointer to a single byte, casting it to a pointer to 4 bytes (the size on an int), and then reading 4 bytes and storing them in month. By luck the extra three bytes you read happen to be zero.

Then there is the endian issue, different cpu architectures store multi-byte values in different orders in memory. A little-endian architecture stores the least significant byte first, a big-endian one the most significant.

E.g. the 4-byte integer 0xDEADBEEF is stored as the byte sequence EF, BE, AD, DE on a little-endian machine and as DE, AD, BE, EF on a big-endian one. What this means in terms of your month value above is if the byte is 06 then you might get back the integer 0x06000000 when you read those 4 bytes (and only if those extra bytes are zeroes).

For the month case you could load the byte and then convert to an integer:

int month = (int *)(*(Byte *)[[date subdataWithRange:NSMakeRange(2, 1)] bytes]);

When converting the year to two bytes you go through the long winded process:

NSMutableData *yearData = [[NSMutableData alloc] initWithBytes:&year length:sizeof(year)];
int year1 = *(int *)[[yearData subdataWithRange:NSMakeRange(0, 1)] bytes];
int year2 = *(int *)[[yearData subdataWithRange:NSMakeRange(1, 1)] bytes];

This converts an integer to an NSData, makes to more NSData values containing 1 byte each, and then loads 4 bytes for each - the same issue as above, but in this case as you will only be storing 1 byte in your bytes array it doesn't matter if the extra bytes are garbage.

The process is convoluted, you would be better off sticking with integer operations to obtain the two values. You can obtain the individual bytes using division and remainder operations, or bit-wise shift and mask operations.

E.g. using decimal first to demonstrate:

int year = 2017;
int firstDigit = year % 10;           // the remainder of year / 10 => 7
int secondDigit = (year / 10) % 10;   // 1
int thirdDigit = (year / 100) % 10;   // 0
int fourthDigit = (year / 1000) % 10; // 2

To extract the bytes just change the divisor:

int year = 2017;                 // = 0x7E1
int loByte = year % 256;         // = 0xE1
int hiByte = (year / 256) % 256; // = 0x7

Finally you can use bit-wise shift and masking:

int year = 2017;                 // = 0x7E1
int loByte = year & 0xFF;        // = 0xE1
int hiByte = (year >> 8) & 0xFF; // = 0x7

Using bit-wise operations makes the byte splitting more obvious, but divide and remainder achieve the same result.

What does all this mean in terms of your Objective-C code? Well the second of your two methods can be written:

+ (NSDate *) dataToDate:(NSData *)data
{
   NSDateComponents *components = [[NSDateComponents alloc] init];
   const Byte *bytes = data.bytes;
   components.year = (NSInteger)bytes[0] | ((NSInteger)bytes[1] << 8); // reassemble 2-byte value
   components.month = (NSInteger)bytes[2];
   components.day = (NSInteger)bytes[3];
   components.hour = (NSInteger)bytes[4];
   components.minute = (NSInteger)bytes[5];
   components.second = (NSInteger)bytes[6];

   NSCalendar *calendar = [NSCalendar currentCalendar];

   return[calendar dateFromComponents:components];
}

This is a lot less complex, doesn't read random memory, and is easier to convert to Swift.

Following the same approach here is your first method in Swift:

func toData(_ date : Date) -> Data
{
   let cal = Calendar(identifier: .gregorian)
   let comp = cal.dateComponents([.day,.month,.year,.hour,.minute,.second], from: date)
   let year = comp.year!
   let yearLo = UInt8(year & 0xFF) // mask to avoid overflow error on conversion to UInt8
   let yearHi = UInt8(year >> 8)
   let settingArray = [UInt8]([
      yearLo
      , yearHi
      , UInt8(comp.month!)
      , UInt8(comp.day!)
      , UInt8(comp.hour!)
      , UInt8(comp.minute!)
      , UInt8(comp.second!)
      ])
   return Data(bytes: settingArray)
}

Finally, you can index the Data type in Swift lust like an array, so the above Objective-C line:

components.month = (NSInteger)bytes[2];

where bytes came from calling NSData's bytes can be written directly in Swift as:

components.month = Int(data[2])

where data is the Data value.

The above approach doesn't answer the issue you actually had, because it avoids messing with splitting data values into bits and trying to extra values from them - just index the byte and convert with a cast.

The rest of the code you need is left as an excercise!

HTH

CRD
  • 52,522
  • 5
  • 70
  • 86
  • There is another problem in the original code which is still present in your answer: `MemoryLayout.size(ofValue: settingArray)` is *not* the number of array elements. Therefore you'll get 8 bytes data instead of 7. It should be `Data(bytes: settingArray, count: settingArray.count)` or simply `Data(bytes: settingArray)`. – Martin R Jul 14 '17 at 06:42
  • 1
    @MartinR - Thanks, I'd missed that completely. Confirmed and edited. – CRD Jul 14 '17 at 08:53
  • @CRD I am sorry to reply late. I solved the problem with a slight modification in the way you suggested. Thank you very much. – bongpro Jul 18 '17 at 04:28
-1

you are fetching year value as UInt8 which only have range of 0-255 so use UInt32

var yearVal: UInt32 = 0
(year as! NSData).getBytes(&yearVal, length: MemoryLayout.size(ofValue: year))
tailor
  • 680
  • 4
  • 12