├── .gitignore ├── PhoneticContacts.xcodeproj ├── project.pbxproj └── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ └── WorkspaceSettings.xcsettings ├── README.md └── main.swift /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/swift,appcode,xcode,osx 3 | 4 | ### Swift ### 5 | # Xcode 6 | # 7 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 8 | 9 | ## Build generated 10 | build/ 11 | DerivedData 12 | 13 | ## Various settings 14 | *.pbxuser 15 | !default.pbxuser 16 | *.mode1v3 17 | !default.mode1v3 18 | *.mode2v3 19 | !default.mode2v3 20 | *.perspectivev3 21 | !default.perspectivev3 22 | xcuserdata 23 | 24 | ## Other 25 | *.xccheckout 26 | *.moved-aside 27 | *.xcuserstate 28 | *.xcscmblueprint 29 | 30 | ## Obj-C/Swift specific 31 | *.hmap 32 | *.ipa 33 | 34 | ## Playgrounds 35 | timeline.xctimeline 36 | playground.xcworkspace 37 | 38 | # Swift Package Manager 39 | # 40 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 41 | # Packages/ 42 | .build/ 43 | 44 | # CocoaPods 45 | # 46 | # We recommend against adding the Pods directory to your .gitignore. However 47 | # you should judge for yourself, the pros and cons are mentioned at: 48 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 49 | # 50 | # Pods/ 51 | 52 | # Carthage 53 | # 54 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 55 | # Carthage/Checkouts 56 | 57 | Carthage/Build 58 | 59 | # fastlane 60 | # 61 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 62 | # screenshots whenever they are needed. 63 | # For more information about the recommended setup visit: 64 | # https://github.com/fastlane/fastlane/blob/master/docs/Gitignore.md 65 | 66 | fastlane/report.xml 67 | fastlane/screenshots 68 | 69 | 70 | ### AppCode ### 71 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 72 | 73 | *.iml 74 | 75 | ## Directory-based project format: 76 | .idea/ 77 | # if you remove the above rule, at least ignore the following: 78 | 79 | # User-specific stuff: 80 | # .idea/workspace.xml 81 | # .idea/tasks.xml 82 | # .idea/dictionaries 83 | # .idea/shelf 84 | 85 | # Sensitive or high-churn files: 86 | # .idea/dataSources.ids 87 | # .idea/dataSources.xml 88 | # .idea/sqlDataSources.xml 89 | # .idea/dynamic.xml 90 | # .idea/uiDesigner.xml 91 | 92 | # Gradle: 93 | # .idea/gradle.xml 94 | # .idea/libraries 95 | 96 | # Mongo Explorer plugin: 97 | # .idea/mongoSettings.xml 98 | 99 | ## File-based project format: 100 | *.ipr 101 | *.iws 102 | 103 | ## Plugin-specific files: 104 | 105 | # IntelliJ 106 | /out/ 107 | 108 | # mpeltonen/sbt-idea plugin 109 | .idea_modules/ 110 | 111 | # JIRA plugin 112 | atlassian-ide-plugin.xml 113 | 114 | # Crashlytics plugin (for Android Studio and IntelliJ) 115 | com_crashlytics_export_strings.xml 116 | crashlytics.properties 117 | crashlytics-build.properties 118 | fabric.properties 119 | 120 | 121 | ### Xcode ### 122 | # Xcode 123 | # 124 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 125 | 126 | ## Build generated 127 | build/ 128 | DerivedData 129 | 130 | ## Various settings 131 | *.pbxuser 132 | !default.pbxuser 133 | *.mode1v3 134 | !default.mode1v3 135 | *.mode2v3 136 | !default.mode2v3 137 | *.perspectivev3 138 | !default.perspectivev3 139 | xcuserdata 140 | 141 | ## Other 142 | *.xccheckout 143 | *.moved-aside 144 | *.xcuserstate 145 | 146 | 147 | ### OSX ### 148 | .DS_Store 149 | .AppleDouble 150 | .LSOverride 151 | 152 | # Icon must end with two \r 153 | Icon 154 | 155 | # Thumbnails 156 | ._* 157 | 158 | # Files that might appear in the root of a volume 159 | .DocumentRevisions-V100 160 | .fseventsd 161 | .Spotlight-V100 162 | .TemporaryItems 163 | .Trashes 164 | .VolumeIcon.icns 165 | 166 | # Directories potentially created on remote AFP share 167 | .AppleDB 168 | .AppleDesktop 169 | Network Trash Folder 170 | Temporary Items 171 | .apdisk 172 | 173 | -------------------------------------------------------------------------------- /PhoneticContacts.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | D744DD001C6738BD00D01E04 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = D744DCFF1C6738BD00D01E04 /* main.swift */; }; 11 | /* End PBXBuildFile section */ 12 | 13 | /* Begin PBXCopyFilesBuildPhase section */ 14 | D7E7F1C71661184E008C3C32 /* CopyFiles */ = { 15 | isa = PBXCopyFilesBuildPhase; 16 | buildActionMask = 2147483647; 17 | dstPath = /usr/share/man/man1/; 18 | dstSubfolderSpec = 0; 19 | files = ( 20 | ); 21 | runOnlyForDeploymentPostprocessing = 1; 22 | }; 23 | /* End PBXCopyFilesBuildPhase section */ 24 | 25 | /* Begin PBXFileReference section */ 26 | D744DCFF1C6738BD00D01E04 /* main.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = ""; }; 27 | D7E7F1C91661184E008C3C32 /* PhoneticContacts */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = PhoneticContacts; sourceTree = BUILT_PRODUCTS_DIR; }; 28 | /* End PBXFileReference section */ 29 | 30 | /* Begin PBXFrameworksBuildPhase section */ 31 | D7E7F1C61661184E008C3C32 /* Frameworks */ = { 32 | isa = PBXFrameworksBuildPhase; 33 | buildActionMask = 2147483647; 34 | files = ( 35 | ); 36 | runOnlyForDeploymentPostprocessing = 0; 37 | }; 38 | /* End PBXFrameworksBuildPhase section */ 39 | 40 | /* Begin PBXGroup section */ 41 | D7E7F1BE1661184E008C3C32 = { 42 | isa = PBXGroup; 43 | children = ( 44 | D744DCFF1C6738BD00D01E04 /* main.swift */, 45 | D7E7F1CA1661184E008C3C32 /* Products */, 46 | ); 47 | sourceTree = ""; 48 | }; 49 | D7E7F1CA1661184E008C3C32 /* Products */ = { 50 | isa = PBXGroup; 51 | children = ( 52 | D7E7F1C91661184E008C3C32 /* PhoneticContacts */, 53 | ); 54 | name = Products; 55 | sourceTree = ""; 56 | }; 57 | /* End PBXGroup section */ 58 | 59 | /* Begin PBXNativeTarget section */ 60 | D7E7F1C81661184E008C3C32 /* PhoneticContacts */ = { 61 | isa = PBXNativeTarget; 62 | buildConfigurationList = D7E7F1D81661184E008C3C32 /* Build configuration list for PBXNativeTarget "PhoneticContacts" */; 63 | buildPhases = ( 64 | D7E7F1C51661184E008C3C32 /* Sources */, 65 | D7E7F1C61661184E008C3C32 /* Frameworks */, 66 | D7E7F1C71661184E008C3C32 /* CopyFiles */, 67 | ); 68 | buildRules = ( 69 | ); 70 | dependencies = ( 71 | ); 72 | name = PhoneticContacts; 73 | productName = PhoneticContacts; 74 | productReference = D7E7F1C91661184E008C3C32 /* PhoneticContacts */; 75 | productType = "com.apple.product-type.tool"; 76 | }; 77 | /* End PBXNativeTarget section */ 78 | 79 | /* Begin PBXProject section */ 80 | D7E7F1C01661184E008C3C32 /* Project object */ = { 81 | isa = PBXProject; 82 | attributes = { 83 | LastSwiftUpdateCheck = 0720; 84 | LastUpgradeCheck = 0900; 85 | ORGANIZATIONNAME = "Lex Tang"; 86 | TargetAttributes = { 87 | D7E7F1C81661184E008C3C32 = { 88 | LastSwiftMigration = 0900; 89 | }; 90 | }; 91 | }; 92 | buildConfigurationList = D7E7F1C31661184E008C3C32 /* Build configuration list for PBXProject "PhoneticContacts" */; 93 | compatibilityVersion = "Xcode 3.2"; 94 | developmentRegion = English; 95 | hasScannedForEncodings = 0; 96 | knownRegions = ( 97 | en, 98 | ); 99 | mainGroup = D7E7F1BE1661184E008C3C32; 100 | productRefGroup = D7E7F1CA1661184E008C3C32 /* Products */; 101 | projectDirPath = ""; 102 | projectRoot = ""; 103 | targets = ( 104 | D7E7F1C81661184E008C3C32 /* PhoneticContacts */, 105 | ); 106 | }; 107 | /* End PBXProject section */ 108 | 109 | /* Begin PBXSourcesBuildPhase section */ 110 | D7E7F1C51661184E008C3C32 /* Sources */ = { 111 | isa = PBXSourcesBuildPhase; 112 | buildActionMask = 2147483647; 113 | files = ( 114 | D744DD001C6738BD00D01E04 /* main.swift in Sources */, 115 | ); 116 | runOnlyForDeploymentPostprocessing = 0; 117 | }; 118 | /* End PBXSourcesBuildPhase section */ 119 | 120 | /* Begin XCBuildConfiguration section */ 121 | D7E7F1D61661184E008C3C32 /* Debug */ = { 122 | isa = XCBuildConfiguration; 123 | buildSettings = { 124 | ALWAYS_SEARCH_USER_PATHS = NO; 125 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 126 | CLANG_CXX_LIBRARY = "libc++"; 127 | CLANG_ENABLE_OBJC_ARC = YES; 128 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 129 | CLANG_WARN_BOOL_CONVERSION = YES; 130 | CLANG_WARN_COMMA = YES; 131 | CLANG_WARN_CONSTANT_CONVERSION = YES; 132 | CLANG_WARN_EMPTY_BODY = YES; 133 | CLANG_WARN_ENUM_CONVERSION = YES; 134 | CLANG_WARN_INFINITE_RECURSION = YES; 135 | CLANG_WARN_INT_CONVERSION = YES; 136 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 137 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 138 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 139 | CLANG_WARN_STRICT_PROTOTYPES = YES; 140 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 141 | CLANG_WARN_UNREACHABLE_CODE = YES; 142 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 143 | COPY_PHASE_STRIP = NO; 144 | ENABLE_STRICT_OBJC_MSGSEND = YES; 145 | ENABLE_TESTABILITY = YES; 146 | GCC_C_LANGUAGE_STANDARD = gnu99; 147 | GCC_DYNAMIC_NO_PIC = NO; 148 | GCC_ENABLE_OBJC_EXCEPTIONS = YES; 149 | GCC_NO_COMMON_BLOCKS = YES; 150 | GCC_OPTIMIZATION_LEVEL = 0; 151 | GCC_PREPROCESSOR_DEFINITIONS = ( 152 | "DEBUG=1", 153 | "$(inherited)", 154 | ); 155 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 156 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 157 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 158 | GCC_WARN_UNDECLARED_SELECTOR = YES; 159 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 160 | GCC_WARN_UNUSED_FUNCTION = YES; 161 | GCC_WARN_UNUSED_VARIABLE = YES; 162 | MACOSX_DEPLOYMENT_TARGET = 10.9; 163 | ONLY_ACTIVE_ARCH = YES; 164 | SDKROOT = macosx; 165 | }; 166 | name = Debug; 167 | }; 168 | D7E7F1D71661184E008C3C32 /* Release */ = { 169 | isa = XCBuildConfiguration; 170 | buildSettings = { 171 | ALWAYS_SEARCH_USER_PATHS = NO; 172 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 173 | CLANG_CXX_LIBRARY = "libc++"; 174 | CLANG_ENABLE_OBJC_ARC = YES; 175 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 176 | CLANG_WARN_BOOL_CONVERSION = YES; 177 | CLANG_WARN_COMMA = YES; 178 | CLANG_WARN_CONSTANT_CONVERSION = YES; 179 | CLANG_WARN_EMPTY_BODY = YES; 180 | CLANG_WARN_ENUM_CONVERSION = YES; 181 | CLANG_WARN_INFINITE_RECURSION = YES; 182 | CLANG_WARN_INT_CONVERSION = YES; 183 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 184 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 185 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 186 | CLANG_WARN_STRICT_PROTOTYPES = YES; 187 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 188 | CLANG_WARN_UNREACHABLE_CODE = YES; 189 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 190 | COPY_PHASE_STRIP = YES; 191 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 192 | ENABLE_STRICT_OBJC_MSGSEND = YES; 193 | GCC_C_LANGUAGE_STANDARD = gnu99; 194 | GCC_ENABLE_OBJC_EXCEPTIONS = YES; 195 | GCC_NO_COMMON_BLOCKS = YES; 196 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 197 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 198 | GCC_WARN_UNDECLARED_SELECTOR = YES; 199 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 200 | GCC_WARN_UNUSED_FUNCTION = YES; 201 | GCC_WARN_UNUSED_VARIABLE = YES; 202 | MACOSX_DEPLOYMENT_TARGET = 10.9; 203 | SDKROOT = macosx; 204 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 205 | }; 206 | name = Release; 207 | }; 208 | D7E7F1D91661184E008C3C32 /* Debug */ = { 209 | isa = XCBuildConfiguration; 210 | buildSettings = { 211 | CLANG_ENABLE_MODULES = YES; 212 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; 213 | PRODUCT_NAME = "$(TARGET_NAME)"; 214 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 215 | SWIFT_SWIFT3_OBJC_INFERENCE = On; 216 | SWIFT_VERSION = 4.0; 217 | }; 218 | name = Debug; 219 | }; 220 | D7E7F1DA1661184E008C3C32 /* Release */ = { 221 | isa = XCBuildConfiguration; 222 | buildSettings = { 223 | CLANG_ENABLE_MODULES = YES; 224 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; 225 | PRODUCT_NAME = "$(TARGET_NAME)"; 226 | SWIFT_SWIFT3_OBJC_INFERENCE = On; 227 | SWIFT_VERSION = 4.0; 228 | }; 229 | name = Release; 230 | }; 231 | /* End XCBuildConfiguration section */ 232 | 233 | /* Begin XCConfigurationList section */ 234 | D7E7F1C31661184E008C3C32 /* Build configuration list for PBXProject "PhoneticContacts" */ = { 235 | isa = XCConfigurationList; 236 | buildConfigurations = ( 237 | D7E7F1D61661184E008C3C32 /* Debug */, 238 | D7E7F1D71661184E008C3C32 /* Release */, 239 | ); 240 | defaultConfigurationIsVisible = 0; 241 | defaultConfigurationName = Release; 242 | }; 243 | D7E7F1D81661184E008C3C32 /* Build configuration list for PBXNativeTarget "PhoneticContacts" */ = { 244 | isa = XCConfigurationList; 245 | buildConfigurations = ( 246 | D7E7F1D91661184E008C3C32 /* Debug */, 247 | D7E7F1DA1661184E008C3C32 /* Release */, 248 | ); 249 | defaultConfigurationIsVisible = 0; 250 | defaultConfigurationName = Release; 251 | }; 252 | /* End XCConfigurationList section */ 253 | }; 254 | rootObject = D7E7F1C01661184E008C3C32 /* Project object */; 255 | } 256 | -------------------------------------------------------------------------------- /PhoneticContacts.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /PhoneticContacts.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | BuildSystemType 6 | Latest 7 | 8 | 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PhoneticContacts 2 | 3 | ![Language](https://img.shields.io/badge/Language-Swift%204-orange.svg) 4 | [![Git](https://img.shields.io/badge/GitHub-lexrus-blue.svg?style=flat)](https://github.com/lexrus) 5 | [![Twitter](https://img.shields.io/badge/Twitter-@lexrus-blue.svg?style=flat)](http://twitter.com/lexrus) 6 | 7 | 为你的联系人加上拼音属性, 8 | 这样即使你的 iPhone 设置成英文, 9 | 也能有__按姓氏拼音首字母分段__的功能。 10 | 11 | 这个工具参考了 [V2EX 网友写的 AppleScript](http://v2ex.com/t/52860), 12 | 但使用 CFStringTransform 查拼音,速度更快。 13 | 14 | 另外,有些汉字姓氏的读音比较特殊 (如「曾」作为姓时读作 zeng)。 15 | 我在网上随便找了一些,做了替换的处理,可能不全,欢迎补充。 16 | 17 | 请在 Xcode 9 里打开此项目,command + r 运行后联系人就有拼音属性了。 18 | 过不了多久(看脸),这些更新后的联系人会通过 iCloud 同步到 iPhone。 19 | 20 | -------------------------------------------------------------------------------- /main.swift: -------------------------------------------------------------------------------- 1 | // 2 | // main.swift 3 | // PhoneticContacts 4 | // 5 | // Created by Lex on 2/7/16. 6 | // Copyright © 2016 Lex Tang. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import AddressBook 11 | 12 | extension String { 13 | 14 | func upcaseInitial() -> String { 15 | var chars = characters 16 | if let firstChar = chars.popFirst().map({ String($0) }) { 17 | return String(firstChar).uppercased() + String(chars) 18 | } 19 | return "" 20 | } 21 | 22 | func phonetic() -> String { 23 | let src = NSMutableString(string: self) as CFMutableString 24 | CFStringTransform(src, nil, kCFStringTransformMandarinLatin, false) 25 | 26 | // Transform NínHǎo to NinHao 27 | CFStringTransform(src, nil, kCFStringTransformStripCombiningMarks, false) 28 | 29 | let s = src as String 30 | if s != self { 31 | return s 32 | .components(separatedBy: " ") 33 | .map { $0.upcaseInitial() } 34 | .reduce("", +) 35 | } 36 | 37 | return self 38 | } 39 | 40 | func phoneticLast() -> String { 41 | let SpecialLastName: [String: String] = [ 42 | "柏": "bai", 43 | "鲍": "bao", 44 | "贲": "ben", 45 | "秘": "bi", 46 | "薄": "bo", 47 | "卜": "bu", 48 | "岑": "cen", 49 | "晁": "chao", 50 | "谌": "chen", 51 | "种": "chong", 52 | "褚": "chu", 53 | "啜": "chuai", 54 | "单": "chan", 55 | "郗": "chi", 56 | "邸": "di", 57 | "都": "du", 58 | "缪": "miao", 59 | "宓": "mi", 60 | "费": "fei", 61 | "苻": "fu", 62 | "睢": "sui", 63 | "区": "ou", 64 | "华": "hua", 65 | "庞": "pang", 66 | "查": "zha", 67 | "佘": "she", 68 | "仇": "qiu", 69 | "靳": "jin", 70 | "解": "xie", 71 | "繁": "po", 72 | "折": "she", 73 | "员": "yun", 74 | "祭": "zhai", 75 | "芮": "rui", 76 | "覃": "tan", 77 | "牟": "mou", 78 | "蕃": "pi", 79 | "戚": "qi", 80 | "瞿": "qu", 81 | "冼": "xian", 82 | "洗": "xian", 83 | "郤": "xi", 84 | "庹": "tuo", 85 | "彤": "tong", 86 | "佟": "tong", 87 | "妫": "gui", 88 | "句": "gou", 89 | "郝": "hao", 90 | "曾": "zeng", 91 | "乐": "yue", 92 | "蔺": "lin", 93 | "隽": "juan", 94 | "臧": "zang", 95 | "庾": "yu", 96 | "詹": "zhan", 97 | "禚": "zhuo", 98 | "迮": "ze", 99 | "沈": "shen", 100 | "沉": "shen", 101 | "尉迟": "yuchi", 102 | "长孙": "zhangsun", 103 | "中行": "zhonghang", 104 | "万俟": "moqi", 105 | "单于": "chanyu" 106 | ] 107 | 108 | if let specialLastName = SpecialLastName[self] { 109 | return specialLastName.upcaseInitial() 110 | } 111 | return self 112 | } 113 | 114 | } 115 | 116 | let ab = ABAddressBook() 117 | ab.people().forEach { 118 | guard let people = $0 as? ABPerson else { 119 | return 120 | } 121 | 122 | if let lastName = people.value(forProperty: kABLastNameProperty) as? String { 123 | _ = try? people.setValue( 124 | lastName.phoneticLast().phonetic(), 125 | forProperty: kABLastNamePhoneticProperty, 126 | error: () 127 | ) 128 | print(lastName, lastName.phoneticLast().phonetic(), separator: "->", terminator: ", ") 129 | } 130 | 131 | if let firstName = people.value(forProperty: kABFirstNameProperty) as? String { 132 | _ = try? people.setValue( 133 | firstName.phonetic(), 134 | forProperty: kABFirstNamePhoneticProperty, 135 | error: () 136 | ) 137 | print(firstName, firstName.phonetic(), separator: "->", terminator: " | ") 138 | } 139 | } 140 | ab.save() 141 | --------------------------------------------------------------------------------