《Effective Objective-C 2.0:52個(gè)有效方法》(全)筆記

《Effective Objective-C 2.0:編寫高質(zhì)量iOS與OS X代碼的52個(gè)有效方法》

在看這本書的時(shí)候,對(duì)每一個(gè)章節(jié)里面的例子,都有一一去驗(yàn)證,并對(duì)一些驗(yàn)證記錄在筆記棍郎。

在項(xiàng)目中很多場(chǎng)景下都有嘗試去用,效果還是比較可觀的银室。

只記錄有用的涂佃。


1 -> Objective-C的特性

Objective-C:消息結(jié)構(gòu)語言,運(yùn)行時(shí)所執(zhí)行的代碼由運(yùn)行時(shí)環(huán)境決定蜈敢;

C++:使用函數(shù)調(diào)用的語言辜荠,由編譯器決定。

小結(jié):

Objective-C為C語言添加了面向?qū)ο筇匦宰ハ粒瞧涑。籓bjective-C使用動(dòng)態(tài)綁定的消息結(jié)構(gòu),在運(yùn)行時(shí)才會(huì)檢查對(duì)象類型否过,接收到消息后午笛,執(zhí)行代碼,由運(yùn)行期的環(huán)境而非編譯器來決定苗桂。


2 -> 在類的頭文件中盡量少引用其它頭文件

#import<Foundation/Foundation.h>

#import“JDSEmployer.h”?(這樣會(huì)一并引入該文件的所有的內(nèi)容药磺,增加編譯時(shí)間)

@class JDSEmployer;?(“向前申明”):可以節(jié)省編譯時(shí)間,將引入頭文件的時(shí)間盡量延后誉察,只在確實(shí)需要時(shí)才引入与涡;降低相互依賴問題;向前申明也可以解決兩個(gè)類相互引用的問題

?#include "JDSPerson.h"

@interfaceJDSPerson :NSObject

@property(nonatomic,copy)NSString*firstName;

@property(nonatomic,copy)NSString*lastName;

@property(nonatomic,strong)JDSEmployer*employer;

@end

小結(jié):

1、必要的情況下驼卖,才引用頭文件氨肌;一般,應(yīng)在某個(gè)類的頭文件中使用向前聲明來提及別的類酌畜,并在實(shí)現(xiàn)文件中引入那些類的頭文件怎囚,來降低類之間的耦合。

2桥胞、無法適用向前聲明時(shí)恳守,比如要聲明某個(gè)類遵循的協(xié)議,比如要聲明某個(gè)類遵循一項(xiàng)協(xié)議贩虾。這種情況下催烘,盡量把“該類遵循某協(xié)議”的這條聲明移至“class-continuation分類”中。如果不行的話缎罢,就把協(xié)議單獨(dú)放在一個(gè)頭文件中伊群,然后將其引入。


3 -> 多用字面量語法策精,少用與之等價(jià)的方法

NSNumber*someNumber = [NSNumbernumberWithInt:1];

NSArray*animals = [NSArrayarrayWithObjects:@"cat",@"dog",nil];

使用下面的字面量語法代碼更整潔舰始。

NSArray*animals =@[@"cat",@"dog"];NSNumber*someNumber =@1;???idobj1 =@"1";???idobj2 =nil;???idobj3 =@"3";?NSArray*arrayA = [NSArrayarrayWithObjects:obj1,obj2,obj3,nil];

?這種方法不會(huì)奔潰,一次處理到nil時(shí)就會(huì)結(jié)束

?NSLog(@"%@",arrayA);打友释唷:(1)

?NSArray*arrayB =@[obj1,obj2,obj3];? ?

這種方法會(huì)拋出異常?NSLog(@"%@",arrayB);

打油杈怼:*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[__NSPlaceholderArray initWithObjects:count:]: attempt to insert nil object from objects[1]'

小結(jié):

1、應(yīng)該使用字面量語法來創(chuàng)建字符串询刹、數(shù)值谜嫉、數(shù)組、字典范抓。與創(chuàng)建此類對(duì)象的常規(guī)方法相比骄恶,這么做更加簡(jiǎn)明扼要。

2匕垫、應(yīng)該通過取下標(biāo)操作來訪問數(shù)組下標(biāo)或字典中的鍵所對(duì)應(yīng)的元素僧鲁。

3、用字面量語法創(chuàng)建數(shù)組或字典時(shí)象泵,若值中有nil寞秃,則會(huì)拋出異常。因此偶惠,務(wù)必確保值里不含nil春寿。?


4- >多用類型常量,少用 #define 預(yù)處理指令

static const NSTimeIntervalkAnimationDuration =0.3;

試圖修改 這個(gè)常量忽孽,編譯就會(huì)報(bào)錯(cuò)

不加static:另一個(gè)文件聲明同樣的變量名绑改,編譯器會(huì)報(bào)錯(cuò):

duplicate symbol _kAnimationDuration in:???

EOCAnimatedView.o???

EOCOtherView.o

如果一個(gè)變量既聲明為static谢床,又聲明為const,那么編譯器根本不會(huì)創(chuàng)建符號(hào)厘线,而是會(huì)像#define預(yù)處理指令一樣识腿,把所有遇到的變量都替換為常值。不過還是要記自熳场:用這種方式定義的常量帶有類型信息渡讼。

.m實(shí)現(xiàn)文件中這樣去聲明一個(gè)變量:NSString *const EOCStringConstant = @“VALUE”,那么需要給外部文件使用的話,這樣寫:extern NSString *const EOCStringConstant;

.h ? ??extern?NSString*constnotyfication;

.m ? NSString? *constnotyfication =@"notyfication";

上面的例子可以寫為:

// EOCAnimatedView.h

extern const NSTimeInterval EOCAnimatedViewAnimationDuration;

// EOCAnimatedView.m

const NSTimeInterval EOCAnimatedViewAnimationDuration = 0.3;

小結(jié):

1耳璧、不要用#define定義常量成箫,只是代碼替換,并沒有減少代碼量旨枯,有人重新定義此常量編譯器也不會(huì)報(bào)錯(cuò)蹬昌,導(dǎo)致常量值出錯(cuò)。

2攀隔、在實(shí)現(xiàn)文件中使用static const來定義“只在編譯單元內(nèi)可見的常量”(translation-unit-specific constant)凳厢。由于此類常量不在全局符號(hào)表中,所以無須為其名稱加前綴竞慢,在頭文件中使用extern來聲明全局常量,并在相關(guān)實(shí)現(xiàn)文件中定義其值治泥。這種常量要出現(xiàn)在全局符號(hào)表中筹煮,所以其名稱應(yīng)加以區(qū)隔,通常用與之相關(guān)的類名做前綴居夹。

?3败潦、const 不可變?cè)瓌t :其右邊的總不能被修改,能提高代碼的安全性准脂,降低程序員的溝通成本劫扒。


5 -> 用枚舉表示選項(xiàng)、狀態(tài)碼

typedefNS_ENUM(NSUInteger, JSConnectionState) {

??? JSConnectionStateDisconnected,

??? JSConnectionStatingConnecting,

??? JSDisConnectionStatedConnected,

};

用命名描述清楚狸膏,狀態(tài)值含義沟饥;

用枚舉作為switch?參數(shù);可以不用寫default分支:如上圖所示湾戳,在枚舉中添加的新的值而又未被使用就會(huì)發(fā)出警告

小結(jié):

1贤旷、應(yīng)該用枚舉來表示狀態(tài)機(jī)的狀態(tài)、傳遞給方法的選項(xiàng)以及狀態(tài)碼等值砾脑,給這些值起一個(gè)易懂的名字幼驶。

2、多個(gè)枚舉狀態(tài)可以同時(shí)使用韧衣,應(yīng)該將選項(xiàng)值定義為2的冥盅藻,以便通過通過按位或操作將其組合起來

3购桑、不需要枚舉值互相組合用NS_ENUM,?如果需要互相組合用NS_OPTIONS氏淑,NS_ENUM勃蜘、NS_OPTIONS:需要指明其底層數(shù)據(jù)結(jié)構(gòu),可以確保是程序員想要的數(shù)據(jù)類型夸政,而不是編譯器所選的類型元旬。

4、處理switch放語句守问,不需要實(shí)現(xiàn)default分支匀归,加入新的枚舉值后,編譯器會(huì)報(bào)錯(cuò):switch并為處理所有枚舉耗帕。


6 -> 理解“屬性”這一概念

1穆端、原子性

atomic:原子屬性,默認(rèn)就是atomic仿便。需要消耗大量的資源体啰。

nonatomic:非原子屬性。適合內(nèi)存小的移動(dòng)設(shè)備嗽仪。

默認(rèn)情況下荒勇,由編譯器所合成(synthesize)的方法會(huì)通過鎖定機(jī)制來確保它是原子的(atomic)。如果屬性具有nonatomic特質(zhì)闻坚,則不使用同步鎖沽翔。

需要注意的是,開發(fā)iOS程序時(shí)一般都會(huì)使用nonatomic屬性窿凤,這是因?yàn)樵?a target="_blank" rel="nofollow">ios中使用同步鎖的開銷較大仅偎,這會(huì)帶來性能問題。但是在Mac OS X中雳殊,使用atomic屬性通常都不會(huì)有性能瓶頸橘沥。

一般情況下,并不要求屬性必須是原子的夯秃,因?yàn)檫@并不能保證“線程安全”座咆,若要實(shí)現(xiàn)線程安全,需要更為深層的鎖定機(jī)制才行仓洼。例如一個(gè)線程要連續(xù)多次讀取某屬性的過程中箫措,另一個(gè)線程對(duì)該屬性值進(jìn)行了修改,那么即便將屬性聲明為atomic衬潦,還是會(huì)讀到不同的屬性值斤蔓。

2、讀/寫權(quán)限

readwirte:表明屬性具有讀取和設(shè)置方法镀岛。

readonly: ?表明屬性只擁有讀取方法

若屬性由@synthesize實(shí)現(xiàn)弦牡,則編譯器才會(huì)自動(dòng)合成與其讀寫權(quán)限相關(guān)的方法友驮。

3、內(nèi)存管理語義

內(nèi)存管理語義僅會(huì)影響屬性的“設(shè)置方法”驾锰。編譯器在合成存取方法時(shí)卸留,要根據(jù)此特質(zhì)來決定所生成的代碼。如果自己來編寫存取方法椭豫,那么就必須與相關(guān)屬性所聲明的特質(zhì)相符耻瑟。

assign:只執(zhí)行針對(duì)“純量類型”(scalar type,例如CGFloat或NSInteger等)的簡(jiǎn)單賦值操作赏酥。

strong:表明該屬性定義了一種擁有關(guān)系喳整。為該屬性設(shè)置新值時(shí),會(huì)先保留新值裸扶,并釋放舊值框都,最后再設(shè)置新值。

weak:表明該屬性定義了一種非擁有關(guān)系呵晨。為該屬性設(shè)置新值時(shí)魏保,既不保留新值,也不釋放舊值摸屠。此特質(zhì)跟assign類似谓罗,然而在屬性所指對(duì)象被銷毀時(shí),該屬性也會(huì)清空(nil out)季二。

unsafe-unretained:此特質(zhì)的語義跟assign相同妥衣,但它適用于對(duì)象類型。表明該屬性定義了一種非擁有關(guān)系戒傻,在屬性所指對(duì)象被銷毀時(shí),該屬性不會(huì)自動(dòng)清空蜂筹,這點(diǎn)跟weak不同需纳。

copy:此特質(zhì)所表達(dá)的所屬關(guān)系跟strong類似。然而設(shè)置方法并不保留新值艺挪,而是將其拷貝不翩。只要實(shí)現(xiàn)屬性所用的對(duì)象是可變的(mutable),就應(yīng)該在設(shè)置新屬性值時(shí)拷貝一份麻裳。當(dāng)屬性類型為NSString*時(shí)口蝠,經(jīng)常用此特質(zhì)來保護(hù)其封裝性,因?yàn)閭鬟f給設(shè)置方法的新值有可能是NSMutableString類型的實(shí)例津坑。

4妙蔗、方法名

可通過如下特質(zhì)來指定存取方法的方法名。

getter=:指定“獲取方法”的方法名疆瑰。

例如:@property(nonatomic,getter=isOn)BOOLon;

setter=:指定“設(shè)置方法”的方法名眉反,這種用法不太常見昙啄。

小結(jié):

1、通過“特質(zhì)”來指定存儲(chǔ)數(shù)據(jù)所需的正確語義寸五。

2梳凛、開發(fā) iOS 程序時(shí)應(yīng)使用?nonatomic?屬性,因?yàn)?atomic?屬性會(huì)嚴(yán)重影響性能梳杏。


