[学习小结]developing iOS7 for iPhone and iPad 1~5

理解MVC

在iOS中,MVC是比较常用的开发策略。M指Model,即存储数据的模型,V指View视图层,C指Controller,如下图:
MVC
他们之间的关系是:

  • C能够分别和M、V单向通信,即C能够从M提取数据,并指派V显示数据
  • M也不能够直接跟C通信,它只能通过KVO或者Notification的方法告知C。
  • V禁止和M通信,但是能够通过Target和Action的方式与C通信,这时可以想到代码定义空间的语法,没错,比如
    [btn addTarget:self action:@selector(click:) forControlEvents:UIControlEventTouchUpInside];

通过这句代码我们可以看到V和C的通信方式,V上面有事件发生就发送消息给Target,如果我们想在V事件过程中做一些事情,怎么办?这时候V就把事件的一些过程点委托给了C,比如should、will、did等,因此我们可以在C的这些方法中进行操作。同样,V不保存数据,它需要的数据应该向C取,于是他就留了一个datasource给C,让C把数据放进来。

深入理解@property、@synthesize、_XXXXX

在iOS中,有一个固定的规则,也就是在

@interface
@property(nonatomic, assign) int x;
@end

@implementation
@synthesize x = _x;
@end

@property中的x是类的属性,_x是实例变量,@synthesize将x与_x绑定起来,自动生成了变量x的setter与getter方法。
what is the advantage of doing @synthesize myvar = _myvar (if any)? 里面解释的很到位,之所以会有_x,是为了避免命名冲突,比如下例中就产生了命名冲突。然而服务器已经帮你考虑到了,自动为你的属性生成一个带_x的变量。

@interface 
{
    int x;
}
@end

@implemetation
-(void)something:(int)x
{

}
@end

@sythesize的作用就是将property和实例变量绑定,并自动生成getter和setter的方法。一般会与setter和getter配套使用,注意其方法的名称与synthesize左边一致,如下:

@implemetation
@synthesize x = _x;

-(int)x
{
    return _x;
}

-(void)setX:(int)x
{
    _x = x;
}
@end

但是假如你没有@sythesize,OC的@property属性也会帮你默认生成setter和getter方法,但是这些东西你是不会看到的,这也就是为什么没有@sythesize也可以正常运行。

self.xxx与_xxx

在Objective-C中,访问变量有两种方法,self.XXX方法和_XXX方法。区别在于,self.XXX方法会触发setter和getter函数,而后者不会,因此建议set数值的时候用self.XXX方法,get数值的时候用_XXX方法,这样子速度稍微快些,不用通过方法转接直接访问地址内容。
然而也有例外情况,比如在课程2的时候有注解,父类setXXX访问YYY使用self.YYY的方法,子类继承父类,假如子类的getter方法需要控制YYY的值,子类的setter方法就不需重写也可以利用self.YYY方式享受到子类getter方法的过程。
但是千万不能在setXXX中用self.XXX访问属性,因为酱紫会无限调用setXXX方法。
Don’t use accessor methods in init and deallocMemory Management都有特别说明:

The only places you shouldn’t use accessor methods to set an instance variable are in initializer methods and dealloc.

琢磨许久没弄明白,做了个小实验,声明了两个类,B继承A,其中A的初始化方法用self.的方法访问了属性name,B重载了A得属性方法。

#import <Foundation/Foundation.h>

@interface BaseClassA : NSObject

@property(nonatomic, strong) NSString *name;

@end

#import "BaseClassA.h"

@implementation BaseClassA

-(instancetype)init
{
    self = [super init];
    if(self){
        self.name = nil;
    }
    return self;
}

-(void)setName:(NSString *)name
{
    _name = name;
    NSLog(@"%@ happen here", self);
}

@end

#import "BaseClassA.h"

@interface SubClassB : BaseClassA

@end

#import "SubClassB.h"

@implementation SubClassB

