获取iOS设备唯一标示UUID

作者: 西木柚子 | 来源:发表于2016-04-23 21:41 被阅读36298次

在开发过程中,我们经常会被要求获取每个设备的唯一标示,以便后台做相应的处理。我们来看看有哪些方法来获取设备的唯一标示,然后再分析下这些方法的利弊。

具体可以分为如下几种:

  1. UDID
  2. IDFA
  3. IDFV
  4. MAC
  5. keychain

下面我们来具体分析下每种获取方法的利弊

1、UDID

什么是UDID

UDID 「Unique Device Identifier Description」是由子母和数字组成的40个字符串的序号,用来区别每一个唯一的iOS设备,包括 iPhones, iPads, 以及 iPod touches,这些编码看起来是随机的,实际上是跟硬件设备特点相联系的,另外你可以到iTunes,pp助手或itools等软件查看你的udid(设备标识)

如下图所示:

image

UDID是用来干什么的?

UDID可以关联其它各种数据到相关设备上。例如,连接到开发者账号,可以允许在发布前让设备安装或测试应用;也可以让开发者获得iOS测试版进行体验。苹果用UDID连接到苹果的ID,这些设备可以自动下载和安装从App Store购买的应用、保存从iTunes购买的音乐、帮助苹果发送推送通知、即时消息。 在iOS 应用早期,UDID被第三方应用开发者和网络广告商用来收集用户数据,可以用来关联地址、记录应用使用习惯……以便推送精准广告。

为什么苹果反对开发人员使用UDID?

iOS 2.0版本以后UIDevice提供一个获取设备唯一标识符的方法uniqueIdentifier,通过该方法我们可以获取设备的序列号,这个也是目前为止唯一可以确认唯一的标示符。 许多开发者把UDID跟用户的真实姓名、密码、住址、其它数据关联起来;网络窥探者会从多个应用收集这些数据,然后顺藤摸瓜得到这个人的许多隐私数据。同时大部分应用确实在频繁传输UDID和私人信息。 为了避免集体诉讼,苹果最终决定在iOS 5 的时候,将这一惯例废除,开发者被引导生成一个唯一的标识符,只能检测应用程序,其他的信息不提供。现在应用试图获取UDID已被禁止且不允许上架。

所以这个方法作废

2、IDFA

  • 全名:advertisingIdentifier

  • 获取代码:

  #import <AdSupport/AdSupport.h>
  NSString *adId = [[[ASIdentifierManager sharedManager] advertisingIdentifier] UUIDString];
  
  • 来源:iOS6.0及以后

  • 说明:直译就是广告id, 在同一个设备上的所有App都会取到相同的值,是苹果专门给各广告提供商用来追踪用户而设的,用户可以在 设置|隐私|广告追踪 里重置此id的值,或限制此id的使用,故此id有可能会取不到值,但好在Apple默认是允许追踪的,而且一般用户都不知道有这么个设置,所以基本上用来监测推广效果,是戳戳有余了。

  • 注意:由于idfa会出现取不到的情况,故绝不可以作为业务分析的主id,来识别用户。

3、IDFV

  • 全名:identifierForVendor
  • 获取代码:
 NSString *idfv = [[[UIDevice currentDevice] identifierForVendor] UUIDString];
  • 来源:iOS6.0及以后

  • 说明:顾名思义,是给Vendor标识用户用的,每个设备在所属同一个Vender的应用里,都有相同的值。其中的Vender是指应用提供商,但准确点说,是通过BundleID的反转的前两部分进行匹配,如果相同就是同一个Vender,例如对于com.taobao.app1, com.taobao.app2 这两个BundleID来说,就属于同一个Vender,共享同一个idfv的值。和idfa不同的是,idfv的值是一定能取到的,所以非常适合于作为内部用户行为分析的主id,来标识用户,替代OpenUDID。

  • 注意:如果用户将属于此Vender的所有App卸载,则idfv的值会被重置,即再重装此Vender的App,idfv的值和之前不同。

4、MAC地址

使用WiFi的mac地址来取代已经废弃了的uniqueIdentifier方法。具体可见:
http://stackoverflow.com/questions/677530/how-can-i-programmatically-get-the-mac-address-of-an-iphone

然而在iOS 7中苹果再一次无情的封杀mac地址,使用之前的方法获取到的mac地址全部都变成了02:00:00:00:00:00。

5、Keychain

Paste_Image.png

我们可以获取到UUID,然后把UUID保存到KeyChain里面。

这样以后即使APP删了再装回来,也可以从KeyChain中读取回来。使用group还可以可以保证同一个开发商的所有程序针对同一台设备能够获取到相同的不变的UDID。

但是刷机或重装系统后uuid还是会改变。

把下面两个类文件放到你的项目中

KeychainItemWrapper.h文件
********************************

#import <UIKit/UIKit.h>

@interface KeychainItemWrapper : NSObject
{
    NSMutableDictionary *keychainItemData;      // The actual keychain item data backing store.
    NSMutableDictionary *genericPasswordQuery;  // A placeholder for the generic keychain item query used to locate the item.
}

@property (nonatomic, retain) NSMutableDictionary *keychainItemData;
@property (nonatomic, retain) NSMutableDictionary *genericPasswordQuery;

// Designated initializer.
- (id)initWithAccount:(NSString *)account service:(NSString *)service accessGroup:(NSString *) accessGroup;

- (id)initWithIdentifier: (NSString *)identifier accessGroup:(NSString *) accessGroup;
- (void)setObject:(id)inObject forKey:(id)key;
- (id)objectForKey:(id)key;

// Initializes and resets the default generic keychain item data.
- (void)resetKeychainItem;

@end

KeychainItemWrapper.h文件
********************************

#import "KeychainItemWrapper.h"
#import <Security/Security.h>

/*

These are the default constants and their respective types,
available for the kSecClassGenericPassword Keychain Item class:

kSecAttrAccessGroup         -       CFStringRef
kSecAttrCreationDate        -       CFDateRef
kSecAttrModificationDate    -       CFDateRef
kSecAttrDescription         -       CFStringRef
kSecAttrComment             -       CFStringRef
kSecAttrCreator             -       CFNumberRef
kSecAttrType                -       CFNumberRef
kSecAttrLabel               -       CFStringRef
kSecAttrIsInvisible         -       CFBooleanRef
kSecAttrIsNegative          -       CFBooleanRef
kSecAttrAccount             -       CFStringRef
kSecAttrService             -       CFStringRef
kSecAttrGeneric             -       CFDataRef
 
See the header file Security/SecItem.h for more details.

*/

@interface KeychainItemWrapper (PrivateMethods)
/*
The decision behind the following two methods (secItemFormatToDictionary and dictionaryToSecItemFormat) was
to encapsulate the transition between what the detail view controller was expecting (NSString *) and what the
Keychain API expects as a validly constructed container class.
*/
- (NSMutableDictionary *)secItemFormatToDictionary:(NSDictionary *)dictionaryToConvert;
- (NSMutableDictionary *)dictionaryToSecItemFormat:(NSDictionary *)dictionaryToConvert;

// Updates the item in the keychain, or adds it if it doesn't exist.
- (void)writeToKeychain;

@end

@implementation KeychainItemWrapper

@synthesize keychainItemData, genericPasswordQuery;

- (id)initWithAccount:(NSString *)account service:(NSString *)service accessGroup:(NSString *) accessGroup;
{
    if (self = [super init])
    {
        NSAssert(account != nil || service != nil, @"Both account and service are nil.  Must specifiy at least one.");
        // Begin Keychain search setup. The genericPasswordQuery the attributes kSecAttrAccount and
        // kSecAttrService are used as unique identifiers differentiating keychain items from one another
        genericPasswordQuery = [[NSMutableDictionary alloc] init];
        
        [genericPasswordQuery setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass];
        
        [genericPasswordQuery setObject:account forKey:(id)kSecAttrAccount];
        [genericPasswordQuery setObject:service forKey:(id)kSecAttrService];
        
        // The keychain access group attribute determines if this item can be shared
        // amongst multiple apps whose code signing entitlements contain the same keychain access group.
        if (accessGroup != nil)
        {
#if TARGET_IPHONE_SIMULATOR
            // Ignore the access group if running on the iPhone simulator.
            //
            // Apps that are built for the simulator aren't signed, so there's no keychain access group
            // for the simulator to check. This means that all apps can see all keychain items when run
            // on the simulator.
            //
            // If a SecItem contains an access group attribute, SecItemAdd and SecItemUpdate on the
            // simulator will return -25243 (errSecNoAccessForItem).
#else
            [genericPasswordQuery setObject:accessGroup forKey:(id)kSecAttrAccessGroup];
#endif
        }
        
        // Use the proper search constants, return only the attributes of the first match.
        [genericPasswordQuery setObject:(id)kSecMatchLimitOne forKey:(id)kSecMatchLimit];
        [genericPasswordQuery setObject:(id)kCFBooleanTrue forKey:(id)kSecReturnAttributes];
        
        NSDictionary *tempQuery = [NSDictionary dictionaryWithDictionary:genericPasswordQuery];
        
        NSMutableDictionary *outDictionary = nil;
        
        if (! SecItemCopyMatching((CFDictionaryRef)tempQuery, (CFTypeRef *)&outDictionary) == noErr)
        {
            // Stick these default values into keychain item if nothing found.
            [self resetKeychainItem];
            
            //Adding the account and service identifiers to the keychain
            [keychainItemData setObject:account forKey:(id)kSecAttrAccount];
            [keychainItemData setObject:service forKey:(id)kSecAttrService];
            
            if (accessGroup != nil)
            {
#if TARGET_IPHONE_SIMULATOR
                // Ignore the access group if running on the iPhone simulator.
                //
                // Apps that are built for the simulator aren't signed, so there's no keychain access group
                // for the simulator to check. This means that all apps can see all keychain items when run
                // on the simulator.
                //
                // If a SecItem contains an access group attribute, SecItemAdd and SecItemUpdate on the
                // simulator will return -25243 (errSecNoAccessForItem).
#else
                [keychainItemData setObject:accessGroup forKey:(id)kSecAttrAccessGroup];
#endif
            }
        }
        else
        {
            // load the saved data from Keychain.
            self.keychainItemData = [self secItemFormatToDictionary:outDictionary];
        }
        
        [outDictionary release];
    }
    
    return self;
}

- (id)initWithIdentifier: (NSString *)identifier accessGroup:(NSString *) accessGroup;
{
    if (self = [super init])
    {
        // Begin Keychain search setup. The genericPasswordQuery leverages the special user
        // defined attribute kSecAttrGeneric to distinguish itself between other generic Keychain
        // items which may be included by the same application.
        genericPasswordQuery = [[NSMutableDictionary alloc] init];
        
        [genericPasswordQuery setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass];
        [genericPasswordQuery setObject:identifier forKey:(id)kSecAttrGeneric];
        
        // The keychain access group attribute determines if this item can be shared
        // amongst multiple apps whose code signing entitlements contain the same keychain access group.
        if (accessGroup != nil)
        {
#if TARGET_IPHONE_SIMULATOR
            // Ignore the access group if running on the iPhone simulator.
            // 
            // Apps that are built for the simulator aren't signed, so there's no keychain access group
            // for the simulator to check. This means that all apps can see all keychain items when run
            // on the simulator.
            //
            // If a SecItem contains an access group attribute, SecItemAdd and SecItemUpdate on the
            // simulator will return -25243 (errSecNoAccessForItem).
#else           
            [genericPasswordQuery setObject:accessGroup forKey:(id)kSecAttrAccessGroup];
#endif
        }
        
        // Use the proper search constants, return only the attributes of the first match.
        [genericPasswordQuery setObject:(id)kSecMatchLimitOne forKey:(id)kSecMatchLimit];
        [genericPasswordQuery setObject:(id)kCFBooleanTrue forKey:(id)kSecReturnAttributes];
        
        NSDictionary *tempQuery = [NSDictionary dictionaryWithDictionary:genericPasswordQuery];
        
        NSMutableDictionary *outDictionary = nil;
        
        if (! SecItemCopyMatching((CFDictionaryRef)tempQuery, (CFTypeRef *)&outDictionary) == noErr)
        {
            // Stick these default values into keychain item if nothing found.
            [self resetKeychainItem];
            
            // Add the generic attribute and the keychain access group.
            [keychainItemData setObject:identifier forKey:(id)kSecAttrGeneric];
            if (accessGroup != nil)
            {
#if TARGET_IPHONE_SIMULATOR
                // Ignore the access group if running on the iPhone simulator.
                // 
                // Apps that are built for the simulator aren't signed, so there's no keychain access group
                // for the simulator to check. This means that all apps can see all keychain items when run
                // on the simulator.
                //
                // If a SecItem contains an access group attribute, SecItemAdd and SecItemUpdate on the
                // simulator will return -25243 (errSecNoAccessForItem).
#else           
                [keychainItemData setObject:accessGroup forKey:(id)kSecAttrAccessGroup];
#endif
            }
        }
        else
        {
            // load the saved data from Keychain.
            self.keychainItemData = [self secItemFormatToDictionary:outDictionary];
        }
       
        [outDictionary release];
    }
    
    return self;
}

