中文字幕在线观看,亚洲а∨天堂久久精品9966,亚洲成a人片在线观看你懂的,亚洲av成人片无码网站,亚洲国产精品无码久久久五月天

那些設(shè)計(jì)iOS API需要知道的事

2018-07-20    來(lái)源:編程學(xué)習(xí)網(wǎng)

容器云強(qiáng)勢(shì)上線!快速搭建集群,上萬(wàn)Linux鏡像隨意使用

為了能夠?qū)⑽覀冺?xiàng)目中的代碼能夠在后續(xù)開(kāi)發(fā)者使用(重用代碼),通常使用的方法是將代碼按照功能模塊編寫(xiě)成API。那么我們就很有必要了解Objective-C語(yǔ)言中常見(jiàn)的編程范式(paradigm),同時(shí)還需了解各種可能碰到的陷阱。

命名

命名沖突的問(wèn)題

Objective-C沒(méi)有其他語(yǔ)言的那種內(nèi)置命名空間(namespace)機(jī)制。因此,我們只能自己想辦法來(lái)解決命名沖突問(wèn)題。最常用的解決方式就是,仿照其他語(yǔ)言(C++)建立自己的namespace,例如,使用前綴。

所選前綴可以是與公司、應(yīng)用程序或二者皆有關(guān)聯(lián)之名。例如,ZAKER User Interface可以使用ZUI作為前綴。使用Cocoa創(chuàng)建應(yīng)用程序時(shí)一定要注意,Apple宣稱(chēng)其保留使用所有“兩字母前綴”(two-letter prefix)的權(quán)利,所以開(kāi)發(fā)者選用的前綴應(yīng)該是三個(gè)字母的。如果開(kāi)發(fā)者使用了兩個(gè)字母作前綴,那么很有可能開(kāi)發(fā)者自定義的API和Apple的API沖突。

不僅僅是類(lèi)名,應(yīng)用程序中的所有名稱(chēng)都應(yīng)該加前綴。如果要為既有類(lèi)新增“分類(lèi)”(category),那么一定要給“分類(lèi)”及“分類(lèi)”中的方法加上前綴。另外,類(lèi)的實(shí)現(xiàn)文件中所用的純C函數(shù)及全局變量也應(yīng)該注意添加前綴。

如果使用了第三方庫(kù)編寫(xiě)自己的代碼,并準(zhǔn)備將其發(fā)布為程序庫(kù)供他人開(kāi)發(fā)應(yīng)用程序所用,則尤其要注意重復(fù)符號(hào)問(wèn)題。這種情況下為了避免使用者使用了與你相同的第三方庫(kù),應(yīng)該為第三方庫(kù)都加上你自己的前綴。

命名方式

類(lèi)、方法和變量的命名是Objective-C編程的重要環(huán)節(jié)。如果命名方式好,可以提高代碼可讀性,減少不必要的注釋。

初學(xué)者通常會(huì)覺(jué)得Objective-C是門(mén)很繁瑣的語(yǔ)言,因?yàn)槠湔Z(yǔ)法結(jié)構(gòu)使得代碼讀起來(lái)和句子一樣。命名中一般都帶有“in”、“for”、“with”等介詞,特別是在命名時(shí)還要講究英文語(yǔ)法。例如:

NSString *text = @"This is a good idea.";
NSString *newText = [text stringByReplacingOccurrencesOfString:@"idea" withString:@"think"];

上面的代碼雖然用了比較啰嗦的方式描述一個(gè)看上去很簡(jiǎn)單的表達(dá)式。對(duì)于執(zhí)行替換的那個(gè)方法,代碼讀起來(lái)就像日常語(yǔ)言里的那個(gè)句子:“Take text and give me a new string by replacing the occurrences of the string ‘idea’ with the string ‘think’”。

這個(gè)句子準(zhǔn)確描述了開(kāi)發(fā)者想做的事。在命名不像Objective-C這般繁瑣的語(yǔ)言中,類(lèi)似的程序可能會(huì)寫(xiě)成:

string text = "This is a good idea.";
string new Text = text.replace("idea", "think");

