├── .gitignore ├── README.md ├── objective-c ├── Preferences.h └── Preferences.m └── swift └── Preferences.swift /.gitignore: -------------------------------------------------------------------------------- 1 | # Mac Finder 2 | .DS_Store 3 | .Trashes 4 | Icon? 5 | 6 | # Thumbnails 7 | ._* 8 | 9 | # Files that might appear on external disk 10 | .Spotlight-V100 11 | .Trashes 12 | 13 | # Windows 14 | Thumbs.db 15 | 16 | # Xcode 17 | *.pbxuser 18 | *.mode1v3 19 | *.mode2v3 20 | *.perspectivev3 21 | *.xccheckout 22 | *.profraw 23 | !default.pbxuser 24 | !default.mode1v3 25 | !default.mode2v3 26 | !default.perspectivev3 27 | xcuserdata 28 | 29 | # VisualStudio 30 | *.suo 31 | *.sdf 32 | *.opensdf 33 | *.vcxproj.user 34 | *.csproj.user 35 | *.VC.db 36 | *.VC.opendb 37 | ipch 38 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | user-defaults 2 | ============= 3 | 4 | [![Build Status](https://img.shields.io/travis/macmade/user-defaults.svg?branch=master&style=flat)](https://travis-ci.org/macmade/user-defaults) 5 | [![Coverage Status](https://img.shields.io/coveralls/macmade/user-defaults.svg?branch=master&style=flat)](https://coveralls.io/r/macmade/user-defaults?branch=master) 6 | [![Issues](http://img.shields.io/github/issues/macmade/user-defaults.svg?style=flat)](https://github.com/macmade/user-defaults/issues) 7 | ![Status](https://img.shields.io/badge/status-active-brightgreen.svg?style=flat) 8 | ![License](https://img.shields.io/badge/license-mit-brightgreen.svg?style=flat) 9 | [![Contact](https://img.shields.io/badge/contact-@macmade-blue.svg?style=flat)](https://twitter.com/macmade) 10 | [![Donate-Patreon](https://img.shields.io/badge/donate-patreon-yellow.svg?style=flat)](https://patreon.com/macmade) 11 | [![Donate-Gratipay](https://img.shields.io/badge/donate-gratipay-yellow.svg?style=flat)](https://www.gratipay.com/macmade) 12 | [![Donate-Paypal](https://img.shields.io/badge/donate-paypal-yellow.svg?style=flat)](https://paypal.me/xslabs) 13 | 14 | About 15 | ----- 16 | 17 | Code examples for the [Better NSUserDefaults with Swift](http://www.xs-labs.com/en/blog/2017/10/08/better-nsuserdefaults-with-swift/) article. 18 | 19 | License 20 | ------- 21 | 22 | Code is released under the terms of the MIT License. 23 | 24 | Repository Infos 25 | ---------------- 26 | 27 | Owner: Jean-David Gadina - XS-Labs 28 | Web: www.xs-labs.com 29 | Blog: www.noxeos.com 30 | Twitter: @macmade 31 | GitHub: github.com/macmade 32 | LinkedIn: ch.linkedin.com/in/macmade/ 33 | StackOverflow: stackoverflow.com/users/182676/macmade 34 | -------------------------------------------------------------------------------- /objective-c/Preferences.h: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2015 Jean-David Gadina - www.xs-labs.com 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | ******************************************************************************/ 24 | 25 | #import 26 | 27 | NS_ASSUME_NONNULL_BEGIN 28 | 29 | @interface Preferences: NSObject 30 | 31 | + ( instancetype )sharedInstance; 32 | 33 | @property( atomic, readwrite, strong, nullable ) NSDate * lastStart; 34 | @property( atomic, readwrite, strong, nullable ) NSString * fontName; 35 | @property( atomic, readwrite, assign ) CGFloat fontSize; 36 | 37 | @end 38 | 39 | NS_ASSUME_NONNULL_END 40 | -------------------------------------------------------------------------------- /objective-c/Preferences.m: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2015 Jean-David Gadina - www.xs-labs.com 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | ******************************************************************************/ 24 | 25 | #import "Preferences.h" 26 | #import 27 | 28 | NS_ASSUME_NONNULL_BEGIN 29 | 30 | @interface Preferences() 31 | 32 | @property( atomic, readwrite, assign ) BOOL inited; 33 | @property( atomic, readwrite, strong ) NSArray< NSString * > * properties; 34 | 35 | @end 36 | 37 | NS_ASSUME_NONNULL_END 38 | 39 | @implementation Preferences 40 | 41 | + ( instancetype )sharedInstance 42 | { 43 | static dispatch_once_t once; 44 | static id instance = nil; 45 | 46 | dispatch_once 47 | ( 48 | &once, 49 | ^( void ) 50 | { 51 | instance = [ [ super allocWithZone: nil ] init ]; 52 | } 53 | ); 54 | 55 | return instance; 56 | } 57 | 58 | + ( instancetype )allocWithZone: ( struct _NSZone * )zone 59 | { 60 | ( void )zone; 61 | 62 | return [ self sharedInstance ]; 63 | } 64 | 65 | - ( instancetype )init 66 | { 67 | NSString * path; 68 | NSDictionary * defaults; 69 | 70 | if( self.inited ) 71 | { 72 | return self; 73 | } 74 | 75 | if( ( self = [ super init ] ) ) 76 | { 77 | self.inited = YES; 78 | 79 | path = [ [ NSBundle mainBundle ] pathForResource: @"Defaults" ofType: @"plist" ]; 80 | defaults = [ NSDictionary dictionaryWithContentsOfFile: path ]; 81 | 82 | { 83 | unsigned int i; 84 | objc_property_t * list; 85 | NSString * name; 86 | NSMutableArray< NSString * > * properties; 87 | id value; 88 | 89 | properties = [ NSMutableArray new ]; 90 | list = class_copyPropertyList( self.class, &i ); 91 | 92 | if( list ) 93 | { 94 | while( i ) 95 | { 96 | name = [ NSString stringWithUTF8String: property_getName( list[ --i ] ) ]; 97 | 98 | if( name == nil || name.length == 0 ) 99 | { 100 | continue; 101 | } 102 | 103 | if( [ name isEqualToString: @"inited" ] || [ name isEqualToString: @"properties" ] ) 104 | { 105 | continue; 106 | } 107 | 108 | [ properties addObject: name ]; 109 | 110 | value = [ [ NSUserDefaults standardUserDefaults ] objectForKey: name ]; 111 | 112 | if( value != nil && [ NSPropertyListSerialization propertyList: value isValidForFormat: NSPropertyListXMLFormat_v1_0 ] ) 113 | { 114 | [ self setValue: value forKey: name ]; 115 | } 116 | 117 | [ self addObserver: self forKeyPath: name options: NSKeyValueObservingOptionNew context: NULL ]; 118 | } 119 | } 120 | 121 | self.properties = [ NSArray arrayWithArray: properties ]; 122 | 123 | free( list ); 124 | } 125 | } 126 | 127 | return self; 128 | } 129 | 130 | - ( void )dealloc 131 | { 132 | for( NSString * p in self.properties ) 133 | { 134 | [ self removeObserver: self forKeyPath: p ]; 135 | } 136 | } 137 | 138 | - ( void )observeValueForKeyPath: ( NSString * )keyPath ofObject: ( id )object change: ( NSDictionary * )change context: ( void * )context 139 | { 140 | if( object == self && [ self.properties containsObject: keyPath ] ) 141 | { 142 | [ [ NSUserDefaults standardUserDefaults ] setObject: [ self valueForKey: keyPath ] forKey: keyPath ]; 143 | [ [ NSUserDefaults standardUserDefaults ] synchronize ]; 144 | } 145 | else 146 | { 147 | [ super observeValueForKeyPath: keyPath ofObject: object change: change context: context ]; 148 | } 149 | } 150 | 151 | @end 152 | -------------------------------------------------------------------------------- /swift/Preferences.swift: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2017 Jean-David Gadina - www.xs-labs.com 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | ******************************************************************************/ 24 | 25 | import Cocoa 26 | 27 | @objc public class Preferences: NSObject 28 | { 29 | @objc public dynamic var lastStart: Date? 30 | @objc public dynamic var fontName: String? 31 | @objc public dynamic var fontSize: CGFloat = 0 32 | 33 | @objc public static let shared = Preferences() 34 | 35 | override init() 36 | { 37 | super.init() 38 | 39 | if let path = Bundle.main.path( forResource: "Defaults", ofType: "plist" ) 40 | { 41 | UserDefaults.standard.register( defaults: NSDictionary( contentsOfFile: path ) as? [ String : Any ] ?? [ : ] ) 42 | } 43 | 44 | for c in Mirror( reflecting: self ).children 45 | { 46 | guard let key = c.label else 47 | { 48 | continue 49 | } 50 | 51 | if let value = UserDefaults.standard.object( forKey: key ) 52 | { 53 | self.setValue( value, forKey: key ) 54 | } 55 | 56 | self.addObserver( self, forKeyPath: key, options: .new, context: nil ) 57 | } 58 | } 59 | 60 | deinit 61 | { 62 | for c in Mirror( reflecting: self ).children 63 | { 64 | guard let key = c.label else 65 | { 66 | continue 67 | } 68 | 69 | self.removeObserver( self, forKeyPath: key ) 70 | } 71 | } 72 | 73 | public override func observeValue( forKeyPath keyPath: String?, of object: Any?, change: [ NSKeyValueChangeKey : Any ]?, context: UnsafeMutableRawPointer? ) 74 | { 75 | var found = false 76 | 77 | for c in Mirror( reflecting: self ).children 78 | { 79 | guard let key = c.label else 80 | { 81 | continue 82 | } 83 | 84 | if( key == keyPath ) 85 | { 86 | UserDefaults.standard.set( change?[ NSKeyValueChangeKey.newKey ], forKey: key ) 87 | 88 | found = true 89 | 90 | break 91 | } 92 | } 93 | 94 | if( found == false ) 95 | { 96 | super.observeValue( forKeyPath: keyPath, of: object, change: change, context: context ) 97 | } 98 | } 99 | } 100 | --------------------------------------------------------------------------------