带共享扩展的后台上传
Background upload with share extension
我创建了一个 macOS ShareExtension,我想用它来上传图片。
我仍在对此进行测试,因此任何请求都会发送至 https://beeceptor.com。
共享扩展工作正常,它显示在预览中,一旦我 运行 它:
我添加一些文字并点击 "Post"
但是图片并没有上传。
这是我启动后台上传的代码:
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
}
请注意,sharedContainerIdentifier
存在于应用程序的权利以及共享扩展权利中。
ShareExtensions 位于相应的 应用程序组 中并且启用了传出连接。
应用程序组标识符必须开始和"group."并且必须匹配它在任何地方的使用——在授权文件中,在您的代码中,以及在 Apple Dev 门户上。
在您的应用和共享扩展权利定义中,您有 $(TeamIdentifierPrefix).group.CreateDaily。这是无效的,因为它不是以 "group.".
开头
在您的代码中,您只有 "group.CreateDaily"。如果它与您的权利文件中的内容相匹配,那就没问题了,尽管 Apple 建议使用反向域名表示法以避免冲突。
我的建议是转到证书、标识符和配置文件/标识符/AppGroups 下的 Apple Dev portal 并定义您的应用程序组。 Apple 不会让您输入不以 "group." 开头的内容。设置完成后,请确保您的权利文件和代码 (config.sharedContainerIdentifier) 中的内容匹配,然后一切正常。
正在执行后台上传
一旦用户完成输入并单击 Post 按钮,扩展程序就会将内容上传到某处的某个 Web 服务。出于本示例的目的,端点的 URL 包含在视图控制器上的 属性 中:
let sc_uploadURL = "http://requestb.in/oha28noh"
这是 Request Bin 服务的 URL,它为您提供一个临时 URL 以允许您测试网络操作。上面的 URL(以及示例代码中的那个)对您不起作用,但是如果您访问 requestb.in,那么您可以获取自己的 URL 进行测试。
如前所述,重要的是扩展对有限的系统资源造成的压力很小。因此,在点击 Post 按钮时,没有时间执行同步的前台网络操作。幸运的是,NSURLSession
提供了一个简单的 API 用于创建后台网络操作,这就是您在这里需要的。
当用户点击 post 时调用的方法是 didSelectPost()
,最简单的形式必须如下所示:
override func didSelectPost() {
// Perform upload
...
// Inform the host that we're done, so it un-blocks its UI.
extensionContext?.completeRequestReturningItems(nil, completionHandler: nil)
}
设置 NSURLSession
非常标准:
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)
上述代码段中需要注意的重要部分是在会话配置中设置 sharedContainerIdentifier 的行。这指定了 NSURLSession 可以用作缓存的容器的名称(因为扩展没有自己的可写磁盘访问权限)。此容器需要设置为主机应用程序的一部分(即本演示中的 ShareAlike),并且可以通过 Xcode:
完成
- 转到应用目标的功能选项卡
- 启用应用程序组
- 创建一个新的应用组,命名为合适的。它必须
从组开始。在演示中,该组称为 group.ShareAlike
- 让 Xcode 为您完成创建此群组的过程。
然后您需要转到扩展的目标,并按照相同的过程进行操作。请注意,您不需要创建新的应用程序组,而是 select 您为主机应用程序创建的应用程序组。
这些应用组是根据您的开发者 ID 注册的,签名过程确保只有您的应用才能访问这些共享容器。
Xcode 将为您的每个项目创建一个权利文件,其中将包含它有权访问的共享容器的名称。
现在您已经正确设置了会话,您需要创建一个 URL 请求来执行:
// Prepare the URL Request
let request = urlRequestWithImage(attachedImage, text: contentText)
这会调用一个构造 URL 请求的方法,该请求使用 HTTP POST 发送一些 JSON,其中包括字符串内容和一些关于图像的元数据属性:
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
}
此方法实际上并没有创建上传图像的请求,尽管它可以修改为这样做。相反,它使用以下方法提取有关图像的一些细节:
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
}
最后,您可以要求会话创建一个与您构建的请求关联的任务,然后对其调用 resume() 以在后台启动它:
// Create the task, and kick it off
let task = session.dataTaskWithRequest(request!)
task.resume()
如果您 运行 现在完成此过程,并且您自己的 requestb.in URL 就位,那么您可以期望看到如下结果:
我创建了一个 macOS ShareExtension,我想用它来上传图片。
我仍在对此进行测试,因此任何请求都会发送至 https://beeceptor.com。
共享扩展工作正常,它显示在预览中,一旦我 运行 它:
我添加一些文字并点击 "Post"
但是图片并没有上传。 这是我启动后台上传的代码:
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
}
请注意,sharedContainerIdentifier
存在于应用程序的权利以及共享扩展权利中。
ShareExtensions 位于相应的 应用程序组 中并且启用了传出连接。
应用程序组标识符必须开始和"group."并且必须匹配它在任何地方的使用——在授权文件中,在您的代码中,以及在 Apple Dev 门户上。
在您的应用和共享扩展权利定义中,您有 $(TeamIdentifierPrefix).group.CreateDaily。这是无效的,因为它不是以 "group.".
开头在您的代码中,您只有 "group.CreateDaily"。如果它与您的权利文件中的内容相匹配,那就没问题了,尽管 Apple 建议使用反向域名表示法以避免冲突。
我的建议是转到证书、标识符和配置文件/标识符/AppGroups 下的 Apple Dev portal 并定义您的应用程序组。 Apple 不会让您输入不以 "group." 开头的内容。设置完成后,请确保您的权利文件和代码 (config.sharedContainerIdentifier) 中的内容匹配,然后一切正常。
正在执行后台上传
一旦用户完成输入并单击 Post 按钮,扩展程序就会将内容上传到某处的某个 Web 服务。出于本示例的目的,端点的 URL 包含在视图控制器上的 属性 中:
let sc_uploadURL = "http://requestb.in/oha28noh"
这是 Request Bin 服务的 URL,它为您提供一个临时 URL 以允许您测试网络操作。上面的 URL(以及示例代码中的那个)对您不起作用,但是如果您访问 requestb.in,那么您可以获取自己的 URL 进行测试。
如前所述,重要的是扩展对有限的系统资源造成的压力很小。因此,在点击 Post 按钮时,没有时间执行同步的前台网络操作。幸运的是,NSURLSession
提供了一个简单的 API 用于创建后台网络操作,这就是您在这里需要的。
当用户点击 post 时调用的方法是 didSelectPost()
,最简单的形式必须如下所示:
override func didSelectPost() {
// Perform upload
...
// Inform the host that we're done, so it un-blocks its UI.
extensionContext?.completeRequestReturningItems(nil, completionHandler: nil)
}
设置 NSURLSession
非常标准:
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)
上述代码段中需要注意的重要部分是在会话配置中设置 sharedContainerIdentifier 的行。这指定了 NSURLSession 可以用作缓存的容器的名称(因为扩展没有自己的可写磁盘访问权限)。此容器需要设置为主机应用程序的一部分(即本演示中的 ShareAlike),并且可以通过 Xcode:
完成- 转到应用目标的功能选项卡
- 启用应用程序组
- 创建一个新的应用组,命名为合适的。它必须 从组开始。在演示中,该组称为 group.ShareAlike
- 让 Xcode 为您完成创建此群组的过程。
然后您需要转到扩展的目标,并按照相同的过程进行操作。请注意,您不需要创建新的应用程序组,而是 select 您为主机应用程序创建的应用程序组。
这些应用组是根据您的开发者 ID 注册的,签名过程确保只有您的应用才能访问这些共享容器。
Xcode 将为您的每个项目创建一个权利文件,其中将包含它有权访问的共享容器的名称。
现在您已经正确设置了会话,您需要创建一个 URL 请求来执行:
// Prepare the URL Request
let request = urlRequestWithImage(attachedImage, text: contentText)
这会调用一个构造 URL 请求的方法,该请求使用 HTTP POST 发送一些 JSON,其中包括字符串内容和一些关于图像的元数据属性:
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
}
此方法实际上并没有创建上传图像的请求,尽管它可以修改为这样做。相反,它使用以下方法提取有关图像的一些细节:
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
}
最后,您可以要求会话创建一个与您构建的请求关联的任务,然后对其调用 resume() 以在后台启动它:
// Create the task, and kick it off
let task = session.dataTaskWithRequest(request!)
task.resume()
如果您 运行 现在完成此过程,并且您自己的 requestb.in URL 就位,那么您可以期望看到如下结果: