iOS开发------获取系统联系人(Contacts篇)

Contacts.framework是Apple在 iOS9.0 替代AddressBook.framework的框架,至于AddressBook是做什么的框架,楼主默认看到博文的开发者是知道的 O(∩_∩)O。

如果想了解AddressBook的使用欢迎查看一下楼主之前关于AddressBook的博文,本篇不做过多的缀余:
iOS开发------获取系统联系人(AddressBook篇)
iOS开发------操作通讯录(AddressBook篇)&通讯录UI(AddressBookUI篇)

每次iOS发布新的版本(甚至每年的WWDC大会举行完毕)很多敏锐的开发者都准备或者对新版本特性进行适配。当然这些大神肯定会在iOS9发布后在第一时间对通讯录功能进行适配,一些稍微不太敏锐的开发者鉴于AddressBook在iOS9下初次提醒以及讨厌适配的繁琐,也就不以为然。

但随着iOS10的发布,那么适配相关框架就显得格外重要(不是说AddressBook不能使用了,但为了项目的健壮性以及良好的体验性,还是非常建议第一时间适配的。当然,这句话不仅限于Contacts部分)。

如果大家的项目还需要适配iOS8(当然,大多数公司肯定是也不会抛弃iOS7的用户),那么使用AddressBook是必然的;但如果在iOS9+的系统上,楼主还是非常建议使用最新的Contacts.framework框架的.

个人推荐的主要是下面两点原因(来源于楼主查看官方文档,编写Demo以及使用instruments的体会):

  1. AddressBook与其他相关废弃框架相似一样 (ex:ALAsset-图片库),语言风格更接近于C语言(当然也可以说就是C语言),不在ARC管理之下(对于习惯使用ARC下的开发者算是不小的挑战),使用不太便利并容易造成内存泄露。

  2. 新的框架无论在查看开发文档、使用、读取速度还是灵活性都远好于废弃框架,内存泄露易于查找以及补漏。

这里还是要分享一下源码,楼主整合AddressBook.framework以及Contacts.framework的DEMO

预览图

左边为AddressBook框架进行的演示,右边为Contact框架进行的演示.
根据不同的版本进行自动适配,如果是iOS9,自动使用Contact.framework.

使用AddressBook.framework
使用Contacts.framework


权限描述

在iOS10上由于权限有很多的坑,本博文的内容需要使用通讯录权限.
那么不要忘记在项目的info.plist文件中加入如下描述:Privacy - Contacts Usage Description,描述字符串:RITL want to use your Contacts(这个随意),尽可能的写点东西吧,听说如果不写上线可能会被Apple拒绝..

获取权限-CNContactStore

负责获得权限、请求权限以及执行操作请求的类就是CNContactStore,具体Demo中的代码如下:

/**
 检测权限并作响应的操作
 */
- (void)__checkAuthorizationStatus
{
    //这里有一个枚举类:CNEntityType,不过没关系,只有一个值:CNEntityTypeContacts
    switch ([CNContactStore authorizationStatusForEntityType:CNEntityTypeContacts])
    {
            //存在权限
        case CNAuthorizationStatusAuthorized:
            //获取通讯录
            [self __obtainContacts:self.completeBlock];
            break;
            
            //权限未知
        case CNAuthorizationStatusNotDetermined:
            //请求权限
            [self __requestAuthorizationStatus];break;
            
            //如果没有权限
        case CNAuthorizationStatusRestricted:
        case CNAuthorizationStatusDenied://需要提示
            self.defendBlock();break;
    }
}


请求联系人列表-CNContactStore

这里有几种比较常用的思路

1.使用自带的枚举方法一次性获得所有的属性

// 使用枚举方法对所有的联系人对象(CNContact)进行列出,该方法是同步的
- (BOOL)enumerateContactsWithFetchRequest:(CNContactFetchRequest *)fetchRequest
                                    error:(NSError *__nullable *__nullable)error
                               usingBlock:(void (^)(CNContact *contact, BOOL *stop))block;

2.先获取所有联系人的identifier,再根据identifier读取联系人信息(Demo中使用的该思路)

// 通过identifer获得一个唯一的CNContact
- (nullable CNContact *)unifiedContactWithIdentifier:(NSString *)identifier
                                         keysToFetch:(NSArray<id<CNKeyDescriptor>> *)keys
                                               error:(NSError *__nullable *__nullable)error;


遍历请求类-CNContactFetchRequest

感觉这里介绍一下CNContactFetchRequest类还是有必要的,毕竟当初在这里也是浪费了点时间,它是一个遍历请求的类,我们可以通过初始化该类的实例对象,告诉contactStore我们需要遍历contact的某些属性:

//实例化CNContactFetchRequest对象,通过一个遍历键的描述数组
- (instancetype)initWithKeysToFetch:(NSArray <id<CNKeyDescriptor>>*)keysToFetch NS_DESIGNATED_INITIALIZER;


键值描述协议-CNKeyDescriptor

如果我们单纯的进入开发文档,我们会发现他是一个空协议,刚开始看到这里的时候楼主表示很蒙B

//没有任何的required和optional方法
@protocol CNKeyDescriptor <NSObject, NSSecureCoding, NSCopying>
@end

但很快就发现了下面这个Category

// //Allows contact property keys to be used with keysToFetch.
// 允许contact的属性键作为遍历的键
@interface NSString (Contacts) <CNKeyDescriptor>
@end

如果还是有点疑惑,那么相信看到下面就不会再有困惑了呢。没错,可以直接将下列字符串当成CNKeyDescriptor对象写入数组

//标识符
CONTACTS_EXTERN NSString * const CNContactIdentifierKey                      NS_AVAILABLE(10_11, 9_0);
//姓名前缀
CONTACTS_EXTERN NSString * const CNContactNamePrefixKey                      NS_AVAILABLE(10_11, 9_0);
//姓名
CONTACTS_EXTERN NSString * const CNContactGivenNameKey                       NS_AVAILABLE(10_11, 9_0);
//中间名
CONTACTS_EXTERN NSString * const CNContactMiddleNameKey                      NS_AVAILABLE(10_11, 9_0);
//姓氏
CONTACTS_EXTERN NSString * const CNContactFamilyNameKey                      NS_AVAILABLE(10_11, 9_0);
//之前的姓氏(ex:国外的女士)
CONTACTS_EXTERN NSString * const CNContactPreviousFamilyNameKey              NS_AVAILABLE(10_11, 9_0);
//姓名后缀
CONTACTS_EXTERN NSString * const CNContactNameSuffixKey                      NS_AVAILABLE(10_11, 9_0);
//昵称
CONTACTS_EXTERN NSString * const CNContactNicknameKey                        NS_AVAILABLE(10_11, 9_0);
//公司(组织)
CONTACTS_EXTERN NSString * const CNContactOrganizationNameKey                NS_AVAILABLE(10_11, 9_0);
//部门
CONTACTS_EXTERN NSString * const CNContactDepartmentNameKey                  NS_AVAILABLE(10_11, 9_0);
//职位
CONTACTS_EXTERN NSString * const CNContactJobTitleKey                        NS_AVAILABLE(10_11, 9_0);
//名字的拼音或音标
CONTACTS_EXTERN NSString * const CNContactPhoneticGivenNameKey               NS_AVAILABLE(10_11, 9_0);
//中间名的拼音或音标
CONTACTS_EXTERN NSString * const CNContactPhoneticMiddleNameKey              NS_AVAILABLE(10_11, 9_0);
//形式的拼音或音标
CONTACTS_EXTERN NSString * const CNContactPhoneticFamilyNameKey              NS_AVAILABLE(10_11, 9_0);
//公司(组织)的拼音或音标(iOS10 才开始存在的呢)
CONTACTS_EXTERN NSString * const CNContactPhoneticOrganizationNameKey        NS_AVAILABLE(10_12, 10_0);
//生日
CONTACTS_EXTERN NSString * const CNContactBirthdayKey                        NS_AVAILABLE(10_11, 9_0);
//农历
CONTACTS_EXTERN NSString * const CNContactNonGregorianBirthdayKey            NS_AVAILABLE(10_11, 9_0);
//备注
CONTACTS_EXTERN NSString * const CNContactNoteKey                            NS_AVAILABLE(10_11, 9_0);
//头像
CONTACTS_EXTERN NSString * const CNContactImageDataKey                       NS_AVAILABLE(10_11, 9_0);
//头像的缩略图
CONTACTS_EXTERN NSString * const CNContactThumbnailImageDataKey              NS_AVAILABLE(10_11, 9_0);
//头像是否可用
CONTACTS_EXTERN NSString * const CNContactImageDataAvailableKey              NS_AVAILABLE(10_12, 9_0);
//类型
CONTACTS_EXTERN NSString * const CNContactTypeKey                            NS_AVAILABLE(10_11, 9_0);
//电话号码
CONTACTS_EXTERN NSString * const CNContactPhoneNumbersKey                    NS_AVAILABLE(10_11, 9_0);
//邮箱地址
CONTACTS_EXTERN NSString * const CNContactEmailAddressesKey                  NS_AVAILABLE(10_11, 9_0);
//住址
CONTACTS_EXTERN NSString * const CNContactPostalAddressesKey                 NS_AVAILABLE(10_11, 9_0);
//其他日期
CONTACTS_EXTERN NSString * const CNContactDatesKey                           NS_AVAILABLE(10_11, 9_0);
//url地址
CONTACTS_EXTERN NSString * const CNContactUrlAddressesKey                    NS_AVAILABLE(10_11, 9_0);
//关联人
CONTACTS_EXTERN NSString * const CNContactRelationsKey                       NS_AVAILABLE(10_11, 9_0);
//社交
CONTACTS_EXTERN NSString * const CNContactSocialProfilesKey                  NS_AVAILABLE(10_11, 9_0);
//即时通信
CONTACTS_EXTERN NSString * const CNContactInstantMessageAddressesKey         NS_AVAILABLE(10_11, 9_0);


