2018-06-04

談一談你對(duì)KVO的理解浪规?

A:添加響應(yīng)者结蟋,監(jiān)聽(tīng)對(duì)象變化脯倚,當(dāng)對(duì)象改變時(shí)調(diào)用代理。

B:動(dòng)態(tài)創(chuàng)建NSKVONotifying_XX類,修改被監(jiān)聽(tīng)對(duì)象isa指針指向,只要調(diào)用對(duì)象的set方法,

就會(huì)調(diào)用NSKVONotifying_XX的set方法采盒。本質(zhì):判斷對(duì)象的set方法有沒(méi)有被調(diào)用

二、蘋果官方文檔描述

Automatic key-value observing is implemented using a technique called isa-swizzling... When an observer is registered for an attribute of an object the isa pointer of the observed object is modified, pointing to an intermediate class rather than at the true class ..

以上文檔的基本內(nèi)容是:

“當(dāng)對(duì)象注冊(cè)觀察者時(shí)憎妙,對(duì)象的ISA指針被修改,指向中間類,而不是原來(lái)真正的類...”,這是蘋果官方給出的文檔尊残。聽(tīng)到是不是很懵炒瘸?沒(méi)關(guān)系,未了解之前夜郁,我和你一樣什燕,看了這篇文章后你將和我一樣(小裝一下粘勒,勿噴)竞端。

舉例說(shuō)明:(監(jiān)聽(tīng)一個(gè)Person類底層實(shí)現(xiàn))

2.1)、動(dòng)態(tài)創(chuàng)建NSKVONotifying_Person,NSKVONotifying_Person是Person子類庙睡,做KVO事富;

2.2)、修改當(dāng)前對(duì)象的isa指針->NSKVONotifying_Person乘陪;

2.3)统台、只要調(diào)用對(duì)象的set,就會(huì)調(diào)用NSKVONotifying_Person的set方法;

2.4)啡邑、重寫NSKVONotifying_Person的set方法贱勃,1[super set:] 2、通知觀察者谤逼,告訴屬性改變贵扰。

三、核心代碼

/**

?添加觀察者?(addObserver:?forKeyPath:?options:?context:)

?@param?observer?觀察者

?@param?keyPath?監(jiān)聽(tīng)對(duì)象屬性

?@param?options?屬性配置

?@param?context?上下文

?*/

-?(void)addObserver:(NSObject?*)observer?forKeyPath:(NSString?*)keyPath

????????????options:(NSKeyValueObservingOptions)options

????????????context:(void*)context

{

????/*

?????*?options

?????*

?????*?NSKeyValueObservingOptionNew???????//?change字典包括改變后的值

?????*?NSKeyValueObservingOptionOld???????//?change字典包括改變前的值

?????*?NSKeyValueObservingOptionInitial???//?注冊(cè)后立刻觸發(fā)KVO通知

?????*?NSKeyValueObservingOptionPrior?????//?值改變前是否也要通知(通知兩次)

?????*/

}

/**

?監(jiān)聽(tīng)按鈕狀態(tài)改變的方法

?@param?keyPath?改變的屬性

?@param?object?被監(jiān)聽(tīng)對(duì)象

?@param?change?改變后的數(shù)據(jù)

?@param?context?注冊(cè)監(jiān)聽(tīng)時(shí)context傳遞過(guò)來(lái)的值

?*/

-?(void)observeValueForKeyPath:(NSString?*)keyPath

??????????????????????ofObject:(id)object

????????????????????????change:(NSDictionary?*)change

???????????????????????context:(void*)context

{

}

到此為止流部,這是我以前了解的所有KVO了戚绕,就這么點(diǎn)東西,用起來(lái)嗖嗖嗖啊枝冀。但是舞丛,大神永遠(yuǎn)是最騷的。前幾天看到一個(gè)大神覺(jué)得系統(tǒng)的KVO不好用果漾,自定義了一個(gè)球切,居然還是15年寫的,我勒個(gè)槽槽槽绒障,如何自己動(dòng)手實(shí)現(xiàn) KVO,就是這篇文章欧聘,讀了半天,終于弄明白了端盆,總結(jié)總結(jié)吧怀骤,別白看了。

首先焕妙,懷著虛榮的心先了解一下蒋伦,為什么要自定義?對(duì)啊焚鹊,蘋果給的多好用痕届,干嘛還要自定義去韧献,脫褲子放屁(多此一舉嘛),別著急研叫,聽(tīng)聽(tīng)big borther 怎么說(shuō)锤窑。

四、進(jìn)階