上面代碼這樣寫(xiě),看起來(lái)方法名簡(jiǎn)潔很多,但是帶來(lái)的代碼不可讀性卻是非常大的。首先,我們不知道 text.replace 方法的兩個(gè)參數(shù)到底按照什么順序解讀(除非查看方法聲明);再者,這兩個(gè)參數(shù)誰(shuí)替換誰(shuí)?

另外,和大多數(shù)語(yǔ)言一樣,Objective-C也是采用“駝峰式大小寫(xiě)命名法”(camel casing)——以小寫(xiě)字母開(kāi)頭,其后每個(gè)單詞首字母大寫(xiě)。

方法命名

清晰的方法名從左至右讀起來(lái)好似一段文章。并不是說(shuō)非得按照那些命名規(guī)則來(lái)給方法起名,不過(guò)這樣做可以令代碼變得更好維護(hù),使他人更容易讀懂。

雖然類(lèi)似C++或Java中那種函數(shù)命名簡(jiǎn)單,但是,若想知道每個(gè)參數(shù)的用途,就得查看函數(shù)原型,這會(huì)令代碼難于讀懂。

NSString這個(gè)類(lèi)展示了一套良好的命名習(xí)慣。下面列舉幾個(gè)方法及命名緣由:

1) + (instancetype)string;
工廠方法(factory method),用于創(chuàng)建新的空字符串。方法名清晰地描述了返回值的類(lèi)型。

2) + (instancetype)stringWithString:(NSString *)string;
工廠方法,根據(jù)某字符串創(chuàng)建出與之內(nèi)容相同的新字符串。與創(chuàng)建空字符串所用的那個(gè)工廠方法一樣,方法名的第一個(gè)單詞也指明了返回類(lèi)型。

3) + (instancetype)localizedStringWithFormat:(NSString *)format, ...;
工廠方法,根據(jù)特定格式創(chuàng)建出新的“本地化字符串”(localized string)。返回值類(lèi)型是方法名的第二個(gè)單詞(string),因?yàn)槠淝懊孢有個(gè)修飾語(yǔ)(localized)用來(lái)描述其邏輯含義。此方法的返回值依然是“字符串”(string),只不過(guò)是一種經(jīng)過(guò)本地化處理的特殊字符串。

4) - (NSUInteger)lengthOfBytesUsingEncoding:(NSStringEncoding)enc;
若字符串是以給定的編碼格式(ASCII、UTF8、UTF16)來(lái)編碼的,則返回其字節(jié)數(shù)組長(zhǎng)度。此方法與length相似,但該方法還需一個(gè)參數(shù),該參數(shù)緊跟著方法名中描述其類(lèi)型的那個(gè)名詞(encoding)。

因此,我們可以總結(jié)成幾條方法命名規(guī)則:

1)如果方法的返回值是新創(chuàng)建的,那么方法名的首個(gè)詞應(yīng)該是返回值的類(lèi)型,除非前面還有修飾語(yǔ),例如localizedString。屬性的存取方法不遵循這種命名方式,因?yàn)橐话阏J(rèn)為這些方法不會(huì)創(chuàng)建新對(duì)象。即便有時(shí)返回內(nèi)部對(duì)象的一份拷貝,我們也認(rèn)為那相當(dāng)于原有對(duì)象。這些存取方法應(yīng)該按照其所對(duì)應(yīng)的屬性來(lái)命名。

2)應(yīng)該把表示參數(shù)類(lèi)型的名詞放在參數(shù)前面。

3)如果方法要在當(dāng)前對(duì)象上執(zhí)行操作,那么就應(yīng)該包含動(dòng)詞;若執(zhí)行操作時(shí)還需要參數(shù),則應(yīng)該在動(dòng)詞后面加上一個(gè)或多個(gè)名詞。

4)不要使用str這種簡(jiǎn)稱(chēng),應(yīng)該使用string這樣的全稱(chēng)。

5)boolean屬性應(yīng)加is前綴。如果某方法返回非屬性的boolean值,那么應(yīng)該根據(jù)其功能,選用has或is當(dāng)前綴。

6)將get這個(gè)前綴留給那些借由“輸出參數(shù)”來(lái)保存返回值的方法,比如說(shuō),把返回值填充到“C語(yǔ)言式數(shù)組”(C-style array)里的那種方法就可以使用這個(gè)詞做前綴。

類(lèi)與協(xié)議命名