获取联系人姓名属性

// RITLContactNameObject获取姓名属性的类目方法
-(void)contactObject:(CNContact *)contact
{
    [super contactObject:contact];
    
    //设置姓名属性
    self.nickName = contact.nickname;                   //昵称
    self.givenName = contact.givenName;                 //名字
    self.familyName = contact.familyName;               //姓氏
    self.middleName = contact.middleName;               //中间名
    self.namePrefix = contact.namePrefix;               //名字前缀
    self.nameSuffix = contact.nameSuffix;               //名字的后缀
    self.phoneticGivenName = contact.phoneticGivenName; //名字的拼音或音标
    self.phoneticFamilyName = contact.phoneticFamilyName;//姓氏的拼音或音标
    self.phoneticMiddleName = contact.phoneticMiddleName;//中间名的拼音或音标
    
#ifdef __IPHONE_10_0
    self.phoneticOrganizationName = contact.phoneticOrganizationName;//公司(组织)的拼音或音标
#endif
}


获取联系人的类型

这里需要判断一下该属性是否可用(不只该属性,所有的属性都应先判断一下)不然会抛出异常.

/**
 *  获得联系人类型信息
 */
+ (RITLContactType)__contactTypeProperty
{
    if (![self.currentContact isKeyAvailable:CNContactTypeKey])
    {
        return RITLContactTypeUnknown;//没有可用就是未知
    }
    
    else if (self.currentContact.contactType == CNContactTypeOrganization)
    {
        return RITLContactTypeOrigination;//如果是组织
    }
    
    else{
        return RITLContactTypePerson;
    }
}


获得联系人的头像图片

/**
 *  获得联系人的头像图片
 */
+ (UIImage * __nullable)__contactHeadImagePropery
{
    //缩略图Data
    if ([self.currentContact isKeyAvailable:CNContactThumbnailImageDataKey])
    {
        NSData * thumImageData = self.currentContact.thumbnailImageData;
        
        return [UIImage imageWithData:thumImageData];
    }
    return nil;
}


获取联系人的电话信息

/**
 *  获得电话号码对象数组
 */
+ (NSArray <RITLContactPhoneObject *> *)__contactPhoneProperty
{
    
    if (![self.currentContact isKeyAvailable:CNContactPhoneNumbersKey])
    {
        return @[];
    }
    
    //外传数组
    NSMutableArray <RITLContactPhoneObject *> * phones = [NSMutableArray arrayWithCapacity:self.currentContact.phoneNumbers.count];
    
    for (CNLabeledValue * phoneValue in self.currentContact.phoneNumbers)
    {
        //初始化PhoneObject对象
        RITLContactPhoneObject * phoneObject = [RITLContactPhoneObject new];
        
        //setValue
        phoneObject.phoneTitle = [CNLabeledValue localizedStringForLabel:phoneValue.label];
        phoneObject.phoneNumber = ((CNPhoneNumber *)phoneValue.value).stringValue;
        
        [phones addObject:phoneObject];
    }
    
    return [NSArray arrayWithArray:phones];
}


获取联系人的工作信息

/**
 *  获得工作的相关属性
 */
+ (RITLContactJobObject *)__contactJobProperty
{
    RITLContactJobObject * jobObject = [[ RITLContactJobObject alloc]init];
    
    if ([self.currentContact isKeyAvailable:CNContactJobTitleKey])
    {
        //setValue
        jobObject.jobTitle = self.currentContact.jobTitle;
        jobObject.departmentName = self.currentContact.departmentName;
        jobObject.organizationName = self.currentContact.organizationName;
    }

    return jobObject;
}


获取联系人的邮件信息

/**
 *  获得Email对象的数组
 */
