抽象的威力
從小老師就耳提面命:要看透問題的表象看本質(zhì)。起初不解而涉,隨著干Coder時間長了著瓶,愈發(fā)察覺要想偷懶就得去解決本質(zhì)問題,而不是圍繞著問題的表層轉(zhuǎn)來轉(zhuǎn)去啼县。而進(jìn)一步說材原,不能停留在使用簡單抽象出來的概念解決問題的層次,應(yīng)該進(jìn)一步使用一些高級抽象季眷。就拿布局這個事情來說吧余蟹。
原始抽象
第一層的抽象是對現(xiàn)實世界在數(shù)字世界的描述,用坐標(biāo)系統(tǒng)子刮。用數(shù)學(xué)語言來描述布局這個事情威酒。其實抽象到這一步也是一個非常大的進(jìn)步了。在iOS中也就是我們常用的spring&struct的布局系統(tǒng)话告。通過frame等來標(biāo)注一個view來控制平面布局受神,通過View-Tree來控制Z軸方向上的布局犹褒。
基于這種抽象的情況下勇皇,我們做的事情就是精準(zhǔn)的描述每一個View在坐標(biāo)系統(tǒng)中的位置嗡靡。這個就是那個繁重的工作。在layoutsubviews函數(shù)里面里面病线,各種計算大小和偏移量吓著。熟悉iOS的同學(xué),隨意看一下自己View里面layoutSubviews函數(shù)的大小就可以感受到其繁重送挑。
描述抽象
抽象這個東西有意思的地方就在于他是可以遞歸使用的绑莺。在第一層抽象得到的概念基礎(chǔ)上,進(jìn)行第二層抽象惕耕;在第二層抽象得到的概念基礎(chǔ)上纺裁,進(jìn)行第三層抽象…..如此反復(fù)。當(dāng)然司澎,Apple牛逼的工程師們也會意識到spring&struct的繁重欺缘,于是就有了Autolayout(自動布局系統(tǒng))。
Autolayout是基于描述的抽象挤安,更多的是描述元素與元素之間的位置關(guān)系谚殊。通過解n元一次方程組來確定元素的位置(在坐標(biāo)系統(tǒng)中的frame)。而這種基于模式的抽象蛤铜,要比剛才第一層的抽象要高級很多嫩絮。在這種抽象概念下丛肢,我們發(fā)現(xiàn)我們寫的布局的代碼有了一個大幅度的減少。原先需要拼了老命計算view frame的情況變成了剿干,描述view之間關(guān)系的過程蜂怎。原先需要非常多代碼才能完成的任務(wù),現(xiàn)在只要簡單的寫一句AView在x軸方向上距離BView固定間隔為10就可以完成了置尔。隨著代碼量的降低派敷,我們的對于布局系統(tǒng)理解成本都在降低。
復(fù)雜性由固有復(fù)雜性和認(rèn)知復(fù)雜性組成撰洗。
而基于描述的抽象,因為是對第一層抽象概念的操作腐芍,忽略了第一層抽象的很多細(xì)節(jié)差导,很大程度上降低了布局系統(tǒng)的認(rèn)知復(fù)雜度。無論從代碼量上還是我們的認(rèn)知成本上猪勇,都讓我們可以『偷懶』了设褐。
任務(wù)抽象
而有了基于描述抽象的Autolayout之后,我們會發(fā)現(xiàn)其實很多任務(wù)還是很繁重泣刹,比如我們要寫個List布局助析,不可能每次都把List中每個元素的位置關(guān)系描述一邊啊。比如我們要寫個Collection的布局椅您,也不可能每次都把每個元素的位置關(guān)系都描述一邊啊…..
于是真對某些特定任務(wù)外冀,會使用任務(wù)抽象。把List布局抽象成UITableView掀泳,把Collection布局抽象成UICollectionView雪隧,把線性布局抽象成UIStackView。员舵。脑沿。。马僻。而且Apple的工程師們也會在這條路上越走越遠(yuǎn)庄拇。在近幾個版本的iOS-SDK更新中,我們也看到了更多的這種布局View的出現(xiàn)韭邓。相信以后也會更多措近。
而這種類型的View在簡化編程工作這件事情上要比autolayout來的更加厲害∪猿樱可以隨意感受一下熄诡,我原先為了實現(xiàn)一個TableView所寫的代碼量DZTableView。就知道當(dāng)我們把通用任務(wù)抽象出來的時候诗力,能偷多大的懶了凰浮。
而關(guān)于這種任務(wù)抽象我們聽到的最多就是面試中經(jīng)常被問及的GoF設(shè)計模式我抠。在想想應(yīng)用設(shè)計模式時的爽,也就知道抽象這個工具的確很好用袜茧。在這里強烈推薦一本書《元素模式》菜拓。個人認(rèn)為這本提供了一整套的瑞士軍刀,來幫你進(jìn)行抽象或者去設(shè)計『設(shè)計模式』笛厦。
….
當(dāng)然這里還會存在更高層次的抽象纳鼎。不過嘛,抽象并非銀彈裳凸。并不是說一味的抽象下去就能夠得到極致的『偷懶』贱鄙。隨著抽象層次提高,概念密度也在提高姨谷,而那些在這個過程中所忽略的特殊場中的細(xì)節(jié)逗宁,將變得難以還原。有些時候梦湘,處理問題反而更加困難瞎颗,編碼量卻在增加。老生常談的問題鞍埔椤:度哼拔。當(dāng)抽象層次滿足業(yè)務(wù)需求和業(yè)務(wù)發(fā)展的時候,就可以臨時先止步了瓣颅。
DZGeometryTools
github地址:https://github.com/yishuiliunian/DZGeometryTools
主要是用了描述抽象和任務(wù)抽象兩種技術(shù)倦逐。看起來忽悠人弄捕,其實實現(xiàn)部分一看就明白了僻孝。無非是將常用的一些任務(wù)轉(zhuǎn)換成了函數(shù)而已。
其實在CoreGraphics框架中又一個CGGeometry.h文件守谓,其中提供了很多方便操作CGRect等幾何類型的函數(shù)穿铆。而在DZGemetryTools中所做的工作可以看做是對CGGeometry的一個擴展。
DZGeometryTools.h中主要是對幾何類型的操作
通過抽象我將比較常見的幾何操作歸結(jié)為以下幾種比較基礎(chǔ)的操作:
1.偏移操作
2.縮放操作
3.間距計算操作
4.margin施加操作
現(xiàn)在把他們提取出來斋荞,基本上就是完成了一個工具集的構(gòu)建荞雏。而后在這些工具集上,就可以來描述每個Rect之間的關(guān)系平酿,這個有點類似于autolayout凤优,不過是提前收工算好了,而autolayout是自動計算并賦值的蜈彼。貼段真實使用中代碼來感受一下:
- (void) layoutSubviews
{
[super layoutSubviews];
CGSize imageSize = {62*2, 84};
CGRect contentRect = CGRectCenterSubSize(self.bounds, CGSizeMake(20, 20));
CGRect textRect;
CGRect imageRect;
CGRectDivide(contentRect, &textRect, &imageRect, 30, CGRectMaxYEdge);
imageRect = CGRectCenter(imageRect, imageSize);
CGRect imgRs[2];
CGRectHorizontalSplit(imageRect, imgRs, 2, 0);
_indicatorImageView.frame = imgRs[0];
_powerImageView.frame = imgRs[1];
_textLabel.frame = textRect;
_backgroundView.frame = self.bounds;
_lastTimeLabel.frame = imageRect;
}
其中使用到了CGRectCenterSubSize 來對contentRect做了margin計算筑辨,并用CGRectHorizontalSplit 對rect坐了縱向均分的操作,這些都是描述UI元素之間相對位置關(guān)系的關(guān)系幸逆,我們通過手工計算來完成了對于UI元素的布局操作棍辕。這些函數(shù)都沒有采用直接操作view.frame的方式暮现,只進(jìn)行了幾何運算,計算出了UI元素坐標(biāo)楚昭。之所以采取這種方式栖袋,是因為想基于目前的抽象層次應(yīng)該是針對于幾何概念的操作。對于UI元素的操作抚太,已經(jīng)簡化成了一個賦值操作塘幅,沒有太大必要去優(yōu)化處理了。而相較于以前的編碼量和思維量來說尿贫,基于目前構(gòu)建的這個工具集來進(jìn)行編碼已經(jīng)省了不少力氣了电媳。體力活少了。
DZLayoutMacros.h 常用的任務(wù)
在這個文件里面依舊是一些常用的布局工具集庆亡,不過和上面不一樣的是采用了另外的表達(dá)方式:宏匆背。針對于一些比較簡單的任務(wù),做了抽象身冀。比如:
1.y依賴于頂部元素,并且盡可能填充滿width的布局
2.頂部固定高度括享,鋪滿width的布局
3.//底部固定高度搂根,鋪滿width的布局
4.…..
StyleSheet
github地址:https://github.com/yishuiliunian/StyleSheet
據(jù)說一個終端開發(fā)人員將會有70%以上的時間在和UI打交道。自己想想也對铃辖,貌似有很大一部分時間花費在了調(diào)整UI樣式剩愧,addSubView還有l(wèi)ayout上面。猛然間就發(fā)現(xiàn)自己的代碼中有大量這種東西存在
self.label.layer.cornerRadius = 3;
self.label.textColor = [UIColor darkTextColor];
self.label.font = [UIFont systemFontOfSize:13];
self.label.backgroundColor = [UIColor greenColor];
self.label.layer.borderWidth = 2;
self.label.layer.borderColor = [UIColor redColor].CGColor;
self.label2.layer.cornerRadius = 3;
self.label2.textColor = [UIColor darkTextColor];
self.label2.font = [UIFont systemFontOfSize:13];
self.label2.backgroundColor = [UIColor greenColor];
self.label2.layer.borderWidth = 2;
self.label2.layer.borderColor = [UIColor redColor].CGColor;
self.button.layer.cornerRadius = 3;
self.button.backgroundColor = [UIColor greenColor];
self.button.layer.borderWidth = 2;
self.button.layer.borderColor = [UIColor redColor].CGColor;
self.aView.layer.cornerRadius = 3;
self.aView.backgroundColor = [UIColor greenColor];
self.aView.layer.borderWidth = 2;
self.aView.layer.borderColor = [UIColor redColor].CGColor;
......
上面的代碼是為了實現(xiàn)這樣的效果而寫的代碼娇斩。
很多幾乎是一毛一樣的代碼仁卷,充斥著整個APP。自己花在這些樣式調(diào)整上的時間也非常多犬第。為了實現(xiàn)一個樣式效果锦积,需要配置各種各樣的屬性。而且很多界面中這些樣式都是一樣的歉嗓。于是又是無數(shù)次的重復(fù)上面的工作丰介。oy my god!時間啊鉴分,就這樣流走了哮幢。做為一個懶人,就會發(fā)問有沒有一種可以少寫點代碼的方式呢志珍?你可以寫一個子類嘛橙垢,但是會有類污染的問題,單純?yōu)榱艘粋€公有樣式伦糯,就創(chuàng)建個子類有點大材小用柜某。那寫一批樣式渲染的函數(shù)唄嗽元,恩這個注意不錯,但是細(xì)想一下工作量也不小莺琳,而且不通用还棱。于是,花了幾天的時間我寫了StyleSheet這個庫惭等。為了的就是來簡化UI樣式的編碼珍手。
通過上述描述我們可以發(fā)現(xiàn),原始的寫UI樣式的問題:
1.繁瑣的代碼辞做,大量重復(fù)性的工作
2.樣式無法共享琳要,每一個View都需要重新進(jìn)行樣式賦值。
而StyleSheet的設(shè)計目標(biāo)就是:
1.樣式配置輕便化秤茅,能夠使用更加少的代碼來描述View的樣式
2.樣式在View之間的共享.不止是相同類的實例之間的共享稚补,甚至是跨類的共享。
So框喳,先看看上述代碼使用StyleSheet之后的效果:
self.label.style =
DZLabelStyleMake(
style.backgroundColor = [UIColor greenColor];
style.cornerRedius = 3;
style.borderColor = [UIColor redColor];
style.borderWidth = 2;
style.textStyle.textColor = [UIColor darkTextColor];
style.textStyle.font = [UIFont systemFontOfSize:13];
);
self.label2.style = self.label.style;
self.aView.style = self.label.style;
[self.button.style copyAttributesWithStyle:self.label.style];
設(shè)計
基礎(chǔ)抽象模型很簡單课幕,就是要讓界面上關(guān)于展示的屬性可以被組合使用。而我們所謂的樣式五垮,其實也就是各種基礎(chǔ)屬性組合出來的結(jié)果乍惊。基于這個模型放仗,在設(shè)計StyleSheet的時候故意淡化了被渲染的View的類型的概念润绎,任何一種類型的Style可以對任何類型的View進(jìn)行渲染,但是必須是這種類型的View支持Style所指稱的屬性诞挨。比如你可以使用真對Button設(shè)計的DZButtonStateStyle來渲染一個UILabel莉撇,但由于UILabel不支持DZButtonStateStyle中的渲染屬性,所以渲染結(jié)果是無效的惶傻。
但是當(dāng)使用DZButtonStyle(繼承自DZViewStyle)來渲染UILabel的時候棍郎,會使用DZButtonStyle中其父類的某些渲染屬性,來渲染UILabel的父類UIView所支持的那些屬性银室。
使用
直接使用Style對View進(jìn)行渲染:
DZLabelStyle* style =
DZLabelStyleMake(
style.backgroundColor = [UIColor greenColor];
style.cornerRedius = 3;
style.borderColor = [UIColor redColor];
style.borderWidth = 2;
style.textStyle.textColor = [UIColor darkTextColor];
style.textStyle.font = [UIFont systemFontOfSize:13];
);
[style decorateView:self.label];
直接渲染的好處是坝撑,不用再次生成Style對象,更加方便樣式在多個View之間渲染粮揉。
賦值渲染
對UIKit中常用的一些組件進(jìn)行了擴張為他們增利了style屬性巡李,直接進(jìn)行style屬性的賦值,會出發(fā)一次渲染操作扶认。當(dāng)?shù)谝淮握{(diào)用style屬性的時候侨拦,會自動生成一個zeroStyle并賦值。
self.label.style = style
或者
self.label.style = DZLabelStyleMake(
style.backgroundColor = [UIColor greenColor];
style.cornerRedius = 3;
style.borderColor = [UIColor redColor];
style.borderWidth = 2;
style.textStyle.textColor = [UIColor darkTextColor];
style.textStyle.font = [UIFont systemFontOfSize:13];
);
當(dāng)進(jìn)行賦值渲染的時候辐宾,會將Style的Copy后的實例與當(dāng)前View綁定狱从,當(dāng)更改Style的屬性的時候膨蛮,對應(yīng)View的樣式會立刻改變。
通用樣式的共享
使用原有的配置季研,進(jìn)行通用樣式的共享是個非常困難的事情敞葛,基本上都是體力活,靠人力來維護(hù)与涡。我們的代碼中會摻雜大量的用于配置樣式的代碼惹谐,而且是獨立且散在。 現(xiàn)在你可以通過StyleSheet解決: 定義共享的樣式:
//在頭文件中使用 xxx.h 聲明一個公有樣式
EXTERN_SHARE_LABEL_STYLE(Content)
//在實現(xiàn)文件中使用 xxx.m 驼卖,實現(xiàn)一個公有樣式
IMP_SHARE_LABEL_STYLE(Content,
style.backgroundColor = [UIColor clearColor];
style.cornerRedius = 2;
style.textStyle.textColor = [UIColor redColor];
)
(1)使用共享樣式,方式一 self.label.style = DZStyleContent();
(2)使用共享樣式氨肌,方式二(推薦) 很多時候, 如果不需要進(jìn)一步更改樣式,可以不采復(fù)制賦值的方式來進(jìn)行渲染,可以直接使用: [DZStyleContent() decorateView:self.label]; 只進(jìn)行渲染酌畜,而不進(jìn)行復(fù)制怎囚。
好了,現(xiàn)在可以嘗試著換這種方式來寫UI樣式了桥胞。
BY:yishuiliunian