7?->?在對(duì)象內(nèi)部盡量直接訪問實(shí)例變量

?NSString*oldName1 =self.firstName; //通過屬性訪問 ?

?NSString*oldName2 =?_firstName; ?? //??直接訪問 ?

小結(jié):

1韧拒、在對(duì)象內(nèi)部讀取數(shù)據(jù)時(shí),應(yīng)該直接訪問實(shí)例變量來讀十性,寫入數(shù)據(jù)時(shí)叛溢,則應(yīng)該通過屬性來寫

2、在初始化方法和?dealloc方法中烁试,總是應(yīng)該直接通過實(shí)例變量來讀寫數(shù)據(jù)

3雇初、有時(shí)會(huì)使用惰性初始化技術(shù)配置某分?jǐn)?shù)據(jù),那么必須通過屬性來讀取數(shù)據(jù)

4减响、直接訪問實(shí)例變量靖诗,不會(huì)掉用“設(shè)置方法”,內(nèi)存管理語義就沒用了支示,相同不會(huì)觸發(fā)“鍵值對(duì)-KVO”


8?->?理解“對(duì)象等同性”這一概念

比較對(duì)象的等同性是一個(gè)非常有用的功能刊橘。不過按照==操作符比較的是比較兩個(gè)對(duì)象的指針地址,而不是其所指的對(duì)象颂鸿。應(yīng)該使用NSObject協(xié)議中的isEqual方法來判斷兩個(gè)對(duì)象的等同性促绵。NSObject類對(duì)isEqual方法的默認(rèn)實(shí)現(xiàn)是當(dāng)且僅當(dāng)兩個(gè)對(duì)象的指針值相等時(shí),才判定這兩個(gè)對(duì)象相等嘴纺,這時(shí)hash方法返回的值也必須相等败晴。

例如:

- (BOOL)isEqual:(id)object{

???if([selfclass] == [objectclass]) {

???????return[selfisEqualToPerson:(JDSPerson*)object];

??? }else{

???????return[superisEqual:object];

??? }

???returnNO;

}

- (BOOL)isEqualToPerson:(JDSPerson*)otherPerson{

???if(self== otherPerson) {

???????returnYES;

??? }

???if([_firstNameisEqualToString:otherPerson.firstName] &&. ? ? [_lastNameisEqualToString:otherPerson.lastName] &&_age!=otherPerson.age) {

???????returnYES;

??? }else{

???????returnNO;

??? }

}

計(jì)算hash值的方法可實(shí)現(xiàn)如下,這樣既能保持高效率栽渴,又能使生成的hash碼至少落在一定范圍之內(nèi)尖坤,不會(huì)頻繁重復(fù):

-(NSUInteger)hash{

???NSIntegerfistNameHash = [_firstNamehash];

???NSIntegerlastNameHash = [_lastNamehash];

???NSIntegerageHash =_age;

???returnfistNameHash^lastNameHash^ageHash;

}

小結(jié):

1、若要檢查對(duì)象的等同性闲擦,請(qǐng)?zhí)峁﹊sEqual和hash方法慢味。

2、相同的對(duì)象必須具有相同的hash碼墅冷,但擁有相同hash碼的對(duì)象卻不一定相同纯路。

3、不要盲目地逐個(gè)檢測(cè)每條屬性寞忿,而是應(yīng)該依照具體需求來制定檢測(cè)方案驰唬。

4、編寫hash方法時(shí),應(yīng)該使用計(jì)算速度快而且哈希碼碰撞幾率低的算法定嗓。


9?->?以“類族模式”蜕琴,隱藏實(shí)現(xiàn)細(xì)節(jié)

在Cocoa中,許多類實(shí)際上是以類簇的方式實(shí)現(xiàn)的宵溅,即它們是一群隱藏在通用接口之下的與實(shí)現(xiàn)相關(guān)的類凌简。例如創(chuàng)建NSString對(duì)象時(shí),實(shí)際上獲得的可能是NSLiteralString恃逻、NSCFString雏搂、NSSimpleCString、NSBallOfString或者其他未寫入文檔的與實(shí)現(xiàn)相關(guān)的對(duì)象寇损。

typedef NS_ENUM(NSUInteger, EOCEmployeeType) {

??? EOCEmployeeTypeDeveloper,

??? EOCEmployeeTypeDesigner,

??? EOCEmployeeTypeFinance,

};

@interfaceEOCEmployee :NSObject

@property(copy)NSString*name;

@propertyNSUIntegersalary;

// Helper for creating Employee objects

+ (EOCEmployee*)employeeWithType:(EOCEmployeeType)type;

// Make Employees do their respective day's work

- (void)doADaysWork;

@end

@implementationEOCEmployee

+ (EOCEmployee*)employeeWithType:(EOCEmployeeType)type {

???switch(type) {

???????caseEOCEmployeeTypeDeveloper:

???????????return[EOCEmployeeDeveloper new];

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

???????caseEOCEmployeeTypeDesigner:

???????????return[EOCEmployeeDesigner new];

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

???????caseEOCEmployeeTypeFinance:

???????????return[EOCEmployeeFinance new];

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

??? }

}

- (void)doADaysWork {

???// Subclasses implement this.

}

@end

類簇的實(shí)體子類的實(shí)現(xiàn)示例:

@interfaceEOCEmployeeDeveloper : EOCEmployee

@end

@implementationEOCEmployeeDeveloper

- (void)doADaysWork {

??? [selfwriteCode];

}

@end

判斷某對(duì)象是否位于類簇中凸郑,不要直接檢測(cè)兩個(gè)“類對(duì)象”(class)是否相等,而應(yīng)該使用類型信息查詢方法:

idmaybeAnArray =/* ... */;

if([maybeAnArray isKindOfClass:[NSArray class]]) {

???// Will be hit

}

小結(jié):

1矛市、類簇模式可以把實(shí)現(xiàn)細(xì)節(jié)隱藏在一套簡(jiǎn)單的公共接口后面芙沥。

2、系統(tǒng)框架中經(jīng)常使用類簇浊吏。

3而昨、從類簇的公共抽象基類中繼承子類時(shí)要當(dāng)心,若有開發(fā)文檔找田,則應(yīng)首先閱讀歌憨。


10?->?在既有類中使用關(guān)聯(lián)對(duì)象存放自定義數(shù)據(jù)

管理關(guān)聯(lián)對(duì)象的方法有:

? ? // Sets up an association of object to value with the given key and policy.

???voidobjc_setAssociatedObject(idobject,void*key,idvalue,objc_AssociationPolicy?policy)

?// Retrieves the value for the association on object with the given key.

???idobjc_getAssociatedObject(idobject,void*key)

?// Removes all associations against object.

?voidobjc_removeAssociatedObjects(idobject)

??:

- (IBAction)button:(id)sender {

???UIAlertView*alertView = [[UIAlertViewalloc]initWithTitle:@"action"message:@"ceshieryi"delegate:selfcancelButtonTitle:@"cancel"otherButtonTitles:@"continue",nil];

???void(^block)(NSInteger)=^(NSIntegerbutSelected){

???????if(butSelected ==0) {

???????????NSLog(@"cancel");

??????? }else{

???????????NSLog(@"continue");

??????? }

??? };

???objc_setAssociatedObject(alertView,alertViewKey, block,OBJC_ASSOCIATION_COPY);

??? [alertViewshow];

}

-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex{

???void(^blockclick)(NSInteger) = objc_getAssociatedObject(alertView, alertViewKey);

??? blockclick(buttonIndex);

}

這樣的好處是可以把實(shí)現(xiàn)方法跟調(diào)用的位置放在一起,代碼看起來方便!!货抄!

小結(jié):

1、可以通過“關(guān)聯(lián)對(duì)象”(associated objects)機(jī)制將兩個(gè)對(duì)象連起來心铃。

2、定義關(guān)聯(lián)對(duì)象時(shí)可指定內(nèi)存管理語義(OBJC_ASSOCIATION_COPY)挫剑,用以模仿定義屬性時(shí)所采用的“擁有關(guān)系”與“非擁有關(guān)系”

3去扣、只有在其它做法不可行時(shí)才應(yīng)選用關(guān)聯(lián)對(duì)象,因?yàn)檫@種做法通常會(huì)引入難查找的bug暮顺。


11、- > 理解obj_msgSend 的作用

C語言中有靜態(tài)綁定和動(dòng)態(tài)綁定兩種函數(shù)調(diào)用方式秀存。Objective-C作為c語言的超集捶码,向?qū)ο蟀l(fā)送消息時(shí)使用動(dòng)態(tài)綁定機(jī)制來決定需要調(diào)用的方法。在底層或链,所有方法都是普通的C語言函數(shù)惫恼,然而在對(duì)象收到消息后究竟調(diào)用哪個(gè)方法則完全于運(yùn)行期決定,甚至可以在程序運(yùn)行時(shí)改變澳盐,這些特性使得Objective-C成為一門真正的動(dòng)態(tài)語言祈纯。

C語言是靜態(tài)綁定

#import

voidprintfHello(){

???printf("hello");

}

voidprintfGoodBey(){

????printf("hello");

}

voiddoTheThing(inttype){

???if(type ==0) {

???????printfHello();

??? }else{

???????printfGoodBey();

??? }

}

OC是動(dòng)態(tài)綁定

#import

voidprintfHello(){

???printf("hello");

}

voidprintfGoodBey(){

????printf("GoodBey");

}

voiddoTheThing(inttype){

???void(*fun)();

???if(type ==0) {

??????? fun =printfHello;

??? }else{

??????? fun =printfGoodBey;

??? }

??? fun();

}

objc_msgSend函數(shù)會(huì)根據(jù)接收者與選擇子的類型來調(diào)用適當(dāng)?shù)姆椒钏蕖榱送瓿纱瞬僮鳎摵瘮?shù)需要在接收者所屬的類中搜尋其“方法列表”腕窥,如果找到與選擇子名稱相符的方法粒没,就跳轉(zhuǎn)到其實(shí)現(xiàn)代碼;否則即沿著繼承體系繼續(xù)向上查找簇爆;如果仍未找到癞松,就執(zhí)行“消息轉(zhuǎn)發(fā)”(message forwarding)操作。

objc_msgSend調(diào)用方法有個(gè)優(yōu)化操作入蛆。它會(huì)將匹配結(jié)果緩存在“快速映射表”(fast map)里响蓉。每個(gè)類都有這樣一塊緩存,若是稍后還發(fā)送相同的消息哨毁,就會(huì)加快執(zhí)行效率枫甲。

前面講的這部分內(nèi)容只描述了部分消息的調(diào)用過程,其他“邊界情況”(edge case)則需要交由Objective-C運(yùn)行環(huán)境中的另外一些函數(shù)來處理:

objc_msgSend_stret 待發(fā)送的消息要返回結(jié)構(gòu)體時(shí)用

objc_msgSend_fpret 待發(fā)送的消息返回浮點(diǎn)類型時(shí)用

objc_msgSendSuper ?如果給超類發(fā)消息時(shí)用

小結(jié):

1扼褪、消息由接收者想幻、選擇子和參數(shù)構(gòu)成。給某對(duì)象“發(fā)送消息”(invoke a message)迎捺,相當(dāng)于在該對(duì)象上“調(diào)用方法”(call a method)

2举畸、發(fā)送給某對(duì)象的全部消息都要經(jīng)過“動(dòng)態(tài)消息派發(fā)機(jī)制”(dynamic message dispatch system)來處理,該系統(tǒng)會(huì)查出對(duì)應(yīng)的方法凳枝,并執(zhí)行其代碼抄沮。


12 -> 理解消息轉(zhuǎn)發(fā)機(jī)制

動(dòng)態(tài)方法解析: 向當(dāng)前對(duì)象的所屬類發(fā)送resolveInstanceMethod:(針對(duì)實(shí)例方法)或resolveClassMethod(針對(duì)類方法)消息,檢查是否動(dòng)態(tài)向該類添加了方法岖瑰。使用此方案的前提是:相關(guān)的實(shí)現(xiàn)代碼已經(jīng)寫好叛买,只等著運(yùn)行時(shí)直接插在類中。此方案常用來實(shí)現(xiàn)@dynamic屬性

#import"JDSPerson.h"

#import

@interfaceJDSPerson()

@property(nonatomic,strong)NSMutableDictionary*backingstore;

@end

@implementationJDSPerson

@dynamicfirstName,lastName;

- (instancetype)init

{

???self= [superinit];

???if(self) {

???????if(!_backingstore) {

???????????_backingstore= [NSMutableDictionarynew];

??????? }

??? }

???returnself;

}

關(guān)鍵在于resolveInstanceMethod:方法的實(shí)現(xiàn)代碼:

