How do I pass a callback function to sqlite3_exec in Swift?
sqlite_str = sqlite_str + "\(sqlite_property_str))";
var str:NSString = sqlite_str;
var sqlite:COpaquePointer = share().sqlite3_db;
var errmsg:UnsafePointer<Int8> = nil
let rc = sqlite3_exec(sqlite, str.cStringUsingEncoding(NSUTF8StringEncoding), <#callback: CFunctionPointer<((UnsafePointer<()>, Int32, UnsafePointer<UnsafePointer<Int8>>, UnsafePointer<UnsafePointer<Int8>>) -> Int32)>#>, <#UnsafePointer<()>#>, <#errmsg: UnsafePointer<UnsafePointer<Int8>>#>)
Swift 2.2 provides two options for implementing the sqlite3_exec callback function: (1) a global, non-instance func procedure or (2) a non-capturing literal {} closure.
The sqlite.org's "SQLite in 5 minutes or less" example is implemented in a Swift Xcode7 project here.
Readable typealias
typealias sqlite3 = COpaquePointer
typealias CCharHandle = UnsafeMutablePointer<UnsafeMutablePointer<CChar>>
typealias CCharPointer = UnsafeMutablePointer<CChar>
typealias CVoidPointer = UnsafeMutablePointer<Void>
Callback Approach
func callback(
resultVoidPointer: CVoidPointer, // void *NotUsed
columnCount: CInt, // int argc
values: CCharHandle, // char **argv
columns: CCharHandle // char **azColName
) -> CInt {
for i in 0 ..< Int(columnCount) {
guard let value = String.fromCString(values[i])
else { continue }
guard let column = String.fromCString(columns[i])
else { continue }
print("\(column) = \(value)")
}
return 0 // status ok
}
func sqlQueryCallbackBasic(argc: Int, argv: [String]) -> Int {
var db: sqlite3 = nil
var zErrMsg:CCharPointer = nil
var rc: Int32 = 0 // result code
if argc != 3 {
print(String(format: "ERROR: Usage: %s DATABASE SQL-STATEMENT", argv[0]))
return 1
}
rc = sqlite3_open(argv[1], &db)
if rc != 0 {
print("ERROR: sqlite3_open " + String.fromCString(sqlite3_errmsg(db))! ?? "" )
sqlite3_close(db)
return 1
}
rc = sqlite3_exec(db, argv[2], callback, nil, &zErrMsg)
if rc != SQLITE_OK {
print("ERROR: sqlite3_exec " + String.fromCString(zErrMsg)! ?? "")
sqlite3_free(zErrMsg)
}
sqlite3_close(db)
return 0
}
Closure Approach
func sqlQueryClosureBasic(argc argc: Int, argv: [String]) -> Int {
var db: sqlite3 = nil
var zErrMsg:CCharPointer = nil
var rc: Int32 = 0
if argc != 3 {
print(String(format: "ERROR: Usage: %s DATABASE SQL-STATEMENT", argv[0]))
return 1
}
rc = sqlite3_open(argv[1], &db)
if rc != 0 {
print("ERROR: sqlite3_open " + String.fromCString(sqlite3_errmsg(db))! ?? "" )
sqlite3_close(db)
return 1
}
rc = sqlite3_exec(
db, // database
argv[2], // statement
{ // callback: non-capturing closure
resultVoidPointer, columnCount, values, columns in
for i in 0 ..< Int(columnCount) {
guard let value = String.fromCString(values[i])
else { continue }
guard let column = String.fromCString(columns[i])
else { continue }
print("\(column) = \(value)")
}
return 0
},
nil,
&zErrMsg
)
if rc != SQLITE_OK {
let errorMsg = String.fromCString(zErrMsg)! ?? ""
print("ERROR: sqlite3_exec \(errorMsg)")
sqlite3_free(zErrMsg)
}
sqlite3_close(db)
return 0
}
This is (currently) not possible. The Xcode 6 beta 4 release notes state:
However, you cannot call a C function pointer or convert a closure to
C function pointer type.
As a workaround, you could put sqlite3_exec together with its callback into a
C wrapper function and call that from Swift.
I wanted to provide an update to #l --marc l answer for Swift 3 and Linux which helped me get up and running. Thank you #l --marc l !
Callback Approach
func callback(
resultVoidPointer: UnsafeMutablePointer<Void>?, // void *NotUsed
columnCount: Int32, // int argc
values:UnsafeMutablePointer<UnsafeMutablePointer<Int8>?>?, // char **argv
columns:UnsafeMutablePointer<UnsafeMutablePointer<Int8>?>? // char **azColName
) -> CInt {
var dic: [String:String] = [:]
for i in 0 ..< Int(columnCount) {
guard let value = values?[i]
else { continue }
guard let column = columns?[i]
else { continue }
let strCol = String(cString:column)
let strVal = String(cString:value)
dic[strCol] = strVal
//print("\(strCol) = \(strVal)")
}
resultSet.append(dic)
return 0 // status ok
}
func sqlQueryCallbackBasic(dbStr:String, query:String) -> Int {
var db: OpaquePointer?
var zErrMsg:UnsafeMutablePointer<Int8>?
var rc: Int32 = 0 // result code
rc = sqlite3_open(dbStr, &db)
if rc != 0 {
print("ERROR: sqlite3_open " + String(sqlite3_errmsg(db)) ?? "" )
sqlite3_close(db)
return 1
}
rc = sqlite3_exec(db, query, callback, nil, &zErrMsg)
if rc != SQLITE_OK {
let errorMsg = zErrMsg
print("ERROR: sqlite3_exec \(errorMsg)")
sqlite3_free(zErrMsg)
}
sqlite3_close(db)
return 0
}
Closure Approach
func sqlQueryClosureBasic(dbStr:String, query:String) -> Int {
var db: OpaquePointer?
var zErrMsg:UnsafeMutablePointer<Int8>?
var rc: Int32 = 0
rc = sqlite3_open(dbStr, &db)
if rc != 0 {
print("ERROR: sqlite3_open " + String(sqlite3_errmsg(db)) ?? "" )
sqlite3_close(db)
return 1
}
rc = sqlite3_exec(
db, // database
query, // statement
{ // callback: non-capturing closure
resultVoidPointer, columnCount, values, columns in
var dic: [String:String] = [:]
for i in 0 ..< Int(columnCount) {
guard let value = values?[i]
else { continue }
guard let column = columns?[i]
else { continue }
let strCol = String(cString:column)
let strVal = String(cString:value)
dic[strCol] = strVal
//print("\(strCol) = \(strVal)")
}
resultSet.append(dic)
return 0
},
nil,
&zErrMsg
)
if rc != SQLITE_OK {
let errorMsg = zErrMsg
print("ERROR: sqlite3_exec \(errorMsg)")
sqlite3_free(zErrMsg)
}
sqlite3_close(db)
return 0
}
Test
import Glibc
var resultSet: [[String: String]] = [[:]]
//sqlQueryClosureBasic(dbStr:"Sqlite_Test.db", query:"SELECT * FROM Employee")
sqlQueryCallbackBasic(dbStr:"Sqlite_Test.db", query:"SELECT * FROM Employee")
for row in resultSet {
for (col, val) in row {
print("\(col): \(val)")
}
}
The first parameter of that C function (the third argument of sqlite3_exec) is the thing you passed into the fourth argument of sqlite3_exec. Therefore, it's possible to pass a Swift closure into sqlite3_exec.
// Define a Type for later loading in the C function
typealias RowHandler = (
Int32,
UnsafeMutablePointer<UnsafeMutablePointer<CChar>?>?,
UnsafeMutablePointer<UnsafeMutablePointer<CChar>?>?
) -> Void
// The Swift closure to pass into sqlite3_exec
var rowHandler: RowHandler = { count, names, values in
// Do anything you want
}
let state = withUnsafeMutablePointer(to: &rowHandler) { rowHandlerPointer in
return sqlite3_exec(
connection,
"SOME SQLITE STATEMENT",
{ rowHandlerRawPointer, columnCount, values, columns in
// Load the pointer as the Type of the closure
let rowHandler = rowHandlerRawPointer!.load(as: RowHandler.self)
// Use it!
rowHandler(columnCount, columns, values)
},
rowHandlerPointer, // This will become rowHandlerRawPointer in the C function
nil
)
}
Because RowHandler is a Swift closure type, we can make it a parameter of a wrapper method:
class SQLiteDatabase {
// Define a closure type for later loading in the C function
typealias RowHandler = (
Int32,
UnsafeMutablePointer<UnsafeMutablePointer<CChar>?>?,
UnsafeMutablePointer<UnsafeMutablePointer<CChar>?>?
) -> Void
var connection: OpaquePointer?
// Other methods...
func execute(_ statement: String, receiveRow: #escaping RowHandler) -> Int32 {
// The Swift closure to pass into sqlite3_exec
var rowHandler = receiveRow
let state = withUnsafeMutablePointer(to: &rowHandler) { rowHandlerPointer in
return sqlite3_exec(
connection,
statement,
{ rowHandlerRawPointer, columnCount, values, columns in
// Load the pointer as the Type of the closure
let rowHandler = rowHandlerRawPointer!.load(as: RowHandler.self)
// Use it!
rowHandler(columnCount, columns, values)
},
rowHandlerPointer, // This will become rowHandlerRawPointer in the C function
nil
)
}
return state
}
}
Usage:
func result(database: SQLiteDatabase) -> [[String: String]] {
var rows = [[String: String]]()
_ = database.execute("SOME STATEMENT") { (count, names, values) in
var row = [String: String]()
for index in 0..<Int(count) {
guard let name = names?[index], let value = values?[index] else { continue }
row[String(cString: name)] = String(cString: value)
}
rows.append(row)
}
return rows
}
Update for the given answers to support Swift 5.5.1
let db: OpaquePointer = // the database connection
let sql: String = // the sql string
func callback(context: UnsafeMutableRawPointer?,
columnCount: Int32,
values: UnsafeMutablePointer<UnsafeMutablePointer<CChar>?>?,
columns: UnsafeMutablePointer<UnsafeMutablePointer<CChar>?>?) -> Int32 {
guard let values = values else { return 0 }
guard let columns = columns else { return 0 }
for column in 0 ..< Int(columnCount) {
guard let columnNamePtr = columns[column] else { continue }
guard let valuePtr = values[column] else { continue }
// Note: String(cString:) will copy the values from C to Swift
let column = String(cString: columnNamePtr)
let value = String(cString: valuePtr)
print("\(column) = \(value)")
}
return 0 // status ok
}
var errorMessage = UnsafeMutablePointer<CChar>(nil)
sqlite3_exec(db, sql, callback, nil, &errorMessage)
if let errorMessage = errorMessage {
print(String(cString: errorMessage))
}
Related
func reqAuth(serviceParams: AuthServiceParams) -> AnyPublisher<AuthResponse, Error> {
var subject = PassthroughSubject<Any,Error>()
AlamofireService.auth(serviceParams: serviceParams).responseObject { (response : DataResponse<AuthResponse>) in
if (response.error != nil ) {
print("❌⭕️❌ Auth login hatalı bir dönüş aldı sorun var.")
subject.send(response.error!)
return
} else {
if let data = response.result.value {
guard let token = data.data?.token else {
print("TOKEN BULUNAMADI")
let authResponse = AuthResponse(
result: "fault",
success: false,
data: nil,
message: "Kullanıcı adı veya şifre hatalı",
errCode: "E0000"
)
subject.send(response)
return
}
print("AuthLogin Token -------> \(token)")
ApplicationVariables.token = token
ApplicationVariables.customer = data.data?.customer
ApplicationVariables.config = data.data?.store?.config
ApplicationVariables.logo = data.data?.store?.logo
subject.send(data)
}else {
let error = NSError(domain: "Bir sorun oluştu. Lütfen yöneticinize başvurunuz.", code: 1001, userInfo: nil)
subject.send(error)
}
}
}
}
This is my code base , the problem is I couldn't find the right return , what should I return in this function or how ? I tried subject.eraseToAnyPublisher() but its not match with return type.
In line with declaring subject var subject = PassthroughSubject<Any,Error>()
change Output generic to AuthResponse
You should send errors as errors rather than values, you can send error with subject.send(completion: .failure(#Error#)).
subject.send(#Output#) sends a value
fixed code:
func reqAuth(serviceParams: AuthServiceParams) -> AnyPublisher<AuthResponse, Error> {
var subject = PassthroughSubject<AuthResponse, Error>()
AlamofireService.auth(serviceParams: serviceParams).responseObject { (response : DataResponse<AuthResponse>) in
guard response.error == nil else {
print("❌⭕️❌ Auth login hatalı bir dönüş aldı sorun var.")
subject.send(completion: .failure(response.error!))
return
}
if let data = response.result.value {
guard let token = data.data?.token else {
print("TOKEN BULUNAMADI")
let authResponse = AuthResponse(
result: "fault",
success: false,
data: nil,
message: "Kullanıcı adı veya şifre hatalı",
errCode: "E0000"
)
subject.send(response)
return
}
print("AuthLogin Token -------> \(token)")
ApplicationVariables.token = token
ApplicationVariables.customer = data.data?.customer
ApplicationVariables.config = data.data?.store?.config
ApplicationVariables.logo = data.data?.store?.logo
subject.send(data)
} else {
let error = NSError(domain: "Bir sorun oluştu. Lütfen yöneticinize başvurunuz.", code: 1001, userInfo: nil)
subject.send(completion: .failure(error))
}
}
}
I need a function, which can only be called once, and you have to wait until the next one can be executed
private func receive(){
var inputBuffer = Array<UInt8>(repeating: 0, count: BUFFER_MAX);
let bytesRead = self.inputStream.read(&inputBuffer, maxLength: BUFFER_MAX);
if(bytesRead > 0){
let string = convertToString(byteArray: inputBuffer, length: bytesRead);
// Call if previous is finished
CommandHandler.convert(string);
}
}
I have tested something like this:
var dispQueue = DispatchQueue(label: "commandConvert")
...
private func convert(){
dispQueue.sync {
...
}
}
but this doesn't work
I just had this issue the other day.
You can solve this using a callback or a Dispatch Group. Here is the Dispatch Group solution:
class test {
let myDispatchGroup = DispatchGroup()
func receive () {
myDispatchGroup.enter()
var inputBuffer = Array<UInt8>(repeating: 0, count: BUFFER_MAX);
let bytesRead = self.inputStream.read(&inputBuffer, maxLength: BUFFER_MAX);
if(bytesRead > 0){
let string = convertToString(byteArray: inputBuffer, length: bytesRead);
myDispatchGroup.leave()
}
myDispatchGroup.notify(queue: DispatchQueue.main) {
// Call if previous is finished
CommandHandler.convert(string);
}
}
I'd like to read the time value of a timecode track. There is an
excellent documentation from Apple (see Technical Note 2310)
but it's written in Objective C.
I have translated the core logic to Swift 3. It works exactly as the
ObjC version, which means that a CMSampleBuffer from a timecode
track is read and converted to a CMBlockBuffer. It fails when I
create the data pointer CMBlockBufferGetDataPointer (in the
timecodeFrame() func), which means, that the raw data is always
giving me 0 frames. So it boils down to the question, how do I
handle the raw data correctly?
import Foundation
import AVFoundation
import CoreMedia
let movie = URL(fileURLWithPath: "videoWithTimecodeTrack.mov")
let asset = AVAsset(url: movie)
asset.loadValuesAsynchronously(forKeys: ["tracks"]) {
var error: NSError?
guard asset.statusOfValue(forKey: "tracks", error: &error) == AVKeyValueStatus.loaded
else { if let error = error { return print(error) } }
readStartTimecode(asset: asset)
}
func readStartTimecode(ofAsset asset: AVAsset) {
let timecodeTracks = asset.tracks(withMediaType: AVMediaTypeTimecode)
guard let timecodeTrack = timecodeTracks.first,
let assetReader = try? AVAssetReader(asset: asset) else { return }
let readerOutput = AVAssetReaderTrackOutput(track: timecodeTrack, outputSettings: nil)
assetReader.add(readerOutput)
guard assetReader.startReading() else { return }
while let sampleBuffer = readerOutput.copyNextSampleBuffer() {
if let frame = timecodeFrame(sampleBuffer: sampleBuffer) {
print("timecodeFrame: \(frame)")
}
}
}
func timecodeFrame(sampleBuffer: CMSampleBuffer) -> UInt32? {
guard let blockBuffer = CMSampleBufferGetDataBuffer(sampleBuffer),
let formatDescription = CMSampleBufferGetFormatDescription(sampleBuffer)
else { return nil }
var rawData: UnsafeMutablePointer<Int8>? = nil
var length: Int = 0
var totalLength: Int = 0
let status = CMBlockBufferGetDataPointer(blockBuffer, 0, &length, &totalLength, &rawData)
guard status == kCMBlockBufferNoErr,
let frameRead = rawData?.pointee
else { return nil }
let type = CMFormatDescriptionGetMediaSubType(formatDescription)
if type == kCMTimeCodeFormatType_TimeCode32 {
let frame = UInt32(frameRead)
let bigFrame = CFSwapInt32BigToHost(frame)
print("kCMTimeCodeFormatType_TimeCode32: \(bigFrame)")
}
if type == kCMTimeCodeFormatType_TimeCode64 {
print("kCMTimeCodeFormatType_TimeCode64")
// todo
}
return nil
}
Edit: the Objective C version of the data pointer retrieval looks like this:
size_t length = 0;
size_t totalLength = 0;
char *rawData = NULL;
CMBlockBufferGetDataPointer(blockBuffer, 0, &length, &totalLength, &rawData);
if (status == kCMBlockBufferNoErr) {
int32_t *frameNumberRead = (int32_t *)rawData;
(int)Endian32_Swap(*frameNumberRead)]
}
The solution is to not convert the Int8 data like UInt32(rawData.pointee) but to access the UnsafeMutablePointer<Int8> pointer's memory as a different type (temporarily). This would look like this:
if let frames = rawData?.withMemoryRebound(to: UInt32.self, capacity: 1, { CFSwapInt32BigToHost($0.pointee) }) {
return frames
}
The full function would look like this:
func timecodeFrame(sampleBuffer: CMSampleBuffer) -> UInt32? {
guard let blockBuffer = CMSampleBufferGetDataBuffer(sampleBuffer),
let formatDescription = CMSampleBufferGetFormatDescription(sampleBuffer)
else { return nil }
var rawData: UnsafeMutablePointer<Int8>? = nil
var length: Int = 0
var totalLength: Int = 0
let status = CMBlockBufferGetDataPointer(blockBuffer, 0, &length, &totalLength, &rawData)
guard status == kCMBlockBufferNoErr else { return nil }
let type = CMFormatDescriptionGetMediaSubType(formatDescription)
if type == kCMTimeCodeFormatType_TimeCode32 {
if let frames = rawData?.withMemoryRebound(to: UInt32.self, capacity: 1, { CFSwapInt32BigToHost($0.pointee) }) {
return frames
}
}
if type == kCMTimeCodeFormatType_TimeCode64 {
if let frames = rawData?.withMemoryRebound(to: UInt64.self, capacity: 1, { CFSwapInt64BigToHost($0.pointee) }) {
return UInt32(frames)
}
}
return nil
}
I hope this is useful to others who want to read the start timecode of a video's timecode track.
I have very-very strange things.
In my simple function I create variable which contains dictionary of settings parameters. It is set as 'let', so inner loop just reads it.
In a random moment of loop time it crashes with "unresolved settings".
It seems like smth makes it nil. Who does it?
private static func preferencesFilter(userIDs: [Int], access: String) -> [User] {
self.sharedInstance.delegate?.updateActionLabel(label: "Filter")
var result = [VKUser]()
let settings = self.parseSettings()
let progressFraction = 1.00 / Float(userIDs.count)
var n = 0
for userID in userIDs {
if sharedInstance.stopped {
return []
}
n += 1
let user = VKUser.getUser(id: userID, access_token: access_token)
if settings["gender"] != nil {
if user.sex == settings["gender"] as! String {
if (user.born?.isBetweeen(date1: settings["minAge"] as! Date, date2: settings["maxAge"] as! Date))! {
if settings["country"] != nil {
if user.country == settings["country"] as! String {
result.append(user)
}
}
else {
result.append(user)
}
}
}
}
else {
if (user.born?.isBetweeen(date1: settings["minAge"] as! Date, date2: settings["maxAge"] as! Date))! {
if settings["country"] != nil {
if user.country == settings["country"] as! String {
result.append(user)
}
}
else {
result.append(user)
}
}
}
self.sharedInstance.delegate?.updateProgress(value: Float(n) * progressFraction)
}
return result
}
I refactored your code into something more swift like:
private static func preferencesFilter(userIDs: [Int], access_token: String) -> [User]? {
guard userIDs.count > 0 else {
return [User]() // no input, return empty list
}
let settings = self.parseSettings()
guard let minAge = settings["minAge"] as? Date,
let maxAge = settings["maxAge"] as? Date
else {
return nil
}
let country = settings["country"] as? String // specified or nil
let gender = settings["gender"] as? String // specified or nil
sharedInstance.delegate?.updateActionLabel(label: "Filter")
var result = [VKUser]()
let progressFraction = 1.00 / Float(userIDs.count)
var n = 0
for userID in userIDs {
if !sharedInstance.stopped {
n += 1
let user = VKUser.getUser(id: userID, access_token: access_token)
var shouldInclude = true
if user.sex != gender { // wrong sex or no required gender specified
shouldInclude = false
}
if user.country != country { // wrong country or no required country specified
shouldInclude = false
}
if let born = user.born {
if !born.isBetweeen(date1: minAge, date2: maxAge) {
shouldInclude = false
}
} else { // no user.born date, cant check if in range
shouldInclude = false
}
if shouldInclude {
result.append(user)
}
sharedInstance.delegate?.updateProgress(value: Float(n) * progressFraction)
}
}
return result
}
Is that what you intended to write? How is that running for you?
Can you change this into a non-static method? Makes more sense to me.
You can see it returns an optional now, since the method might fail with a nil. Your calling code should handle that correctly.
Eventually I want to be able to input a string like "\mycard{front1}{back1} \mycard{front2}{back2} \mycard{front3}{back3}" and return the front and back of each card.
I found this website on NSRegularExpression, but I'm having a hard time adjusting it to my problem.
Here is what I have so far.
import Foundation
func rangeFromNSRange(nsRange: NSRange, forString str: String) -> Range<String.Index>? {
let fromUTF16 = str.utf16.startIndex.advancedBy(nsRange.location, limit: str.utf16.endIndex)
let toUTF16 = fromUTF16.advancedBy(nsRange.length, limit: str.utf16.endIndex)
if let from = String.Index(fromUTF16, within: str), let to = String.Index(toUTF16, within: str) {
return from ..< to
}
return nil
}
do {
// let input = "My name is Taylor Swift"
// let regex = try NSRegularExpression(pattern: "My name is (.*)", options: NSRegularExpressionOptions.CaseInsensitive)
let input = "mycard{front}{back}"
let regex = try NSRegularExpression(pattern: "mycard{(.*)}{(.*)}", options: NSRegularExpressionOptions.CaseInsensitive)
let matches = regex.matchesInString(input, options: [], range: NSMakeRange(0, input.characters.count))
if let match = matches.first {
let range = match.rangeAtIndex(1)
if let swiftRange = rangeFromNSRange(range, forString: input) {
let name = input.substringWithRange(swiftRange)
}
}
} catch {
// regex was bad!
}
As stated in my comment you need to escape the { and }. That results in the following regex: mycard\\{(.*)\\}\\{(.*)\\}.
You then might want to change your match logic a little bit to output the expected results:
if let match = matches.first {
for i in 1..<match.numberOfRanges {
let range = match.rangeAtIndex(i)
if let swiftRange = rangeFromNSRange(range, forString: input) {
let name = input.substringWithRange(swiftRange)
print(name)
}
}
}
Which outputs
front
back
If you want to match multiple cards use the following regex:
mycard\\{([^{]*)\\}\\{([^{]*)\\}
Then iterate over the matches
for match in matches {
for i in 1..<match.numberOfRanges {
let range = match.rangeAtIndex(i)
if let swiftRange = rangeFromNSRange(range, forString: input) {
let name = input.substringWithRange(swiftRange)
print(name)
}
}
}
For the input mycard{front}{back} mycard{front1}{back1} the output correctly is
front
back
front1
back1
I gave up on regex. I just don't think it will do the trick here. I came up with another solution.
import Foundation
extension String {
subscript (r: Int) -> Character? {
var cur = 0
for char in self.characters {
if cur == r {
return char
}
cur += 1
}
return nil
}
subscript (r: Range<Int>) -> String {
return substringWithRange(Range(start: startIndex.advancedBy(r.startIndex), end: startIndex.advancedBy(r.endIndex)))
}
func parseBrackets () -> [String]? {
var list: [String] = []
var level = 0
var start = 0
for var i=0; i < self.characters.count - 1; i++ {
if self[i] == "{" {
level += 1
if level == 1 {
start = i + 1
}
} else if self[i] == "}" {
if level == 1 {
list.append(self[start..<i])
}
level -= 1
}
}
if list.count > 0 {
return list
} else {
return nil
}
}
}
let testString = "mycard{f{}ront}{termins{x}{n}} mycard{front1}{back1} mycard{front2}{back2}"
let list = testString.parseBrackets()
for a in list! {
print(a)
}
Which gives the desired output
f{}ront
termins{x}{n}
front1
back1
front2