具有共享扩展名的后台上传 [英] Background upload with share extension

查看:67
本文介绍了具有共享扩展名的后台上传的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我创建了要用于上传图片的macOS ShareExtension.

我仍在测试,因此所有请求都将发送到

我添加一些文字并点击发布"

但是图像没有被上传.这是我启动后台上传的代码:

  let sc_uploadURL ="https://xyz.free.beeceptor.com/api/posts"//https://beeceptor.com/console/xyz覆盖func didSelectPost(){//这是在用户选择发布后调用的.执行contentText和/或NSExtensionContext附件的上传.让configName ="com.shinobicontrols.ShareAlike.BackgroundSessionConfig"让sessionConfig = URLSessionConfiguration.background(withIdentifier:configName)//不允许扩展使用自己的缓存磁盘空间.需要与应用程序共享sessionConfig.sharedContainerIdentifier ="group.CreateDaily"let session = URLSession(配置:sessionConfig)//准备网址请求让请求= urlRequestWithImage(图像:AttachedImage,文本:contentText)//创建任务,然后开始让任务= session.dataTask(with:request!as URLRequest)task.resume()//通知主机我们已经完成,因此它解除了对UI的阻塞.注意:或者,您可以调用super的-didSelectPost,这将类似地完成扩展上下文.extensionContext?.completeRequest(returningItems:[AnyObject](),completeHandler:无)}私有函数urlRequestWithImage(image:NSImage ?, text:String)->NSURLRequest?{让url = URL(string:sc_uploadURL)!让请求:NSMutableURLRequest?= NSMutableURLRequest(URL:URL作为URL)request?.addValue("application/json",forHTTPHeaderField:"Content-Type")request?.addValue("application/json",forHTTPHeaderField:"Accept")请求?httpMethod ="POST"让 jsonObject = NSMutableDictionary()jsonObject ["text"] =文字如果让图像 = 图像 {jsonObject ["image_details"] = extractDetailsFromImage(image:image)}//创建JSON有效负载让jsonData =试试!JSONSerialization.data(withJSONObject:jsonObject,选项:JSONSerialization.WritingOptions.prettyPrinted)请求?.httpBody = jsonData退货要求} 

请注意, sharedContainerIdentifier 存在于应用程序的权利以及共享扩展权利中.

ShareExtensions位于相应的 App组中,并且启用了传出连接.

解决方案

执行后台上传

用户完成输入后,单击发布"按钮,则扩展程序应将内容上传到某处的某些Web服务.就本示例而言,端点的URL包含在视图控制器上的一个属性内:

  let sc_uploadURL ="http://requestb.in/oha28noh" 

这是Request Bin服务的URL,它为您提供了一个临时URL,使您可以测试网络操作.上面的URL(以及示例代码中的URL)对您不起作用,但是如果您访问requestb.in,则可以保留自己的URL进行测试.

如前所述,扩展对有限的系统资源的压力应很小,这一点很重要.因此,在点按发布"按钮时,没有时间执行同步的前台网络操作.幸运的是, NSURLSession 提供了一个用于创建后台网络操作的简单API,这就是您所需要的.

