iOS 应用程序的最佳实践
在开发 iOS 应用程序时,可使用这些最佳实践提高 Citrix Endpoint Management 与 iOS 设备的移动应用程序之间的兼容性。
MDX 应用程序 SDK 框架和封装
如果您的应用程序使用 MDX 应用程序 SDK 框架,则必须使用与之匹配的 MDX Toolkit 版本进行封装。如果这两个组件的版本不匹配,可能会导致运行不正常。
为了防止发生此不匹配情况,可将应用程序封装为 ISV 应用程序,并指定高级应用程序模式或一般应用程序模式。让您可以提供预先封装的应用程序。因此,您的客户不需要封装应用程序,从而避免使用不匹配的 MDX Toolkit。有关 ISV 封装的详细信息,请参阅封装 iOS 移动应用程序。
使用显式应用程序 ID
如果您的 iOS Developer Enterprise 帐户不支持通配符应用程序 ID,请确保为您计划使用 MDX Toolkit 封装的每个应用程序创建一个显式应用程序 ID。此外,请为每个应用程序 ID 创建一个预配配置文件。
请勿阻止主线程
在主线程上运行时,请勿使用阻止代码。这是一项 Apple 准则,但此准则对 Citrix Endpoint Management 而言更为重要。在托管应用程序中,某些操作可能需要较长时间才能完成,设置会阻止进一步执行线程。例如,在执行文件、数据库和网络操作时可能会阻止当前正在运行的线程,因此,应避免在主线程上执行此类操作。
编写稳定可靠的代码
具体而言,您应按照 Apple 编程指南(如Apple 应用程序编程指南)中所述的最佳实践来编写应用程序。
仅使用 Apple 所发布的接口
检查所有 API 调用的返回值,并处理可能会带来副作用的 API 调用的任何异常。这种努力确保了正常执行错误恢复或正常终止应用程序。虽然这是常见的最佳编程实践,但它对于托管应用程序尤其重要。
如果基础功能由于 Citrix Endpoint Management 策略而被阻止,预期可以正常运行的各种 API 将会失败。以前面所述的功能为例︰
- 网络 API 将失败,就如同没有任何可用网络一样。
- 传感器 API(例如 GPS 和相机)将返回 null 或引发异常。
如果基础功能由于 Citrix Endpoint Management 策略而被阻止,下列 Objective-C 运行库选择器将返回 nil,因此应进行相应处理。
对象类: AVCaptureDevice
- 选择器名称: devicesWithMediaType:
对象类: MFMailComposeViewController
- 选择器名称: init:
对象类: MFMessageComposeViewController
- 选择器名称: initWithNibName:bundle:
对象类: NSFileManager
- 选择器名称: URLForUbiquityContainerIdentifier:
对象类: NSUbiquitousKeyValueStore
- 选择器名称: defaultStore:
对象类: PHPhotoLibrary
- 选择器名称: sharedPhotoLibrary:
对象类: UIImagePickerController
- 选择器名称: availableCaptureModesForCameraDevice:
对象类: UIPasteboard
-
选择器名称:
dataForPasteboardType:
valueForPasteboardType:
项目:
dataForPasteboardType:inItemSet:
valuesForPasteboardType:inItemSet:
对象类: UIPopoverController
- 选择器名称: initWithContentViewController:
对象类: UINavigationController
-
选择器名称:
ctxInitWithRootViewController:
ctxPopToViewController:animated:
重定向运行时接口
Citrix Endpoint Management 提供“UI PIN 提示”交互,因此您不必在自己的应用程序中执行此操作。
为了确保 Citrix Endpoint Management 已准备就绪,我们建议您不要重定向或替换 Objective-C 运行库选择器。因为 Citrix Endpoint Management 会混合多个对象类选择器的基本方法以控制或修改应用程序的运行时行为。下表列出了 Citrix Endpoint Management 重定向的 Objective-C 类选择器:
对象类名称: NSURLProtectionSpace
- 选择器名称: serverTrust
对象类名称: NSURLAuthenticationChallenge
- 选择器名称: sender
对象类名称: NSURLConnection
-
选择器名称:
sendSynchronousRequest:returningResponse:error:
initWithRequest:delegate:startImmediately:
initWithRequest:delegate:
connectionWithRequest:delegate:
对象类名称: NSURLConnectionDelegate
-
选择器名称:
connection:canAuthenticateAgainstProtectionSpace:
connection:didReceiveAuthenticationChallenge:
connection:willSendRequestForAuthenticationChallenge:
对象类名称: NSURLSessionConfiguration
-
选择器名称:
defaultSessionConfiguration
ephemeralSessionConfiguration
对象类名称: ALAssetsLibrary
- 选择器名称: authorizationStatus
对象类名称: AVAudioRecorder
-
选择器名称:
record
prepareToRecord
recordForDuration:
recordAtTime:
recordAtTime:ForDuration:
对象类名称: AVAudioSession
- 选择器名称: recordPermission
对象类名称: AVCaptureDevice
-
选择器名称:
设备
devicesWithMediaType:
对象类名称: AVAsset
- 选择器名称: assetWithURL:
对象类名称: AVURLAsset
-
选择器名称:
initWithURL:options:
URLAssetWithURL:options:
对象类名称: AVPlayerItem
-
选择器名称:
playerItemWithAsset:
initWithURL:
playerItemWithURL:
对象类名称: AVPlayer
-
选择器名称:
playerWithPlayerItem:
initWithPlayerItem:
initWithURL:
对象类名称: CLLocationManager
- 选择器名称: startUpdatingLocation
对象类名称: UIScrollView
- 选择器名称: setContentOffset:
对象类名称: MFMailComposeViewController
-
选择器名称:
canSendMail
init
对象类名称: MFMessageComposeViewController
-
选择器名称:
canSendText
initWithNibName:bundle:
对象类名称: NSFileManager
- 选择器名称: URLForUbiquityContainerIdentifier:
对象类名称: NSUbiquitousKeyValueStore
- 选择器名称: defaultStore
对象类名称: PHPhotoLibrary
- 选择器名称: authorizationStatus
对象类名称: QLPreviewController
-
选择器名称:
setDataSource:
canPreviewItem:
对象类名称: QLPreviewControllerDataSource
-
选择器名称:
numberOfPreviewItemsInPreviewController:
previewController:previewItemAtIndex:
对象类名称: SLComposeViewController
- 选择器名称: isAvailableForServiceType:
对象类名称: UIActivityViewController
-
选择器名称:
initWithActivityItems:applicationActivities:
setExcludedActivityTypes:
对象类名称: UIApplication
-
选择器名称:
openURL:
canOpenURL:
setApplicationIconBadgeNumber:
对象类名称: UIDocument
-
选择器名称:
closeWithCompletionHandler:
contentsForType:error:
对象类名称: UIDocumentInteractionController
-
选择器名称:
interactionControllerWithURL:
setURL:
setDelegate:
presentPreviewAnimated:
presentOpenInMenuFromBarButtonItem:animated:
presentOpenInMenuFromRect:inView:animated:
presentOptionsMenuFromBarButtonItem:animated:
presentOptionsMenuFromRect:inView:animated:
对象类名称: UIDocumentMenuViewController
- 选择器名称: initWithDocumentTypes:inMode:
对象类名称: UIImage
- 选择器名称: imageNamed:
对象类名称: UIImagePickerController
-
选择器名称: setSourceType:
takePicture
startVideoCapture
isSourceTypeAvailable:
isCameraDeviceAvailable:
isFlashAvailableForCameraDevice:
availableCaptureModesForCameraDevice:
setMediaTypes
对象类名称: UINavigationController
-
选择器名称:
ctxInitWithRootViewController:
ctxPushViewController:animated:
ctxPopToViewController:animated:
对象类名称: UIPasteboard
-
选择器名称:
generalPasteboard
pasteboardWithName:create:
pasteboardWithUniqueName
setValue:forPasteboardType:
setData:forPasteboardType:
setItems:
addItems:
dataForPasteboardType:
valueForPasteboardType:
numberOfItems
pasteboardTypes
pasteboardTypesForItemSet:
containsPasteboardTypes:
containsPasteboardTypes:inItemSet:
项目
itemSetWithPasteboardTypes:
dataForPasteboardType:inItemSet:
valuesForPasteboardType:inItemSet:
字符串
strings
URL
URL
映像
images
颜色
colors
对象类名称: UIPopoverController
- 选择器名称: initWithContentViewController
对象类名称: UIPrintInteractionController
-
选择器名称:
isPrintingAvailable
presentAnimated:completionHandler:
presentFromBarButtonItem:animated:completionHandler:
presentFromRect:inView:animated:completionHandler:
对象类名称: UIViewController
- 选择器名称: presentViewController:animated:completion:
对象类名称: UIWebView
-
选择器名称:
loadRequest:
setDelegate:
UIWebViewDelegate
webView:shouldStartLoadWithRequest:navigationType:
webViewDidStartLoad:
webViewDidFinishLoad:
webView:didFailLoadWithError:
对象类名称: UIWindow
- 选择器名称: makeKeyAndVisible
对象类名称: UIApplicationDelegate
-
选择器名称:
applicationDidFinishLaunching:
application:didFinishLaunchingWithOptions:
application:willFinishLaunchingWithOptions:
applicationWillResignActive:
applicationDidEnterBackground:
applicationWillEnterBackground:
applicationDidBecomeActive:
applicationWillTerminate:
application:openURL:sourceApplication:annotation:
application:handleOpenURL:
applicationProtectedDataWillBecomeUnavailable:
applicationProtectedDataDidBecomeAvailable:
application:performFetchWithCompletionHandler:
application:handleEventsForBackgroundURLSession:completionHandler:
application:didReceiveLocalNotification:
application:didReceiveRemoteNotification:
application:didReceiveRemoteNotification:fetchCompletionHandler:
application:didRegisterForRemoteNotificationsWithDeviceToken:
application:didFailToRegisterForRemoteNotificationsWithError:
applicationSignificantTimeChange:
application:shouldAllowExtensionPointIdentifier:
对象类名称: QLPreviewController
- 选择器名称: allocWithZone:
确保数据加密兼容性
MDX 的主要功能之一是对所有永久保留的数据进行透明加密。您不需要修改应用程序即可使用此功能,实际上,您并不能直接避免此功能。Citrix Endpoint Management 管理员可以有选择性地禁用加密,也可以完全禁用加密,但应用程序则不能。
这是 MDX 的更重要的方面之一,其要求了解以下要点︰
-
文件加密功能目前可用于在托管进程中运行的所有本机代码。
文件数据加密功能实现方式可支持所有本机代码,而不只是使用 Apple 框架和 Apple Objective-C 运行时的应用程序的代码。在仅限于 Objective-C 运行时中实现的任何文件数据加密很容易被破坏。
-
一些框架 API(例如 AVPlayer 类、UIWebView 类和 QLPreviewController)由不同于用户的托管应用程序的执行上下文中的 iOS 服务进程执行。
这些服务进程无法解密 MDX 加密的文件数据。因此,托管应用程序必须为服务进程提供临时未加密的数据副本。副本在 5 秒后由托管应用程序删除。在使用这些类时,请务必了解其局限性。因为我们无法对提供给这些类的数据进行限制控制(由于 Apple 对这些特定类的执行方式而导致)。
-
内存映射对于 Citrix Endpoint Management 加密功能有问题,因为它依赖于调用文件 I/O 系统调用接口的应用程序。
文件映射到内存后,针对文件的 I/O 请求将在用户应用程序上下文之外进行管理,从而绕过 Citrix Endpoint Management 加密。由托管应用程序执行的所有 POSIX mmap(2) 调用将映射为 MAP_PRIVATE 和 MAP_ANON,并且不与任何文件说明相关联。如果文件说明被指定为在所有数据中出错,则在 mmap 调用期间,将尝试在所有映射的数据中进行读取,因为由操作系统执行的任何后续页面换入数据会导致读取加密数据(而不由 Citrix Endpoint Management 解密)。已使用 Citrix Endpoint Management 在所有应用程序中成功测试该技术,因为进行内存映射的数据量小,所以应用程序内未发生内存页面回收。
-
加密功能会显著增加开销。开发者应优化磁盘 I/O,以防止性能下降。例如,如果重复读取和写入相同的信息,则您可能希望实施应用程序级别的缓存。
-
Citrix Endpoint Management 仅加密 Apple libsqlite.dylib 的实例。如果应用程序直接与 libsqlite.dylib 的专用版本链接和/或嵌入了 libsqlite.dylib 的专用版本,Citrix Endpoint Management 将不加密该专用库的数据库实例。
-
Apple SQLite 数据库由 Citrix Endpoint Management 使用 SQLite 虚拟文件系统层加密。
可能会发生性能问题。标准数据库缓存大小为 2000 页(或 8 MB)。如果您的数据库很大,则开发者可能需要指定 SQLite 杂注来增大数据库缓存大小。在 Objective-C 核心数据框架中,可在向持久存储控制器对象添加持久性存储对象时,将 SQLite 杂注作为选项字典进行添加。
-
由于库重新链接到文件 I/O 接口,并在内部大量使用内存映射,因此不支持 SQLite WAL 模式。
-
NSURLCache 磁盘缓存由 iOS 使用 SQLite 数据库实现。Citrix Endpoint Management 将禁用关联的磁盘缓存,因为该数据库由非托管的 iOS 服务进程引用。
-
下表是硬编码的排除路径文件名模式的列表:
- .plist:被排除,因为 iOS 系统进程在进程上下文外部进行访问。
- .app:应用程序软件包名称中的旧版子字符串。此子字符串将弃用,因为显式应用程序捆绑包路径现已被排除。
- .db:具有此后缀的文件(如果该文件不是 SQLite 数据库)将不加密。
- /System/Library:存在于应用程序捆绑包沙盒目录中的文件路径,以及位于应用程序数据沙盒以外的文件路径不能加密。在 iOS 中,已安装的应用程序为只读模式,并与应用程序运行时所生成和存储的应用程序数据文件位于不同的目录中。
- Library/Preferences:直接通过 iOS 访问文件。通常此目录路径中只存在 .plist 文件。
- /com.apple.opengl/: iOS 直接访问这些文件。
- csdk.db:旧版 Citrix SSLSDK SQLite 数据库
- /Library/csdk.sql:Citrix SSLSDK SQLite 数据库
- CtxLog_:Citrix 日志文件名前缀
- CitrixMAM.config:MDX 内部文件名
- CitrixMAM.traceLog:旧版 MDX 内部文件名
- CtxMAM.log:MDX 内部文件名
- data.999:MDX 内部文件名
- CTXWrapperPersistentData:MDX 内部文件名
- /Documents/CitrixLogs:MDX 日志目录
- /Document/CitrixLogs.zip:压缩的 MDX 日志目录名称
- 应用程序捆绑包目录路径中的任何文件:应用程序文件的只读目录
-
在运行时,Citrix Endpoint Management 会用一个 Citrix Endpoint Management SecureViewController 类实例替换 Apple Objective-C QLPreviewController 对象类实例。Citrix Endpoint Management SecureViewController 类派生自 Apple Objective-C UIWebView 对象类。QLPreviewController 对象类在本机支持几种不受 UIWebView 对象类支持的文件格式,例如音频和 PDF 类型。
-
为了获得最佳性能,应向值为 4096 字节的倍数的文件偏移量发出文件 I/O 请求,以及为值为 4096 字节的倍数的长度发出此请求。
-
O_NONBLOCK 文件模式标志不受 Citrix Endpoint Management 加密功能支持。当由 Citrix Endpoint Management 处理时,此文件模式标志将从模式列表中删除。
加密用户熵
一个用于加密功能的 Citrix Endpoint Management 选项,要求最终用户输入 PIN,然后才能生成加密密钥。此选项称为用户熵。它可导致应用程序出现特殊问题。
具体而言,在用户输入 PIN 之前,将无法执行任何文件或数据库访问。如果在可显示 PIN UI 之前运行的某个位置中存在此 I/O 操作,则此操作始终会失败。
为确保您的应用程序中不存在此问题,请在已启用用户熵功能的情况下进行测试。可使用 Citrix Endpoint Management 客户端属性“Encrypt secrets using Passcode”(使用通行码加密机密)添加用户熵。可以在 Citrix Endpoint Management 控制台的配置 > 设置 > 更多 > 客户端属性下配置默已禁用的客户端属性。
数据防泄漏技术兼容性
- 任何远程视图控制器不会存在安全控制(例如,数据加密;复制、剪切和粘贴策略阻止,等等),因为远程视图控制器与 MDX 托管应用程序在不同的进程上下文中运行。
- “复制”操作是 UIResponder 中唯一受支持的操作。其他操作(如“剪切”和“删除”)不受支持。
- 仅会在 UI 级别截获 AirDrop,而不会在更低的级别截获。
- 不会截获 MFI 和蓝牙。
图标文件支持
MDX 封装过程要求至少存在一个可用作主屏幕图标或应用程序图标的图标。应用程序开发人员可将其图标添加到资产目录,或使用 Info.plist 中的 CFBundleIcons 或 CFBundleIconFiles 密钥。
MDX Toolkit 选择 Info.plist 中已知 plist 位置列表中的第一个 plist 位置:
- CFBundleIcons
- CFBundlePrimaryIcon
- CFBundleIconFiles
- UINewsstandIcon
- CFBundleDocumentTypes
如果在 Info.plist 中找不到这些密钥,MDX Toolkit 将在应用程序捆绑包的根文件夹中标识以下图标之一:
- Icon.png
- Icon-60
@
2x.png - Icon-72.png
- Icon-76.png
网络连接和 Micro VPN
MDX 目前仅管理由应用程序直接发出的网络调用。一些 DNS 查询由 Apple 框架直接发出,所以不受 MDX 管理。
管理员可以使用多个 Citrix Endpoint Management 策略选项执行网络操作。
“网络访问”策略可阻止、允许或重定向应用程序的网络活动。
重要:
MDX Toolkit 版本 18.12.0 包含了合并或替换较旧的策略的新策略。 “网络访问”策略结合了“网络访问”、“首选 VPN 模式”和“允许 VPN 模式切换”。“排除列表”策略替换拆分通道排除列表。“要求 Micro VPN 会话”策略替换“要求联机会话”。有关详细信息,请参阅早期版本中的新增功能。
“通道 - Web SSO”是设置中安全浏览的名称。该行为是相同的。
这些选项如下所示:
- 使用以前的设置:默认值为您已在更早版本的策略中设置的值。如果更改了此选项,则您不应还原为使用以前的设置。另请注意,在用户将应用程序升级到版本 18.12.0 或更高版本之前,对新策略所做的更改将不起作用。
- 阻止:由您的应用程序使用的网络 API 将失败。根据以前的原则,应正确处理此类故障。
- 不限制:所有网络调用都将直接传输,而不通过通道传输。
- 通道 - 完整 VPN:来自托管应用程序的所有流量均通过 Citrix Gateway 进行通道传输。
- 通道 - Web SSO:重写 HTTP/HTTPS URL。此选项仅允许通过通道传输 HTTP 和 HTTPS 流量。通道 - Web SSO 的一个重要优点是可针对 HTTP 和 HTTPS 流量进行单点登录 (SSO),以及执行 PKINIT 身份验证。在 Android 中,此选项的设置开销低,因此是适用于 Web 浏览操作类型的优先选项。
- 通道 - 完整 VPN 和 Web SSO:根据需要允许在 VPN 模式之间自动切换。如果网络请求由于不能在特定的 VPN 模式下处理身份验证请求而失败,则会在备选模式下重试。
限制
- 用户无法在 iOS 包装的 MDX 应用程序中播放内部 Web 站点上托管的视频,因为这些视频在设备上 MDX 不拦截的 Media Player 进程中播放。
- 不支持 NSURLSession 后台下载 (NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier)。
- 如果“网络访问”策略设置为阻止,则会阻止 UDP 流量。如果“网络访问”策略设置为通道 - 完整 VPN,则不通过通道传输 UDP 流量。
- MDX 封装的应用程序无法实例化用于侦听入站连接的套接字服务器。但是,MDX 封装的应用程序可以使用客户端套接字连接到服务器。
第三方库支持
一些应用程序框架存在与 Citrix Endpoint Management 的兼容性问题︰
- 支持通过 Xamarin 跨平台开发环境开发的应用程序。由于使用和测试示例不足,因此,Citrix 未正式声明支持其他跨平台开发环境。
- SQLCipher 无法用于加密,因为它使用内存映射。一种解决方案是不使用 SQLCipher。另一种解决方案是使用加密排除策略在加密期间排除数据库文件。Citrix Endpoint Management 管理员必须在 Citrix Endpoint Management 控制台中配置策略。
- 直接链接到 OpenSSL libcrypto.a 和 libssl.a 的应用程序和第三方库可导致因缺少符号和存在多个符号定义而发生的链接错误。
- 需支持 Apple 推送通知服务的应用程序需要执行 Apple 所要求的具体步骤。
- Citrix Endpoint Management 将 SQLite 数据库版本显式地设置为 1,以在 SQLite 数据库内禁用 Write Ahead Logging (WAL) 文件和内存映射文件支持。任何在 SQLite 版本 2 或 3 中直接访问 SQLite 接口的尝试将失败。