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

??本文包含第三章:接口與API設(shè)計(jì)第四章:協(xié)議與分類、第五章:內(nèi)存管理

用前綴避免命名空間問題

??因?yàn)镺C不支持命名空間,所以在定義類、方法等時(shí)應(yīng)盡量加入自己的公司前綴或者名字前綴什么的,最好是三個(gè)字母的,比如XYZViewController,為什么不是兩位數(shù)呢?因?yàn)樘O果宣稱保留使用“兩字母前綴”的權(quán)利...比如UI、NS等等...

實(shí)現(xiàn)description方法

??為自己創(chuàng)建的類定義description方法非常有用,這樣在使用NSLog打印當(dāng)前對(duì)象的時(shí)候,就可以自定義打印一些對(duì)象相關(guān)的信息了,like this:

// 可以包含類名、地址名、以及打印一些屬性的值等,看自己需求來擴(kuò)充
- (NSString *)description {
    return [NSString stringWithFormat:@"<%@: %p, \"%@ %@\">",
            [self class], self, _name, _sex];
}


// 調(diào)用和打印的時(shí)候可以這樣用
ZHCaluVC *vc = [[ZHCaluVC alloc] init];
vc.name = @"小明";
vc.sex = @"男";
NSLog(@"%@", vc);

// 打印如下
<ZHCaluVC: 0x133d06ff0, "小明 男">

??另外如果是在控制臺(tái)用po的方式,查看某個(gè)對(duì)象的調(diào)試信息,NSObject還提供了一個(gè)debugDescription方法用于在控制臺(tái)調(diào)試用:

// 可以打印一些簡(jiǎn)單的屬性
- (NSString *)description {
    return [NSString stringWithFormat:@"%@ %@", _name, _sex];
}

// 可以打印一些需要調(diào)試用的信息
- (NSString *)debugDescription {
    return [NSString stringWithFormat:@"<%@: %p> %@ %@", [self class], self, _name, _sex];
}


// 創(chuàng)建一個(gè)對(duì)象
ZHCaluVC *vc = [[ZHCaluVC alloc] init];
vc.name = @"小明";
vc.sex = @"男";

??在控制臺(tái)中直接po vc的話,可以看到調(diào)用了debugDescription方法,當(dāng)然如果不重寫debugDescription,則在po的時(shí)候會(huì)直接調(diào)用description方法:


readonly的運(yùn)用

??當(dāng)一個(gè)類對(duì)外暴露的屬性,不希望被其他類修改的時(shí)候,應(yīng)盡量設(shè)置為readonly,當(dāng)然如果屬性所在類想修改這個(gè)屬性,可以在匿名內(nèi)部類中將該屬性設(shè)置為readwrite(貌似Swift就沒法實(shí)現(xiàn)類似的效果了?):

// .h中
@interface ZHCaluVC : UIViewController
// 設(shè)置為對(duì)外readonly
@property (nonatomic, copy, readonly) NSString *name;
@end


// .m中
@interface ZHCaluVC ()
// 設(shè)置為對(duì)自身readwrite
@property (nonatomic, copy, readwrite) NSString *name;
@end

為私有方法名加前綴

??可以為一個(gè)類內(nèi)部要用的私有的方法加上一個(gè)前綴,比如p_,代表private,用于跟其他對(duì)外的方法做區(qū)分:

// 私有方法
- (void)p_someMethod {
}

??然后我突發(fā)奇想,如果一個(gè)創(chuàng)建UI的方法,需要?jiǎng)?chuàng)建一整個(gè)頁(yè)面,如果方法過長(zhǎng),需要拆分的話,是不是可以用s_(sub)來表示分支的方法呢?

- (void)viewDidLoad {
    [super viewDidLoad];
    
    [self p_createUI];
}

// 總的創(chuàng)建UI方法
- (void)p_createUI {
    [self s_createTopUI];
    [self s_createBottomUI];
    [self s_createBottomUI];
}

// 幾個(gè)分支的創(chuàng)建UI的方法
- (void)s_createTopUI {
    // 創(chuàng)建頂部UI
}
- (void)s_createMiddleUI {
    // 創(chuàng)建中間UI
}
- (void)s_createBottomUI {
    // 創(chuàng)建底部UI
}

NSException和NSError

??NSException應(yīng)該用于那種導(dǎo)致App崩潰的嚴(yán)重錯(cuò)誤,而NSError可用于一些不嚴(yán)重的錯(cuò)誤,而且NSError包含了很多錯(cuò)誤相關(guān)的信息,可用于處理錯(cuò)誤:

// 可以設(shè)置ErrorDomain、code、userInfo
NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:404 userInfo:nil];

理解NSCopying協(xié)議

??如果一個(gè)類可以被copy,就需要實(shí)現(xiàn)NSCopying中的- copyWithZone方法(這也是為什么在寫單例對(duì)象的時(shí)候,需要重寫該方法):

// 假設(shè)當(dāng)前類有一個(gè)屬性
@property (nonatomic, copy, readwrite) NSString *name;
// 一個(gè)實(shí)例變量
{
NSString *_sex;
}