用户点击帖子时调用的方法是 didSelectPost(),并且以最简单的形式,它必须看起来像这样:

  override func didSelectPost(){//执行上传...//通知主机我们已完成,因此它解除阻止其 UI.extensionContext?.completeRequestReturningItems(nil,completedHandler:nil)} 

设置 NSURLSession 非常标准:

  let configName ="com.shinobicontrols.ShareAlike.BackgroundSessionConfig"让sessionConfig = NSURLSessionConfiguration.backgroundSessionConfigurationWithIdentifier(configName)//不允许扩展使用自己的缓存磁盘空间.需要与应用程序共享sessionConfig.sharedContainerIdentifier ="group.ShareAlike"let session = NSURLSession(配置:sessionConfig) 

上面代码段要注意的重要部分是在会话配置上设置sharedContainerIdentifier的行.这指定了NSURLSession可以用作缓存的容器的名称(因为扩展名没有其自己的可写磁盘访问权限).该容器需要设置为主机应用程序的一部分(即本演示中的ShareAlike),并且可以通过Xcode完成:

  1. 转到应用目标的功能标签
  2. 启用应用程序组
  3. 创建一个新的应用程序组,标题为适当的名称.它必须从组开始.在演示中,该组称为组.ShareAlike
  4. 让Xcode完成为您创建该组的过程.

然后,您需要转到扩展程序的目标,并按照相同的过程进行操作.请注意,您无需创建新的应用程序组,而是选择为主机应用程序创建的应用程序组.

这些应用组是根据您的开发者ID注册的,并且签名过程可确保只有您的应用才能访问这些共享容器.

Xcode将为您的每个项目创建一个权利文件,其中将包含它有权访问的共享容器的名称.

现在您已经正确设置了会话,您需要创建一个URL请求才能执行:

 //准备URL请求让请求= urlRequestWithImage(attachedImage,文本:contentText) 

这将调用一个方法,该方法构造一个URL请求,该请求使用HTTP POST发送一些JSON,其中包括字符串内容以及有关图像的一些元数据属性:

func urlRequestWithImage(image: UIImage?, text: String) ->NSURLRequest?{让url = NSURL.URLWithString(sc_uploadURL)让请求 = NSMutableURLRequest(URL: url)request.addValue("application/json",forHTTPHeaderField:"Content-Type")request.addValue("application/json",forHTTPHeaderField:"Accept")request.HTTPMethod ="POST"var jsonObject = NSMutableDictionary()jsonObject ["text"] =文字如果让image = image {jsonObject ["image_details"] = extractDetailsFromImage(image)}//创建JSON有效负载var jsonError:NSError?让jsonData = NSJSONSerialization.dataWithJSONObject(jsonObject,选项:nil,错误:& jsonError)如果jsonData {request.HTTPBody = jsonData} 别的 {如果让错误= jsonError {println("JSON错误:\(error.localizedDescription)")}}退货要求} 

此方法实际上不会创建上传图片的请求,尽管可以对其进行调整.而是使用以下方法提取有关图像的一些详细信息:

  func extractDetailsFromImage(image:UIImage)->NSDictionary {var resultDict = [String:AnyObject]()resultDict ["height"] = image.size.heightresultDict ["width"] = image.size.widthresultDict ["orientation"] = image.imageOrientation.toRaw()resultDict["scale"] = image.scaleresultDict ["description"] = image.description返回resultDict} 

最后,您可以要求会话创建与您所建立的请求相关联的任务,然后在其上调用resume()使其在后台启动:

 //创建任务,然后开始让任务= session.dataTaskWithRequest(请求!)task.resume() 

如果您现在使用自己的requestb.in URL运行此过程,那么您可以期望看到如下结果:

I created an macOS ShareExtension which I want to use to upload pictures.

I'm still testing this so any requests will be sent to https://beeceptor.com.

The share extension works fine and it shows up in Preview, once I run it:

I add some text and hit "Post"

But the image is then not uploaded. This is my code that initiates the background upload:

let sc_uploadURL = "https://xyz.free.beeceptor.com/api/posts" // https://beeceptor.com/console/xyz

override func didSelectPost() {
    // This is called after the user selects Post. Do the upload of contentText and/or NSExtensionContext attachments.
    let configName = "com.shinobicontrols.ShareAlike.BackgroundSessionConfig"
    let sessionConfig = URLSessionConfiguration.background(withIdentifier: configName)
    // Extensions aren't allowed their own cache disk space. Need to share with application
    sessionConfig.sharedContainerIdentifier = "group.CreateDaily"
    let session = URLSession(configuration: sessionConfig)

    // Prepare the URL Request
    let request = urlRequestWithImage(image: attachedImage, text: contentText)

    // Create the task, and kick it off
    let task = session.dataTask(with: request! as URLRequest)
    task.resume()

    // Inform the host that we're done, so it un-blocks its UI. Note: Alternatively you could call super's -didSelectPost, which will similarly complete the extension context.
    extensionContext?.completeRequest(returningItems: [AnyObject](), completionHandler: nil)
}

private func urlRequestWithImage(image: NSImage?, text: String) -> NSURLRequest? {
    let url = URL(string: sc_uploadURL)!
    let request: NSMutableURLRequest? =  NSMutableURLRequest(url: url as URL)
    request?.addValue("application/json", forHTTPHeaderField: "Content-Type")
    request?.addValue("application/json", forHTTPHeaderField: "Accept")
    request?.httpMethod = "POST"

    let jsonObject = NSMutableDictionary()
    jsonObject["text"] = text
    if let image = image {
        jsonObject["image_details"] = extractDetailsFromImage(image: image)
    }

    // Create the JSON payload
    let jsonData = try! JSONSerialization.data(withJSONObject: jsonObject, options: JSONSerialization.WritingOptions.prettyPrinted)
    request?.httpBody = jsonData
    return request
}

Please note that the sharedContainerIdentifier is present in the entitlements of the app as well as in the sharing extensions entitlements.

The ShareExtensions is in the respective App Group and has outgoing connections enabled.

解决方案

Performing a background upload

Once the user has completed their entry, and clicks the Post button, then the extension should upload the content to some web service somewhere. For the purposes of this example, the URL of the endpoint is contained within a property on the view controller:

let sc_uploadURL = "http://requestb.in/oha28noh"

This is a URL for the Request Bin service, which gives you a temporary URL to allow you to test network operations. The above URL (and the one in the sample code) won’t work for you, but if you visit requestb.in then you can get hold of your own URL for testing.

As mentioned previously, it’s important that extensions put very little strain on the limited system resources. Therefore, at the point the Post button is tapped, there is no time to perform a synchronous, foreground network operation. Luckily, NSURLSession provides a simple API for creating background network operations, and that’s what you’ll need here.

The method which gets called when the user taps post is didSelectPost(), and in its simplest form it must look like this:

override func didSelectPost() {
  // Perform upload
  ...

  // Inform the host that we're done, so it un-blocks its UI.
  extensionContext?.completeRequestReturningItems(nil, completionHandler: nil)
}

Setting up an NSURLSession is pretty standard:

let configName = "com.shinobicontrols.ShareAlike.BackgroundSessionConfig"
let sessionConfig = NSURLSessionConfiguration.backgroundSessionConfigurationWithIdentifier(configName)
// Extensions aren't allowed their own cache disk space. Need to share with application
sessionConfig.sharedContainerIdentifier = "group.ShareAlike"
let session = NSURLSession(configuration: sessionConfig)

The important part to note of the above code segment is the line which sets the sharedContainerIdentifier on the session configuration. This specifies the name of the container that NSURLSession can use as a cache (since extensions don’t have their own writable disc access). This container needs to be set up as part of the host application (i.e. ShareAlike in this demo), and can be done through Xcode:

  1. Go to the capabilities tab of the app’s target
  2. Enable App Groups
  3. Create a new app group, entitled something appropriate. It must start with group.. In the demo the group is called group.ShareAlike
  4. Let Xcode go through the process of creating this group for you.

Then you need to go to the extension’s target, and follow the same process. Note that you won’t need to create a new app group, but instead select the one that you created for your host application.

These app groups are registered against your developer ID, and the signing process ensures that only your apps are able to access these shared containers.

Xcode will have created an entitlements file for each of your projects, and this will contain the name of the shared container it has access to.

Now that you’ve got your session set up correctly, you need to create a URL request to perform:

// Prepare the URL Request
let request = urlRequestWithImage(attachedImage, text: contentText)

This calls a method which constructs a URL request which uses HTTP POST to send some JSON, which includes the string content, and some metadata properties about the image:

func urlRequestWithImage(image: UIImage?, text: String) -> NSURLRequest? {
  let url = NSURL.URLWithString(sc_uploadURL)
  let request = NSMutableURLRequest(URL: url)
  request.addValue("application/json", forHTTPHeaderField: "Content-Type")
  request.addValue("application/json", forHTTPHeaderField: "Accept")
  request.HTTPMethod = "POST"

  var jsonObject = NSMutableDictionary()
  jsonObject["text"] = text
  if let image = image {
    jsonObject["image_details"] = extractDetailsFromImage(image)
  }

  // Create the JSON payload
  var jsonError: NSError?
  let jsonData = NSJSONSerialization.dataWithJSONObject(jsonObject, options: nil, error: &jsonError)
  if jsonData {
    request.HTTPBody = jsonData
  } else {
    if let error = jsonError {
      println("JSON Error: \(error.localizedDescription)")
    }
  }

  return request
}

This method doesn’t actually create a request which uploads the image, although it could be adapted to do so. Instead, it extracts some details about the image using the following method:

func extractDetailsFromImage(image: UIImage) -> NSDictionary {
  var resultDict = [String : AnyObject]()
  resultDict["height"] = image.size.height
  resultDict["width"] = image.size.width
  resultDict["orientation"] = image.imageOrientation.toRaw()
  resultDict["scale"] = image.scale
  resultDict["description"] = image.description
  return resultDict
}

Finally, you can ask the session to create a task associated with the request you’ve built, and then call resume() on it to kick it off in the background:

// Create the task, and kick it off
let task = session.dataTaskWithRequest(request!)
task.resume()

If you run through this process now, with your own requestb.in URL in place, then you can expect to see results like this:

这篇关于具有共享扩展名的后台上传的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