Encode/DecodeBase64 in iOS?

I’m not having a great day finding information. What is the equivalent in EncodeBase64/DecodeBase64 in the new framework?

There is none. I needed encodeBase64, so I made my own. It is in the XojoiOSWrapper GitHub - Mitchboo/XojoiOSWrapper: Module that brings legacy and additional functions to Xojo iOS

I built it with the info at Base64 - Wikipedia . It is not entirely bullet proof, though. Pass 10K data, it crashes randomly. I don’t know if it is dues to memory management or something else, and plan on coming back to it soon, as I need it to process >20K files in a future project.

DecodeBase64 should not be terribly complicated to achieve.

Ug. Seems like a major oversight. I would expect that to be added in another round.

Thanks for the info. For now I’ll use your method.

I can’t seem to find it in the project. I’m not on my regular development computer today but I’ll look at it again tomorrow.

Function EncodeBase64(Extends t As Text) As Text
Declare Function dataWithBytes Lib “UIKit” Selector “dataWithBytes:length:” (class_id As Ptr, bytes As Ptr, length As UInt32) As Ptr
Declare Function base64EncodedStringWithOptions Lib “UIKit” Selector “base64EncodedStringWithOptions:” (class_id As Ptr, options As UInt32) As CFStringRef

// Create NSData pointer to be point of reference.
Dim data As Ptr
data = NSClassFromString(“NSData”)

// Create global ASCII TextEncoding
Dim te As Xojo.Core.TextEncoding
te = Xojo.Core.TextEncoding.FromIANAName(“ISO-8859-1”)

// Convert Text to MemoryBlock
Dim tmb As Xojo.Core.MemoryBlock
tmb = te.ConvertTextToData(t)

// Create NSData object using MemoryBlock
Dim dataRef as Ptr = dataWithBytes(data, tmb.Data, tmb.Size)

// Create Text object to hold Base64 encoded string.
Dim x As Text
x = base64EncodedStringWithOptions(dataRef, 0)

// Return Base64 encoded string
Return x
End Function


Enjoy!

Philip, do you have a decode version as well that you’d be willing to share?

Encode/Decode using declares:
Add a constant “FoundationLib = Foundation.framework”

[code]
Function decodeBase64(aText as text) As Text
declare function initWithBase64EncodedString lib FoundationLib selector “initWithBase64EncodedString:options:” _
(obj_id as ptr, str as CFStringRef, options as Integer) as ptr
declare function alloc lib FoundationLib selector “alloc” (clsRef as ptr) as ptr
declare function NSClassFromString lib FoundationLib (clsName as CFStringRef) as ptr

dim mData as ptr = initWithBase64EncodedString(alloc(NSClassFromString(“NSData”)), aText, 1)

const NSUTF8StringEncoding = 4

declare function initWithData lib FoundationLib selector “initWithData:encoding:” (obj_id as ptr, data as ptr, encoding as Integer) as CFStringRef
Return initWithData(alloc(NSClassFromString(“NSString”)), mData, NSUTF8StringEncoding)
End Function[/code]

Function encodeBase64(aText as Text) As Text
  declare function dataUsingEncoding lib FoundationLib selector "dataUsingEncoding:" (obj_id as ptr, encoding as Integer) as ptr
  declare function stringWithString lib FoundationLib selector "stringWithString:" (obj_id as ptr, str as CFStringRef) as ptr
  declare function NSClassFromString lib FoundationLib (clsName as CFStringRef) as ptr
  
  const NSUTF8StringEncoding = 4
  dim str as Ptr = stringWithString(NSClassFromString("NSString"), aText)
  dim mData as ptr = dataUsingEncoding(str,NSUTF8StringEncoding)
  
  declare function base64EncodedStringWithOptions lib FoundationLib selector "base64EncodedStringWithOptions:" _
  (obj_id as ptr, options as Integer) as CFStringRef
  
  Return base64EncodedStringWithOptions(mData,1)
End Function