// 需要先遵守NSCopying協(xié)議
- (id)copyWithZone:(NSZone *)zone {
    // NSZone不用管,以前內(nèi)存中是分為不同的zone的,現(xiàn)在只有一個(gè)zone了->default zone

// 生成一個(gè)當(dāng)前類的實(shí)例,并把當(dāng)前對(duì)象的name屬性copy給新的實(shí)例,并返回這個(gè)實(shí)例
// 這里需要注意就是name這個(gè)屬性應(yīng)該copy,因?yàn)閮蓚€(gè)對(duì)象屬于拷貝關(guān)系
// 則name這個(gè)屬性兩個(gè)對(duì)象不應(yīng)該共享,而是應(yīng)該各自持有一份
ZHCaluVC *vc = [[ZHCaluVC alloc] init];
// 屬性可以直接用點(diǎn)語(yǔ)法set
vc.name = [self.name copy];
// 實(shí)例變量需要用"->"
vc->_sex = [_sex copy];
    
return vc;
}

??當(dāng)前類的對(duì)象的copy方法實(shí)際上就調(diào)用了copyWithZone這個(gè)方法:



??copy和mutableCopy總結(jié):copy的話,應(yīng)該總是返回一個(gè)不可變的實(shí)例,mutableCopy應(yīng)該總是返回一個(gè)可變的實(shí)例。
??關(guān)于深拷貝和淺拷貝:深拷貝就是在拷貝一個(gè)對(duì)象后,將其底層數(shù)據(jù)一并拷貝,比如上面的例子,而NSArray、NSDictionary在拷貝的時(shí)候都是淺拷貝。而深淺拷貝實(shí)際上是沒有專門的協(xié)議對(duì)一個(gè)類的深淺拷貝方法進(jìn)行限制的,一般實(shí)現(xiàn)NSCopying中的copy方法的時(shí)候,應(yīng)該設(shè)置為淺拷貝,如果想提供一個(gè)深拷貝功能,也可以自定義一個(gè)- deepCopy方法

代理模式的性能優(yōu)化

??核心思想是減少對(duì)delegate進(jìn)行respondsToSelector的操作次數(shù),emmmmm,還是上代碼吧,主要就是把[delegate respondsToSelector]緩存到結(jié)構(gòu)體中,存儲(chǔ)起來:

// .h
@protocol SomeClassDelegate <NSObject>

- (void)didDoSomethingA;
- (void)didDoSomethingB;
- (void)didDoSomethingC;

@end

@interface SomeClass: NSObject

@property (nonatomic, assign) id<SomeClassDelegate> delegate;

@end
// .m
@interface SomeClass () {
    struct data {
        // 利用“位段”來設(shè)置結(jié)構(gòu)體中某個(gè)字段所占用的二進(jìn)制位個(gè)數(shù)
        // 如下為1,則可以表示0和1,如果是8,則可以表示0-255
        unsigned int didDoSomethingA : 1;
        unsigned int didDoSomethingB : 1;
        unsigned int didDoSomethingC : 1;
    } _delegateFlags;
}

@end

@implementation SomeClass

// 在setDelegate方法中,緩存delegate是否已經(jīng)實(shí)現(xiàn)了代理方法
- (void)setDelegate:(id<SomeClassDelegate>)delegate {
    _delegate = delegate;
    _delegateFlags.didDoSomethingA = [delegate respondsToSelector:@selector(didDoSomethingA)];
    _delegateFlags.didDoSomethingB = [delegate respondsToSelector:@selector(didDoSomethingB)];
    _delegateFlags.didDoSomethingC = [delegate respondsToSelector:@selector(didDoSomethingC)];
}


- (void)doSomeAction {
    // 直接使用緩存好的flag來判斷delegate是否實(shí)現(xiàn)了某個(gè)代理方法
    // 以此減少多次判斷respondsToSelector帶來的性能問題
    if (_delegateFlags.didDoSomethingA) {
        [self.delegate didDoSomethingA];
    }
}

@end

??雖然我覺得對(duì)性能的影響并不大...但對(duì)于那些需要周期性調(diào)用的代理方法,比如每秒刷新UI什么的,還是很有用的。

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

??可以將一個(gè)類中的多個(gè)方法,進(jìn)行歸類,然后分散到當(dāng)前類的多個(gè)分類中,這樣可以方便管理不同類型的方法,比如處理UI的、處理網(wǎng)絡(luò)請(qǐng)求的、處理數(shù)據(jù)包裝的等等(當(dāng)然MVC可以這樣,MVVM的話,邏輯就可以放到ViewModel中了)。一些代理的實(shí)現(xiàn),也可以放到分類中去實(shí)現(xiàn),方便管理,比如UITableViewDelegate、UIAlertViewDelegate等:

// 定義一個(gè)ZHCaluVC類的分類,并遵守UITableViewDelegate
@interface ZHCaluVC (TableView) <UITableViewDelegate>

@end

@implementation ZHCaluVC (TableView)

// 實(shí)現(xiàn)UITableViewDelegate的方法
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
}

