本文大体是一些细节性的小坑,不定期更新,欢迎纠正。
关于变量的读写
变量的读写通常使用 self. 和 _ 两种方式,但他们有什么区别呢?我们先看下面两个例子。
@property (nonatomic, strong) NSMutableString * str1;
@property (nonatomic, copy) NSMutableString * str2;
假设我们定义了两个变量,一个用strong属性修饰,一个用copy属性修饰。
NSMutableString * strA = [[NSMutableString alloc] initWithString:@"A"];
self.str1 = strA;
self.str2 = strA;
[strA appendFormat:@"C"];
NSLog(@"%@, %@", _str1, _str2); // 输出AC, A
第一个例子使用了self.来给变量赋值,由于str2使用了copy属性修饰,因此它给strA创建了一个不可变副本,当strA发生变化时,不影响str2。
NSMutableString * strB = [[NSMutableString alloc] initWithString:@"B"];
_str1 = strB;
_str2 = strB;
[strB appendFormat:@"C"];
NSLog(@"%@, %@", _str1, _str2); // 输出BC, BC
第二个例子直接使用来赋值,但是输出的结果却与第一个不一致,str2也发生了改变。
当我们使用self.方式时,实际上是调用了set方法来对变量赋值,而使用赋值时绕过了set方法,因此copy修饰符没生效,采用了默认的strong修饰符,所以str2的值也跟着发生改变。改进方法:
_str2 = [strB copy];
关于常量的定义
常量我们通常使用#define或者extern来定义。
define其实就是个替代宏,编译器在编译的时候会把该位置转为具体的代码,如果使用define来定义常量,并且该常量被应用于多个位置,那么就可能造成内存的浪费。举一个简单的例子:
#define NAME @"name"
NSString * name1 = [NSString stringWithString:NAME];
NSString * name2 = [NSString stringWithString:NAME];
// 上面两行代码在编译的时候就相当于下面这种写法,多开辟了两个name的内存空间
NSString * name1 = [NSString stringWithString:@"name"];
NSString * name2 = [NSString stringWithString:@"name"];
因此比较合理的做法是采用extern的方式来声明。
// 头文件(.h)中定义
extern NSString * const name;
// 实现文件(.m),在import下方书写
NSString * const name = @"name";
这种方式的写法在未使用name这个常量时,它是个nil值不占内存空间。当第一次使用name时,系统自动为其分配内存,并持续存在,后面的第N次使用都相当于访问这块内存。
另外const的位置也是有讲究的,举个例子:
NSString * const constVal1 = @"A";
NSString const * constVal2 = @"B";
const NSString * constVal3 = @"C";
constVal1 = @"A1"; // 报错
constVal2 = @"B1";
constVal3 = @"C1";
constVal1就是我们常规意义上的常量,它的值是无法改变的。而对于constVal2和constVal3它俩是没有区别的,都是代表指针地址不变,但可指向新的值。
iOS7的手势返回
这里说的手势返回并不是指深度定制的返回动画,而是自带的从屏幕边缘滑动返回。
重新设定导航栏的leftBarButtonItem后会导致手势返回失效,此时可以通过一行简单的代码来使其重新生效:
self.navigationController.interactivePopGestureRecognizer.delegate
= (id)self;
但我们的项目通常会给VC设定一个公共的父类,在父类中设置公共样式的leftBarButtonItem,并且把这行代码写在viewDidLoad中。此时会遇到一个问题——在第一个VC中触发边缘手势,然后点击任意可跳转入二级VC的按钮,会发现界面假死了。
最大的原因就在于把上述代码写在父类的viewDidLoad时,由于第一个VC也继承了该父类,因此也会触发边缘返回手势,但因为已经是栈底了无法返回,就会导致返回出错,导致想要进入二级界面时,界面假死。
一个合理的解决方案是将其写在viewDidAppear中,并且过滤第一个VC:
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
if([self.navigationController.viewControllers count] >= 2) {
self.navigationController.interactivePopGestureRecognizer.enabled = YES;
self.navigationController.interactivePopGestureRecognizer.delegate = (id)self;
}else{
self.navigationController.interactivePopGestureRecognizer.delegate = nil;
self.navigationController.interactivePopGestureRecognizer.enabled = NO;
}
}
状态栏
在iOS7之后控制状态栏的样式通常使用-preferredStatusBarStyle方法来设定状态栏的风格。(注:View controller-based status bar appearance 要设定为 YES)
但有时候你会发现,在导航控制器里面的VC重写此方法是无效的。原因很简单,主控权不在当前VC中,而是在它的父VC——NavigationController中。有两种解决方式,重写NavC中的-preferredStatusBarStyle方法或-childViewControllerForStatusBarStyle方法。
- (UIStatusBarStyle)preferredStatusBarStyle
{
return [self.topViewController preferredStatusBarStyle];
}
// 上下两种方法任选一
- (UIViewController *)childViewControllerForStatusBarStyle
{
return self.topViewController;
}
另外,状态栏的隐藏是可以带动画,一种是Fade,一种是Slide
- (BOOL)prefersStatusBarHidden
{
return ![UIApplication sharedApplication].statusBarHidden;
}
- (UIStatusBarAnimation)preferredStatusBarUpdateAnimation
{
return UIStatusBarAnimationSlide;
}
- (IBAction)hideOrShowStatusBar:(id)sender {
[UIView animateWithDuration:0.2 animations:^{
[self setNeedsStatusBarAppearanceUpdate];
}];
}