4.1嚷炉、KVO 缺陷

big brother原話:

"KVO 很強(qiáng)大渊啰,沒(méi)錯(cuò)。知道它內(nèi)部實(shí)現(xiàn)申屹,或許能幫助更好地使用它绘证,或在它出錯(cuò)時(shí)更方便調(diào)試。但官方實(shí)現(xiàn)的 KVO 提供的 API 實(shí)在不怎么樣哗讥。

比如嚷那,你只能通過(guò)重寫 -observeValueForKeyPath:ofObject:change:context: 方法來(lái)獲得通知。想要提供自定義的 selector 杆煞,不行魏宽;想要傳一個(gè) block ,門都沒(méi)有决乎。而且你還要處理父類的情況 - 父類同樣監(jiān)聽(tīng)同一個(gè)對(duì)象的同一個(gè)屬性队询。但有時(shí)候,你不知道父類是不是對(duì)這個(gè)消息有興趣瑞驱。雖然 context 這個(gè)參數(shù)就是干這個(gè)的娘摔,也可以解決這個(gè)問(wèn)題 - 在 -addObserver:forKeyPath:options:context: 傳進(jìn)去一個(gè)父類不知道的 context。但總覺(jué)得框在這個(gè) API 的設(shè)計(jì)下唤反,代碼寫的很別扭凳寺。至少至少,也應(yīng)該支持 block 吧彤侍。

有不少人都覺(jué)得官方 KVO 不好使的肠缨。Mike Ash 的 Key-Value Observing Done Right,以及獲得不少分享討論的 KVO Considered Harmful 都把 KVO 拿出來(lái)吊打了一番盏阶。所以在實(shí)際開(kāi)發(fā)中 KVO 使用的情景并不多晒奕,更多時(shí)候還是用 Delegate 或 NotificationCenter。"

目的:自定義一個(gè)KVO

操作:創(chuàng)建 NSObject+KVO 類名斟,封裝自定義API脑慧;

4.2、創(chuàng)建一個(gè)NSObject的Category,?.h文件中暴露兩個(gè)API砰盐,用于添加和刪除KVO闷袒。例:

#import//?宏定義

NSString?*constkPGKVOClassPrefix?=?@"PGKVOClassPrefix_";

NSString?*constkPGKVOAssociatedObservers?=?@"PGKVOAssociatedObservers";

/**?監(jiān)聽(tīng)回調(diào)用block?*/

typedef?void(^PGObservingBlock)(id?observedObject,

????????????????????????????????NSString?*observedKey,

????????????????????????????????id?oldValue,?id?newValue);

@interfaceNSObject?(KVO)

/**?添加觀察者?*/

-?(void)PG_addObserver:(NSObject?*)observer

????????????????forKey:(NSString?*)key

?????????????withBlock:(PGObservingBlock)block;

/**?移除觀察者?*/

-?(void)PG_removeObserver:(NSObject?*)observer

???????????????????forKey:(NSString?*)key;

@end

.m內(nèi)部實(shí)踐

/*

?*?1、檢查對(duì)象的類有沒(méi)有相應(yīng)的?setter?方法岩梳。如果沒(méi)有拋出異常囊骤;

?*?2晃择、檢查對(duì)象?isa?指向的類是不是一個(gè)?KVO?類。如果不是也物,新建一個(gè)繼承原來(lái)類的子類宫屠,并把?isa?指向這個(gè)新建的子類;

?*?3滑蚯、檢查對(duì)象的?KVO?類重寫過(guò)沒(méi)有這個(gè)?setter?方法浪蹂。如果沒(méi)有,添加重寫的?setter?方法膘魄;

?*?4乌逐、添加這個(gè)觀察者

?*/

-?(void)PG_addObserver:(NSObject?*)observer

????????????????forKey:(NSString?*)key

?????????????withBlock:(PGObservingBlock)block