/**

?*?如果尚未實(shí)現(xiàn)的方法是實(shí)例方法蹋订,則調(diào)用此函數(shù)

?*

?*? @param selector未處理的方法

?*

?*? @return返回布爾值率挣,表示是否能新增實(shí)例方法用以處理selector

?*/

+(BOOL)resolveInstanceMethod:(SEL)sel{

???NSString*selectorstring =NSStringFromSelector(sel);

?if([selectorstringhasPrefix:@"set"]) {

//?添加?setter?方法

???????class_addMethod(self, sel, (IMP)autoDictionarysetter,"v@:@");

??? }else{

//?添加 getter?方法

???????class_addMethod(self, sel, (IMP)autodictionaryGetter,"@@:");

??? }

???returnYES;

}

/**

?*?如果尚未實(shí)現(xiàn)的方法是類方法,則調(diào)用此函數(shù)

?*

?*? @param selector未處理的方法

?*

?*? @return返回布爾值露戒,表示是否能新增類方法用以處理selector

?*/

//+ (BOOL)resolveClassMethod:(SEL)selector;

voidautoDictionarysetter(idself,SEL_cmd,idvalue){

???JDSPerson*typeSelf = (JDSPerson*)self;

???NSMutableDictionary*backingstore = typeSelf.backingstore;


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

???NSMutableString*key = [selectorStringmutableCopy];

???//delete @":"

??? [keydeleteCharactersInRange:NSMakeRange(key.length-1,1)];

???//delete @"set"

??? [keydeleteCharactersInRange:NSMakeRange(0,3)];

???NSString*lowercasefirstchar = [[keysubstringToIndex:1]lowercaseString];

??? [keyreplaceCharactersInRange:NSMakeRange(0,1)withString:lowercasefirstchar];


???if(value) {

??????? [backingstoresetObject:valueforKey:key];

??? }else{

??????? [backingstoreremoveObjectForKey:key];

??? }

}

idautodictionaryGetter(idself,SEL_cmd){

???JDSPerson*typeSelf = (JDSPerson*)self;

???NSMutableDictionary*backingstore = typeSelf.backingstore;


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

???NSString*value = [backingstoreobjectForKey:key];

???return[backingstoreobjectForKey:key];

}

@end

?- (void)viewDidLoad {

??? [superviewDidLoad];

???// Do any additional setup after loading the view, typically from a nib.

???JDSPerson*person = [JDSPersonnew];

??? person.firstName=@"jds";

?NSLog(@"%@",person.firstName);

? ? //輸出:jds

}

/**

?*?此方法詢問是否能將消息轉(zhuǎn)給其他接收者來處理

?*

?*? @param aSelector未處理的方法

?*

?*? @return如果當(dāng)前接收者能找到備援對(duì)象椒功,就將其返回;否則返回nil智什;

?*/

- (id)forwardingTargetForSelector:(SEL)aSelector;

使用這個(gè)方法通常是在對(duì)象內(nèi)部动漾,可能還有一系列其它對(duì)象能處理該消息,我們便可借這些對(duì)象來處理消息并返回荠锭,這樣在對(duì)象外部看來旱眯,還是由該對(duì)象親自處理了這一消息。

標(biāo)準(zhǔn)消息轉(zhuǎn)發(fā): 經(jīng)過上述兩步之后,如果還是無法處理選擇子删豺,則啟動(dòng)完整的消息轉(zhuǎn)發(fā)機(jī)制共虑。我們需要重寫methodSignatureForSelector:和forwardInvocation:實(shí)例方法。runtime發(fā)送?methodSignatureForSelector:消息獲取選擇子對(duì)應(yīng)的方法簽名呀页,即參數(shù)與返回值的類型信息妈拌。runtime則根據(jù)方法簽名創(chuàng)建描述該消息的NSInvocation,以創(chuàng)建的NSInvocation對(duì)象作為參數(shù)赔桌,向當(dāng)前對(duì)象發(fā)送forwardInvocation:消息供炎。forwardInvocation:方法定位能夠響應(yīng)封裝在此NSInvocation中的消息的對(duì)象。此NSInvocation對(duì)象將會(huì)保留調(diào)用結(jié)果疾党,運(yùn)行時(shí)系統(tǒng)會(huì)提取這一結(jié)果并將其發(fā)送到消息的原始發(fā)送者音诫。在這個(gè)方法中我們可以實(shí)現(xiàn)一些更復(fù)雜的功能,可對(duì)消息內(nèi)容進(jìn)行修改雪位,比如追回一個(gè)參數(shù)等竭钝,然后再去觸發(fā)消息。另外雹洗,若發(fā)現(xiàn)某個(gè)消息不應(yīng)由本類處理香罐,則應(yīng)調(diào)用父類的同名方法,以便繼承體系中的每個(gè)類都有機(jī)會(huì)處理此調(diào)用請(qǐng)求时肿。NSObject的forwardInvocation:方法實(shí)現(xiàn)只是簡(jiǎn)單調(diào)用了doesNotRecognizeSelector:方法庇茫,它不會(huì)轉(zhuǎn)發(fā)任何消息,只拋出異常導(dǎo)致程序退出

/**

?*?消息派發(fā)系統(tǒng)通過此方法螃成,將消息派發(fā)給目標(biāo)對(duì)象

?*

?*? @param anInvocation之前創(chuàng)建的NSInvocation實(shí)例對(duì)象旦签,用于裝載有關(guān)消息的所有內(nèi)容

?*/

- (void)forwardInvocation:(NSInvocation*)anInvocation;

這個(gè)方法可以實(shí)現(xiàn)的很簡(jiǎn)單,通過改變調(diào)用的目標(biāo)對(duì)象寸宏,使得消息在新目標(biāo)對(duì)象上得以調(diào)用即可宁炫。然而這樣實(shí)現(xiàn)的效果與 備援接收者 差不多,所以很少人會(huì)這么做氮凝。更加有用的實(shí)現(xiàn)方式為:在觸發(fā)消息前羔巢,先以某種方式改變消息內(nèi)容,比如追加另一個(gè)參數(shù)罩阵、修改 selector 等等竿秆。

小結(jié):

1、若對(duì)象無法響應(yīng)某個(gè)選擇子稿壁,則進(jìn)入消息轉(zhuǎn)發(fā)流程

2幽钢、通過運(yùn)行期的動(dòng)態(tài)方法解析功能,可以在需要用到某個(gè)地方時(shí)再將其加入類中

3常摧、對(duì)象可以將其無法解讀的某些選擇子轉(zhuǎn)交給其他對(duì)象來處理

4搅吁、經(jīng)過上述兩步后還是沒辦法處理選擇子,就啟動(dòng)完整的消息轉(zhuǎn)發(fā)機(jī)制落午。


13 -> 用“方法調(diào)試技術(shù)”調(diào)試“黑盒方法”

1谎懦、用運(yùn)行時(shí)對(duì)特性獲取兩個(gè)方法的實(shí)現(xiàn),然后進(jìn)行交換

- (void)viewDidLoad {

??? [superviewDidLoad];

? ??MethodoriginalMethod =class_getInstanceMethod([selfclass],@selector(greenBtn:));

?MethodswappedMethod =class_getInstanceMethod([selfclass],@selector(yellowBtn:));??

?method_exchangeImplementations(originalMethod, swappedMethod);

}

- (IBAction)greenBtn:(id)sender {

?self.view.backgroundColor= [UIColorgreenColor];

}

- (IBAction)yellowBtn:(id)sender {

?self.view.backgroundColor= [UIColoryellowColor];

}

2溃斋、實(shí)現(xiàn)dealloc打印

- (void)viewDidLoad {

??? [superviewDidLoad];

?MethodoriginalMethod =class_getInstanceMethod([selfclass],NSSelectorFromString(@"dealloc"));

?MethodswappedMethod =class_getInstanceMethod([selfclass],@selector(swappedDealloc));

???method_exchangeImplementations(originalMethod, swappedMethod);

}

- (void)swappedDealloc{

???NSLog(@"%@:dealloc",NSStringFromClass([selfclass]));

??? [selfswappedDealloc];

? ? ??????看起來像是進(jìn)入了無限遞歸界拦,因?yàn)榻粨Q了方法,這里實(shí)際是在執(zhí)行:dealloc 這個(gè)方法

}

- (IBAction)back:(id)sender {

??? [selfdismissViewControllerAnimated:YEScompletion:nil];

}

小結(jié):

1梗劫、在運(yùn)行期享甸,可以向類中新增或替換方法實(shí)現(xiàn)

2、例如第二個(gè)??一樣用一個(gè)新的實(shí)現(xiàn)方法替換原來的實(shí)現(xiàn)梳侨,可以在原有的實(shí)現(xiàn)中加入新的功能

3蛉威、??????不宜濫用

4、http://www.cocoachina.com/ios/20150911/13260.html?可以解決button重復(fù)點(diǎn)擊

http://www.reibang.com/p/e4f1fb537af9

14 -> 理解“類對(duì)象”的用意

“在運(yùn)行期檢視對(duì)象類型”這一操作也叫做“類型信息查詢”(introspection走哺,“內(nèi)省”)蚯嫌,這個(gè)強(qiáng)大有用的特性內(nèi)置于Foundation框架的NSObject協(xié)議里,凡是由公共根類(common root class丙躏,即NSObject與NSProxy)繼承而來的對(duì)象都遵從此協(xié)議择示。在程序中,不要直接比較對(duì)象所屬的類晒旅,明智的做法是調(diào)用“類型信息查詢方法”栅盲。

類型信息查詢方法包括isMemberOfClass:(判斷對(duì)象是否為某個(gè)特定類的實(shí)例),isKindOfClass:(判斷對(duì)象是否為某類或其派生類的實(shí)例)废恋。像這樣的類型信息查詢方法使用isa指針獲取對(duì)象所屬的類谈秫,然后通過super_class指針在繼承體系里游走。

另外一種可精確判斷出對(duì)象是否為某類實(shí)例的辦法是:

idobject =/* ... */;

if([object class] == [EOCSomeClass class]){

???// 'object' is an instance of EOCSomeClass

}

即使這樣拴签,應(yīng)盡量使用類型信息查詢方法孝常,而不應(yīng)直接比較兩個(gè)類對(duì)象是否等同,因?yàn)榍罢呖梢哉_處理那些使用了消息轉(zhuǎn)發(fā)機(jī)制的對(duì)象蚓哩。比如构灸,某對(duì)象可能會(huì)把它收到的所有選擇子都轉(zhuǎn)發(fā)給另外一個(gè)對(duì)象。這樣的對(duì)象叫做代理岸梨,此種對(duì)象均以NSProxy為根類喜颁。

isMemberOfClass: 能夠判斷出對(duì)象是否為某個(gè)特定類的實(shí)例。

isKindOfClass: 能夠判斷出對(duì)象是否為某類或其派生類的實(shí)例曹阔。

? ?NSMutableDictionary*dic = [NSMutableDictionarynew];

??? [dicisMemberOfClass:[NSDictionaryclass]];//no

??? [dicisMemberOfClass:[NSMutableDictionaryclass]];//yes

??? [dicisKindOfClass:[NSDictionaryclass]];//yes

[dic?isKindOfClass:[NSArrayclass]];//no

小結(jié):

1半开、每個(gè)實(shí)例都有一個(gè)指向Class對(duì)象的指針,用以表明其類型赃份,而這些Class對(duì)象則構(gòu)成了類的繼承體系寂拆。

2奢米、如果對(duì)象類型無法在編譯期確定,那么就應(yīng)該使用類型信息查詢方法來探知

3纠永、盡量使用類型信息查詢方法來確定對(duì)象類型鬓长,而不要直接比較類對(duì)象,因?yàn)槟承?duì)象可能實(shí)現(xiàn)了消息轉(zhuǎn)發(fā)功能尝江。


15 -> 用前綴避免命名空間沖突

Objective-C沒有其他語言哪種內(nèi)置的命名空間(namespace)機(jī)制涉波。

避免命名沖突的唯一辦法就是變相實(shí)現(xiàn)命名空間:為所有名稱都加上適當(dāng)?shù)那熬Y。

創(chuàng)建應(yīng)用程序時(shí)一定要注意:Apple宣稱其保留使用所有“兩字母前綴”的權(quán)利炭序,所以你自己選用的前綴應(yīng)該是三個(gè)字母或者更多啤覆。

這么做還有一個(gè)好處:如果此符號(hào)出現(xiàn)在棧回溯信息中惭聂,則很容易就能判明問題源自哪塊代碼窗声。

小結(jié):

1、選擇與你的公司辜纲、應(yīng)用程序或者二者皆有關(guān)聯(lián)之名作為類名的前綴嫌佑,并在所有代碼中均使用這一前綴。

2侨歉、若自己所開發(fā)的程序庫中用到了第三方庫屋摇,則應(yīng)為其中的名稱加上前綴。


