NSUserDefaultsのregisterDefaultsで登録された値を削除する

皆さんこんばんは。 優雅に京都でラーメンを食べているDayBySayです。

今回はタイトルの通りNSUserDefaultsregisterDefaultsで登録された値を削除することに関しての記事です。

モチベーション

当時開発していたアプリケーションで少々特殊な要件があり、UserAgentを柔軟に変えていくみたいな機能を実装する必要がありました。

iOS9以降でかつWKWebView限定に限定するのであればWKWebViewcustomUserAgentを設定するだけで済みますが、その時はiOS8以前もサポートしていたためNSUserDefaultsregisterDefaultsを使用しており、その場合において1度設定したカスタムなUserAgentからシステムの初期UserAgent状態に戻す方法がわかりませんでした。

ちなみに下記のようにUserDefaultsに設定を行うことでUserAgentのカスタマイズを行っています。

var webview = UIWebView.init()
webview.stringByEvaluatingJavaScriptFromString("navigator.userAgent") // ""Mozilla/5.0 (iPhone; CPU iPhone OS 9_2 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Mobile/13C75""

NSUserDefaults.standardUserDefaults().registerDefaults(["UserAgent": "CustomUserAgent"])

webview = UIWebView.init() // registerDefaults後に初期化されたものからUAの変更が適用されます
webview.stringByEvaluatingJavaScriptFromString("navigator.userAgent") // "CustomUserAgent"

registerDefaultsで登録された値を初期化する

つまり、今回の要件としてはregisterDefaultsに登録された値を初期化すれば良いということになります。

普通に考えれば登録された値を削除するAPINSUserDefaultsにありそうな予感がしますが。。

NSUserDefaultsのリファレンスをみてみても、RegistaringDefaultsにはregisterDefaultsメソッドしか存在しません。

ふむ。。と思いつつも、もう少し探ってみるとregisterDefaultsの説明として下記のような記述がありました。

Discussion

If there is no registration domain, one is created using the specified dictionary, and NSRegistrationDomain is added to the end of the search list.

なるほど。registerDefaultsされた値はNSRegistrationDomainというドメインに保存されるらしいことがわかりました。

つまり、NSRegistrationDomainの値を書き換える(あるいは削除する)方法がわかれば対応できる可能性が出てきましたね。

NSRegistrationDomainに登録されている値を削除する

どんなドメインがあるかについてはこちらで多少説明されています。

NSRegistrationDomain

The domain consisting of a set of temporary defaults whose values can be set by the application to ensure that searches will always be successful.

NSRegistrationDomainは検索を必ず成功させるために設定されるものらしいです。 そういう意味では、値を簡単に削除できないようなインターフェイスになっているのは納得感が有りますね。

ただし今回は削除できないと困るので方法を探して見たところ、- setVolatileDomain:forName:というドメインの辞書をまるまる上書き出来そうなメソッドを見つけました。

後は、ドメインに登録されている辞書の一覧が取得できればなんとかなりそうですね!と、思いながら探すと- volatileDomainForName:というなにやら雰囲気のあるメソッドを見つけました。

説明は下記のようになっています。

Returns the dictionary for the specified volatile domain.

Return Value

The dictionary of keys and values belonging to the domain. The keys in the dictionary are names of defaults, and the value corresponding to each key is a property list object (NSData, NSString, NSNumber, NSDate, NSArray, or NSDictionary).

ドメインの文字列を受け取って中身の辞書を返す、というように読み取ることが出来ます。

ということで実際に中身を見てみましょう

