6

I am trying to pass contextInfo of typeUnsafeMutablePointer<Void> to UISaveVideoAtPathToSavedPhotosAlbum and use it in the callback function. For some reason I am unable to access contextInfo as a string using UnsafePointer<String>(x).memory when I am in the callback function.

I am pretty sure it is something simple I am missing but have spent way to many hours trying to figure this out.

Below is some code that I have tried.

The following code works.

var testStr:String = "hello"
takesAMutableVoidPointer(&testStr)

func takesAMutableVoidPointer(x: UnsafeMutablePointer<Void>){
    var pStr:String = UnsafePointer<String>(x).memory
    println("x = \(x)")
    println("pStr = \(pStr)")
}

However the following code does not work.

var testStr:String = "hello"

if UIVideoAtPathIsCompatibleWithSavedPhotosAlbum(filePath){ //the filePath is compatible
    println("Compatible")
    //UISaveVideoAtPathToSavedPhotosAlbum(filePath, self, nil, nil)
    UISaveVideoAtPathToSavedPhotosAlbum(filePath, self, "video:didFinishSavingWithError:contextInfo:", &testStr)
}
else{
    println("Not Compatible")
}

func video(video: NSString, didFinishSavingWithError error:NSError, contextInfo:UnsafeMutablePointer<Void>){
    var pStr:String = UnsafePointer<String>(contextInfo).memory
    println("contextInfo = \(contextInfo)")
    println("pStr = \(pStr)")
}

Once I get to the following line:

var pStr:String = UnsafePointer<String>(contextInfo).memory

I keep getting the following error:

Thread 1: EXC_BAD_ACCESS(code=1, address=0x0)

Any help with this would be greatly appreciated.

Thanks.

Update

Rintaro commented that testStr needs to be top level but the following code works.

import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        var testStr:String = "hello"
        takesAMutableVoidPointer(&testStr)
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    func takesAMutableVoidPointer(x: UnsafeMutablePointer<Void>){
        var answer = UnsafePointer<String>(x).memory
        println("x = \(x)")
        println("answer = \(answer)")
    }
}

I am trying not to use global variables unless I have to. I may have to but since I am able to execute the above code, it seems as though I do not need to use a global variable.

user2517182
  • 1,241
  • 3
  • 15
  • 37
  • Where do you declare `testStr`? It's need to be statically allocated. So, I think, it should be at top-level. – rintaro Nov 06 '14 at 10:25
  • I can define testStr in a function, then pass its reference to another function and get the contents of the reference in the other function. That already works. I believe that means that it does not have to be 'top-level'. But I may not understand what you mean by 'top-level'. The issue is it does not work when using UISaveVideoAtPathToSavedPhotosAlbum. The function is called. It gets into the function. I can even print the memory address, but I cannot get the contents of the memory address. – user2517182 Nov 06 '14 at 18:01
  • 1
    Your updated code performs synchronous call. In this case, `testStr` remains on memory until the end of `viewDidLoad` method. But, `UISaveVideoAtPathToSavedPhotosAlbum` notifies the callback asynchronously, and `testStr` has been freed at that time. – rintaro Nov 07 '14 at 02:22
  • Oh ok, that makes sense. Is there any way to force the retaining of a variable that has been created in a function? Then release it later? I ask because in obj-c i was able to create a `NSString*` variable in a function then pass it as the contextInfo param to `UISaveVideoAtPathToSavedPhotosAlbum`. I did cast it as `(__bridge void *)` though. I do not think that had any bearing on the retaining of the variable, but I am not sure. – user2517182 Nov 07 '14 at 02:57

1 Answers1

7

As discussed in OP comments, testStr has already been freed.

Is there any way to force the retaining of a variable that has been created in a function? Then release it later?

It's not impossible, but I don't know this is the best way to do that.

Anyway, try this with Playground or OS X "Command Line Tool" template:

import Foundation

func foo() {
    var str:NSString = "Hello World"
    let ptr = UnsafePointer<Void>(Unmanaged<NSString>.passRetained(str).toOpaque())
    bar(ptr)
}

func bar(v:UnsafePointer<Void>) {
    let at = dispatch_time(
        DISPATCH_TIME_NOW,
        Int64(2.0 * Double(NSEC_PER_SEC))
    )
    dispatch_after(at, dispatch_get_main_queue()) {
        baz(v)
    }
}

func baz(v:UnsafePointer<Void>) {
    println("notified")
    let str = Unmanaged<NSString>.fromOpaque(COpaquePointer(v)).takeRetainedValue()
    println("info: \(str)")
}


foo()

println("started")

dispatch_main()
  • Unmanaged<NSString>.passRetained(str) increments the retain count.
  • Unmanaged<NSString>.fromOpaque(...).takeRetainedValue() decrements it, and extract the object.

I think, using pure Swift String is impossible. because String is struct and is allocated in stack memory. Maybe the buffer of it is allocated in heap, but we cannot access it directly.

rintaro
  • 51,423
  • 14
  • 131
  • 139
  • Thanks! I really appreciate your help. I must admit, it seems a bit unsettling that there is no way to pass contextInfo to UISaveVideoAtPathToSavedPhotosAlbum in swift with out using a global variable or 'jumping through hoops' like above to retain a variable. – user2517182 Nov 09 '14 at 05:13