不僅僅是方法,類(lèi)和協(xié)議也應(yīng)該加上前綴,避免命名空間沖突。例如:

  • UIView
  • UIViewController
  • UITableViewDelegate

錯(cuò)誤模型

目前有很多編程語(yǔ)言都有“異常”(exception)機(jī)制,Objective-C也不例外。

“自動(dòng)引用計(jì)數(shù)”(ARC, Automatic Reference Counting)在默認(rèn)情況下不是“異常安全的”。這意味著:如果拋出異常,那么本應(yīng)該在作用域末尾釋放的對(duì)象現(xiàn)在卻不會(huì)自動(dòng)釋放了。如果想生成“異常安全”的代碼,可以通過(guò)設(shè)置編譯器的標(biāo)志來(lái)實(shí)現(xiàn),不過(guò)這將引入額外代碼,在不拋出異常時(shí),也照樣要執(zhí)行這部分代碼。需要打開(kāi)的編譯器標(biāo)志叫做 -fobjc-arc-exception 。

Objective-C現(xiàn)在所采用的辦法是:只在極其罕見(jiàn)的情況下拋出異常,異常拋出之后,無(wú)須考慮恢復(fù)問(wèn)題,而且應(yīng)用程序此時(shí)也應(yīng)該退出。這就是說(shuō),不用再編寫(xiě)復(fù)雜的“異常安全”代碼了。

異常只應(yīng)該用于極其嚴(yán)重的錯(cuò)誤,比如,你編寫(xiě)了某個(gè)抽象基類(lèi),它的正確用法是先從中繼承一個(gè)子類(lèi),然后使用這個(gè)子類(lèi)。在這種情況下,如果有人直接使用了這個(gè)抽象基類(lèi),那么可以考慮拋出異常。與其他語(yǔ)言不同,Objective-C中沒(méi)辦法將某個(gè)類(lèi)標(biāo)識(shí)為“抽象類(lèi)”。要想達(dá)成類(lèi)似效果,最好的辦法是在那些子類(lèi)必須覆寫(xiě)的超類(lèi)方法里拋出異常。

異常只用于處理嚴(yán)重錯(cuò)誤(fatal error),對(duì)于其他錯(cuò)誤,Objective-C語(yǔ)言所用的編程范式為:令方法返回nil/0,或使用NSError,以表明有錯(cuò)誤發(fā)生。

NSError對(duì)象里封裝了三條信息:

  • Error domain (錯(cuò)誤范圍,其類(lèi)型為字符串)

錯(cuò)誤發(fā)生的范圍,也就是產(chǎn)生錯(cuò)誤的根源,通常用一個(gè)特有的全局變量來(lái)定義。例如,URL-handling-subsystem,在從URL中解析或獲取數(shù)據(jù)時(shí)如果出錯(cuò)了,那么就使用NSURLErrorDomain來(lái)表示錯(cuò)誤范圍。

  • Error code (錯(cuò)誤碼,其類(lèi)型為整數(shù))

獨(dú)有的錯(cuò)誤碼,用以指明在某個(gè)范圍內(nèi)具體發(fā)生了何種錯(cuò)誤。某個(gè)特定范圍內(nèi)可能會(huì)發(fā)生一系列相關(guān)錯(cuò)誤,這些錯(cuò)誤情況通常采用enum來(lái)定義。

  • User info (用戶(hù)信息,其類(lèi)型為字典)

有關(guān)此錯(cuò)誤的額外信息,其中或許包含一段“本地化描述”,或許還包含有導(dǎo)致該錯(cuò)誤發(fā)生的另外一個(gè)錯(cuò)誤,經(jīng)由此種信息,可將相關(guān)錯(cuò)誤串成一條“錯(cuò)誤鏈”。

使用不可變對(duì)象

設(shè)計(jì)類(lèi)的時(shí)候,應(yīng)充分使用屬性來(lái)封裝數(shù)據(jù)。而在使用屬性時(shí),則可將其聲明為 readonly 。默認(rèn)情況下,屬性是 readwrite 。