-(void)setName:(NSString *)name
{
    if(![name isEqualToString:@"h"]){
        NSLog(@"%@ something wrong here", self);
    }
    name = name;
}

@end

此时我们声明一个子类B对象

SubClassB *sb = [[SubClassB alloc] init];
sb = nil;

情况发生了,输出了如下结果:

HelloWorldForiPad[19342:5187945] <SubClassB:0x12d650880> something wrong here

恩,想想也对,子类声明的对象时,构造顺序是祖先->子类,但函数调用时候顺序是子类->祖先,因此假如父类init方法中用self.访问了属性,那么其实这句代码就没有作用了,而且影响到了子类的属性访问方法,如上面代码就可能发生异常。
同样做了dealloc的实验,返现析构的顺序是子类->祖先,调用的顺序不变。输入结果如下:

2015-12-09 22:52:12.096 HelloWorldForiPad[19388:5196285] <SubClassB:0x12cd460c0> something wrong here
2015-12-09 22:52:14.233 HelloWorldForiPad[19388:5196285] Remain(null)
2015-12-09 22:52:14.233 HelloWorldForiPad[19388:5196285] <SubClassB:0x12cd460c0> <script type="math/tex" id="MathJax-Element-3"> </script> i’m out B
2015-12-09 22:52:14.234 HelloWorldForiPad[19388:5196285] <SubClassB:0x12cd460c0> something wrong here
2015-12-09 22:52:14.234 HelloWorldForiPad[19388:5196285] <SubClassB:0x12cd460c0> i’m out A

那么问题来了,i’m out B后面还继续调用了子类的name的setter函数,问题是,应该已经把子类B析构了先的,也就是应该B不存在。这样显然不符合逻辑。

weak or strong

使用Storyboard或nib开发app时,IBOutlet属性使用weak特质,如下:

@interface
@property(weak, nonatomic)IBOutlet UILabel *flipsLabel
@end

作者的原因是MVC的View已经持有了对象UILabel,Controller无需再持有该对象,因为当UILabel离开View的时候,可能Controller并不像保留一个指向该对象的指针,假如你想继续保留该指针,那么你应该使用strong(但是这种情况很少)

然而这和我的所知有点出入,作者说的并没错,也许某个控件被移除出View并不会再回来,控件占有空间被释放,还省内存。然而在stackoverflow看到Should IBOutlets be strong or weak under ARC?苹果的工程师是酱紫说的:

And the last option I want to point out is the storage type, which can either be strong or weak. In general you should make your outlet strong, especially if you are connecting an outlet to a subview or to a constraint that’s not always going to be retained by the view hierarchy. The only time you really need to make an outlet weak is if you have a custom view that references something back up the view hierarchy and in general that’s not recommended.

就用途来说,可以采用weak也可以用strong,使用的情况和上面分析的一直,但是还是不推荐用weak的做法。

懒初始化方法

一般情况下都会用init去初始化属性,但其实有更简便的方法,如下:

-(NSMutableArray*)contents
{
    if(!_contents){
        _contents = [[NSMutableArray alloc] init];
    }
    return _contents;
}

在第一次访问到contents的时候初始化,以后都是使用初始化后的对象。这种方法会更加容易管理。

类方法只用于两种情况

  • 创建对象的时候,比如[NSString stringFormatWith:]
  • 工具方法,比如返回常量值,
+(NSArray*)validSuits
{
    return @[@"1", @"2", @"3"];
}

instancetype

用instancetype代替id作返回类型有什么好处?中说道:

In your code, replace occurrences of id as a return value with instancetype where appropriate. This is typically the case for init methods and class factory methods. Even though the compiler automatically converts methods that begin with “alloc,” “init,” or “new” and have a return type of id to return instancetype, it doesn’t convert other methods. Objective-C convention is to write instancetype explicitly for all methods.