- (void)dealloc
{
    [keychainItemData release];
    [genericPasswordQuery release];
    
    [super dealloc];
}

- (void)setObject:(id)inObject forKey:(id)key 
{
    if (inObject == nil) return;
    id currentObject = [keychainItemData objectForKey:key];
    if (![currentObject isEqual:inObject])
    {
        [keychainItemData setObject:inObject forKey:key];
        [self writeToKeychain];
    }
}

- (id)objectForKey:(id)key
{
    return [keychainItemData objectForKey:key];
}

- (void)resetKeychainItem
{
    OSStatus junk = noErr;
    if (!keychainItemData) 
    {
        self.keychainItemData = [[NSMutableDictionary alloc] init];
    }
    else if (keychainItemData)
    {
        NSMutableDictionary *tempDictionary = [self dictionaryToSecItemFormat:keychainItemData];
        junk = SecItemDelete((CFDictionaryRef)tempDictionary);
        NSAssert( junk == noErr || junk == errSecItemNotFound, @"Problem deleting current dictionary." );
    }
    
    // Default attributes for keychain item.
    [keychainItemData setObject:@"" forKey:(id)kSecAttrAccount];
    [keychainItemData setObject:@"" forKey:(id)kSecAttrLabel];
    [keychainItemData setObject:@"" forKey:(id)kSecAttrDescription];
    
    // Default data for keychain item.
    [keychainItemData setObject:@"" forKey:(id)kSecValueData];
}

- (NSMutableDictionary *)dictionaryToSecItemFormat:(NSDictionary *)dictionaryToConvert
{
    // The assumption is that this method will be called with a properly populated dictionary
    // containing all the right key/value pairs for a SecItem.
    
    // Create a dictionary to return populated with the attributes and data.
    NSMutableDictionary *returnDictionary = [NSMutableDictionary dictionaryWithDictionary:dictionaryToConvert];
    
    // Add the Generic Password keychain item class attribute.
    [returnDictionary setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass];
    
    // Convert the NSString to NSData to meet the requirements for the value type kSecValueData.
    // This is where to store sensitive data that should be encrypted.
    NSString *passwordString = [dictionaryToConvert objectForKey:(id)kSecValueData];
    [returnDictionary setObject:[passwordString dataUsingEncoding:NSUTF8StringEncoding] forKey:(id)kSecValueData];
    
    return returnDictionary;
}

- (NSMutableDictionary *)secItemFormatToDictionary:(NSDictionary *)dictionaryToConvert
{
    // The assumption is that this method will be called with a properly populated dictionary
    // containing all the right key/value pairs for the UI element.
    
    // Create a dictionary to return populated with the attributes and data.
    NSMutableDictionary *returnDictionary = [NSMutableDictionary dictionaryWithDictionary:dictionaryToConvert];
    
    // Add the proper search key and class attribute.
    [returnDictionary setObject:(id)kCFBooleanTrue forKey:(id)kSecReturnData];
    [returnDictionary setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass];
    
    // Acquire the password data from the attributes.
    NSData *passwordData = NULL;
    if (SecItemCopyMatching((CFDictionaryRef)returnDictionary, (CFTypeRef *)&passwordData) == noErr)
    {
        // Remove the search, class, and identifier key/value, we don't need them anymore.
        [returnDictionary removeObjectForKey:(id)kSecReturnData];
        
        // Add the password to the dictionary, converting from NSData to NSString.
        NSString *password = [[[NSString alloc] initWithBytes:[passwordData bytes] length:[passwordData length] 
                                                     encoding:NSUTF8StringEncoding] autorelease];
        [returnDictionary setObject:password forKey:(id)kSecValueData];
    }
    else
    {
        // Don't do anything if nothing is found.
        NSAssert(NO, @"Serious error, no matching item found in the keychain.\n");
    }
    
    [passwordData release];
   
    return returnDictionary;
}

