Objective-C(四)協議與分類

這是Objective-C系列的第4篇。緩存

1、最佳實踐

  • 在開發中合理巧妙的使用位段
  • 將類的實現代碼分散到便於管理的數個分類之中框架

    • 使用分類機制把類的實現代碼劃分紅易於管理的小模塊;
    • 將應該視爲「私有」的方法納入名叫Private的分類中,以隱藏實現細節。
  • 老是爲第三方類的分類名稱添加前綴ide

    • 向第三方類中添加分類時,總應該給其名稱加上你專用的前綴;
    • 向第三方類中添加分類時,總應該給其中的方法加上你專用的前綴。
  • 勿在分類中聲明屬性post

    • 把封裝數據所用的所有屬性都定義在主接口裏;fetch

    • 在「class-continuation」分類以外的其餘分類中,能夠定義存取方法,但儘可能不要定義屬性。優化

      編者按:在不少第三方開源庫中,使用「關聯對象」來在分類中定義屬性是很常見的手段。ui

  • 使用「class-continuation」分類隱藏實現細節this

    • 經過「class-continuation」分類向類中新增實例變量;
    • 若是某屬性在主接口總聲明爲「readonly(只讀)」,而類的內部又要用設置方法修改此屬性,那麼就在「class-continuation」中將其擴展爲「readwrite(可讀寫)」;
    • 把私有方法的原型聲明聲明在「class-continuation」裏面;
    • 若想使類所遵循的協議不爲人所知,則可與「class-continuation」中聲明。
  • 經過協議提供匿名對象atom

    • 協議可在某某種程度上提供匿名類型。具體的對象類型能夠淡化成聽從某些一的id類型,協議裏規定了對所應事先的方法。
    • 使用匿名對象來隱藏類型名稱(或類名)。
    • 若是具體類型不重要,重要的是對象可以響應(定義在協議裏的)特定方法,那麼可以使用匿名對象來表示。

2、實踐詳解

2.1 位段

在委託代理中,若是要頻繁檢查該代理是否響應某個方法,那麼將代理相應能力緩存起來達到優化。而優化的最佳途徑就使用「位段」。「位段」是一個C語言數據類型。url

關於位段,簡要作個說明:

2.1.1 定義

struct bs{
    int a:1;
    int  :2;		//無位段名,它只用來做填充或調整位置
    int b:3;		
    int  :0;		//空域
    int c:5;		//從下一單元開始存放
};

struct bs data;
複製代碼

或者:

struct bs{
    int a:1;
    int  :2;
    int b:3;
    int  :0;
    int c:5;
} data;
複製代碼

2.1.2 示例

#include <stdio.h>
int main(){
    struct{
        unsigned a:1;
        unsigned b:3;
        unsigned c:4;
    } bit, *pbit;
    bit.a=1;
    bit.b=7;
    bit.c=15;
    printf("%d, %d, %d\n", bit.a, bit.b, bit.c);
    pbit=&bit;
    pbit->a=0;
    pbit->b&=3;
    pbit->c|=1;
    printf("%d, %d, %d\n", pbit->a, pbit->b, pbit->c);
    return 0;
}
複製代碼
  1. 位段的類型只能是int,unsigned int,signed int三種類型,不能是char型或者浮點型;
  2. 位段佔的二進制位數不能超過該基本類型所能表示的最大位數,好比在VC中int是佔4個字節,那麼最多隻能是32位;
  3. 位域能夠無位域名,這時它只用來做填充或調整位置。無名的位域是不能使用的;
  4. 若位段佔的二進制位數爲0,則這個位段必須是無名位段,下一個位段從下一個位段存儲單元開始存放;

2.1.3 位段在委託代理模式中的應用

@class HONetworkFetcher;
@protocol NetworkFetcherDelegate <NSObject>

@optional
- (void)networkFetcher:(HONetworkFetcher*)fetcher didReceiveData:(NSData*)data;
- (void)networkFetcher:(HONetworkFetcher*)fetcher didFailerWithError:(NSError *)error;
- (void)networkFetcher:(HONetworkFetcher*)fetcher didUpdateProgerssTo:(float)progress;

@end

@interface HONetworkFetcher : NSObject
@property (nonatomic ,weak) id<HONetworkFetcherDelegate> delegate;
@end
複製代碼
#import "HONetworkFetcher.h"
@interface HONetworkFetcher()
{
    struct {
        unsigned int didReceiveData                 :1;
        unsigned int didFailedWithError             :1;
        unsigned int didUpdateProgressTo            :1;
    } _delegateFlags;
    
}

@end

@implementation HONetworkFetcher

/** * 在設置代理的時候檢查方法可達性,並緩存起來 */
- (void)setDelegate:(id<HONetworkFetcherDelegate>)delegate
{
    _delegate = delegate;
    _delegateFlags.didReceiveData = [delegate respondsToSelector:@selector(networkFetcher:didReceiveData:)];
    _delegateFlags.didFailedWithError = [delegate respondsToSelector:@selector(networkFetcher:didFailerWithError:)];
    _delegateFlags.didUpdateProgressTo = [delegate respondsToSelector:@selector(networkFetcher:didUpdateProgerssTo:)];
}