就说这种返回id的方式一般用在初始化(init)方法和工场(factory)方法中,即使编译器会自动将id转换成为合适的instancetype,但是也仅限于以”alloc”、”init”、”new”开始的方法。而OC大会上明确的将所有类似返回id的方法替换成为instancetype。
使用instancetype有三个好处:

  • 显示化
    在编译器眼里,其实id和instancetype没有什么不同,因为编译器暗地里自动将id转换成为instanceype;但是在你眼里,他们是有区别的,然而你却忽视了这种区别。而使用instancetype能让你注意到编译器底层的转换。
  • 模式化
    init和其他方法并没有什么不同,但是当用做工厂方法时,比如类方法,这种不同就显现出来了。
+(id)initWithBar:(NSString*)Bar;
+(instancetype)initWithFoo:(NSString*)Foo;

如上面,当你用instancetype作为构造函数的返回值时,你将不会出错。因为编译器能够明确的知道你返回的是什么实例类型

  • 一致性
    既然你的类工厂方法不得不用instancetype,那么假如你的init方法用id,那么代码看起来是不一致的。如下面两种款式,很显然,一致的更加易懂。
//不一致
- (id)initWithBar:(NSInteger)bar;
+ (instancetype)fooWithBar:(NSInteger)bar;
//一致
- (id)initWithBar:(NSInteger)bar;
+ (instancetype)fooWithBar:(NSInteger)bar;

designated initializer 和convenience initializer

看到这个名词觉得和中文联系不上。查了下,Apple Swift (programming language): What is a convenience initializer?,虽然有点名字不对,但是参考下:

There are two kinds of initializer for a class: designated and convenience. The differentiation is there primarily to aid the creation of subclasses.
The basic idea is: all initialization happens through one of the class’s designated initializers. Convenience initializers — variants that maybe take a path instead of a URL for example — are implemented by calling one of the designated initializers.

也就是说designated initializer是指定构造函数,所有其他函数,即convenience initializer都需要调用designated initializer,有点万剑归一的味道。这也比较容易维护,从more effective OC中说法是虽然其他接口在变,但是总接口不变,不影响其他功能。那么designated initializer如何正确编写?这篇文章讨论做出了总结

  • 每个类的正确初始化过程应当是按照从子类到父类的顺序,依次调用每个类的Designated Initializer。并且用父类的Designated Initializer初始化一个子类对象,也需要遵从这个过程。
  • 如果子类指定了新的初始化器,那么在这个初始化器内部必须调用父类的Designated Initializer。并且需要重写父类的Designated Initializer,将其指向子类新的初始化器。
  • 你可以不自定义Designated Initializer,也可以重写父类的Designated Initializer,但需要调用直接父类的Designated Initializer。
  • 如果有多个Secondary initializers(次要初始化器),它们之间可以任意调用,但最后必须指向Designated Initializer。在Secondary initializers内不能直接调用父类的初始化器。
  • 如果有多个不同数据源的Designated Initializer,那么不同数据源下的Designated Initializer应该调用相应的[super (designated initializer)]。如果父类没有实现相应的方法,则需要根据实际情况来决定是给父类补充一个新的方法还是调用父类其他数据源的Designated Initializer。比如UIView的initWithCoder调用的是NSObject的init。

nil

  • 发送一个消息给nil,返回zero
    如:int i = [obj methodWhichReturnsAnInt];//i will be zero if obj is nil
  • 如果返回的是一个结构体,那么返回值未定义
    如:CGPoint p = [obj getLocation];//p will have an undefined value if obj is nil

NSValue

通用对象,用于封装一些非OC对象、非基本数据类型的数据。
如:NSValue *edgeInsetsObject = [NSValue valueWithUIEdgeInsets:UIEdgeInsetsMake(1,1,1,1)]

property list

属性列表,就是一个collection中的所有元素只包含以下数据类型
NSArray, NSDictionary, NSNumber, NSString, NSDate, NSData
之所以这么定义,是SDK中又一些方法只能在这些数据上进行操作,如:
- (void)writeToFile:(NSString *)path atomically:(BOOL)atom;
非属性列表的数据应该通过转换成为属性列表的数据才能够进行读写操作。