16 -> “提供全能初始化方法”

我們把這種可為對(duì)象提供必要信息以便其能完成工作的初始化方法叫做“全能初始化方法”(designated initializer)幽邓。

如果創(chuàng)建類的實(shí)例的方式不止一種炮温,那么這個(gè)類就會(huì)有多個(gè)初始化方法。

這當(dāng)然會(huì)很好牵舵,不過仍然要在其中選定一個(gè)作為“全能初始化方法”柒啤,令其他初始化方法都來調(diào)用它。

于是畸颅,只有在全能初始化方法中担巩,才會(huì)存儲(chǔ)內(nèi)部數(shù)據(jù)。

這樣的話没炒,當(dāng)?shù)讓訑?shù)據(jù)存儲(chǔ)機(jī)制改變時(shí)涛癌,只需要修改此方法的代碼就好,無需改動(dòng)其他初始化方法送火。

類繼承時(shí)需要注意的一個(gè)重要問題:如果子類的全能初始化方法與超類方法的名稱不同拳话,那么總應(yīng)覆寫超類的全能初始化方法。每個(gè)子類的全能初始化方法都應(yīng)該調(diào)用其超類的對(duì)應(yīng)方法种吸,并逐層向上弃衍,然后再執(zhí)行與本類有關(guān)的任務(wù)。

如不用:就拋出異常

- (instancetype)initWithWidth:(float)wigth

{ ? ? ? ? ?

?@throw[NSException exceptionWithName:NSInternalInconsistencyException ?reason:@"Must use initWithFrame: insteat"userInfo:nil];

}

小結(jié):

1坚俗、?在類中提供一個(gè)全能初始化方法镜盯,并于文檔里指明岸裙。其他初始化方法均應(yīng)調(diào)用此方法。

2速缆、若全能初始化方法與超類不同哥桥,則需覆寫超類中的對(duì)應(yīng)方法。

3激涤、如果超類的初始化方法不適用于子類,那么應(yīng)該覆寫這個(gè)超類方法判呕,并在其中拋出異常倦踢。


17 -> 實(shí)現(xiàn)description ?方法

覆寫description方法,否則打印信息時(shí)侠草,就會(huì)調(diào)用NSObject類所實(shí)現(xiàn)的默認(rèn)方法辱挥。

- (NSString*)description

{

???return[NSStringstringWithFormat:@"<%@:%p:%@>",[selfclass],self,_lastName];

}

用LLDB?“op” 完成打印

小結(jié):

1、?實(shí)現(xiàn)description方法返回一個(gè)有意義的字符串边涕,用以描述該實(shí)例晤碘。

2、若想在調(diào)式時(shí)打印出更詳盡的對(duì)象描述信息功蜓,則應(yīng)實(shí)現(xiàn)debugDescription方法园爷。


18 -> 盡量使用不可變對(duì)象

一般情況下,對(duì)外公開的接口一般聲明為readOnly,也可以在類內(nèi)部實(shí)現(xiàn)重新聲明為readwrite式撼,這樣可以在類內(nèi)部修改參數(shù)童社,在類的實(shí)現(xiàn)代碼內(nèi)部設(shè)置這些屬性了。

寫一個(gè)類別去改寫屬性


小結(jié):

1著隆、盡量創(chuàng)建不可變的對(duì)象扰楼。

2、若某屬性僅可于對(duì)象內(nèi)部修改美浦,則在“class-continuation分類”中將其由readonly屬性擴(kuò)展為readwrite屬性弦赖。

3、不要把可變的collection作為屬性公開浦辨,而應(yīng)提供相關(guān)方法蹬竖,以此修改對(duì)象中的可變collection。


19 -> 使用清晰而協(xié)調(diào)的命名方法

主要了解命名規(guī)范流酬,OC的命名和其他語言比較起來案腺,OC方法寫起來比較長(zhǎng),但更像是一句通熟易懂的話康吵。

小結(jié):

1劈榨、起名時(shí)應(yīng)遵循標(biāo)準(zhǔn)的OC規(guī)范,這樣的接口更容易讓開發(fā)者讀懂

2晦嵌、方法名要言簡(jiǎn)意賅同辣,從左至右讀起來像日常語句一樣

3拷姿、方法名不要使用縮略后的類型名稱

4、必須保證方法名的風(fēng)格與自己的代碼或所集成的框架相符

20 -> 為私有方法名加前綴

- (void)publicMethod{

}

私有方法可以加前綴旱函,區(qū)分開來

-(void)p_privateMethod{

}

小結(jié):

1响巢、私有方法加上前綴,這樣很容將公共方法與其區(qū)分開來

2棒妨、不能使用單一的下劃線做私有方法的前綴踪古,這種做法是預(yù)留給蘋果公司的的


21 -> 理解OC的錯(cuò)誤模型

- (void)publicMethod{

???idsomerResource =/* ? */;

???if(/*check? for error*/) {

???????@throw[NSExceptionexceptionWithName:@"exceptionName"

??????????????????????????????????????reason:@"There was as error"

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

??? }

??? [somerResource doSomething];

??? [somerResourcerelease];

}

拋出異常的方式不要輕易使用,這樣會(huì)造成內(nèi)存泄漏券腔,只有發(fā)生了可使整個(gè)應(yīng)用程序崩潰的嚴(yán)重錯(cuò)誤時(shí),才應(yīng)使用異常

NSError

Error domain (錯(cuò)誤范圍伏穆,其類型為字符串)

Error code ?? (錯(cuò)誤代碼,其類型為整型)

User info ? ? ? ??????? (用戶信息纷纫,其類型為字典)

小結(jié):

1枕扫、只有發(fā)生了可使整個(gè)程序奔潰的嚴(yán)重錯(cuò)誤時(shí),才跑出異常

2辱魁、錯(cuò)誤不嚴(yán)重的時(shí)候可以指派“委托方法”(delegate)來處理錯(cuò)誤烟瞧,也可以把錯(cuò)誤信息放在NSError對(duì)象里,經(jīng)由“輸出參數(shù)”返回給調(diào)用者染簇。


22 -> 理解NSCoping協(xié)議

@interfacePerson :NSObject

@property(nonatomic,copy)NSString*name;

@property(nonatomic,copy)NSString*image;

@property(nonatomic,copy)NSString*total;

@property(nonatomic,copy)NSString*name_enabled;

-(id)copyWithZone:(NSZone*)zone

{

???Person*person = [self.classallocWithZone:zone];

??? person.name= [self.namecopyWithZone:zone];

??? person.image= [self.imagecopyWithZone:zone];

??? person.total= [self.totalcopyWithZone:zone];

??? person.name_enabled= [self.name_enabledcopyWithZone:zone];

???returnperson;

}

只有遵守的以上兩個(gè)協(xié)議参滴,才能進(jìn)行拷貝和對(duì)象序列化的保存

對(duì)象數(shù)組序列化:

- (void)writeDataWithArray:(NSArray*)array andName:(NSString*)name{

???NSData*boadData = [NSKeyedArchiverarchivedDataWithRootObject:array];

??? [[NSUserDefaultsstandardUserDefaults]setObject:boadDataforKey:name];

??? [[NSUserDefaultsstandardUserDefaults]synchronize];

}

- (NSArray*)getDataWithIdentifier:(NSString*)name

{

???NSData*boardData = [[NSUserDefaultsstandardUserDefaults]objectForKey:name];

???return[NSKeyedUnarchiverunarchiveObjectWithData:boardData];

}

小結(jié):

1、對(duì)象需要拷貝锻弓,必須遵守NSCopying協(xié)議

2卵洗、對(duì)象分為可變和不可變版本,必須同時(shí)遵守NSCopying MutableNSCopying協(xié)議

3弥咪、一般情況下執(zhí)行淺拷貝:淺拷貝是“影子”过蹂,深拷貝是“克隆人”;

4聚至、對(duì)象需要深拷貝酷勺,可以考慮新增一個(gè)專門執(zhí)行深拷貝的方法


23 -> 通過委托與數(shù)據(jù)協(xié)議進(jìn)行對(duì)象間通信

#import

@classJDNetworkingFetcher;

@protocolJDNetworkingFetcherDelegate

@required ?//代理必須要實(shí)現(xiàn)的方法?

- (void)netWorkFetcher:(JDNetworkingFetcher*)fetcher didReceiveData:(NSData*)data;

@optional?//可供代理按需實(shí)現(xiàn)此方法 ?也叫做“可選方法”

- (void)netWorkFetcher:(JDNetworkingFetcher*)fetcher didFailWithError:(NSError*)error;

@end

@interfaceJDNetworkingFetcher :NSObject

@property(nonatomic,weak)iddelegate;

@end

如果委托者執(zhí)行可選方法,那么必須提前使用類型信息查詢方法扳躬,判斷這個(gè)代理能否選用相關(guān)選擇子;這樣做的好處是可以避免脆诉,沒有實(shí)現(xiàn)相關(guān)法方法而導(dǎo)致程序的奔潰:

#import"JDNetworkingFetcher.h"

@implementationJDNetworkingFetcher

- (void)didRequestData:(NSData*)receiveData{

???NSData*data = receiveData;

???if([_delegaterespondsToSelector:@selector(netWorkFetcher:didReceiveData:)]) {

??????? [_delegatenetWorkFetcher:selfdidReceiveData:data];

??? }

}

BTW:

@optional

//新增很多可選的方法,會(huì)頻繁執(zhí)行以上方法贷币。那么除了第一次是有效的击胜,后面其實(shí)再執(zhí)行就多余了,因此可以把某個(gè)協(xié)議的信息緩存起來役纹,優(yōu)化效率

- (void)netWorkFetcher:(JDNetworkingFetcher*)fetcher didUpDataProgress:(float)progress;

.

.

.

這就比較頻繁了 偶摔,得優(yōu)化。促脉。辰斋。

@end

定義一個(gè)結(jié)構(gòu)體

@interfaceJDNetworkingFetcher :NSObject

{

???structdata{

???????unsignedintfieldA:8;//? 8個(gè)二進(jìn)制位?可以表示0到255之間

???????unsignedintfieldB:4;//? 4個(gè)二進(jìn)制位

???????unsignedintfieldC:2;//? 2個(gè)二進(jìn)制位

???????unsignedintfieldD:1;//? 1個(gè)二進(jìn)制位?可以表示0或者1兩個(gè)值

??? };

???//C語言的特性:"位段"數(shù)據(jù)類型

}

#import"JDNetworkingFetcher.h"

@interfaceJDNetworkingFetcher()

{

???struct{

???????unsignedintdidReceiveData ??? :1;

???????unsignedintdidFailWithError ? :1;

???????unsignedintdidUploadProgress? :1;

??? }delegateFlags;

}

@end

@implementationJDNetworkingFetcher

-(void)setDelegate:(id)delegate{

???_delegate= delegate;

???_delegateFlags.didReceiveData= [_delegaterespondsToSelector:@selector(netWorkFetcher:didReceiveData:)];

???_delegateFlags.didFailWithError= [_delegaterespondsToSelector:@selector(netWorkFetcher:didFailWithError:)];

???_delegateFlags.didUploadProgress= [_delegaterespondsToSelector:@selector(netWorkFetcher:didUploadProgress:)];

}

//緩存委托者是否能響應(yīng)選擇子策州,緩存功能的實(shí)現(xiàn)下載set方法內(nèi),這樣每次調(diào)用delegate相關(guān)方法宫仗,就直接查詢結(jié)構(gòu)體的的標(biāo)志位

?if(_delegateFlags.didReceiveData) {

??????? [_delegatenetWorkFetcher:selfdidReceiveData:receiveData];

??? }

小結(jié):

1够挂、委托模式為對(duì)象提供一套接口,使其能告知其他對(duì)象

2藕夫、委托者把接口定義成協(xié)議孽糖,在協(xié)議中把可能需要處理的事件定義成方法

3、有必要時(shí)毅贮,可實(shí)現(xiàn)含有段的結(jié)構(gòu)體办悟,將是否能相關(guān)協(xié)議方法這一信息緩存至其中


24 -> 將類的實(shí)現(xiàn)代碼分散到便于管理的數(shù)個(gè)分類中

通過分類機(jī)制,把類代碼分成很多個(gè)易于管理的小模塊嫩码,需要在類的.h文件中引入分類 的頭文件,可以按照不同的功能區(qū)分類別

#import

#import"JDSPerson+Play.h"

#import"JDSPerson+Work.h"

#import"JDSPerson+Friendship.h"

@interfaceJDSPerson :NSObject

@property(nonatomic,copy)NSString*firstName;

@property(nonatomic,copy)NSString*lastName;

- (instancetype)initWithWidth:(float)wigth;

@end

@interfaceJDSPerson (Friendship)

- (void)addFreind:(JDSPerson*)person;

@end

@interfaceJDSPerson (Work)

-(void)performDaysWork;

@end

@interfaceJDSPerson (Play)

- (void)goTosports;

@end

小結(jié):

1罪既、使用分類實(shí)現(xiàn)代碼劃分成易于管理的小塊

2铸题、將應(yīng)該視為“私有”的方法歸入名叫private的分類中,隱藏實(shí)現(xiàn)細(xì)節(jié)


25 -> 總是為第三方類的分類名稱加前綴

小結(jié):

1琢感、分類命名重復(fù)丢间,后者會(huì)覆蓋前者的實(shí)現(xiàn),特別是在用到第三方代碼的時(shí)候驹针,使用前綴可以減少此錯(cuò)誤出現(xiàn)的概率

2烘挫、向第三方類添加分類時(shí),總是要給其名稱和方法加上專用的前綴


26 -> 勿在分類中聲明屬性

#import"JDSPerson.h"

@interfaceJDSPerson (Friendship)

@property(nonatomic,strong)NSArray*freinds;

@end

在分類中添加屬性會(huì)報(bào)錯(cuò)誤??柬甥,可以聲明變量為?@dynamicfreinds饮六,編譯器不會(huì)抱錯(cuò)誤??;

用運(yùn)行時(shí)關(guān)聯(lián)對(duì)象解訣不能合成實(shí)例變量的問題:

#import"JDSPerson+Friendship.h"

#import

staticchar? *constkfreindsKey ="kFriendsKey";

@implementationJDSPerson (Friendship)

-(NSArray*)freinds{

???returnobjc_getAssociatedObject(self,kfreindsKey);

}

-(void)setFreinds:(NSArray*)freinds{

???objc_setAssociatedObject(self,

????????????????????????????kfreindsKey,

???????????????????????????? freinds,

????????????????????????????OBJC_ASSOCIATION_RETAIN_NONATOMIC);

}

@end

小結(jié):

1苛蒲、把分裝的數(shù)據(jù)所用到的全部屬性都定義在主接口里

2卤橄、在分類中盡量不要定義屬性


27 -> 使用分類隱藏實(shí)現(xiàn)細(xì)節(jié)

#import"JDSPerson+Play.h"

通過分類,定義實(shí)例變量和方法

@interfaceJDSPerson()

@property(nonatomic,strong)NSMutableDictionary*backingstore;

@property(nonatomic,readwrite,copy)NSString*addres;

@end

小結(jié)臂外;

1窟扑、通過分類向類中新增實(shí)例變量,需要遵守的協(xié)議也可卸乳分類中

2漏健、如果屬性在主接口聲明為只讀嚎货,類的內(nèi)部又要用設(shè)置方法修改此屬性,那么可以在.m中擴(kuò)展為可讀寫

3蔫浆、把私有方法原型聲明在分類里面


28 -> 通過協(xié)議提供匿名對(duì)象

?@property(nonatomic,weak)iddelegate;

由于該屬性是id殖属,所以任何類的對(duì)象都可以充當(dāng)這個(gè)屬性,即使該類不繼承自NSObject瓦盛,只要遵循lykDelegate協(xié)議就可以忱辅。如果有需要七蜘,可以在運(yùn)行期查出此對(duì)象所屬的類型。

NSDictionary:在字典中墙懂,鍵的標(biāo)準(zhǔn)內(nèi)存語義是“設(shè)置時(shí)拷貝”橡卤,值得內(nèi)存語義是“設(shè)置時(shí)保留”。因此损搬,可變版本中碧库,設(shè)置鍵值對(duì)所用的方法是

- (void)setObject:(ObjectType)anObject forKey:(KeyType )aKey;

表示鍵的那個(gè)參數(shù)可以是任意類型,只要遵從NSCopying協(xié)議就行巧勤,這樣嵌灰,就可以向?qū)ο蟀l(fā)送拷貝消息了。