Honestly no because I haven’t needed to decode yet.

I’ve spent like two weeks writing an abstraction/porting library. Too much code doesn’t work between Desktop/iOS. So I have replaced all the TCP/HTTP sockets, Timers, EasyTCPSockets, etc.

They mimic the new framework exactly but gracefully downgrade to the old framework on Desktop apps.

[quote=157021:@Phillip Zedalis]Function EncodeBase64(Extends t As Text) As Text

Enjoy![/quote]

Hey, that is neat :slight_smile:

Thank you Phillip !

[quote=157024:@Jason King]Encode/Decode using declares:
[/quote]

Whaoo !

Great ! Thank you Jason.

Challenge accepted! :slight_smile:

Ever interested in learning, I came up with this. I confirmed that it works in iOS, although I have not done extensive testing against the native Xojo methods:

Encode:

Protected Function EncodeBase64(t As Text) As Text
  if t.Empty then
    return ""
  end if
  
  dim lookup() as Text = Base64LookupTable
  
  dim maskByte1 as UInt8 = &b11111100
  dim maskByte2 as UInt16 = &b0000001111110000
  dim maskByte3 as UInt16 = &b0000111111000000
  dim maskByte4 as Uint8 = &b00111111
  
  //
  // Pad the text if needed
  //
  dim nullCount as integer = t.Length mod 3
  if nullCount <> 0 then
    nullCount = 3 - nullCount
    t = t + Text.FromUnicodeCodepoint( 0 )
  end if
  
  dim chars() as text
  dim mb as MemoryBlock
  #if TargetIOS then
    mb = Xojo.Core.TextEncoding.UTF8.ConvertTextToData( t )
  #else
    mb = t
  #endif
  mb.LittleEndian = false
  
  
  dim lastByteIndex as integer = mb.Size - 1
  for masterIndex as integer = 0 to lastByteIndex step 3
    dim byteIndex as integer = masterIndex
    
    dim lookupIndex as integer = mb.UInt8Value( byteIndex ) and maskByte1
    lookupIndex = lookupIndex \\ 4
    chars.Append lookup( lookupIndex )
    
    if byteIndex = lastByteIndex then
      exit
    end if
    
    lookupIndex = mb.UInt16Value( byteIndex ) and maskByte2
    lookupIndex = lookupIndex \\ 16
    chars.Append lookup( lookupIndex )
    
    byteIndex = byteIndex + 1
    if byteIndex = lastByteIndex then
      exit
    end if
    
    lookupIndex = mb.UInt16Value( byteIndex ) and maskByte3
    lookupIndex = lookupIndex \\ 64
    chars.Append lookup( lookupIndex )
    
    byteIndex = byteIndex + 1
    
    lookupIndex = mb.UInt8Value( byteIndex ) and maskByte4
    chars.Append lookup( lookupIndex )
  next
  
  if nullCount = 2 then
    chars.Append "=="
  elseif nullCount = 1 then
    chars( chars.Ubound ) = "="
  end if
  
  dim whole as Text = Text.Join( chars, "" )
  
  dim sections() as Text
  dim lastTextIndex as integer = whole.Length - 1
  for i as integer = 0 to lastTextIndex step 76
    dim lineLen as integer = if( i + 76 <= lastTextIndex, 76, lastTextIndex - i + 1 )
    sections.Append whole.Mid( i, lineLen )
  next i
  
  static eol as Text = Text.FromUnicodeCodepoint( 13 ) + Text.FromUnicodeCodepoint( 10 )
  dim r as text = Text.Join( sections, eol )
  return r
End Function

Private Function Base64LookupTable() As Text()
  static r() as text
  if r.Ubound = -1 then
    dim bunch as Text = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
    for each char as text in bunch.Characters
      r.Append char
    next
  end if
  
  return r
End Function

Decode:

Protected Function DecodeBase64(encoded As Text) As MemoryBlock
  static last4BitsMask as UInt8 = &b00001111
  static last2BitsMask as UInt8 = &b00000011
  
  if encoded.Empty then
    return nil
  end if
  
  //
  // Get rid of the EOL
  //
  encoded = encoded.ReplaceAll( Text.FromUnicodeCodepoint( 10 ), "" )
  encoded = encoded.ReplaceAll( Text.FromUnicodeCodepoint( 13 ), "" )
  
  if encoded.Length mod 4 <> 0 then
    raise new OutOfBoundsException
  end if
  
  dim nullCount as integer
  if encoded.Right( 2 ) = "==" then
    nullCount = 2
  elseif encoded.Right( 1 ) = "=" then
    nullCount = 1
  end if
  
  dim mb as MemoryBlock = TextEncoding.UTF8.ConvertTextToData( encoded )
  dim result as new MemoryBlock( ( mb.Size * 3 / 4 ) - nullCount )
  dim resultIndex as integer
  
  dim lastByteIndex as integer = mb.Size - 1
  dim sourcePtr as Ptr = mb.Data
  dim resultPtr as Ptr = result.Data
  
  for i as integer = 0 to lastByteIndex step 4
    dim raw1 as UInt8 = sourcePtr.Byte( i )
    dim raw2 as UInt8 = sourcePtr.Byte( i + 1 )
    dim raw3 as UInt8 = sourcePtr.Byte( i + 2 )
    dim raw4 as UInt8 = sourcePtr.Byte( i + 3 )
    
    dim lookup1 as UInt8 = Base64ValueOf( raw1 )
    dim lookup2 as UInt8 = Base64ValueOf( raw2 )
    dim lookup3 as UInt8 = Base64ValueOf( raw3 )
    dim lookup4 as UInt8 = Base64ValueOf( raw4 )
    
    dim thisByte as UInt8
    
    thisByte = ( lookup1 * 4 ) + ( lookup2 \\ 16 )
    resultPtr.Byte( resultIndex ) = thisByte
    resultIndex = resultIndex + 1
    
    if resultIndex >= result.Size then
      exit
    end if
    
    dim last4Bits as UInt8 = lookup2 and last4BitsMask
    dim first4Bits as UInt8 = lookup3 \\ 4
    thisByte = ( last4Bits * 16 ) + first4Bits
    resultPtr.Byte( resultIndex ) = thisByte
    resultIndex = resultIndex + 1
    
    if resultIndex >= result.Size then
      exit
    end if
    
    dim last2Bits as UInt8 = lookup3 and last2BitsMask
    thisByte = ( last2Bits * 64 ) + lookup4
    resultPtr.Byte( resultIndex ) = thisByte
    resultIndex = resultIndex + 1
    
  next
  
  return result
End Function

Private Function Base64ValueOf(byteValue As Integer) As UInt8
  select case byteValue
  case 65 to 90 // A to Z
    return byteValue - 65
    
  case 97 to 122 // a to z
    return 26 + ( byteValue - 97 )
    
  case 48 to 57 // 0 - 9
    return 52 + ( byteValue - 48 )
    
  case 43 // +
    return 62
    
  case 47 // /
    return 63
    
  case 61 // =
    return 0
    
  else
    raise new OutOfBoundsException
  end
End Function

Thanks for the responses guys. However, with both the iOS and Xojo code it doesn’t work well with large amounts of data. Not quite sure what’s going on.

Here’s my current task: I’m sending data from Xojo desktop app via a JSON string that’s encoded to an iOS app. The JSON string is kind of similar to what EasyTCP does with a Command and Data but before I send it I Base64 encoded and add my end of message string.

When I send simple data there’s no issue. When I send something larger (like an image) that’s when things start to go wonky with either DecodeBase64 solution. With the Xojo solution I get an out of bounds exceptions on large data. With the iOS declares I get blank strings back and it doesn’t have to be quite as big. I am limiting my data chunks to 500 KB at a time but those sizes seem to overwhelm iOS.

Any advice on where to go next?