@end

??當(dāng)然我個(gè)人覺得OC的Category和Swift的Extension比,還是有些麻煩的,因?yàn)榇硪彩切枰暶鰼interface的(.h和.m中都可以有),一個(gè)類分散出多個(gè)分類,也會(huì)導(dǎo)致聲明很多@interface,不便于管理,而且分類在新增屬性時(shí)也比較麻煩,所以以上做法在Swift中使用Extension來做會(huì)更理想。

給分類添加屬性

??分類雖然可以通過@property添加屬性,但不會(huì)自動(dòng)添加get/set方法,這樣在訪問分類中的屬性的時(shí)候會(huì)因?yàn)檎也坏絞et/set方法而導(dǎo)致崩潰,編譯器也告訴了我們這些信息:



??不過上面的警告信息已經(jīng)很明顯的告訴了我們解決辦法,通過@dynamic+關(guān)聯(lián)對(duì)象的方式實(shí)現(xiàn):

// .h
@interface SomeClass (SomeCategory)

// 在分類中聲明一個(gè)屬性
@property (nonatomic, copy) NSString *name;

@end
// .m
static const char *kNamePropertyKey = "kNamePropertyKey";

@implementation SomeClass (SomeCategory)

// 聲明自己實(shí)現(xiàn)get/set方法
@dynamic name;

// 通過關(guān)聯(lián)對(duì)象的方式實(shí)現(xiàn)get/set方法
- (NSString *)name {
    return objc_getAssociatedObject(self, kNamePropertyKey);
}

- (void)setName:(NSString *)name {
    objc_setAssociatedObject(self, kNamePropertyKey, name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

@end

??不過還是不建議在分類中新增屬性的,畢竟脫離開主接口,這些屬性存在意義可能不大。

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

??try/catch中的變量,arc是不會(huì)自動(dòng)幫我們r(jià)elease的,也就是下面的代碼,如果在try中發(fā)生了異常,實(shí)際上arr這個(gè)變量是不會(huì)被釋放掉的,也就引發(fā)了內(nèi)存泄漏,當(dāng)然arc也不允許手動(dòng)調(diào)用[arr release]。

 @try {
    NSArray *arr = [[NSArray alloc] init];
    arr[0];
} @catch (NSException *exception) {
} @finally {
}

??當(dāng)然如果想讓arc幫我們自動(dòng)釋放,需要在項(xiàng)目設(shè)置中的“other linker flag”中添加-fobjc-arc-exceptions,這樣在編譯時(shí)arc就會(huì)自動(dòng)幫我們加入try/catch中的內(nèi)存管理相關(guān)代碼,當(dāng)然也會(huì)拖慢編譯速度。

關(guān)于unsafe_unretained

??簡(jiǎn)單理解的話,__weak弱引用一個(gè)對(duì)象,當(dāng)對(duì)象釋放后,指針置空,也就是nil,當(dāng)調(diào)用方法的時(shí)候,不會(huì)引發(fā)崩潰,雖然unsafe_unretained也是弱引用一個(gè)對(duì)象,但對(duì)象釋放后,指針指向的還是釋放的對(duì)象的內(nèi)存,當(dāng)調(diào)用方法的時(shí)候,就會(huì)引發(fā)崩潰。Swift中有unowned(無主引用)的概念跟這個(gè)對(duì)應(yīng)。

關(guān)于自動(dòng)釋放池

??實(shí)際上自動(dòng)釋放池這一塊,之前在面試的時(shí)候,就被問過一次,當(dāng)時(shí)的題目是這樣的(貌似是這樣的),下面的方法,如何避免內(nèi)存方面的問題:

- (void)dealString {
    for (int i=0; i<10000; i++) {
        NSString *str = [NSString stringWithFormat:@"%d", i];
        NSLog(@"%@", str);
    }
}

??當(dāng)然我答上來了哈哈哈,書中也是這么寫的:

- (void)dealString {
    for (int i=0; i<10000; i++) {
        
        @autoreleasepool {
            NSString *str = [NSString stringWithFormat:@"%d", i];
            NSLog(@"%@", str);
        }
        
    }
}

??正常來說,我們聲明的10000個(gè)局部變量str,會(huì)在方法執(zhí)行結(jié)束進(jìn)行釋放,也就是循環(huán)結(jié)束,但如果加了自動(dòng)釋放池,局部變量出了自動(dòng)釋放池就釋放了,所以局部變量是一邊創(chuàng)建一邊釋放的,會(huì)避免局部?jī)?nèi)存高峰。

僵尸對(duì)象

??這一節(jié)干貨太多了,不好總結(jié),參見:第35條:用“僵尸對(duì)象”調(diào)試內(nèi)存管理問題。大體說下來,就是如果編輯target的scheme,打開“Enable Zombie Objects”后,在調(diào)試階段,所有對(duì)象dealloc時(shí),都會(huì)被系統(tǒng)自動(dòng)替換為NSZombie類,并保留當(dāng)前對(duì)象的一些信息,方便調(diào)試階段定位空指針問題。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容