- (void)writeToKeychain
{
    NSDictionary *attributes = NULL;
    NSMutableDictionary *updateItem = NULL;
    OSStatus result;
    
    if (SecItemCopyMatching((CFDictionaryRef)genericPasswordQuery, (CFTypeRef *)&attributes) == noErr)
    {
        // First we need the attributes from the Keychain.
        updateItem = [NSMutableDictionary dictionaryWithDictionary:attributes];
        // Second we need to add the appropriate search key/values.
        [updateItem setObject:[genericPasswordQuery objectForKey:(id)kSecClass] forKey:(id)kSecClass];
        
        // Lastly, we need to set up the updated attribute list being careful to remove the class.
        NSMutableDictionary *tempCheck = [self dictionaryToSecItemFormat:keychainItemData];
        [tempCheck removeObjectForKey:(id)kSecClass];
        
#if TARGET_IPHONE_SIMULATOR
        // Remove the access group if running on the iPhone simulator.
        // 
        // Apps that are built for the simulator aren't signed, so there's no keychain access group
        // for the simulator to check. This means that all apps can see all keychain items when run
        // on the simulator.
        //
        // If a SecItem contains an access group attribute, SecItemAdd and SecItemUpdate on the
        // simulator will return -25243 (errSecNoAccessForItem).
        //
        // The access group attribute will be included in items returned by SecItemCopyMatching,
        // which is why we need to remove it before updating the item.
        [tempCheck removeObjectForKey:(id)kSecAttrAccessGroup];
#endif
        
        // An implicit assumption is that you can only update a single item at a time.
        
        result = SecItemUpdate((CFDictionaryRef)updateItem, (CFDictionaryRef)tempCheck);
        NSAssert( result == noErr, @"Couldn't update the Keychain Item." );
    }
    else
    {
        // No previous item found; add the new one.
        result = SecItemAdd((CFDictionaryRef)[self dictionaryToSecItemFormat:keychainItemData], NULL);
        NSAssert( result == noErr, @"Couldn't add the Keychain Item." );
    }
}

@end

我们在写一个工具类用来保存UUID到keychain和从keychain中读取UUID.

实现代码

AppUntils.m文件
*********************

#import  <Security/Security.h>
#import "KeychainItemWrapper.h"

#pragma mark - 保存和读取UUID
+(void)saveUUIDToKeyChain{
    KeychainItemWrapper *keychainItem = [[KeychainItemWrapper alloc] initWithAccount:@"Identfier" service:@"AppName" accessGroup:nil];
    NSString *string = [keychainItem objectForKey: (__bridge id)kSecAttrGeneric];
    if([string isEqualToString:@""] || !string){
        [keychainItem setObject:[self getUUIDString] forKey:(__bridge id)kSecAttrGeneric];
    }
}

+(NSString *)readUUIDFromKeyChain{
    KeychainItemWrapper *keychainItemm = [[KeychainItemWrapper alloc] initWithAccount:@"Identfier" service:@"AppName" accessGroup:nil];
    NSString *UUID = [keychainItemm objectForKey: (__bridge id)kSecAttrGeneric];
    return UUID;
}

+ (NSString *)getUUIDString
{
    CFUUIDRef uuidRef = CFUUIDCreate(kCFAllocatorDefault);
    CFStringRef strRef = CFUUIDCreateString(kCFAllocatorDefault , uuidRef);
    NSString *uuidString = [(__bridge NSString*)strRef stringByReplacingOccurrencesOfString:@"-" withString:@""];
    CFRelease(strRef);
    CFRelease(uuidRef);
    return uuidString;
}