小結(jié):

1.協(xié)議可以提供匿名類型颅悉。具體對(duì)象類型可以淡化為id類型沽瞭,協(xié)議里規(guī)定了對(duì)象應(yīng)該實(shí)現(xiàn)的具體方法。

?2.使用匿名對(duì)象來隱藏類型名稱剩瓶。

?3.類型不重要驹溃,重要的是對(duì)象能夠響應(yīng)的方法,這種情況可以用匿名對(duì)象來表示延曙。


29 -> 理解引用計(jì)數(shù)

Retain:遞增保留計(jì)數(shù)

release:遞減保留計(jì)數(shù)

autorelease:帶稍后清理“自動(dòng)釋放池”時(shí)豌鹤,再遞減保留計(jì)數(shù)。

1枝缔、屬性存儲(chǔ)方法中的內(nèi)存管理:

屬性為strong 或者retain 時(shí) ?會(huì)保留新值风瘦,釋放舊值漾峡,然后更新實(shí)例變量,令其指向新值

-(void)setFirstName:(NSString*)firstName{

??? [firstNameretain];

??? [_firstName realease];

?_firstName= firstName;

}

2、自動(dòng)釋放池:

調(diào)用release會(huì)立刻遞減該對(duì)象的保留計(jì)數(shù)呢诬,autorelease在稍后遞減計(jì)數(shù)伟恶,通常是在下一次“事件循環(huán)”時(shí)遞減晌梨。

autorelease能延長(zhǎng)對(duì)象生命周期垂攘,使其在跨越方法調(diào)用邊界后依然可以存活一段時(shí)間

3、保留環(huán):

相互引用多個(gè)對(duì)象赊舶,不能釋放睁搭,會(huì)產(chǎn)生內(nèi)存泄漏,通常采用弱引用解決笼平。

小結(jié):

1园骆、引用計(jì)數(shù)機(jī)制通過可以遞增遞減的計(jì)數(shù)器來管理內(nèi)存,對(duì)象創(chuàng)建好后寓调,計(jì)數(shù)至少為1锌唾,降為0時(shí)對(duì)象被銷毀

2、對(duì)象生命周期中,其余對(duì)象通過引用來保留或者釋放對(duì)象晌涕。


30 -> 以ARC簡(jiǎn)化引用計(jì)數(shù)

在應(yīng)用程序中滋捶,可用下列修飾符來改變局部變量與實(shí)例變量的語義:

__strong:默認(rèn)語義,保留此值余黎。

__unsafe_unretained:不保留此值重窟,這么做可能不安全,因?yàn)榈鹊皆俅问褂米兞繒r(shí)惧财,其對(duì)象可能已經(jīng)回收了巡扇。

__weak:不保留此值,但是變量可以安全使用垮衷,因?yàn)槿绻到y(tǒng)把這個(gè)對(duì)象回收了厅翔,那么變量也會(huì)自動(dòng)清空。

__autoreleasing:把對(duì)象“按引用傳遞”(pass by reference)給方法時(shí)搀突,使用這個(gè)特殊的修飾符刀闷。此值在方法返回時(shí)自動(dòng)釋放。

用了ARC之后仰迁,就不需要再編寫來釋放強(qiáng)引用的dealloc方法了甸昏。因?yàn)锳RC會(huì)借用Objective-C++的一項(xiàng)特性來生成清理例程(cleanup routine)⌒保回收Objective-C++對(duì)象時(shí)筒扒,待回收的對(duì)象會(huì)調(diào)用所有C++對(duì)象的析構(gòu)函數(shù)(destructor)怯邪。編譯器如果發(fā)現(xiàn)某個(gè)對(duì)象里含有C++對(duì)象绊寻,就會(huì)生成名為.cxx_destruce的方法。而ARC則借助此特性悬秉,在該方法中生成清理內(nèi)存所需代碼澄步。

不過如果有非Objective-C的對(duì)象,比如CoreFoundation中的對(duì)象或是由malloc()分配在堆中的內(nèi)存和泌,那么仍然需要清理村缸。然而不需要像原來馬羊調(diào)用超類的dealloc方法。ARC會(huì)自動(dòng)在.cxx_destruct方法中生成代碼并運(yùn)行此方法武氓,而在生成的代碼中會(huì)自動(dòng)調(diào)用超類的dealloc方法梯皿。ARC環(huán)境下,dealloc可以這樣寫:

- (void)dealloc

{

??? CFRelease(_coreFoundationObject);

??? free(_heapAllocatedMemeoryBlob);

}

小結(jié):

1县恕、有ARC后东羹,正確使用成員變量修飾符就好了,不用擔(dān)心內(nèi)存管理問題

2忠烛、ARC只負(fù)責(zé)Objective-C的內(nèi)存属提,?CoreFoundation對(duì)象不歸ARC管理,必須適時(shí)使用CFRetain/CFRelease


31 -> 在dealloc方法中只釋放引用并解除監(jiān)聽

?- (void)dealloc{

??? CFRelease(coreFoundationObject);

??? [[NSNotificationCenterdefaultCenter]removeObserver:self];

}

在dealloc中釋放掉所擁有的對(duì)象,dealloc中盡量不要寫其他的代碼冤议,因?yàn)槟承┣闆r下時(shí)不會(huì)執(zhí)行的dealloc的斟薇,例如:循環(huán)引用了對(duì)象,那這個(gè)類就不會(huì)執(zhí)行的dealloc

小結(jié):

1恕酸、在dealloc中堪滨,應(yīng)該釋放指向其他對(duì)象的引用,并取消原來訂閱的KVO 或著NSNotificationCenter通知尸疆,不要做其他事

2椿猎、如果對(duì)象持有文件描述符等系統(tǒng)資源,那么應(yīng)該專門寫一個(gè)方法來釋放資源寿弱,約定好:用完之后必須close掉

3犯眠、執(zhí)行異步的方法不能在dealloc中調(diào)用,因?yàn)榇藭r(shí)對(duì)象已處于正在回收的狀態(tài)了


32 -> 編寫“異常安全代碼”時(shí)留意內(nèi)存管理問題

?@try{

???????JDSPerson*newPerson = [JDSPersonnew];

??????? [newPersonsetAddres:@"sz"];

??? }@catch(NSException *exception) {

??? }@finally{

??? }

這樣的異常會(huì)出現(xiàn)內(nèi)存泄漏

在開啟ARC之后正常情況下一切和內(nèi)存有關(guān)的申請(qǐng)和釋放操作皆不用你關(guān)心了症革,ARC全全幫你包辦了筐咧。但是還有極少數(shù)的情況下,編譯器無法為你生成合適的ARC額外代碼噪矛,比如obj-c異常就是這么一個(gè)例子量蕊。

話句話說在ARC中異常可能會(huì)導(dǎo)致對(duì)象的內(nèi)存泄露艇挨。因?yàn)锳RC是顆敛信冢化對(duì)象為一個(gè)文件:即可以在obj-c文件上啟用ARC.所以我們可以選擇性的在編譯某個(gè)文件上加上-fobjc-arc-exceptions選項(xiàng),如果開啟了該選項(xiàng)缩滨,則ARC會(huì)額外為異常中的對(duì)象申請(qǐng)和釋放操作添加代碼势就,保證異常中ARC管理的對(duì)象也不會(huì)造成內(nèi)存泄露。當(dāng)然這樣一來缺點(diǎn)就是可能會(huì)生成大量平陈雎可能根本用不到的代碼苞冯。(只有發(fā)生異常才會(huì)執(zhí)行)

所以我們可以只在必要的obj-c文件上啟用-fobjc-arc-exceptions標(biāo)志,而其他文件禁用該標(biāo)志侧巨,這樣才可以做到萬無一失舅锄。

小結(jié):

1、捕獲異常司忱,一定要注意將try塊內(nèi)存所創(chuàng)立的對(duì)象清理干凈

2皇忿、默認(rèn)情況下,ARC不生成安全處理異常所需的清理代碼坦仍。開啟編譯器標(biāo)志后鳍烁,可生成這種代碼,不過會(huì)導(dǎo)致應(yīng)用程序變大桨踪,而且會(huì)降低運(yùn)行效率


33 -> 以弱引用避免保留環(huán)

#import<Foundation/Foundation.h>

@class JDSPerson;

@class JDSEmployer;

@interface JDSPerson :NSObject

@property(nonatomic,strong)JDSEmployer*other;

@end

@interface JDSEmployer :NSObject

@property(nonatomic,unsafe_unretained)JDSPerson*other;

@end

JDSEmployer不擁有JDSPerson老翘,避免了循環(huán)引用;?unsafe_unretained跟?assign特質(zhì)等價(jià),assign通常用于“整體類型”(int铺峭、float結(jié)構(gòu)體等)墓怀,unsafe_unretained多用于對(duì)象類型

小結(jié):

1、將某些引用設(shè)為weak卫键,可避免出現(xiàn)“保留環(huán)”