{

????/*

?????一竭讳、?檢查對(duì)象的類有沒(méi)有相應(yīng)的setter方法创葡,如果沒(méi)有拋出異常


????具體細(xì)節(jié):

????????1.1)、先通過(guò)?setterForGetter()?方法獲得相應(yīng)的?setter?的名字(SEL)绢慢。

????????1.2)灿渴、把key的首字母大寫;?前面加上set胰舆;?key就變成了setKey骚露。

????????1.3)、再用class_getInstanceMethod去獲得setKey:的實(shí)現(xiàn)(Method)缚窿,如果沒(méi)有棘幸,自然要拋出異常

?????*/

????SEL?setterSelector?=?NSSelectorFromString(setterForGetter(key));

????Method?setterMethod?=?class_getInstanceMethod([self?class],?setterSelector);

????if(!setterMethod)

????{

????????NSString?*reason?=?[NSString?stringWithFormat:

????????????????????????????@"Object?%@?does?not?have?a?setter?for?key?%@",?self,?key];

????????@throw[NSException?exceptionWithName:NSInvalidArgumentException

???????????????????????????????????????reason:reason

?????????????????????????????????????userInfo:nil];

????????return;

????}


????/*

?????二、檢查對(duì)象isa指向的類是不是一個(gè)KVO類倦零。如果不是误续,新建一個(gè)繼承原來(lái)類的子類,并把isa指向這個(gè)新建的子類

?????*/


????Class?clazz?=?object_getClass(self);

????NSString?*clazzName?=?NSStringFromClass(clazz);


????//?2.1)扫茅、先看類名有沒(méi)有我們定義的前綴蹋嵌。如果沒(méi)有,我們就去創(chuàng)建新的子類

????if(![clazzName?hasPrefix:kPGKVOClassPrefix])

????{

????????clazz?=?[self?makeKvoClassWithOriginalClassName:clazzName];

????????object_setClass(self,?clazz);

????}

????//?2.2)葫隙、到這里為止,?object的類已不是原類了,?而是KVO新建的類

????//?2.3)栽烂、例如官方的API:?Person?->?NSKVONotifying_Person()

????//?2.4)、kPGKVOClassPrefix是自己定義的一個(gè)宏恋脚,便于區(qū)分系統(tǒng)



????/*

?????三腺办、重寫setter方法。新的setter在調(diào)用原setter方法后糟描,通知每個(gè)觀察者(調(diào)用之前傳入的block)

?????*/

????if(![self?hasSelector:setterSelector])

????{

????????constchar?*types?=?method_getTypeEncoding(setterMethod);

????????class_addMethod(clazz,?setterSelector,?(IMP)kvo_setter,?types);

????}


????/*

?????四怀喉、把這個(gè)觀察的相關(guān)信息存在associatedObject里。


?????具體相關(guān):

????????觀察的相關(guān)信息(觀察者蚓挤,被觀察的?key,?和傳入的?block?)封裝在?PGObservationInfo?類里磺送。

?????*/

????PGObservationInfo?*info?=?[[PGObservationInfo?alloc]?initWithObserver:observer?Key:key?block:block];

????NSMutableArray?*observers?=?objc_getAssociatedObject(self,?(__bridge?constvoid*)(kPGKVOAssociatedObservers));

????if(!observers)

????{

????????observers?=?[NSMutableArray?array];

????????objc_setAssociatedObject(self,?(__bridge?constvoid*)(kPGKVOAssociatedObservers),?observers,?OBJC_ASSOCIATION_RETAIN_NONATOMIC);

????}

????[observers?addObject:info];

}

這就是自定義的核心代碼了驻子,內(nèi)容注視都整理在里面,有幾個(gè)方法需要具體介紹一下:

4.3估灿、核心方法解讀:

setterForGetter

getterForSetter

makeKvoClassWithOriginalClassName

kvo_class

PG_removeObserver

4.3.1崇呵、setterForGetter (name -> Name -> setName:)

/**?根據(jù)getter方法名獲得對(duì)應(yīng)的setter方法名?*/

staticNSString?*?setterForGetter(NSString?*getter)

{

????if(getter.length?<=?0)?{

????????returnnil;

????}


????//?upper?case?the?first?letter

????NSString?*firstLetter?=?[[getter?substringToIndex:1]?uppercaseString];

????NSString?*remainingLetters?=?[getter?substringFromIndex:1];


????//?add?'set'?at?the?begining?and?':'?at?the?end

????NSString?*setter?=?[NSString?stringWithFormat:@"set%@%@:",?firstLetter,?remainingLetters];


????returnsetter;

}

4.3.2、getterForSetter(setName: -> Name -> name)

/**?根據(jù)setter方法名獲得對(duì)應(yīng)的getter方法名?*/

staticNSString?*?getterForSetter(NSString?*setter)

{

????if(setter.length?<=0||?![setter?hasPrefix:@"set"]?||?![setter?hasSuffix:@":"])?{

????????returnnil;

????}


????//?remove?'set'?at?the?begining?and?':'?at?the?end

????NSRange?range?=?NSMakeRange(3,?setter.length?-?4);

????NSString?*key?=?[setter?substringWithRange:range];


????//?lower?case?the?first?letter

????NSString?*firstLetter?=?[[key?substringToIndex:1]?lowercaseString];

????key?=?[key?stringByReplacingCharactersInRange:NSMakeRange(0,?1)

???????????????????????????????????????withString:firstLetter];?

????returnkey;

}