写入UUID到keychain

我们最好在程序启动之后把UUID写入到keychain,代码如下:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    [AppUtils saveUUIDToKeyChain];
}

读取UUID

在需要读取的地方直接调用AppUtils的类方法readUUIDFromKeyChain即可。


注意

1.设置非ARC编译环境

因为KeychainItemWrapper.m文件是在非ARC环境下运行的,所以需要设置非arc编译环境,
如图所示:

image

2. 让同一开发商的所有APP在同一台设备上获取到UUID相同

在每个APP的项目里面做如下设置

2.1、设置accessgroup

keychainItemWrapper *keychainItem = [[KeychainItemWrapper alloc] initWithAccount:@"Identfier" service:@"AppName" accessGroup:@"YOUR_BUNDLE_SEED.com.yourcompany.userinfo"];

此处设置accessGroup为YOUR_BUNDLE_SEED.com.yourcompany.userinfo

2.2、创建plist文件

然后在项目相同的目录下创建KeychainAccessGroups.plist文件。

该文件的结构是一个字典,其中中最顶层的节点必须是一个键为“keychain-access-groups”的Array,并且该Array中每一项都是一个描述分组的NSString。YOUR_BUNDLE_SEED.com.yourcompany.userinfo就是要设置的组名。

如图:

image

2.3、 设置code signing

接着在Target--->Build Settings---->Code Signing栏下的Code Signing Entitlements右侧添加KeychainAccessGroups.plist

如图:

image

这样就可以保证每个app都是从keychain中读取出来同一个UUID


参考文章:

1、http://blog.sheliw.com/blog/2015/02/16/keychain/

2、http://cnbin.github.io/blog/2015/08/18/ios-keychain-ji-chu/

3、http://www.xuebuyuan.com/1963303.html


iOS10注意:

iOS10系统导入上面的两个类运行会崩溃,需要做如下处理:

Paste_Image.png

相关文章

