上一篇: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了。我说它语法奇怪是由于第一次看见时候我很惊讶,多是少见多怪了吧。