let registeredDefaults = NSUserDefaults.standardUserDefaults().volatileDomainForName(NSRegistrationDomain) // ["WebKitHiddenPageCSSAnimationSuspensionEnabled": 0, "WebKitMediaControlsScaleWithPageZoom": 0, "WebKitJavaScriptCanAccessClipboard": 0, "WebKitMediaPlaybackAllowsInline": 0, "WebKitLoadSiteIconsKey": 0, "WebKitDisplayImagesKey": 1, "WebKitAVKitEnabled": 1, "WebKitSuppressesIncrementalRendering": 0, "WebKitPictographFont": "AppleColorEmoji", "WebKitFantasyFont": "Papyrus", "WebKitWantsBalancedSetDefersLoadingBehavior": 0, "WebKitFrameFlatteningEnabled": 1, "WebKitDiagnosticLoggingEnabled": 0, "WebKitForceSoftwareWebGLRendering": 0, "WebKitSansSerifFont": "Helvetica", "AppleLanguages": ["en"], "WebKitSimpleLineLayoutDebugBordersEnabled": 0, "WebKitNetworkDataUsageTrackingEnabledPreferenceKey": 0, "WebKitExperimentalNotificationsEnabledPreferenceKey": 0, "WebKitPrivateBrowsingEnabled": 0, "WebKitUsesPageCachePreferenceKey": 1, "WebKitApplicationCacheDefaultOriginQuota": 26214400, "WebKitCursiveFont": "Snell Roundhand", "WebKitEnablePasswordEchoPreferenceKey": 1, "WebKitAcceleratedDrawingEnabled": 0, "WebKitSpatialNavigationEnabled": 0, "WebKitJavaScriptRuntimeFlagsPreferenceKey": 0, "WebKitStandardFont": "Times", "WebKitTelephoneParsingEnabledPreferenceKey": 0, "WebKitHistoryItemLimit": "1000", "WebKitStorageBlockingPolicy": 0, "WebKitNetworkInterfaceNamePreferenceKey": "", "WebKitInterpolationQualityPreferenceKey": 2, "WebKitCacheModelPreferenceKey": 0, "WebKitWebAudioEnabled": 1, "WebKitJavaScriptEnabled": 1, "WebKitApplicationCacheTotalQuota": 9223372036854775807, "WebKitUseLegacyTextAlignPositionedElementBehavior": 0, "WebKitPasswordEchoDurationPreferenceKey": 2, "WebKitUserStyleSheetEnabledPreferenceKey": 0, "AntialiasedFontDilationEnabled": 0, "WebKitEditableLinkBehavior": 0, "WebKitAudioSessionCategoryOverride": 0, "UserAgent": "CustomUserAgent", "WebKitLocalFileContentSniffingEnabledPreferenceKey": 0, "WebKitShowRepaintCounter": 0, "WebKitHiddenPageDOMTimerThrottlingEnabled": 0, "WebKitFixedFont": "Courier", "WebKitPageCacheSupportsPluginsPreferenceKey": 1, "WebKitUseSiteSpecificSpoofing": "0", "NSLanguages": ["en"], "WebKitJavaScriptMarkupEnabled": 1, "WebKitCSSRegionsEnabled": 1, "WebKitHyperlinkAuditingEnabled": 1, "WebKitAllowAnimatedImageLoopingPreferenceKey": 1, "WebKitLocalStorageEnabledPreferenceKey": 1, "WebKitWebArchiveDebugModeEnabledPreferenceKey": 0, "WebKitLowPowerVideoAudioBufferSizeEnabled": 0, "WebKitDeveloperExtrasEnabledPreferenceKey": 0, "WebKitAllowAnimatedImagesPreferenceKey": 1, "WebKitCSSCompositingEnabled": 1, "WebKitSerifFont": "Times", "WebKitQTKitEnabled": 0, "WebKitDefaultFixedFontSize": "13", "WebKitDOMTimersThrottlingEnabledPreferenceKey": 1, "WebKitLayoutIntervalPreferenceKey": -1, "WebKitAsynchronousSpellCheckingEnabled": 0, "WebKitPlugInSnapshottingEnabled": 0, "WebKitOfflineWebApplicationCacheEnabled": 0, "WebKitHistoryAgeInDaysLimit": "7", "WebKitAccelerated2dCanvasEnabled": 0, "WebKitDNSPrefetchingEnabled": 0, "WebKitSubpixelCSSOMElementMetricsEnabled": 0, "WebKitShouldRespectImageOrientation": 1, "WebKitDatabasesEnabledPreferenceKey": 1, "WebKitAllowFileAccessFromFileURLs": 1, "NSInterfaceStyle": "macintosh", "WebKitAuthorAndUserStylesEnabledPreferenceKey": 1, "WebKitAcceleratedCompositingEnabled": 1, "WebKitShowDebugBorders": 0, …, "WebKitFullScreenEnabled": 0, "WebKitWebSecurityEnabled": 1, "WebKitAlwaysRequestGeolocationPermission": 0, "WebKitAllowMultiElementImplicitFormSubmissionPreferenceKey": 0, "WebKitMaxParseDurationPreferenceKey": -1, "WebKitMinimumZoomFontSizePreferenceKey": 15, "WebKitXSSAuditorEnabled": 1, "WebKitEnableInheritURIQueryComponent": 0, "WebKitShrinksStandaloneImagesToFit": 0, "WebKitWebGLEnabled": 1, "WebKitBackForwardCacheExpirationIntervalKey": "1800", "WebKitAllowUniversalAccessFromFileURLs": 1, "WebKitUsesEncodingDetector": 0, "WebKitDefaultFontSize": "16", "WebKitPluginsEnabled": 1, "WebKitMinimumFontSize": "0", "WebKitCanvasUsesAcceleratedDrawing": 0, "WebKitMinimumLogicalFontSize": "9", "WebKitMediaPlaybackRequiresUserGesture": 1, "WebKitDefaultTextEncodingName": "ISO-8859-1"]

なるほど、なんかそれっぽいのが返ってきている!

すげーわかりづらいけど真ん中らへんに"UserAgent": "CustomUserAgent"を見つけたので、これらはregisterDefaultsで登録された辞書の値だということがわかりました。

ということは、- volatileDomainForName:で辞書を取得し、特定のキー(今回は'UserAgent')を削除し、- setVolatileDomain:forName:で辞書を登録すれば行けそうな雰囲気を感じます。

ということでやってみました。

var webview = UIWebView.init()
webview.stringByEvaluatingJavaScriptFromString("navigator.userAgent") // ""Mozilla/5.0 (iPhone; CPU iPhone OS 9_2 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Mobile/13C75""

// UAの登録
NSUserDefaults.standardUserDefaults().registerDefaults(["UserAgent": "CustomUserAgent"])

webview = UIWebView.init()
webview.stringByEvaluatingJavaScriptFromString("navigator.userAgent") // "CustomUserAgent"

// 登録されたUAの削除
var registeredDefaults = NSUserDefaults.standardUserDefaults().volatileDomainForName(NSRegistrationDomain)
registeredDefaults.removeValueForKey("UserAgent")
NSUserDefaults.standardUserDefaults().setVolatileDomain(registeredDefaults, forName: NSRegistrationDomain)

webview = UIWebView.init()
webview.stringByEvaluatingJavaScriptFromString("navigator.userAgent") // ""Mozilla/5.0 (iPhone; CPU iPhone OS 9_2 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Mobile/13C75"" <- 戻ってる!

ヒュー!

まとめ

NSUserDefaultsregistrationDefaultsで登録した値を消したい時は- volatileDomainForName:で辞書を取得し、特定のキーを削除し、- setVolatileDomain:forName: で上書き登録し直す。