4.3.3馅袁、makeKvoClassWithOriginalClassName

-?(Class)makeKvoClassWithOriginalClassName:(NSString?*)originalClazzName

{

????//?生成kPGKVOClassPrefix_class的類名

????NSString?*kvoClazzName?=?[kPGKVOClassPrefix?stringByAppendingString:originalClazzName];

????Class?clazz?=?NSClassFromString(kvoClazzName);


????//?如果kvo?class已經(jīng)被注冊(cè)過(guò)了,?則直接返回

????if(clazz)?{

????????returnclazz;

????}


????/*

?????*??如果kvo?class不存在,?則創(chuàng)建這個(gè)類

?????*??class?doesn't?exist?yet,?make?it

?????*/

????Class?originalClazz?=?object_getClass(self);

????Class?kvoClazz?=?objc_allocateClassPair(originalClazz,?kvoClazzName.UTF8String,?0);


????/*

?????*??修改kvo?class方法的實(shí)現(xiàn),?學(xué)習(xí)Apple的做法,?隱瞞這個(gè)kvo_class

?????*??grab?class?method's?signature?so?we?can?borrow?it

?????*/

????Method?clazzMethod?=?class_getInstanceMethod(originalClazz,?@selector(class));

????constchar?*types?=?method_getTypeEncoding(clazzMethod);

????class_addMethod(kvoClazz,?@selector(class),?(IMP)kvo_class,?types);


????//?注冊(cè)kvo_class

????objc_registerClassPair(kvoClazz);


????returnkvoClazz;

}

4.3.4域慷、kvo_class

1

2

3

4

staticClass?kvo_class(id?self,?SEL?_cmd)

{

????returnclass_getSuperclass(object_getClass(self));

}

4.3.5、kvo_setter

#pragma?mark?-?Overridden?Methods

/**?重寫setter方法,?新方法在調(diào)用原方法后,?通知每個(gè)觀察者(調(diào)用傳入的block)?*/

staticvoidkvo_setter(id?self,?SEL?_cmd,?id?newValue)

{

????NSString?*setterName?=?NSStringFromSelector(_cmd);

????NSString?*getterName?=?getterForSetter(setterName);


????//?如果不存在getter方法

????if(!getterName)

????{

????????NSString?*reason?=?[NSString?stringWithFormat:@"Object?%@?does?not?have?setter?%@",?self,?setterName];

????????@throw[NSException?exceptionWithName:NSInvalidArgumentException

???????????????????????????????????????reason:reason

?????????????????????????????????????userInfo:nil];

????????return;

????}


????//?獲取舊值

????id?oldValue?=?[self?valueForKey:getterName];


????//?調(diào)用原類的setter方法

????struct?objc_super?superclazz?=?{

????????.receiver?=?self,

????????.super_class?=?class_getSuperclass(object_getClass(self))

????};

????/*

?????*???這里需要做個(gè)類型強(qiáng)轉(zhuǎn),?否則會(huì)報(bào)too?many?argument的錯(cuò)誤

?????*???cast?our?pointer?so?the?compiler?won't?complain

?????*/

????void(*objc_msgSendSuperCasted)(void*,?SEL,?id)?=?(void*)objc_msgSendSuper;


????/*

?????*???call?super's?setter,?which?is?original?class's?setter?method

?????*/

????objc_msgSendSuperCasted(&superclazz,?_cmd,?newValue);


????/*

?????*??找出觀察者的數(shù)組,?調(diào)用對(duì)應(yīng)對(duì)象的callback

?????*??look?up?observers?and?call?the?blocks

?????*/

????NSMutableArray?*observers?=?objc_getAssociatedObject(self,?(__bridge?constvoid*)(kPGKVOAssociatedObservers));

?????//?遍歷數(shù)組

????for(PGObservationInfo?*eachinobservers)

????{

????????if([each.key?isEqualToString:getterName])

????????{

????????????//?gcd異步調(diào)用callback

????????????dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,?0),?^{

????????????????each.block(self,?getterName,?oldValue,?newValue);

????????????});

????????}

????}

}

4.3.6汗销、PG_removeObserver

/**?移除觀察者?*/

-?(void)PG_removeObserver:(NSObject?*)observer?forKey:(NSString?*)key