@end
複製代碼

2.2 將類的實現代碼分散到便於管理的數個分類之中

  • 使用分類機制把類的實現代碼劃分紅易於管理的小模塊;
  • 將應該視爲「私有」的方法納入名叫Private的分類中,以隱藏實現細節。

2.3 老是爲第三方類的分類名稱添加前綴

分類爲現有類添加新功能,假如多個分類都爲該類添加了同一個方法名的某一個方法,那麼在運行時,會形成該方法名屢次覆蓋,以最後一次覆蓋爲主。假如遇到這種狀況的bug,很難追溯源頭,由於你不知道,其餘人也重寫了該方法。因此爲了不這種狀況的發生,就須要爲分類加上前綴,做爲一個「命名空間」,好比:

@interface NSString (HOG_HTTP)
- (NSString*)hog_urlEncodedString;
@end
複製代碼

即使加了前綴,也難保其餘分類不會覆蓋你所寫的放方法。可是下降了機率。

  • 向第三方類中添加分類時,總應該給其名稱加上你專用的前綴;
  • 向第三方類中添加分類時,總應該給其中的方法加上你專用的前綴。

2.4 勿在分類中聲明屬性

屬性是封裝數據的方式。在技術上,分類也能夠聲明屬性,可是要避免這種作法。

聲明文件:

@interface HOPerson (Friends)
@property (nonatomic ,strong)NSSet *friends;
@end
複製代碼

實現文件:

@implementation HOPerson(Friends)
@end
複製代碼

這時會發出警告:

HOPerson+Friends.m:11:17: Property 'friends' requires method 'friends' to be defined - use @dynamic or provide a method implementation in this category
	HOPerson+Friends.m:11:17: Property 'friends' requires method 'setFriends:' to be defined - use @dynamic or provide a method implementation in this category
複製代碼

要消除警告,要麼添加@dynamic,要麼添加對應的setter/getter方法。

下面是在實現文件裏添加setter/getter方法:

#import "HOPerson+Friends.h"
#import <objc/runtime.h>

static const char *kFriendPropertyKey  = "kFriendPropertyKey";

@implementation HOPerson(Friends)
//@dynamic friends;

- (NSSet *)friends
{
    return objc_getAssociatedObject(self, kFriendPropertyKey);
}

- (void)setFriends:(NSSet *)friends
{
    objc_setAssociatedObject(self,
                             kFriendPropertyKey,
                             friends,
                             OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

@end
複製代碼

在本例中,正確的作法是將全部的屬性都定義在主接口裏。主接口是惟一能定義成員變量(數據)的地方。而屬性只是定義實例變量及相關存取方法所用的「語法糖」,因此也應遵循同實例變量同樣的規則。至於分類機制應將其理解爲一種手段,目標在於擴展類的功能,而非封裝數據。

  • 把封裝數據所用的所有屬性都定義在主接口裏;
  • 在「class-continuation」分類以外的其餘分類中,能夠定義存取方法,但儘可能不要定義屬性。

2.5 使用「class-continuation」分類隱藏實現細節

class-continuation和其餘分類不一樣,它必須定義在其所接續的那個類的實現文件裏。其重要之處在於,這是惟一能聲明實例變量的分類,並且此分類沒有特定的實現文件,其中的方法都應定義在類的主實現文件裏。

可參考本文上段中「位段在委託代理模式中的應用」中定義的位段,即實例變量。

在class-continuation中定義實例變量,主要是爲了將細節隱藏起來。

另外,在class-continuation中聲明只有在類的實現代碼中的私有方法也是較爲可取的。在編寫類的實現代碼以前,先在class-continuation中將須要實現的方法原型聲明,而後逐一實現。好比:

@interface HOPerson()
{
    NSMutableSet *_internalFriends;
}

- (void)hog_findFriends;

@end

@implementation HOPerson
@end
複製代碼

最後,還有一種狀況,就是對象所遵循的協議只應視爲私有的話,那麼最好也在class-continuation中聲明。好比:

@interface HOPerson()<NSCopying,NSCoding>
{
    NSMutableSet *_internalFriends;
}

- (void)hog_findFriends;
@end

@implementation HOPerson
@end
複製代碼
  • 經過「class-continuation」分類向類中新增實例變量;
  • 若是某屬性在主接口總聲明爲「readonly(只讀)」,而類的內部又要用設置方法修改此屬性,那麼就在「class-continuation」中將其擴展爲「readwrite(可讀寫)」;
  • 把私有方法的原型聲明聲明在「class-continuation」裏面;
  • 若想使類所遵循的協議不爲人所知,則可與「class-continuation」中聲明。

2.5 經過協議提供匿名對象

  • 協議可在某某種程度上提供匿名類型。具體的對象類型能夠淡化成聽從某些一的id類型,協議裏規定了對所應事先的方法。
  • 使用匿名對象來隱藏類型名稱(或類名)。
  • 若是具體類型不重要,重要的是對象可以響應(定義在協議裏的)特定方法,那麼可以使用匿名對象來表示。
相關文章
相關標籤/搜索