因?yàn)槿绻芽勺儗?duì)象(mutable object)放入collection之后又修改其內(nèi)容,那么很容易就會(huì)破壞set的內(nèi)部數(shù)據(jù)結(jié)構(gòu),使其失去固有的語(yǔ)義。故此,我們應(yīng)該盡量減少對(duì)象中的可變內(nèi)容。具體到編程實(shí)踐中,則應(yīng)該盡量把對(duì)外公布出來(lái)的屬性設(shè)為 readonly ,而且只在有必要時(shí)才將屬性對(duì)外公布。

定義類(lèi)的公共API時(shí),需要注意,對(duì)象里表示各種collection的那些屬性究竟應(yīng)該設(shè)成可變的,還是不可變的。如果某個(gè)屬性可以為外界所增刪,那么這個(gè)屬性就需要用可變的set來(lái)實(shí)現(xiàn)。在這種情況下,通常應(yīng)該提供一個(gè)readonly屬性供外界使用,該屬性將返回不可變的set,而此set則是內(nèi)部那個(gè)可變set的一份拷貝。

//  ZKRPointOfInterest.h

#import <UIKit/UIKit.h>

@interface ZKRPointOfInterest : NSObject

@property (nonatomic, copy, readonly) NSString *identifier;
@property (nonatomic, copy, readonly) NSString *title;
@property (nonatomic, assign, readonly) CGFloat latitude;
@property (nonatomic, assign, readonly) CGFloat longitude;
@property (nonatomic, strong, readonly) NSSet *locations;

- (instancetype)initWithIdentifier:(NSString *)identifier
                             title:(NSString *)title
                          latitude:(CGFloat)latitude
                         longitude:(CGFloat)longitude;

- (void)addLocation:(ZKRPointOfInterest *)location;
- (void)removeLocation:(ZKRPointOfInterest *)location;

@end

//  ZKRPointOfInterest.m

#import "ZKRPointOfInterest.h"

@implementation ZKRPointOfInterest
{
    NSMutableSet *_internalLocations;
}

- (instancetype)initWithIdentifier:(NSString *)identifier
                             title:(NSString *)title
                          latitude:(CGFloat)latitude
                         longitude:(CGFloat)longitude
{
    self = [super init];
    if (self) {
        
    }
    return self;
}

- (NSSet *)locations
{
    return [_internalLocations copy];
}

- (void)addLocation:(ZKRPointOfInterest *)location
{
    if (location) {
        [_internalLocations addObject:location];
    }
}

- (void)removeLocation:(ZKRPointOfInterest *)location
{
    [_internalLocations removeObject:location];
}

@end

注意:不要在返回的對(duì)象上查詢(xún)類(lèi)型以確定其是否可變。(即使不用 isKindOfClass: 方法來(lái)判斷返回值類(lèi)型是否可變)

description方法

在調(diào)試程序時(shí),經(jīng)常需要打印并查看對(duì)象信息。一種辦法是編寫(xiě)代碼把對(duì)象的全部屬性都log到日志中。 NSLog(@"object=%@", object);

在構(gòu)建需要打印到日志的字符串時(shí),object對(duì)象會(huì)收到description消息,該方法所返回的描述信息將取代“格式字符串”(format string)里的“%@”。

NSArray *obj = @[@"A string", @(123)];
NSLog(@"object=%@", obj);

輸出:

object=(
	"A string",
	123
)

如果在自定義類(lèi)上這么做,那么則輸出的信息卻是如下:

object=<ZKRSqure: 0x7656d8a90060>

如果想要像上面NSArray那樣打印出有用的信息,那么我們就應(yīng)該在自己的類(lèi)中覆寫(xiě)description方法,否則打印信息時(shí)就會(huì)調(diào)用NSObject類(lèi)所實(shí)現(xiàn)的默認(rèn)方法。此方法定義在NSObject協(xié)議里,不過(guò)NSObject類(lèi)也實(shí)現(xiàn)了它。

- (NSString *)description
{
    return [NSString stringWithFormat:@"<%@: %p, \"%f %f\">", [self class], self, _width, _height];
}

使用結(jié)果:

ZKRRectangle *rectangle = [[ZKRRectangle alloc] initWithWidth:5.0 height:7.0];
NSLog(@"%@", rectangle);

//Output
<ZKRRectangle: 0x60000002fc20, "5.000000 7.000000">

