iOS開發的技巧,新的一篇,持續更新。。。(系列二)

上一篇: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屬性對應的gettersetter方法。

下面是內存管理方面的一些規則和經驗總結:

(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了。我說它語法奇怪是由於第一次看見時候我很驚訝,多是少見多怪了吧。

相關文章
相關標籤/搜索