[quote=159755:@Bob Keeney]Thanks for the responses guys. However, with both the iOS and Xojo code it doesn’t work well with large amounts of data. Not quite sure what’s going on.

Here’s my current task: I’m sending data from Xojo desktop app via a JSON string that’s encoded to an iOS app. The JSON string is kind of similar to what EasyTCP does with a Command and Data but before I send it I Base64 encoded and add my end of message string.

When I send simple data there’s no issue. When I send something larger (like an image) that’s when things start to go wonky with either DecodeBase64 solution. With the Xojo solution I get an out of bounds exceptions on large data. With the iOS declares I get blank strings back and it doesn’t have to be quite as big. I am limiting my data chunks to 500 KB at a time but those sizes seem to overwhelm iOS.

Any advice on where to go next?[/quote]

I had written a Xojo encodeBase64 during beta which worked perfectly for small files but exploded when data was too big as well. Seems memory management loses its marbles past a certain size.

I would have hopped the declare methods t work better, as they make use of Apple own framework…

I shall use them soon in a coming app, but file size will probably never exceed 200 K (font files).

Yeah, I started off using Kem’s code which worked great for small amounts of data. After it failed with large image files I switched to the declare version and it fails on even smaller files.

Major drag to not have a stable and safe way to do this. That immediately kills two or three of my upcoming projects. :frowning:

I wonder if all that could not be related to memory management. That OutOfMemoryException is particularly worrysome. It seems iOS is somewhat less flexible than Mac OS X.

Kem’s method, like mine, makes use of a lot of operations on memoryblocks, and this in particular may explain :

Now, how would one do some house cleaning to prevent issues ?

I bet one of you “declare” experts can do something with this…

Swift Code

let plainString = “foo”

[h]Encoding[/h]

let plainData = plainString.dataUsingEncoding(NSUTF8StringEncoding) let base64String = plainData?.base64EncodedStringWithOptions(.allZeros) println(base64String!) // Zm9v

[h]Decoding[/h]

let decodedData = NSData(base64EncodedString: base64String!, options: .allZeros) let decodedString = NSString(data: decodedData, encoding: NSUTF8StringEncoding) println(decodedString) // foo

[quote=159773:@Dave S]I bet one of you “declare” experts can do something with this…

Swift Code

let plainString = “foo”

[h]Encoding[/h]

let plainData = plainString.dataUsingEncoding(NSUTF8StringEncoding) let base64String = plainData?.base64EncodedStringWithOptions(.allZeros) println(base64String!) // Zm9v

[h]Decoding[/h]

let decodedData = NSData(base64EncodedString: base64String!, options: .allZeros) let decodedString = NSString(data: decodedData, encoding: NSUTF8StringEncoding) println(decodedString) // foo[/quote]

That is exactly what Jason King did :wink:

What about looping through and appending the results to a file in chunks? If you split your string into, say, chunks of 5700 bytes, encode those and append it to a file, the memory limitations shouldn’t come into play.

(I came up with 5700 this way: Each encoded line is 76 characters, and 3/4 of 76 is 57, so each line represents 57 bytes of data. 100 lines represents 5700 bytes of data.)

[quote=159779:@Kem Tekinay]What about looping through and appending the results to a file in chunks? If you split your string into, say, chunks of 5700 bytes, encode those and append it to a file, the memory limitations shouldn’t come into play.

(I came up with 5700 this way: Each encoded line is 76 characters, and 3/4 of 76 is 57, so each line represents 57 bytes of data. 100 lines represents 5700 bytes of data.)[/quote]

57 will work. Divisible by 3, it won’t produce any = at the end, so the chunks can be concatenated. Only the last chunk may be less, and the = will then not be an issue. Excellent idea, Kem :slight_smile:

I just hope garbage collection is efficient, and that manipulating memoryblocks that many times will not leak.

In a desktop app, I’d recommend reusing the same MemoryBlocks and writing the chunks to them with StringValue. I don’t see how to do the equivalent in an iOS project.