NSObject協(xié)議中還有個(gè)需要注意的方法,就是 debugDescription ,此方法用意與 description 相似。二者區(qū)別在于, debugDescription 方法是開(kāi)發(fā)者在調(diào)試器(debugger)中以控制臺(tái)命令打印對(duì)象時(shí)才調(diào)用的。在NSObject類(lèi)的默認(rèn)實(shí)現(xiàn)中,它只是直接調(diào)用 description 。

初始化方法

所有對(duì)象均要初始化,在初始化時(shí),有些對(duì)象可能無(wú)須開(kāi)發(fā)者向其提供額外信息,不過(guò)一般來(lái)說(shuō)還是需要提供的。通常情況下,對(duì)象若不知道必要的信息,則無(wú)法完成其工作。例如,UITAbleViewCell類(lèi)初始化該類(lèi)對(duì)象時(shí),需要指明其樣式及標(biāo)識(shí)符,標(biāo)識(shí)符能夠區(qū)分不同類(lèi)型的單元格。由于這種對(duì)象的創(chuàng)建成本較高,所以繪制表格時(shí)可依照標(biāo)識(shí)符來(lái)復(fù)用,以提升程序效率。這種可為對(duì)象提供必要信息以便其能完成工作的初始化方法叫做“全能初始化方法”(designated initializer)。

如果創(chuàng)建類(lèi)實(shí)例的方式不止一種,那么這個(gè)類(lèi)就會(huì)有多個(gè)初始化方法。但是,我們?nèi)匀恍枰x定一個(gè)作為全能初始化方法,令其他初始化方法都來(lái)調(diào)用它。例如,NSDate類(lèi)

- (instancetype)init NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithTimeIntervalSinceReferenceDate:(NSTimeInterval)ti NS_DESIGNATED_INITIALIZER;
- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithTimeIntervalSinceNow:(NSTimeInterval)secs;
- (instancetype)initWithTimeIntervalSince1970:(NSTimeInterval)secs;
- (instancetype)initWithTimeInterval:(NSTimeInterval)secsToBeAdded sinceDate:(NSDate *)date;

在上面幾個(gè)初始化方法中, initWithTimeIntervalSinceReferenceDate: 是全能初始化方法。只有在全能初始化方法中,才會(huì)存儲(chǔ)內(nèi)部數(shù)據(jù)。這樣的話(huà),當(dāng)?shù)讓訑?shù)據(jù)存儲(chǔ)機(jī)制改變時(shí),只需修改此方法的代碼就好,無(wú)須改動(dòng)其他初始化方法。

示例代碼:

//  ZKRRectangle.h

#import <UIKit/UIKit.h>

@interface ZKRRectangle : NSObject<NSCopying>

@property (nonatomic, assign, readonly) CGFloat width;
@property (nonatomic, assign, readonly) CGFloat height;

- (instancetype)initWithWidth:(CGFloat)width height:(CGFloat)height;

@end

//  ZKRRectangle.m

#import "ZKRRectangle.h"

@implementation ZKRRectangle

- (instancetype)initWithCoder:(NSCoder *)aDecoder
{
    self = [super init];
    if (self) {
        _width = [[aDecoder decodeObjectForKey:@"width"] floatValue];
        _height = [[aDecoder decodeObjectForKey:@"height"] floatValue];
    }
    return self;
}

- (instancetype)init
{
    @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"Must use initWithWidth:height: instad." userInfo:nil];
    
    return [self initWithWidth:0 height:0];
}

- (instancetype)initWithWidth:(CGFloat)width height:(CGFloat)height
{
    self = [super init];
    if (self) {
        _width = width;
        _height = height;
    }
    return self;
}

@end

//  ZKRSquare.h

#import "ZKRRectangle.h"

@interface ZKRSquare : ZKRRectangle

- (instancetype)initWithDimension:(CGFloat)dimension;

@end

//  ZKRSquare.m

#import "ZKRSquare.h"

@implementation ZKRSquare

- (instancetype)init
{
    @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"Must use initWithDimension: instad." userInfo:nil];
    
    return [self initWithDimension:0];
}

- (instancetype)initWithDimension:(CGFloat)dimension
{
    return [super initWithWidth:dimension height:dimension];
}

- (instancetype)initWithWidth:(CGFloat)width height:(CGFloat)height
{
    @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"Must use initWithDimension: instad." userInfo:nil];
    CGFloat dimension = MIN(width, height);
    return [self initWithDimension:dimension];
}