2傀履、weak引用可以自動(dòng)清空,也可以不自動(dòng)清空莉炉。自動(dòng)清空是隨著ARC引入的新特性钓账,由運(yùn)行期系統(tǒng)來實(shí)現(xiàn)。在具備自動(dòng)清空的功能等弱引用上絮宁,可以隨意讀去其數(shù)據(jù)梆暮,因?yàn)檫@種引用不會(huì)指向已經(jīng)會(huì)收過 的對(duì)象


34 -> 以“自動(dòng)釋放池塊”降低內(nèi)存峰值

? ? NSMutableArray *peoples= [[NSMutableArrayalloc]init];

? ? ?for(inti=0; i

???????@autoreleasepool{

???????????JDSPerson*person = [JDSPersonnew];

??????????? [peoples addObject:person];

??????? }

??? }

循環(huán)時(shí),降低內(nèi)存峰值绍昂,不會(huì)在執(zhí)行循環(huán)的時(shí)候啦粹,內(nèi)存暴漲。

小結(jié):

1窘游、自動(dòng)釋放池排布在棧中唠椭,對(duì)象受到autoreleasepool ?消息后,系統(tǒng)將其放入最頂端端池里忍饰;

2贪嫂、合理運(yùn)用自動(dòng)釋放池,可降低內(nèi)存峰值艾蓝;@autoreleasepool可以創(chuàng)建更輕便的自動(dòng)釋放池


35 -> 用“僵尸對(duì)象”調(diào)試內(nèi)存管理問題

設(shè)置 zombie objects 力崇,遇到僵尸對(duì)象會(huì)拋出異常,控制臺(tái)會(huì)打印改對(duì)象

小結(jié):

1饶深、系統(tǒng)在回收對(duì)象時(shí)餐曹,可以不將其真大回收逛拱,而是把它轉(zhuǎn)化為僵尸對(duì)象敌厘,通過環(huán)境變量 NSZombieEnable可以開啟此功能

2、系統(tǒng)會(huì)修改對(duì)象的isa指針朽合,令其指向特殊的僵尸類俱两,使其變?yōu)榻┦瑢?duì)象。僵尸類能響應(yīng)所有的選擇子曹步,響應(yīng)方式為:打印一條包含消息內(nèi)容及其接受者的消息宪彩,然后終止程序。


37 -> 理解“塊”這一概念

塊與函數(shù)類似讲婚,只不過是定義在另一個(gè)函數(shù)里的尿孔,和定義它的那個(gè)函數(shù)共享同一范圍類的東西

塊的基本用法:

???__blockintresult;

???void(^block)(int,int) = ^(inta,intb){

??????? result = a+b;

??? };


block(6,9);

全局塊、棧塊、堆塊:

定義塊的時(shí)候活合,內(nèi)存區(qū)域是分布在棧中的雏婶,塊只在定義的范圍內(nèi)有效,下面的寫法白指,等離開了相應(yīng)的范圍之后留晚,編譯器可能會(huì)把分配給塊的內(nèi)存覆寫掉,if 和 else 只有一個(gè)是有效的告嘲,可能會(huì)導(dǎo)致奔潰

?void(^block)();

???if(arr.count>8) {

??????? block = ^{

???????????NSLog(@"blockA");

??????? };

??? }else{

??????? block = ^{

????????????NSLog(@"blockA");

??????? };

??? }

??? block();

解決方法:copy到堆區(qū)

void(^block)();

???if(arr.count>8) {

??????? block = [^{

???????????NSLog(@"blockA");

??????? }copy];

??? }else{

??????? block = [^{

????????????NSLog(@"blockA");

??????? }copy];

??? }

??? block();

全局block:不會(huì)被系統(tǒng)所回收错维,實(shí)際相當(dāng)于單利

?void(^block)() = ^{

???????NSLog(@"global block");

??? };

小結(jié):

1、塊是C橄唬、C++赋焕、Objective-C 中的詞法閉包。

2仰楚、塊可接受參數(shù)宏邮,也可返回值

3、塊可分配在椄籽或者堆上蜜氨,也可以是全局的。分配在棧上的塊可以拷貝到堆里捎泻,這樣就和標(biāo)準(zhǔn)的Objective-C對(duì)象一樣飒炎,具備引用計(jì)數(shù)了。


38 -> 為常用的塊類型創(chuàng)建typedef

typedef int(^Completion)(BOOL,NSError*);

- (void)requestwithCompletionHandle:(void(^)(BOOL,NSError*))completion;

- (void)requestwithCompletionHandle:(Completion)completion;

使用typedef“類型定義”關(guān)鍵字給塊定義一個(gè)易讀懂名字笆豁,使代碼讀起來更順暢郎汪;

塊中參數(shù)需要更改時(shí)也可以先修改“類型定義”內(nèi)的參數(shù),后面再編譯就會(huì)報(bào)錯(cuò)闯狱,這樣就可以根據(jù)報(bào)錯(cuò)一個(gè)不差的全部修改到位

typedefvoid(^ACAccountStoreSaveCompletionHandler)(BOOLsuccess,NSError*error);

typedefvoid(^ACAccountStoreRequsetAccessCompletionHandler)(BOOLsuccess,NSError*error);

使用同一個(gè)簽名

小結(jié):

1煞赢、用typedef重新定義塊類型,可讓塊變量用起來更加簡(jiǎn)單

2哄孤、定義新類型時(shí)應(yīng)遵從現(xiàn)有的命名習(xí)慣照筑,勿使其名稱與別的類型相沖突

3、可以為同一個(gè)塊簽名定義多個(gè)類型別名瘦陈,如果重構(gòu)代碼凝危,使用了塊類型的某個(gè)別名,只需要修改相應(yīng)typedef中的塊簽名即可


39 - >用handle塊降低代碼分散程度

//成功和失敗一起處理:

缺點(diǎn): ?? 全部邏輯寫在了一起晨逝,塊的代碼會(huì)很長(zhǎng)且比較復(fù)雜

優(yōu)點(diǎn):1蛾默、數(shù)據(jù)請(qǐng)求斷開時(shí),可以處理請(qǐng)求到的數(shù)據(jù)捉貌,可以根據(jù)數(shù)據(jù)判斷問題做適當(dāng)處理支鸡。

? ? ? ? ? ?2冬念、調(diào)用API的代碼可能會(huì)在處理成功響應(yīng)的過程中發(fā)現(xiàn)錯(cuò)誤,例如數(shù)據(jù)太短牧挣、某些數(shù)據(jù)為空

JDNetworkingFetcher*fetcher = [[JDNetworkingFetcheralloc]initWithURL:url];

??? [fetcherstartWithCompletionHandler:^(NSData*data,NSError*error) {

???????if(error) {

???????????//handle faile

??????? }else{

???????????//handle success

??????? }

??? }];

//成功和失敗分別處理:代碼更容易讀懂刘急,可以把處理失敗或成功所用的代碼省略?

JDNetworkingFetcher*fetcher = [[JDNetworkingFetcheralloc]initWithURL:url];

??? [fetcherstartWithCompletionHandlerSuucess:^(NSData*data) {

???????//handle success

??? }failureHandle:^(NSError*error) {

???????//handle failure

??? }];

小結(jié):

1、在創(chuàng)建對(duì)象時(shí)浸踩,可以使用內(nèi)聯(lián)的handle塊將相關(guān)業(yè)務(wù)邏輯一并申明

2叔汁、在有很多個(gè)實(shí)例需要監(jiān)控時(shí),如果采用委托模式检碗,那么經(jīng)常需要根據(jù)傳入的對(duì)象來切換据块,而若改用handle塊來實(shí)現(xiàn),可直接將快與相關(guān)對(duì)象放在一起

3折剃、設(shè)計(jì)API用到塊時(shí)另假,可以增加一個(gè)參數(shù),使調(diào)用者可以通過此參數(shù)來決定應(yīng)該把塊安排在哪個(gè)隊(duì)列上執(zhí)行(自己貌似很少用到)怕犁。


40 - >用塊引用其所屬對(duì)象時(shí)不要出現(xiàn)保留環(huán)

?JDNetworkingFetcher*fetcher = [[JDNetworkingFetcheralloc]initWithURL:url];

??? [fetcherstartWithCompletionHandlerSuucess:^(NSData*data) {

?//handle success

???????///!!!:保留環(huán)

?self.data= data;

???????//FIXME:解決保留環(huán)

???????self.fetcher=nil;

??? }failureHandle:^(NSError*error) {

???????//handle failure

??? }];

另一種保留環(huán)边篮,下載完畢,保留環(huán)接觸奏甫,下載對(duì)象也會(huì)被回收

?JDNetworkingFetcher*fetcher = [[JDNetworkingFetcheralloc]initWithURL:url];

??? [fetcherstartWithCompletionHandlerSuucess:^(NSData*data) {

???????//handle success

???????///!!!:保留環(huán)

???????self.data= data;

??? }failureHandle:^(NSError*error) {

???????//handle failure

??? }];

- (void)p_requsetCompetion{

???if(self.completionHandle) {

???????self.completionHandle(self.downloadData);

??? }

???self.completionHandle=nil;

}

小結(jié):

1戈轿、如果塊所捕獲的對(duì)象直接或間接地保留了塊本身,那么就得當(dāng)心保留環(huán)問題

2阵子、一定要找個(gè)適當(dāng)?shù)臅r(shí)機(jī)解除保留環(huán)思杯,而不能把責(zé)任推給API的調(diào)用者。


41 -> 多用派發(fā)隊(duì)列少用同步鎖

加同步鎖挠进,確保線程安全

?-(dispatch_queue_t)syncQueue{

???if(!_syncQueue) {

???????_syncQueue=dispatch_queue_create("com.effectiveobjectivec.sycnQueue",NULL);

??? }

???return_syncQueue;

}

-(NSString*)firstName{

???__blockNSString*firstName;

???dispatch_sync(self.syncQueue, ^{

??????? firstName =_firstName;

??? });

???returnfirstName;

}

-(void)setFirstName:(NSString*)firstName{

???dispatch_sync(self.syncQueue, ^{

??????? _firstName = firstName;

??? });

}

并發(fā)隊(duì)列色乾,但是這樣是隨意執(zhí)行的,沒有順序

-(dispatch_queue_t)syncQueue{

???if(!_syncQueue) {

???????_syncQueue=dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);

??? }

???return_syncQueue;

}

-(NSString*)firstName{

???__blockNSString*firstName;

???dispatch_sync(self.syncQueue, ^{

??????? firstName =_firstName;

??? });

???returnfirstName;

}

-(void)setFirstName:(NSString*)firstName{

???dispatch_async(self.syncQueue, ^{

??????? _firstName = firstName;

??? });

}

可通過棧欄解決順序執(zhí)行的問題dispatch_barrier_async :并發(fā)隊(duì)列如果發(fā)現(xiàn)接下來要處理的塊是個(gè)棧欄塊领突,那么就要一直等到當(dāng)前所有并發(fā)塊都執(zhí)行完畢暖璧,才會(huì)單獨(dú)執(zhí)行這個(gè)棧欄塊。待棧欄塊執(zhí)行過后君旦,再按正常方式繼續(xù)向下處理澎办。

-(void)setFirstName:(NSString*)firstName{

???dispatch_barrier_async(self.syncQueue, ^{

??????? _firstName = firstName;

??? });

}

小結(jié):

1、派發(fā)隊(duì)列可用來表述同步語義于宙,這種做法比?@synchronized 塊和NSLock?對(duì)象更簡(jiǎn)單

2浮驳、將同步和異步派發(fā)結(jié)合起來悍汛,可以實(shí)現(xiàn)與普通加鎖機(jī)制一樣的同步行為捞魁,這么做不會(huì)阻塞執(zhí)行異步派發(fā)的線程

3、使用同步隊(duì)列及棧欄塊离咐,可以令同步行為更加高效


42 -> 多用GCD谱俭,少用performSelect 系列方法

?SELselector;

?if(/*?some condition*/) {

??????? selector =@selector(greenBtn:);

??? }else{

??????? selector =@selector(yellowBtn:);

??? }

編程靈活奉件,可用來簡(jiǎn)化代碼,編譯器必須到了運(yùn)行期才能確定選擇子是哪個(gè)

?SELselector;

???if(/* DISABLES CODE */(1)) {

??????? selector =@selector(newObject:);

??? }else{

??????? selector =@selector(copy);

??? }

?idret = [object performSelector:selector];

存在內(nèi)存泄漏昆著,這段代碼即使在ARC環(huán)境下編譯器也不會(huì)主動(dòng)去釋放它

小結(jié):

1县貌、?performSelector 系列方法內(nèi)存管理方面容易疏失,它無法確定將要執(zhí)行的選擇子具體是什么凑懂,ARC編譯器也就無法插入適當(dāng)?shù)膬?nèi)存管理方法