+ (NSArray <RITLContactEmailObject *> *)__contactEmailProperty
{
    if (![self.currentContact isKeyAvailable:CNContactEmailAddressesKey])
    {
        return @[];
    }
    
    //外传数组
    NSMutableArray <RITLContactEmailObject *> * emails = [NSMutableArray arrayWithCapacity:self.currentContact.emailAddresses.count];
    
    for (CNLabeledValue * emailValue in self.currentContact.emailAddresses)
    {
        //初始化RITLContactEmailObject对象
        RITLContactEmailObject * emailObject = [[RITLContactEmailObject alloc]init];
        
        //setValue
        emailObject.emailTitle =  [CNLabeledValue localizedStringForLabel:emailValue.label];
        emailObject.emailAddress = emailValue.value;
        
        [emails addObject:emailObject];
        
    }
    
    return [NSArray arrayWithArray:emails];
}


获取联系人的地址信息

/**
 *  获得Address对象的数组
 */
+ (NSArray <RITLContactAddressObject *> *)__contactAddressProperty
{
    if (![self.currentContact isKeyAvailable:CNContactPostalAddressesKey]) {
        
        return @[];
        
    }
    
    //外传数组
    NSMutableArray <RITLContactAddressObject *> * addresses = [NSMutableArray arrayWithCapacity:self.currentContact.postalAddresses.count];
    
    for (CNLabeledValue * addressValue in self.currentContact.postalAddresses)
    {
        //初始化地址对象
        RITLContactAddressObject * addressObject = [[RITLContactAddressObject alloc]init];
        
        //setValues
        addressObject.addressTitle =  [CNLabeledValue localizedStringForLabel:addressValue.label];
        
        //setDetailValue
        [addressObject contactObject:addressValue.value];
        
        //add object
        [addresses addObject:addressObject];
    }
    
    return [NSArray arrayWithArray:addresses];
}


获得联系人的生日信息

/**
 *  获得生日的相关属性
 */
+ (RITLContactBrithdayObject *)__contactBrithdayProperty
{
    //实例化对象
    RITLContactBrithdayObject * brithdayObject = [[RITLContactBrithdayObject alloc]init];
    
    
    if ([self.currentContact isKeyAvailable:CNContactBirthdayKey])
    {
        //set value
        brithdayObject.brithdayDate = [self.currentContact.birthday.calendar dateFromComponents:self.currentContact.birthday];
        brithdayObject.leapMonth = self.currentContact.birthday.isLeapMonth;
    }

    if ([self.currentContact isKeyAvailable:CNContactNonGregorianBirthdayKey])
    {
        brithdayObject.calendar = self.currentContact.nonGregorianBirthday.calendar.calendarIdentifier;
        brithdayObject.era = self.currentContact.nonGregorianBirthday.era;
        brithdayObject.day = self.currentContact.nonGregorianBirthday.day;
        brithdayObject.month = self.currentContact.nonGregorianBirthday.month;
        brithdayObject.year = self.currentContact.nonGregorianBirthday.year;
    }

    //返回对象
    return brithdayObject;
}


获取联系人的即时通信信息

/**
 *  获得即时通信账号相关信息
 */
+ (NSArray <RITLContactInstantMessageObject *> *)__contactMessageProperty
{
    if (![self.currentContact isKeyAvailable:CNContactInstantMessageAddressesKey])
    {
        return @[];
    }
    
    //存放数组
    NSMutableArray <RITLContactInstantMessageObject *> * instantMessages = [NSMutableArray arrayWithCapacity:self.currentContact.instantMessageAddresses.count];
    
    for (CNLabeledValue * instanceAddressValue in self.currentContact.instantMessageAddresses)
    {
        RITLContactInstantMessageObject * instaceObject = [[RITLContactInstantMessageObject alloc]init];
        
        //set value
        instaceObject.identifier = instanceAddressValue.identifier;
        instaceObject.service = ((CNInstantMessageAddress *)instanceAddressValue.value).service;
        instaceObject.userName = ((CNInstantMessageAddress *)instanceAddressValue.value).username;
        
        //add
        [instantMessages addObject:instaceObject];
    }
    
    return [NSArray arrayWithArray:instantMessages];
}


获得联系人的关联人信息

/**
 *  获得联系人的关联人信息
 */