{

????NSMutableArray*?observers?=?objc_getAssociatedObject(self,?(__bridge?constvoid*)(kPGKVOAssociatedObservers));

????PGObservationInfo?*infoToRemove;

????for(PGObservationInfo*?info?inobservers)

????{

????????if(info.observer?==?observer?&&?[info.key?isEqual:key])

????????{

????????????infoToRemove?=?info;

????????????break;

????????}

????}

????[observers?removeObject:infoToRemove];

}

4.3.7犹褒、調(diào)用

[self.message?PG_addObserver:self?forKey:NSStringFromSelector(@selector(text))?withBlock:^(id?observedObject,?NSString?*observedKey,?id?oldValue,?id?newValue)?{

}];

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市弛针,隨后出現(xiàn)的幾起案子叠骑,更是在濱河造成了極大的恐慌,老刑警劉巖削茁,帶你破解...
    沈念sama閱讀 217,734評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件宙枷,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡茧跋,警方通過(guò)查閱死者的電腦和手機(jī)慰丛,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,931評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)瘾杭,“玉大人诅病,你說(shuō)我怎么就攤上這事≈嗨福” “怎么了贤笆?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,133評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)页徐。 經(jīng)常有香客問(wèn)我苏潜,道長(zhǎng),這世上最難降的妖魔是什么变勇? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,532評(píng)論 1 293
  • 正文 為了忘掉前任恤左,我火速辦了婚禮,結(jié)果婚禮上搀绣,老公的妹妹穿的比我還像新娘飞袋。我一直安慰自己,他們只是感情好链患,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,585評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布巧鸭。 她就那樣靜靜地躺著,像睡著了一般麻捻。 火紅的嫁衣襯著肌膚如雪纲仍。 梳的紋絲不亂的頭發(fā)上呀袱,一...
    開(kāi)封第一講書(shū)人閱讀 51,462評(píng)論 1 302
  • 那天,我揣著相機(jī)與錄音郑叠,去河邊找鬼夜赵。 笑死,一個(gè)胖子當(dāng)著我的面吹牛乡革,可吹牛的內(nèi)容都是我干的寇僧。 我是一名探鬼主播,決...
    沈念sama閱讀 40,262評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼沸版,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼嘁傀!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起视粮,我...
    開(kāi)封第一講書(shū)人閱讀 39,153評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤细办,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后馒铃,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體蟹腾,經(jīng)...
    沈念sama閱讀 45,587評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡痕惋,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,792評(píng)論 3 336
  • 正文 我和宋清朗相戀三年区宇,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片值戳。...
    茶點(diǎn)故事閱讀 39,919評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡议谷,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出堕虹,到底是詐尸還是另有隱情卧晓,我是刑警寧澤,帶...
    沈念sama閱讀 35,635評(píng)論 5 345
  • 正文 年R本政府宣布赴捞,位于F島的核電站逼裆,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏赦政。R本人自食惡果不足惜胜宇,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,237評(píng)論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望恢着。 院中可真熱鬧桐愉,春花似錦、人聲如沸掰派。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,855評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)靡羡。三九已至系洛,卻和暖如春俊性,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背描扯。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,983評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工磅废, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人荆烈。 一個(gè)月前我還...
    沈念sama閱讀 48,048評(píng)論 3 370
  • 正文 我出身青樓拯勉,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親憔购。 傳聞我的和親對(duì)象是個(gè)殘疾皇子宫峦,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,864評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 1,715評(píng)論 0 9
  • 1.ios高性能編程 (1).內(nèi)層 最小的內(nèi)層平均值和峰值(2).耗電量 高效的算法和數(shù)據(jù)結(jié)構(gòu)(3).初始化時(shí)...
    歐辰_OSR閱讀 29,385評(píng)論 8 265
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒(méi)有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對(duì)...
    cosWriter閱讀 11,101評(píng)論 1 32
  • 買不起那些亂七八糟的貼紙膠帶玫鸟,就自己動(dòng)手畫貼紙导绷,然后復(fù)印再上色! 不會(huì)畫畫也沒(méi)關(guān)系從網(wǎng)上下載小圖彩色打邮浩妥曲!然后開(kāi)始...
    兔子姐姐愛(ài)畫畫閱讀 1,421評(píng)論 2 13
  • 陌生的城市 錯(cuò)亂的腳步 不安的心 東望望 西看看 忽閃忽閃眼睛 神秘的事物被吸引 左碰碰 右摸摸 陣陣風(fēng)兒隨地飄 ...
    文藝臉兒閱讀 102評(píng)論 9 5