网友评论

  • 3763f32f77aa:楼主,我将uuid写入到keychain之后,重签名,发现uuid还是会变得,遇到过么,在线等:scream:
  • 字母B一路向北:文章中怎么有两个 KeychainItemWrapper.h class
  • 晚熟人:APP没有上线呢,不知道appID,公共区域怎么配置
  • 吊炸:楼主可以在优化一下.不用这么麻烦的.
  • 谢谢生活:打算借用你部分文章哈,谢谢,
    西木柚子:@谢谢生活 不客气,注明来源即可
  • 谢谢生活:KeychainItemWrapper这个库没有用过。用的SSKeychain,感觉钥匙串不太稳定,测试大概每50次有一次获取不到。且在那段时间都获取不到。所以本人自有第一次的时候通过钥匙串获取uuid然后存NSUserDefault,后面都是存的NSUserDefault。
    西木柚子:@谢谢生活 KeychainItemWrapper是apple的,建议使用,目前还没遇到过获取不到的问题
  • 司马捷:一本正经的胡说八道,fuck:-1:
    司马捷:https://github.com/qixin1106/KeychainIDFA
  • 男神已认证:http://www.jianshu.com/p/791f4784e73a
    我已封装了一个比较简单的方法
  • Dr_zhang:Code Signing Entitlements项目中为什么没有这个字段
  • zcApple:请问为什么序列号不能作为唯一标识呢?
  • vicool:mark
  • 大号鱼骨头:有时候是需要这种唯一的字符串来做一些逻辑的判断,辣么好用的udid给禁止了。
  • JohnHow:写得很详细,是个不错的解决方案
  • 机器猫的百宝袋:楼主有没有获取过本机号码
    机器猫的百宝袋:@西木柚子 恩恩
    西木柚子:@机器猫的百宝袋 无法获取了 现在,苹果不允许
  • PURE蓝胖子:正好遇到这个问题,今天发现app卸载重装会重置UUID,看到这篇文章,收藏了
  • 小小的邓:请问,我如果需要使用UDID来签名的话,我可以用IDFV来代替使用吗
    西木柚子:@小小的邓 使用IDFA
    小小的邓:就是为了标识每个用户的,那如果IDFV不可以的话,那需要获取哪个呢,现在UDID好像是被限制获取到了
    西木柚子:@小小的邓 签名需要保证唯一性,IDFV会让同一个厂商的app的标示都是相同的,不能保证唯一性。具体看你签名的用途,标示每个用户的话,就不行了
  • 寂寞水蛙:请问,这个方法写入的钥匙串是手机里所有的APP读取到的值都一样的吗?
    寂寞水蛙:@西木柚子 好的,谢谢柚子
    西木柚子:@寂寞水蛙 只能你自己的app读取到
    寂寞水蛙:@寂寞水蛙 补充一句,我指的是这个写入钥匙串的方法,而不是获取的UUID,也就是说如果我把一个很长的字符串用你的方法写入到钥匙串中,其他的APP能否读取到这个字符串,能否修改或者删除这个字符串?
  • 一抹远方:error: The file “KeychainAccessGroups.plist” couldn’t be opened because there is no such file.
    报错怎么解决,在线等,挺急的!
    96614bc8d3db:@西木柚子 博主,我也是一直这个错误,创建了还是这样
    风清扬丶:@西木柚子 我创建了 。咋还是抱着错呢 。 你说的创建是在代码里写 还是直接创建一个plist文件啊,我只是在同一目录下创建的plist文件啊
    西木柚子:@情醉 你要创建这个文件“KeychainAccessGroups.plist”
  • 风与鸾:对2.1设置不是很懂,能具体点吗?
    风与鸾:@西木柚子 appid 和yourcompany
    西木柚子:@LeeFengHY 哪里不懂
  • 有生之莲花:柚子,问一下啊!你的2.1的步骤我不是很懂改在哪里弄啊!具体该怎么写啊!是改那个单例里的初始化方法吗?我的 accessGroup写成这个$(AppIdentifierPrefix)yourcompany.userinfo 可以吗?
    西木柚子:@灭天瞳 是的,初始化方法里面,我上面的accessgroup是nil,你这里加上一个名字就好了。plist文件格式参照我上面的截图就好了
  • Eason_Gao:楼主用这个KeychainItemWrapper 是否出现过[KeychainItemWrapper writeToKeychain] 报错,Couldn't add the Keychain Item. 我线上项目,有个一个设备 6 plus 9.3.2报错
    西木柚子:@SunlightInMyLif 越狱机会出现保存失败的情况,不能保证百分百保存成功
    863c73f31933:作者你的意思是要是设备是越狱的,就不能用你上面写的创建Keychain这个方法么?
    西木柚子:@Eason_Gao 越狱设备无法保存,保存到沙盒吧
  • 码修:mark
  • Dr鸩:mark 博主辛苦
  • 随风风流:不是说一个app试图访问设备的UUID是禁止上线到AppStore的吗?怎么能用这个方法访问并存储UUID呢?
    晴天有雨丶:@随风风流 好像禁止的是UDID
    随风风流:@西木柚子 看明白了,就是自己写个类获取UUID,在使用的时候发现readUUIDFromKeyChain没有变化,getUUIDString有变化,在项目应用的时候该如何使用呢?
    西木柚子:@随风风流 文章写的很详细了,你没仔细看吧
  • 3a44fa0aea55:辛苦楼主了
  • feca61443dfa:问下 怎么删除呢?
    西木柚子:@猫佐拉 看文章末尾的参考文章,里面有提到
  • 幻想无极:我报了这样一个错,解决http://blog.csdn.net/sjcode/article/details/51014679
    西木柚子:@零度幻想 不客气,能帮上你忙就好
    幻想无极:@西木柚子 已经投入项目使用,thank you
    西木柚子:@零度幻想 额,这个没注意到,我的7.2没有报错
  • 星形菠萝糖:虽然暂时还没有需要用到这些,但是以后应该有用。写这么多辛苦了
    西木柚子:@星形菠萝糖 谢谢,多来看看

本文标题:获取iOS设备唯一标示UUID

本文链接:https://www.haomeiwen.com/subject/hmffrttx.html