适用于 iOS 应用程序的最佳实践
在开发 iOS 应用程序时,请遵循以下最佳实践,以提高 Citrix Endpoint Management™ 与 iOS 设备移动应用程序之间的兼容性。
MDX App SDK Framework 和打包
如果您的应用程序使用 MDX App SDK Framework,则必须使用匹配的 MDX Toolkit 版本进行打包。这两个组件之间的版本不匹配可能会导致操作不当。
为防止此类不匹配,请将应用程序打包为 ISV 应用程序,并指定应用程序模式为 Premium 或 General。这使您能够交付预打包的应用程序。因此,您的客户无需打包应用程序,从而避免使用不匹配的 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:
-
items:
dataForPasteboardType:inItemSet:
valuesForPasteboardType:inItemSet:
对象类: UIPopoverController
- 选择器名称: initWithContentViewController:
对象类: UINavigationController
-
选择器名称:
ctxInitWithRootViewController:
ctxPopToViewController:animated:
重定向运行时接口
Citrix Endpoint Management 提供 UI Pin Prompt 交互,因此您无需在应用程序中执行此操作。
为确保 Citrix Endpoint Management 的就绪状态,我们建议您不要重定向或替换 Objective-C 运行时选择器。原因是 Citrix Endpoint Management 会对多个对象类选择器的底层方法进行 swizzle,以控制或修改应用程序的运行时行为。下表列出了 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
-
选择器名称:
devices
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:
items
itemSetWithPasteboardTypes:
dataForPasteboardType:inItemSet:
valuesForPasteboardType:inItemSet:
string
strings
URL
URLs
image
images
color
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,则该私有库的数据库实例不会由 Citrix Endpoint Management 加密。
-
Apple SQLite 数据库由 Citrix Endpoint Management 使用 SQLite 虚拟文件系统层加密。
性能可能是一个问题。标准数据库缓存大小为 2000 页或 8 兆字节。如果数据库很大,开发人员可能需要指定 SQLite pragma 来增加数据库缓存大小。在 Objective-C Core Data 框架中,在将持久性存储对象添加到持久性存储控制器对象时,可以将 SQLite pragma 作为选项字典添加。
-
SQLite WAL 模式不受支持,因为该库已重新链接到文件 I/O 接口,并且内部广泛使用内存映射。
-
NSURLCache DiskCache 由 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 类型。
-
为了获得最佳性能,文件 I/O 请求应发送到 4096 字节的倍数的文件偏移量,并且应以 4096 字节的倍数长度发出。
-
O_NONBLOCK 文件模式标志不受 Citrix Endpoint Management 加密支持。此文件模式标志在由 Citrix Endpoint Management 处理时会从模式列表中删除。
加密用户熵
Citrix Endpoint Management 的一个加密选项要求最终用户在生成加密密钥之前输入 PIN。此选项称为用户熵。它可能会给应用程序带来特定问题。
具体来说,在用户输入 PIN 之前,无法执行任何文件或数据库访问。如果此类 I/O 操作存在于 PIN UI 显示之前运行的位置,则它将始终失败。
为确保您的应用中不存在此问题,请在启用用户熵的情况下进行测试。Citrix Endpoint Management 客户端属性“使用密码加密密钥”会增加用户熵。您可以在 Citrix Endpoint Management 控制台中,通过导航到 配置 > 设置 > 更多 > 客户端属性 来配置此客户端属性,该属性默认处于禁用状态。
数据隔离兼容性
- 任何远程视图控制器都不会具有安全隔离(例如,数据加密;复制、剪切和粘贴策略阻止等),因为远程视图控制器在与 MDX 管理的应用不同的进程上下文中运行。
- 仅 UIResponder 支持“复制”操作。不支持其他操作,例如“剪切”和“删除”。
- AirDrop 仅在 UI 级别被拦截,而不在较低级别被拦截。
- 不会拦截 MFI 和蓝牙。
图标文件支持
MDX 封装要求至少存在一个可用作主屏幕图标或应用图标的图标。应用开发人员可以将其图标添加到资产目录中,或者在 Info.plist 中使用 CFBundleIcons 或 CFBundleIconFiles 键。
MDX Toolkit 会从 Info.plist 中已知 plist 位置列表中选择第一个:
- CFBundleIcons
- CFBundlePrimaryIcon
- CFBundleIconFiles
- UINewsstandIcon
- CFBundleDocumentTypes
如果在 Info.plist 中未找到这些键中的任何一个,MDX Toolkit 将在应用捆绑包的根文件夹中识别以下图标之一:
- Icon.png
- Icon-60
@2x.png - Icon-72.png
- Icon-76.png
网络和微 VPN
MDX 目前仅管理应用直接发出的网络调用。某些 DNS 查询由 Apple 框架直接发出,因此不受 MDX 管理。
管理员可以使用多种 Citrix Endpoint Management 网络策略选项。
网络访问策略可阻止、允许或重定向应用网络活动。
重要提示:
MDX Toolkit 版本 18.12.0 包含合并或替换旧策略的新策略。 网络访问策略结合了网络访问、首选 VPN 模式和允许 VPN 模式切换。排除列表策略取代了拆分隧道排除列表。微 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 应用中播放内部网站上托管的视频,因为这些视频在设备上的媒体播放器进程中播放,而 MDX 不会拦截该进程。
- 不支持 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 数据库中的预写日志 (WAL) 文件和内存映射文件支持。任何直接访问 SQLite 版本 2 或版本 3 中的 SQLite 接口的尝试都将失败。