UIFont

例子:

//preferredFontForTextStyle用于修改用户内容的字体样式
//一般用以下函数来修改控件标题
+ (UIFont *)systemFontOfSize:(CGFloat)pointSize;
+ (UIFont *)boldSystemFontOfSize:(CGFloat)pointSize;
//字体样式还有:UIFontTextStyleHeadline, UIFontTextStyleCaption1, UIFontTextStyleFootnote, etc.
UIFont *bodyFont = [UIFont preferredFontForTextStyle:UIFontTextStyleBody];
UIFontDescriptor *existingDescriptor = [bodyFont fontDescriptor];
//symbolicTraits获取UIFontDescriptor中的字体系统特征
UIFontDescriptorSymbolicTraits traits = existingDescriptor.symbolicTraits;
traits |= UIFontDescriptorTraitBold;
//fontDescriptorWithSymbolicTraits添加字体特征
UIFontDescriptor *newDescriptor = [existingDescriptor fontDescriptorWithSymbolicTraits:traits];
//fontWithFontDescriptor:size:构建新的字体样式
UIFont *boldBodyFont = [UIFont fontWithFontDescriptor:newDescriptor size:0];

Attributed Strings

例子:

//Attributed Strings属性有如下属性
UIColor *yellow = [UIColor yellowColor];
UIColor *transparentYellow = [yellow colorWithAlphaComponent:0.3];
 @{ NSFontAttributeName :
      [UIFont preferredFontWithTextStyle:UIFontTextStyleHeadline]
   NSForegroundColorAttributeName : [UIColor greenColor],
   NSStrokeWidthAttributeName : @-5,//-表示填充和轮廓,+表示轮廓
   NSStrokeColorAttributeName : [UIColor redColor],
   NSUnderlineStyleAttributeName : @(NSUnderlineStyleNone),
   NSBackgroundColorAttributeName : transparentYellow }
//Attributed Strings运用例子   
NSMutableAttributedString *labelText = [myLabel.attributedText mutableCopy]; [labelText setAttributes:...];
myLabel.attributedText = labelText;

view controller lifecycle

  • viewDidLoad: 适合一些设置代码,但此时还没设置view的bounds,因此不应该在这里进行与几何依赖相关的初始化
  • view{will, did}Appear: 适合一些screen off同时显示内容会改变的事情,相对于一次调用viewdidload:, viewwillappear会调用多次
  • view{will, did}LayoutSubviews; 当一个view的frame改变,都会调用该方法,以调整subview

Autorotation

  • The view controller returns YES from shouldAutorotate.
  • The view controller returns the new orientation in supportedInterfaceOrientations.
  • The application allows rotation to that orientation (defined in Info.plist file).
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)anOrientation
                                duration:(NSTimeInterval)seconds;
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOriention)orient
                                duration:(NSTimeInterval)seconds;
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)anOrientation;

view controller lifecycle主要调用的函数如下:

  • awakeFromNib //在storyboard资源加载之后
  • viewDidLoad //outlets设置完成后
  • viewWillLayoutSubviews: and viewDidLayoutSubviews: //视图几何状态已经定好
  • viewWillAppear: and viewDidAppear: //会在MVC出现消失屏幕期间反复调用
    • viewWillLayoutSubviews: and viewDidLayoutSubviews: //屏幕旋转或者一些可见的界面修改时调用
      (if it is autorotation, then you also get will/didRotateTo/From messages–rare to use these)
  • viewWillDisappear: and viewDidDisappear: //view移出屏幕的时候
  • didReceiveMemoryWarning //内存不够时候调用
  • (there is no “unload” anymore, so that’s all there is)

课程代码&ppt

https://github.com/shawjan/NetEaseHomework

Lecture 1 Slides
Lecture 2 Slides
Developing iOS 7 Apps_ Assignment 1
Lecture 3 Slides
Developing iOS 7 Apps_ Assignment 2
Lecture 4 Slides
Lecture 5 Slides

©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页