2煤痕、performSelector ?系列方法所能處理的選擇子太過局限,選擇子的返回值類型及發(fā)送給方法的參數(shù)個(gè)數(shù)都受限制

3接谨、如果想把任務(wù)放到另一個(gè)線程上摆碉,最好用GCD不要用這個(gè)。


43 -> 掌握GCD及操作隊(duì)列的使用時(shí)機(jī)

GCD是純C的API脓豪,而操作隊(duì)列是Objective-C的對(duì)象

GCD中巷帝,任務(wù)用塊來表示,而塊是個(gè)輕量級(jí)數(shù)據(jù)結(jié)構(gòu)扫夜;“操作”則是個(gè)更為重量級(jí)的Objective-C的對(duì)象楞泼;

NSBlockOperation;

[queue addOperationWithBlock:^{

?}];

這兩個(gè)結(jié)合起來使用,與GCD類似笤闯;

使用“操作”的好處:

1堕阔、取消某個(gè)操作:

GCD無法無法取消;不過颗味,已經(jīng)執(zhí)行的任務(wù)就無法取消了印蔬。

NSInvocationOperation*operation = [[NSInvocationOperationalloc]initWithTarget:selfselector:@selector(yellowBtn:)object:nil];

???NSOperationQueue*queue = [[NSOperationQueuealloc]init];

??? [queueaddOperation:operation];

[operation?cancel];

2、指定操作間的依賴關(guān)系:

一個(gè)操作可以依賴多個(gè)操作脱衙,必須在其他依賴的操作結(jié)束以后才能執(zhí)行該操作

??NSInvocationOperation*operation3 = [[NSInvocationOperationalloc]initWithTarget:selfselector:@selector(log)object:nil];

??? [operation3addDependency:operation1];

??? [operation3addDependency:operation2];

[queue?addOperation:operation3];

注意:不能循環(huán)依賴(不能A依賴于B侥猬,B又依賴于A)

3、通過鍵值觀察機(jī)制監(jiān)控?NSOperation對(duì)象的屬性 :

1)捐韩、NSOperation?很多屬性都可以通過KVO監(jiān)聽:可以通過?isCancelled屬性來判斷人物是否已經(jīng)取消退唠,通過?isFinished屬性判斷任務(wù)是否完成.

2)、?可以監(jiān)聽一個(gè)操作的執(zhí)行完畢

//創(chuàng)建對(duì)象荤胁,封裝操作

???NSBlockOperation*operation=[NSBlockOperationblockOperationWithBlock:^{

???????for(inti=0; i<10; i++) {

???????????NSLog(@"-operation-下載圖片-%@",[NSThread currentThread]);

??????? }

??? }];

???//監(jiān)聽操作的執(zhí)行完畢

??? operation.completionBlock=^{

???????//.....下載圖片后繼續(xù)進(jìn)行的操作

???????NSLog(@"--接著下載第二張圖片--");

??? };

???//創(chuàng)建隊(duì)列

???NSOperationQueue*queue=[[NSOperationQueue alloc]init];

???//把任務(wù)添加到隊(duì)列中(自動(dòng)執(zhí)行瞧预,自動(dòng)開線程)

[queue?addOperation:operation];

?說明:在上一個(gè)任務(wù)執(zhí)行完后,會(huì)執(zhí)行operation.completionBlock=^{}代碼段仅政,且是在當(dāng)前線程執(zhí)行(2)垢油。

4、并發(fā)數(shù)

(1)并發(fā)數(shù):同時(shí)執(zhí)?行的任務(wù)數(shù).比如,同時(shí)開3個(gè)線程執(zhí)行3個(gè)任務(wù),并發(fā)數(shù)就是3

(2)最大并發(fā)數(shù):同一時(shí)間最多只能執(zhí)行的任務(wù)的個(gè)數(shù)圆丹。

(3)最?并發(fā)數(shù)的相關(guān)?方法

- (NSInteger)maxConcurrentOperationCount;

- (void)setMaxConcurrentOperationCount:(NSInteger)cnt;?

說明:如果沒有設(shè)置最大并發(fā)數(shù)滩愁,那么并發(fā)的個(gè)數(shù)是由系統(tǒng)內(nèi)存和CPU決定的,可能內(nèi)存多久開多一點(diǎn)辫封,內(nèi)存少就開少一點(diǎn)硝枉。

注意:num的值并不代表線程的個(gè)數(shù)廉丽,僅僅代表線程的ID。

提示:最大并發(fā)數(shù)不要亂寫(5以內(nèi))妻味,不要開太多正压,一般以2~3為宜,因?yàn)殡m然任務(wù)是在子線程進(jìn)行處理的责球,但是cpu處理這些過多的子線程可能會(huì)影響UI焦履,讓UI變卡。

5雏逾、指定操作的優(yōu)先級(jí):

1)設(shè)置NSOperation在queue中的優(yōu)先級(jí),可以改變操作的執(zhí)?優(yōu)先級(jí)

- (NSOperationQueuePriority)queuePriority;

- (void)setQueuePriority:(NSOperationQueuePriority)p;

?(2)優(yōu)先級(jí)的取值

typedefNS_ENUM(NSInteger, NSOperationQueuePriority) {

NSOperationQueuePriorityVeryLow = -8L,

NSOperationQueuePriorityLow = -4L,

NSOperationQueuePriorityNormal =0,

NSOperationQueuePriorityHigh =4,

NSOperationQueuePriorityVeryHigh =8

};

說明:優(yōu)先級(jí)高的任務(wù)裁良,調(diào)用的幾率會(huì)更大。

小結(jié):

1校套、在解決多線程任務(wù)管理問題時(shí)价脾,GCD并非唯一方案

2、操作隊(duì)列提供了一套高層的Objective-C API 笛匙,能實(shí)現(xiàn)純GCD所具備的絕大部分功能侨把,而且還能完成一些復(fù)雜的操作,這些操作如果該用GCD來實(shí)現(xiàn)妹孙,則需要另外編寫代碼


44 -> 通過?Dispatch Group 機(jī)制秋柄,根據(jù)系統(tǒng)資源狀況來執(zhí)行任務(wù)

任務(wù)編組的兩組方式:

1》dispatch_group_async(dispatch_group_t?_Nonnullgroup, dispatch_queue_t?_Nonnullqueue, ^{? ? ?

})

?2》dispatch_group_enter(dispatch_group_t?_Nonnullgroup)

dispatch_group_leave(dispatch_group_t?_Nonnullgroup)

等待dispatch group 執(zhí)行完畢的兩種方式:

1》dispatch_group_wait(dispatch_group_t?_Nonnullgroup, dispatch_time_t timeout)

2》dispatch_group_notify(dispatch_group_t?_Nonnullgroup, dispatch_queue_t?_Nonnullqueue, ^{

?? })

第二種方式:開發(fā)者可以向函數(shù)傳入塊,等dispatch group執(zhí)行完畢之后蠢正,塊會(huì)在特定的線程上執(zhí)行骇笔;假如當(dāng)前線程不應(yīng)阻塞,而開發(fā)者又想在那些任務(wù)執(zhí)行完畢時(shí)得到通知嚣崭,那么久很有必要使用第二種方式了

遍歷某個(gè)集合

dispatch_apply(10,dispatch_queue_create("com.jds.queue",NULL), ^(size_ti) {

?????? [selflog];

?});

for(inti=0; i<10; i++) {

??????? [selflog];

??? }

也可以迸發(fā)執(zhí)行笨触,但是要考慮瑣死的問題

dispatch_queue_tque =dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);

???dispatch_apply(10, que, ^(size_ti) {

??????? [selflog];

?});

小結(jié):

1、一些列任務(wù)可歸入一個(gè)?dispatch group 中雹舀,開發(fā)者可在這組任務(wù)執(zhí)行完時(shí)獲得通知

2芦劣、通過dispatch group,可以在并發(fā)式派發(fā)隊(duì)列里同時(shí)執(zhí)行多項(xiàng)任務(wù)说榆。此時(shí)GCD會(huì)根據(jù)系統(tǒng)資源狀況來調(diào)度這些并發(fā)執(zhí)行的任務(wù)虚吟。


45 -> 使用dispatch_once來執(zhí)行只需要運(yùn)行一次的代碼

單利可以這樣寫,可以簡(jiǎn)化代碼并且保證線程的安全签财,無需擔(dān)心同步和加鎖

+(instancetype)shareInstance{

???staticJDSEmployer*shareInstance =nil;

???staticdispatch_once_tonceToken;

???dispatch_once(&onceToken, ^{

??????? shareInstance = [[JDSEmployeralloc]init];

??? });

???returnshareInstance;

}

小結(jié):

1串慰、一次性安全代碼,通過GCD?dispatch_once很容易實(shí)現(xiàn)此功能

2唱蒸、標(biāo)記應(yīng)該申明在 static 個(gè) global作用域中邦鲫,這樣的話,在把只需要執(zhí)行一次的塊傳給?dispatch_once函數(shù)的時(shí)候油宜。穿進(jìn)去的標(biāo)記也是相同


46 -> 不要使用dispatch_get_current_queue?(注意: ?iOS 6.0 已經(jīng)棄用這個(gè)方法了)

小結(jié):

1掂碱、此方法已經(jīng)廢棄怜姿,僅可用于調(diào)試

2慎冤、由于派發(fā)隊(duì)列是按層級(jí)來組織的疼燥,所以無法單用某個(gè)隊(duì)列對(duì)象來描述“當(dāng)前隊(duì)列”這一概念

47 -> 熟悉系統(tǒng)框架

Objective-C的重要特點(diǎn)是:經(jīng)常使用底層的C語言API

在編寫新的工具類之前,最好在系統(tǒng)框架里搜索一下蚁堤,通常都有寫好的類可供直接使用

1》CFNetwork ?此框架提供了C語言級(jí)別的網(wǎng)絡(luò)通信醉者,它將“BSD 套接字” (BSD soket)抽象成易于使用的網(wǎng)絡(luò)接口

2》CoreAudio ?此框架提供的C語言API可用來操作設(shè)備上的音頻硬件,比較難用

3》AVFundation 提供 Objective-C 對(duì)象可用來回放并錄制音頻及視頻

4》CoreData 提供的接口可將對(duì)象放入數(shù)據(jù)庫披诗,便于持久保存

5》CoreText ?此框架提供的C語言接口可以高效的執(zhí)行文字排版及渲染

小結(jié):

1撬即、許多系統(tǒng)框架都可以直接使用,F(xiàn)undation 和 CoreFundation 提供了核心的功能

2呈队、很多常見任務(wù)都用框架來做剥槐,比如:音頻與視頻的處理、網(wǎng)絡(luò)通信宪摧、數(shù)據(jù)管理

3粒竖、請(qǐng)記住:用純C寫成的框架與用 Objective-C 寫成的一樣重要几于,若想成為優(yōu)秀的 Objective-C開發(fā)者蕊苗,應(yīng)該掌握C語言的核心概念


48 -> 多用枚舉塊,少用 for 循環(huán)

下面用枚舉塊的寫法比較for的寫法讀起來更順暢

數(shù)組:

NSArray*enumArray =@[@"1",@"2",@"33",@"23"];

???NSEnumerator*enumerator = [enumArrayobjectEnumerator];

???idobj;

???while((obj =[enumeratornextObject])!=nil) {

???????NSLog(@"%@",obj);

??? }

字典:

?NSDictionary*enumDic =@{@"11":@"11",@"22":@"22",@"33":@"33",@"44":@"44"};

???NSEnumerator*enumeratorDic = [enumDickeyEnumerator];

???idobjDic ;

???while((objDic = [enumeratorDicnextObject]) !=nil) {

???????NSLog(@"%@",objDic);

??? }

集合:

NSSet*set =? [NSSetsetWithArray:enumArray];

???NSEnumerator*enumeratorSet = [setobjectEnumerator];

???idobjSet;

???while((objSet =[enumeratorSetnextObject])!=nil) {

???????NSLog(@"%@",obj);

??? }

快速查詢:

?for(id obj in enumArray) {

???????NSLog(@"%@",obj);

??? }

語法最簡(jiǎn)單效率最高的遍歷方法:

for(id obj in [enumArray reverseObjectEnumerator]) {

?NSLog(@"%@",obj);

??? }

基于塊的遍歷方式:遍歷時(shí)可以直接從塊里獲取更多信息沿彭;

數(shù)組:

?[enumArray enumerateObjectsUsingBlock:^(id?_Nonnullobj, NSUInteger idx,BOOL*_Nonnullstop) {

???????/*do something*/

???????if(shouldStop) {

??????????? stop =YES;

??????? }

??? }];

字典:

[enumDicenumerateKeysAndObjectsUsingBlock:^(id?_Nonnullkey,id?_Nonnullobj,BOOL*_Nonnullstop) {

???????/*do something*/

???????if(shouldStop) {

??????????? *stop=YES;

??????? }

??? }];

小結(jié):

1朽砰、遍歷集合的有四種方式:for 、枚舉喉刘、 快速瞧柔、塊最基本的方法是for,其次是NSEnumerator非遍歷法及快速遍歷睦裳,最新非剃、最先進(jìn)的方法是“塊枚舉法”

2、“塊枚舉法”本身就能通過GCD來并發(fā)執(zhí)行遍歷操作推沸,無需另行編寫代碼备绽,而采用其他遍歷方式無法輕易實(shí)現(xiàn)這一點(diǎn)

3、如提前知道待遍歷的集合含有何種對(duì)象鬓催,則應(yīng)修改塊簽名肺素,指出對(duì)象的具體類型


49 ?-> 對(duì)自定義其內(nèi)存管理語義的collection 使用無縫橋接

使用C語言框架API需要橋接一下

?NSArray*aNSArray =@[@1,@2,@3];

?CFArrayRefaCFArrayRef = (__bridgeCFArrayRef)(aNSArray);

?aNSArray = (__bridgeNSArray*)(aCFArrayRef);

__bridge:告訴ARC如果處理轉(zhuǎn)換所涉及的Objective-C對(duì)象,ARC仍然具備這個(gè)Objective-C對(duì)象的所有權(quán)

__bridge_retained:與__bridge相反,意味著ARC交出對(duì)象的所有權(quán)宇驾,用完記得釋放

__bridge_transfer:想把CFArrayRef非轉(zhuǎn)換為NSArray*法倍靡,并且想令A(yù)RC獲得所有權(quán),就可以采取這種方式

這三種方式被稱為橋式轉(zhuǎn)換课舍。

#import <CoreFoundation/CoreFoundation.h>

constvoid*JSRetainCallBack(CFAllocatorRefallcator,constvoid*value){

???returnCFRetain(value);

}

voidJSReleaseCallBack(CFAllocatorRefallcator,constvoid*value){

???returnCFRelease(value);

}

//CFIndex version;

//CFDictionaryRetainCallBack retain;

//CFDictionaryReleaseCallBack release;

//CFDictionaryCopyDescriptionCallBack copyDescription;

//CFDictionaryEqualCallBack equal;

//CFDictionaryHashCallBack hash;

CFDictionaryKeyCallBackskeyCallBacks = {

???0,

???JSRetainCallBack,

???JSReleaseCallBack,

???NULL,//null表示默認(rèn)

???CFEqual,

???CFHash

};

CFDictionaryValueCallBacksvaluecallBacks = {

???0,

???JSRetainCallBack,

???JSReleaseCallBack,

???NULL,

???CFEqual,

};

-(void)p_privateMethod{

???CFMutableDictionaryRefaCFDictionary =CFDictionaryCreateMutable(NULL,0, &keyCallBacks, &valuecallBacks);

???NSMutableDictionary*aNSDictionary = (__bridgeNSMutableDictionary*)(aCFDictionary);

}

小結(jié):

1塌西、通過無縫橋接技術(shù)他挎,可以在Foudation框架中的Objective-C對(duì)象雨CoreFoundation框架中的C語言數(shù)據(jù)結(jié)構(gòu)之間來回轉(zhuǎn)換

2、在CoreFoundation層面創(chuàng)建collection時(shí)捡需,可以指定許多回調(diào)函數(shù)办桨,這些函數(shù)表示此collection應(yīng)如何處理其元素,然后站辉,可用無縫橋接技術(shù)呢撞,將其轉(zhuǎn)換成具備特殊內(nèi)存管理語義的Objective-C。


50 -> 構(gòu)建緩存時(shí)選用NSCache而非NSDictionary

{

??? NSCache *_cache;

}

- (id)initWithURL:(NSURL*)url{

???if([superinit]) {

???????_url= url;

???????_cache= [NSCachenew];

???????_cache.countLimit? =100;//最大儲(chǔ)存數(shù)

???????_cache.totalCostLimit=5*1024*1024;//不能超過的數(shù)據(jù)大小5MB

??? }

???returnself;

}

- (void)downloadDataForUrl:(NSURL*)url{

???NSData*cacheData = [_cacheobjectForKey:url];

???if(cacheData) {

???????NSLog(@"緩存過的了直接使用緩存數(shù)據(jù)");

??? }else{

???????NSLog(@"沒有緩存需要下載");

??????? [selfstartWithCompletionHandler:^(NSData*data,NSError*error) {

??????????? [_cachesetObject:dataforKey:urlcost:data.length];

???????????NSLog(@"保存并且使用數(shù)據(jù)");

??????? }];

??? }

}

使用NSPurgeableData系統(tǒng)資源緊張時(shí)會(huì)把保存的緩存對(duì)象內(nèi)存丟棄

- (void)downloadDataForUrl:(NSURL*)url{

???NSPurgeableData*cacheData = [_cacheobjectForKey:url];

???if(cacheData) {

??????? [cacheDatabeginContentAccess];//停止正在清除該data的操作

???????NSLog(@"緩存過的了直接使用緩存數(shù)據(jù)");

??????? [selfuseData:cacheData];

??????? [cacheDataendContentAccess];

??? }else{

???????NSLog(@"沒有緩存需要下載");

??????? [selfstartWithCompletionHandler:^(NSData*data,NSError*error) {

???????????//??創(chuàng)建好的NSPurgeableData對(duì)象后饰剥,其“引用計(jì)數(shù)”會(huì)多1殊霞,所以無需再調(diào)用beginContentAccess,但是必須要調(diào)用endContentAccess抵消這個(gè)”1“

???????????NSPurgeableData*purgeableData = [NSPurgeableDatadataWithData:data];

??????????? [_cachesetObject:purgeableDataforKey:urlcost:data.length];

???????????NSLog(@"保存并且使用數(shù)據(jù)");

??????????? [selfuseData:data];

??????????? [purgeableDataendContentAccess];

??????? }];

??? }

}

小結(jié):

1汰蓉、實(shí)現(xiàn)緩存時(shí)喲哦難過NSCache因?yàn)樗蔷€程安全的

2绷蹲、NSCache對(duì)象設(shè)置上限,不是“硬限制”顾孽,只是起指導(dǎo)作用

3祝钢、NSPurgeableData與NSCache搭配使用,可實(shí)現(xiàn)自動(dòng)清除功能岩齿,被丟棄時(shí)太颤,該對(duì)象自身也會(huì)從緩存清除

4、一般盹沈,從網(wǎng)絡(luò)獲取或者磁盤獲取的數(shù)據(jù)草緩存龄章,會(huì)提高程序效率


51 -> 精簡(jiǎn) initialize 與 load的實(shí)現(xiàn)代碼

+ (void)load :程序啟動(dòng)的時(shí)候分類或者類,會(huì)執(zhí)行此方法

+(void)initialize :它是惰性調(diào)用的乞封,程序首次用該類之前調(diào)用做裙,且只調(diào)用一次

小結(jié):

1、在加載階段肃晚,如果類實(shí)現(xiàn)了load方法锚贱,那么系統(tǒng)就會(huì)調(diào)用它跛梗;類的load方法要比分類先調(diào)用臣樱,且不參與覆寫機(jī)制

2、首次使用某個(gè)類之前啄清,系統(tǒng)會(huì)向其發(fā)送initialize消息晋修。因?yàn)樵摲椒ㄗ駨母矊懸?guī)則吧碾,所以通常應(yīng)該在里面判斷當(dāng)前初始化的是哪個(gè)類

3、initialize 與 load盡量精簡(jiǎn)和避免使用

4墓卦、無法在編譯器設(shè)定的全局變量倦春,可以放在initialize方法里初始化

52 -> 別忘了NSTimer 會(huì)保留其目標(biāo)對(duì)象

這段代碼采用了一種很有效的寫法,它先定義了一個(gè)弱引用,令其指向 self睁本,然后使塊捕獲這個(gè)引用尿庐,而不直接去捕獲普通的 self 變量,也就是說呢堰,self 不會(huì)為計(jì)時(shí)器所保留抄瑟。當(dāng)塊開始執(zhí)行時(shí),立刻生成 strong 引用暮胧,以保證實(shí)例在執(zhí)行期間持續(xù)存活锐借。

?@interface NSTimer (JSBlockSupport)

+ (NSTimer*)js_scheduledTimerWithTimeInterval:(NSTimeInterval)interval block:(void(^)())block repeats:(BOOL)repeats;

@end

@implementationNSTimer (JSBlockSupport)

+ (NSTimer*)js_scheduledTimerWithTimeInterval:(NSTimeInterval)interval block:(void(^)())block repeats:(BOOL)repeats {

?return[selfscheduledTimerWithTimeInterval:intervaltarget:selfselector:@selector(js_blockInvoke:)userInfo: [blockcopy]repeats:repeats];

}

+ (void)js_blockInvoke:(NSTimer*)timer {

?void(^block)() = timer.userInfo;

???if(block) {

? ? ? ? block();

??? }

}

@end

小結(jié):

1问麸、NSTimer 對(duì)象會(huì)保留其目標(biāo)往衷,直到計(jì)時(shí)器本身失效為止,調(diào)用 invalidate 方法可令計(jì)時(shí)器失效严卖,另外席舍,一次性的計(jì)時(shí)器在觸發(fā)完任務(wù)之后也會(huì)失效。

2哮笆、反復(fù)執(zhí)行任務(wù)的計(jì)時(shí)器(repeating timer)来颤,很容易引入保留環(huán),如果這種計(jì)時(shí)器的目標(biāo)對(duì)象又保留了計(jì)時(shí)器本身稠肘,那肯定會(huì)導(dǎo)致保留環(huán)福铅。這種環(huán)狀保留關(guān)系,可能是直接發(fā)生的项阴,也可能是通過對(duì)象圖里的其他對(duì)象間接發(fā)生的滑黔。

3、可以擴(kuò)充 NSTimer 的功能环揽,用 “塊”來打破保留環(huán)略荡。不過,除非 NSTimer 將來在公共接口里提供此功能歉胶,否則必須創(chuàng)建分類汛兜,將相關(guān)實(shí)現(xiàn)代碼加入其中。

主要來源:《Effective Objective-C 2.0》

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末通今,一起剝皮案震驚了整個(gè)濱河市粥谬,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌辫塌,老刑警劉巖漏策,帶你破解...
    沈念sama閱讀 216,470評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異璃氢,居然都是意外死亡哟玷,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來巢寡,“玉大人喉脖,你說我怎么就攤上這事∫衷拢” “怎么了树叽?”我有些...
    開封第一講書人閱讀 162,577評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)谦絮。 經(jīng)常有香客問我题诵,道長(zhǎng),這世上最難降的妖魔是什么层皱? 我笑而不...
    開封第一講書人閱讀 58,176評(píng)論 1 292
  • 正文 為了忘掉前任性锭,我火速辦了婚禮,結(jié)果婚禮上叫胖,老公的妹妹穿的比我還像新娘草冈。我一直安慰自己,他們只是感情好瓮增,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,189評(píng)論 6 388
  • 文/花漫 我一把揭開白布怎棱。 她就那樣靜靜地躺著,像睡著了一般绷跑。 火紅的嫁衣襯著肌膚如雪拳恋。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,155評(píng)論 1 299
  • 那天砸捏,我揣著相機(jī)與錄音谬运,去河邊找鬼。 笑死带膜,一個(gè)胖子當(dāng)著我的面吹牛吩谦,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播膝藕,決...
    沈念sama閱讀 40,041評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼式廷,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了芭挽?” 一聲冷哼從身側(cè)響起滑废,我...
    開封第一講書人閱讀 38,903評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎袜爪,沒想到半個(gè)月后蠕趁,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,319評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡辛馆,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,539評(píng)論 2 332
  • 正文 我和宋清朗相戀三年俺陋,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了豁延。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,703評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡腊状,死狀恐怖诱咏,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情缴挖,我是刑警寧澤袋狞,帶...
    沈念sama閱讀 35,417評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站映屋,受9級(jí)特大地震影響苟鸯,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜棚点,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,013評(píng)論 3 325
  • 文/蒙蒙 一早处、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧乙濒,春花似錦陕赃、人聲如沸卵蛉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,664評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽傻丝。三九已至甘有,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間葡缰,已是汗流浹背亏掀。 一陣腳步聲響...
    開封第一講書人閱讀 32,818評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留泛释,地道東北人滤愕。 一個(gè)月前我還...
    沈念sama閱讀 47,711評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像怜校,于是被迫代替她去往敵國(guó)和親间影。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,601評(píng)論 2 353