上一篇:iOS開發的一些小技巧,持續更新。。。。(系列一)ios
20140331
objective-c
一、說一說通知NSNotification和NSNotificationCenter安全
在iOS應用開發中有一個Notification Center的概念,它是一個單利對象,容許當某個事件發生時候通知一些對象。它容許咱們在地程度耦合的狀況下,知足控制器與一個任意的對象進行通訊的目的。這種模式的基本特徵是爲了讓其餘的對象可以收到在該controller中發生某種事件而產生的消息,controller使用一個key。這樣對於Controller來講是匿名的,其餘的使用一樣地key來註冊了該通知的對象(即觀察者)可以對通知的事件做出反應。app
通知的優點在於:它可以實現一對多的通訊,也就是一個對象發出通知,多個該通知的觀察者均可以收到通知,並做出對應的響應,如執行某個方法;同時controller可以傳遞NSDictionary信息,該NSDictionary對象context懈怠了關於發送通知的自定義的信息。ide
一樣通知也有自身的缺點:佈局
(1)在編譯期間不會檢查通知是否可以被觀察者正確地處理;post
(2)在釋放註冊的時候,須要在通知中心取消註冊;測試
(3)通知發出後,controller不能從觀察者得到任何的反饋信息。字體
上面的總結我是參照另外一篇博客寫的,由於本身知道怎麼用,知道概念,可是又不能準確無誤地表述,這篇博客說的挺好的。怎麼樣簡單而又明白地說出通知的用途呢?我作過的多個項目中都有設置應用程序主題模式的功能,也就是每一個頁面的風格和背景圖片保持一致,這種狀況下就是要求在一個頁面更改主題模式,在其他的頁面也要修改背景圖片和現實風格。也就是說其他的多個頁面都是修改主題模式這個事件的觀察者,而修改主題模式的頁面就是修改主題模式事件的發送者,這樣一個發送者將修改主題模式的事件通知給全部的觀察者,起到了一對多的做用。編碼
下面我來簡單說說經過NSNotification Center來實現修改主題模式的關鍵代碼,如今ViewController1和ViewController2中都有一個名爲backImageView的UIImageView對象,在viewDidLoad中註冊通知事件,也就是將ViewCtontroller1和ViewController2註冊爲觀察者,
- (void)viewDidLoad
{
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(newTheme:)
name:@"ThemeDidChangeNotification"
object:nil];
}
這裏指定的觀察者observer爲self也就是本身,因此要在ViewController1和ViewController2中實現newTheme:方法,其代碼以下,
- (void)newTheme:(NSNotification *)notificaiotn
{
//這個方法裏的notification形參,包含了發送傳遞的信息
NSString *imageStr = [[notiifcation userInfo] objectForKey:@"THEME"];
self.backImageView.image = [UIImage imageNamed:imageStr];
}
在dealloc裏面將從通知中心刪除觀察者,
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self name:@"ThemeDidChangeNotification" object:nil];
[super dealloc];
}
上面的代碼是在ViewController1中,將ViewController1註冊爲名稱爲ThemeDidChangeNotification事件的觀察者,只要這個事件發生,那麼ViewController1將會執行-(void)newTheme:(NSNotification *)notification方法,修改背景圖片。
有了事件的觀察者,那麼接下來完成ThemeDidChangeNotification事件發送者ViewController2中的代碼,它的view界面上面有兩個Button,就叫btn0和btn1吧,他們的tag分別爲0和1,對應的點擊事件就是
- (void)ButtonClicked:(UIButton *)btn
{
NSString *imageStr = nil;
if (btn.tag == 0) {
imageStr = @"theme1";
}
else if(btn.tag == 1)
{
imageStr = @"theme2";
}
NSDictionary *dict = [[NSDictionary alloc] initWithObjectsAndKeys:imageStr,@"THEME", nil];
//發送通知,通知各個觀察者
[[NSNotificationCenter defaultCenter] postNotificationName:@"ThemeDidChangeNotification" object:nil userInfo:dict];
}
固然,ViewController2本身也要註冊爲ThemeDidChangeNotification事件的觀察者,而後再收到通知的時候修改本身的背景圖片。不在這裏重複寫相關代碼,添加觀察者的步驟以下,
(1)將本身註冊爲事件的ThemeDidChangeNotification觀察者,並指定事件發生時候執行的方法newTheme
(2)編寫- (void)newTheme:(NSNotification *)notification;方法
(3)在dealloc時候移除觀察者
上面的場景使咱們自定義指定的事件ThemeDidChangeNotification,實際上蘋果公司已經給咱們提供了不少封裝好的系統級別的事件,例如鍵盤的彈出和消失對應的通知事件UIKeyboardWillShowNotification & UIKeyboardWillHideNotification,修改了本地語言環境的通知事件NSCurrentLocaleDidChangeNotification,這個時候咱們就只須要編寫事件觀察者的代碼,而無需編寫事件發送者的代碼,由於事件通知的放鬆,是系統來作的。這方面的代碼,我在以前的一篇博客UITextField的一些使用技巧中使用到,若有疑問請參見該篇博客。
二、iOS內存管理的代碼怎麼寫(本段內存管理的內容,我尚未寫完,能夠跳過不看)
這篇文章是看Raywenderlich的教程,感受越看越明白,可是也有點迷糊。
1、實例變量(instance variable)的內存管理
(1)step one
在ViewController.h中添加實例變量(instance variable),
@interface ViewController:UIViewController
{
NSArray *_dataArray1;
NSArray *_dataArray2;
NSString *_name1;
NSString *_name2;
}
在大括號內{}寫實例變量(instance variable),最好在前面添加_下劃線,這是一種編碼習慣,之後會說明這種編碼方式的好處。
(2)step two
在viewDidLoad中實例化實例變量(instance variable)
- (void)viewDidLoad
{
//經過alloc/init建立的實例變量(instance variable)要在dealloc中release,因此下面的_dataArray1和_name1要在dealloc中release
_dataArray1 = [[NSArray alloc] initWithObject:@"One",@"Two",@"Three",nil];
_name1 = [[NSString alloc] initWithFormat:@"Jack"];
//經過類方法建立的實例變量(instance variable)不須要在dealloc中release,由於在類方法實現的內部已經將它加入了自動釋放池
_dataArray2 = [NSArray arrayWithObject:@"Red",@"Blue",@"Green",nil];
_name2 = [NSString stringWithFormat:@"John"];
}
(3)step three
在dealloc中釋放實例變量,
- (void)dealloc
{
[_dataArray1 release];
_dataArray1 = nil;
[_name1 release];
_name1 = nil;
//不須要再release
_dataArray2 = nil;
_name2 = nil;
[super dealloc];
}
之因此把實例變量置爲nil,是爲了防止該對象的retainCount爲0時候,還調用該對象的方法,設置爲nil,這時候再調用方法的話其實就是像nil發送消息,在objective-c中,向空的對象nil發送消息不會致使應用程序crush。
2、爲實例變量(instance variable)編寫getter、setter方法(此處以_dataArray1和_name1爲例)
(1)step one
在ViewController.h文件中聲明實例變量_dataArray1和_name1的getter和setter
- (NSArray *)dataArray1
{
return _dataArray1;
}
- (void)setDataArray1:(NSArray *)dataArray1
{
[dataArray1 retain];
[_dataArray1 release];
_dataArray1 = dataArray;
}
- (NSString *)name1
{
return _name1;
}
- (void)setName1:(NSString *)name1
{
[name1 retain];
[_name1 release];
_name1 = name;
}
getter只要返回實例變量_dataArray1和_name1便可;setter先要將傳入來的參數retain,而後向實例變量_dataArray1和_name1發送release消息,使其retainCount減1。命名實例變量的時候加上前綴下劃線,是爲了區別setter方法時候傳入的參數,這樣就不會引發歧義和衝突。
(3)step three
使用self.來調用getter、setter
如今viewDidLoad中的_dataArray1和_name1可使用self.來進行初始化,
self.dataArray1 = [[NSArray alloc] initWithObject:@"one",@"two",@"three",nil];
self.name1 = [[NSString alloc] initWithFormat:@"jack"];
經過self.就能夠直接對實例變量進行賦值或者獲取到實例變量的內容。
(4)step four
在dealloc中釋放實例變量
- (void)dealloc
{
[self.dataArray1 release];
self.dataArray1 = nil;
[self.name1 release];
self.name1 = nil;
[super dealloc];
}
3、使用屬性@property替代冗長的getter、setter
(1)step one
在ViewController.h大括號中聲明實例變量_dataArray一、_dataName1
@interface ViewController :NSObject
{
NSArray *_dataArray1;
NSString *_name1;
}
(2)step two
在ViewController.h中聲明@property屬性dataArray1和name1
@property (nonatomic, retain) NSArray *dataArray1;
@property (nonatomic, retain) NSString *name1;
nonatomic修飾符是非原子操做,表示線程安全的;retain修飾符就是告訴編譯器,調用setter方法時候向傳入的參數發送retain消息,使其retainCount增長1。
(3)step three
在ViewController.m中實現@synthesize
@synthesize dataArray1 = _dataArray;
@synthesize name1 = _name1;
@synthesize語句告訴編譯器自動生成在.h文件中聲明的@property屬性對應的getter和setter方法。
下面是內存管理方面的一些規則和經驗總結:
(1)爲每個實例變量(instance variable)建立一個屬性(@property)與之對應
(2)若是屬性變量(@property)是Objective-c類型,那麼就用retain修飾符,例如NSString、NSArray、UIButton、UIViewController。。。;不然使用assign修飾符,例如id<CustomViewDelegate> delegate,int等等。
(3)不管何時,建立實例變量(instance variable)時候使用alloc/init方式建立
(4)給實例變量(instance variable)賦值時候,使用self.xxx = yyy,換句話說就是使用property
(5)對於每個實例變量(instance variable),在dealloc方法中添加self.xxx = nil。
三、繼續說一說view的bounds的使用
在前一個系列中,說過view的bounds是相對於本身自己的座標,而frame是相對於superView的座標,bounds和frame對應的寬高都是同樣的,不一樣的就是起始點(x,y)。那麼咱們就很奇怪了,frame咱們能夠用來設置座標和大小,那還要bounds來幹什麼呢?你能夠寫一個tempView測試一下,看看是什麼樣的效果,如今咱們在黑色的tempView上面放一個紅色的UIImageView,以下圖所示,
能夠明顯看到,它如今是水平居中顯示,
經過下面的代碼修改tempView的bounds,
self.tempView.bounds = CGrectOffset(self.tempView.bounds,10,10);
這段話的意思就是,保持寬高不變,(x,y)相對於本身向左上角偏移(-30,-30),而後button裏面的文字內容和圖片內容就改變了顯示的位置,以下圖,
能夠看到,它向左上角偏移了位置。若是咱們設置tempView背景色爲clearColor,那麼看起來就是它內部的imageView向左上角偏移了(30,30)。這樣的效果有什麼做用呢?我最近恰好用到了這個bounds屬性,在iOS7系統中,導航欄的BarButtonItem座標好像有所改變,表現起來就是LeftItem向右偏、RightItem向左偏,這樣看起來有點怪怪的。那麼經過下面的代碼,能夠解決我所遇到的問題,
UIButton *backBtn = [UIButton buttonWithType:UIButtonTypeCustom];
[backBtn setBackgroundImage:[UIImage imageNamed:@"changeAcc"] forState:UIControlStateNormal];
[backBtn setBackgroundImage:[UIImage imageNamed:@"changeAcc_hgiglight"] forState:UIControlStateHighlighted];
[backBtn addTarget:self action:@selector(changeGatewayId) forControlEvents:UIControlEventTouchUpInside];
backBtn.frame = CGRectMake(0, 0, 48, 48);
UIView *backButtonView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 48, 48)];
if(_IOS_VERSION_7_OR_LATER)
{
backButtonView.bounds = CGRectOffset(backButtonView.bounds, -10, 0);
}
[backButtonView addSubview:backBtn];
UIBarButtonItem *backItem = [[UIBarButtonItem alloc] initWithCustomView:backButtonView];
self.navigationItem.rightBarButtonItem = backItem;
[backButtonView release];
[backItem release];
這裏的關鍵代碼就是backButtonView.bounds = CGRectOffser(backButtonView.bounds,-10,0)。
四、類別Category和擴展Extension,以及一些奇怪可是有用的語法
(1)Category
一般狀況下,咱們可使用SDK中的類來完成大部分的功能,若是感受這個類提供的方法不夠用,那麼咱們就能夠子類話這個類,而後添加並實現咱們想要的方法,可是這樣帶來的問題是若是項目中多個地方都使用到新增的功能,那麼咱們就要在使用的地方都去實例化咱們新增的子類,而後調用新增的方法。好在Object-C爲咱們提供了Category來擴展現有類(包括SDK中的全部類),來增長新的方法,而不須要去改動源代碼,實際上蘋果也不會讓你改動它SDK中類的源代碼的,你通常只能看見.h文件。
說一個使用的場景,UILabel負責顯示多行文字內容,若是高度小而文字多則顯示不下,若是文字少而高度大那麼顯示的又不美觀,這時候就須要根據文字內容動態計算Label的高度。正常狀況下,咱們知道了字體大小、Label寬度,就能夠肯定Label的高度,那麼咱們就來擴展NSString,Command+N新建文件,新建Category的過程以下圖(貌似不少人都不知道怎樣新建,我就多截個圖方便你們),
兩個輸入框內容分別爲
Category->DynamicHeight
Category->NSStirng
而後在新建的NSStirng+DynamicHeight的.h文件中聲明新增的方法,
@interface NSString (DynamicHeight)
- (CGFloat)getTextHeight:(NSString *)text textFont:(UIFont *)font width:(CGFloat)width;
@end
在.m文件中實現新增的方法,
@implementation NSString (DynamicHeight)
- (CGFloat)getTextHeight:(NSString *)text textFont:(UIFont *)font width:(CGFloat)width
{
CGSize titleSize = [text sizeWithFont:font forWidth:width lineBreakMode:NSLineBreakByCharWrapping];
return titleSize.height;
}
@end
這裏的實現方法不夠嚴謹,這裏只是說明Category的使用,因此不嚴謹的地方還請注意。咱們這裏新增的方法是以-開頭,因此是實例方法,此時,只要在使用的地方導入NSStirng+Dynamic文件,就可使用NSString對象來調用getTextHeight:textFont:width:方法了,以下所示
#import "NSString+DynamicHeight.h"
......
NSString *string = @"Going to A British High School for One year";
CGFloat height = [string getTextHeight:string textFont:[UIFont systemFontOfSize:13] width:200];
height就是用來UILabel佈局時候的高度
(2)擴展Extension
一個類中有實例變量(instance variable),也有屬性(@property),通常狀況下實例變量是在.h文件的大括號{}內定義,只能在本類中使用,而屬性的定義在大括號外面,其餘類能夠經過點操做符(.)來訪問類中的屬性。那麼你以爲本類中的實例變量總是要在.h和.m中來回切換是否是很麻煩,使用擴展Extension能夠不用來回切換了。咱們能夠直接在.m中添加擴展的代碼,以下所示,
#import "ViewController.h"
@interface ViewController()
//這是擴展Extension
@end
@implementation ViewController
@end
這樣咱們能夠直接在擴展中寫本類的實力變量,以下所示
@interface ViewController()
{
NSString *_name;
}
@end
@implementation ViewController
@end
擴展中也能夠定義屬性@property,以下,
@interface ViewController()
{
NSString *_name;//實例變量
}
@property (nonoatomic, retain) NSString *name;
@end
@implementation ViewController
@synthesize name = _name;
@end
這樣的話就不用在.h和.m文件中來回切換了。還有就是要注意的時擴展Extension中定義的屬性,也是隻有本類中才能使用的,而其餘類不能經過點操做符(.)來訪問,因此若是想要本類跟外部類有所「交流」,仍是要在.h文件中聲明方法或者屬性。
看類別Category和擴展Extension的區別
類別:
@interface NSString (DynamicHeight)
- (CGFloat)getTextHeight:(NSString *)text textFont:(UIFont *)font width:(CGFloat)width;
@end
擴展:
@interface ViewController()
{
NSString *_name;//實例變量
}
@property (nonoatomic, retain) NSString *name;
@end
貌似擴展中的小括號()裏面沒有內容,而類別小括號()裏面有內容,能夠想象擴展是類別的超集,而類別是擴展的子集。還有就是類別中不能定義變量和屬性,而擴展中能夠定義變量和屬性,也能夠聲明方法。
(3)奇怪的語法
擴展Extension可讓咱們不用在.h和.m文件切換,加快了咱們編碼的速度,可是畢竟也要手寫擴展的關鍵代碼,確實我也受夠了多寫那麼幾行代碼,那咱們不寫擴展的代碼能不能在.m文件中添加本類的實例變量呢?恩,是能夠的,以下,
@implementation ViewController
{
NSString *_name;
}
@end
這樣你就能夠直接使用本類實例變量_name了。我說它語法奇怪是由於第一次看見時候我很驚訝,多是少見多怪了吧。