@end

小結(jié)

  • 在類(lèi)中提供一個(gè)全能初始化方法,并于文檔里指明。其他初始化方法均調(diào)用此方法。
  • 若全能方法于超類(lèi)不同,則需要覆寫(xiě)超類(lèi)中的對(duì)應(yīng)方法。
  • 如果超類(lèi)的初始化方法不適用于子類(lèi),那么應(yīng)該覆寫(xiě)這個(gè)超類(lèi)方法,并在其中拋出異常。

NSCopying協(xié)議

使用對(duì)象時(shí)經(jīng)常需要拷貝它。在Objective-C中,此操作通過(guò)copy方法完成。如果想令自己的類(lèi)支持拷貝操作,那就要實(shí)現(xiàn)NSCopying協(xié)議,該協(xié)議只有一個(gè)方法:

- (id)copyWithZone:(nullable NSZone *)zone;

為什么會(huì)出現(xiàn)NSZone呢?因?yàn)橐郧伴_(kāi)發(fā)程序時(shí),會(huì)據(jù)此把內(nèi)容分成不同的“區(qū)”(zone),而對(duì)象會(huì)創(chuàng)建在某個(gè)區(qū)里面,F(xiàn)在不用了,每個(gè)程序只有一個(gè)區(qū):“默認(rèn)區(qū)”(default zone)。所以說(shuō),盡管必須實(shí)現(xiàn)這個(gè)方法,但是你不必?fù)?dān)心其中的zone參數(shù)。

copy方法由NSObject實(shí)現(xiàn),該方法只是以“默認(rèn)區(qū)”為參數(shù)來(lái)調(diào)用 copyWithZone: 。我們總是想覆寫(xiě)copy方法,其實(shí)真正需要實(shí)現(xiàn)的是 copyWithZone: 方法。若想使某個(gè)類(lèi)支持拷貝功能,只需聲明該類(lèi)遵從NSCopying協(xié)議,并實(shí)現(xiàn)其中的那個(gè)方法即可。

- (id)copyWithZone:(NSZone *)zone
{
    ZKRRectangle *copy = [[[self class] allocWithZone:zone] initWithWidth:_width height:_height];
    return copy;
}

說(shuō)到copy方法,除了NSString這樣的不可變類(lèi)型的copy,與之類(lèi)似的還有NSMutableString類(lèi)的 mutableCopy 方法。與 copyWithZone: 方法相對(duì)應(yīng)的可變內(nèi)容的copy方法 mutableCopyWithZone: 方法來(lái)自于 NSMutableCopying 協(xié)議。如果你的類(lèi)分為可變版本(mutable)與不可變版本(immutable),那么就應(yīng)該實(shí)現(xiàn)NSMutableCopying協(xié)議。若采用此模式,則在可變類(lèi)中覆寫(xiě) copyWithZone: 方法時(shí),不要返回可變的拷貝,而應(yīng)該返回一份不可變的版本。無(wú)論當(dāng)前實(shí)例是否可變,需要獲取其可變版本的拷貝,均應(yīng)調(diào)用mutableCopy方法;獲取不可變版本的拷貝,則總應(yīng)該通過(guò)copy方法。

深拷貝就是在拷貝對(duì)象自身時(shí),將其底層數(shù)據(jù)也一并復(fù)制過(guò)去。

淺拷貝就是在拷貝對(duì)象時(shí),只拷貝容器對(duì)象本身,而不復(fù)制其中數(shù)據(jù)。

 

來(lái)自:http://chars.tech/2017/07/09/ios-design-api-guide/

 

標(biāo)簽: 安全 代碼 開(kāi)發(fā)者

版權(quán)申明:本站文章部分自網(wǎng)絡(luò),如有侵權(quán),請(qǐng)聯(lián)系:west999com@outlook.com
特別注意:本站所有轉(zhuǎn)載文章言論不代表本站觀點(diǎn)!
本站所提供的圖片等素材,版權(quán)歸原作者所有,如需使用,請(qǐng)與原作者聯(lián)系。

上一篇:軟件架構(gòu)設(shè)計(jì)中要注意的六個(gè)方面

下一篇:10個(gè)你必須知道的ios框架