+ (NSArray <RITLContactRelatedNamesObject *> *)__contactRelatedNamesProperty
{
    if (![self.currentContact isKeyAvailable:CNContactRelationsKey])
    {
        return @[];
    }
    
    //存放数组
    NSMutableArray <RITLContactRelatedNamesObject *> * relatedNames = [NSMutableArray arrayWithCapacity:self.currentContact.contactRelations.count];
    
    for (CNLabeledValue * relationsValue in self.currentContact.contactRelations)
    {
        RITLContactRelatedNamesObject * relatedObject = [[RITLContactRelatedNamesObject alloc]init];
        
        //set value
        relatedObject.identifier = relationsValue.identifier;
        relatedObject.relatedTitle =  [CNLabeledValue localizedStringForLabel:relationsValue.label];
        relatedObject.relatedName = ((CNContactRelation *)relationsValue.value).name;
        
        [relatedNames addObject:relatedObject];
        
    }

    return [NSArray arrayWithArray:relatedNames];
}


获取联系人的社交简介信息

/**
 *  获得联系人的社交简介信息
 */
+ (NSArray <RITLContactSocialProfileObject *> *)__contactSocialProfilesProperty
{
    if (![self.currentContact isKeyAvailable:CNContactSocialProfilesKey])
    {
        return @[];
    }
    
    //外传数组
    NSMutableArray <RITLContactSocialProfileObject *> * socialProfiles = [NSMutableArray arrayWithCapacity:self.currentContact.socialProfiles.count];
    
    for (CNLabeledValue * socialProfileValue in self.currentContact.socialProfiles) {
        
        RITLContactSocialProfileObject * socialProfileObject = [[RITLContactSocialProfileObject alloc]init];
        
        //获得CNSocialProfile对象
        CNSocialProfile * socialProfile = socialProfileValue.value;

        //set value
        socialProfileObject.identifier = socialProfileValue.identifier;
        socialProfileObject.socialProfileTitle = socialProfile.service;
        socialProfileObject.socialProFileAccount = socialProfile.username;
        socialProfileObject.socialProFileUrl = socialProfile.urlString;
        
        [socialProfiles addObject:socialProfileObject];
    }
    
    return [NSArray arrayWithArray:socialProfiles];
}


获取联系人的备注信息

/**
 获得联系人的备注信息
 */
+ (NSString * __nullable)__contactNoteProperty
{
    if ([self.currentContact isKeyAvailable:CNContactNoteKey])
    {
        return self.currentContact.note;
    }
    
    return nil;
}


接收外界通讯录发生变化的方法

这里不再是直接使用C语言的函数赋址来进行方法注册,方法更加的ObjC,选用了更多的通知中心。

/**
 添加变化监听
 */
- (void)__addStoreDidChangeNotification
{
    if (self.notificationDidAdd == false)
    {
        //添加通知
        [[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(__contactDidChange:) name:CNContactStoreDidChangeNotification object:nil];
        self.notificationDidAdd = !self.notificationDidAdd;
    }  
}

下面是执行变化后的方法:

楼主的测试的时候通讯录变化会连续触发3次通知方法,后面的延迟3s就是解决连续触发的问题,也不知道是个人的程序出问题还是Contact框架的bug,如果大家有什么好办法或者什么好的建议,也请告知一下,十分感谢。

/**
 通讯录发生变化进行的回调

 @param notication 发送的通知
 */
- (void)__contactDidChange:(NSNotification *)notication
{
    //重新获取通讯录
    if (self.contactDidChange != nil )
    {
        //如果可以进行回调
        if (self.shouldResponseContactChange == true)
        {
            //重新加载缓存
            [[RITLContactCatcheManager sharedInstace]reloadContactIdentifiers:^(NSArray<NSString *> * _Nonnull identifiers) {
                
                NSArray * contacts = [self __contactHandleWithIdentifiers:identifiers];
                
                //回调
                self.contactDidChange([contacts mutableCopy]);
            }];
            
            self.responseContactChange = false;
            
            //延迟3s
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                
                self.responseContactChange = true;
                
            });
        }
    }
}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,117评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,963评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,897评论 0 240
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,805评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,208评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,535评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,797评论 2 311
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,493评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,215评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,477评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,988评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,325评论 2 252
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,971评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,807评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,544评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,455评论 2 266

推荐阅读更多精彩内容

  • Swift版本点击这里欢迎加入QQ群交流: 594119878最新更新日期:18-09-17 About A cu...
    ylgwhyh阅读 24,857评论 7 249
  • 老板曾在酒桌上说过,找对象的,得考验他两件事。第一件,便是灌醉他,看他酒后的真实模样。第二件,便是拖着他,两...
    sunshinelina阅读 349评论 0 0
  • 前言 市面上招聘网站有很多,个人常用的是拉勾,但大街也很有特色,今天尝试分析下这款app的产品逻辑。 大街网—中国...
    番茄汤圆阅读 642评论 0 0
  • 总结 其中绿色的部分与蓝色可能在一个线程,也可能不在一个线程 初始化handler时,指定了对应的Looper以及...
    Rancune阅读 145评论 0 0