在iOS中,MVC是比较常用的开发策略。M指Model,即存储数据的模型,V指View视图层,C指Controller,如下图:
他们之间的关系是:
[btn addTarget:self action:@selector(click:) forControlEvents:UIControlEventTouchUpInside];
通过这句代码我们可以看到V和C的通信方式,V上面有事件发生就发送消息给Target,如果我们想在V事件过程中做一些事情,怎么办?这时候V就把事件的一些过程点委托给了C,比如should、will、did等,因此我们可以在C的这些方法中进行操作。同样,V不保存数据,它需要的数据应该向C取,于是他就留了一个datasource给C,让C把数据放进来。
在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也可以正常运行。
在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 dealloc、Memory 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> 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不存在。这样显然不符合逻辑。
使用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的时候初始化,以后都是使用初始化后的对象。这种方法会更加容易管理。
+(NSArray*)validSuits
{
return @[@"1", @"2", @"3"];
}
用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)initWithBar:(NSString*)Bar;
+(instancetype)initWithFoo:(NSString*)Foo;
如上面,当你用instancetype作为构造函数的返回值时,你将不会出错。因为编译器能够明确的知道你返回的是什么实例类型
//不一致
- (id)initWithBar:(NSInteger)bar; + (instancetype)fooWithBar:(NSInteger)bar; //一致 - (id)initWithBar:(NSInteger)bar; + (instancetype)fooWithBar:(NSInteger)bar;
看到这个名词觉得和中文联系不上。查了下,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。
通用对象,用于封装一些非OC对象、非基本数据类型的数据。
如:NSValue *edgeInsetsObject = [NSValue valueWithUIEdgeInsets:UIEdgeInsetsMake(1,1,1,1)]
属性列表,就是一个collection中的所有元素只包含以下数据类型
NSArray, NSDictionary, NSNumber, NSString, NSDate, NSData
之所以这么定义,是SDK中又一些方法只能在这些数据上进行操作,如:
- (void)writeToFile:(NSString *)path atomically:(BOOL)atom;
非属性列表的数据应该通过转换成为属性列表的数据才能够进行读写操作。
例子:
//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属性有如下属性
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;
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)anOrientation duration:(NSTimeInterval)seconds; - (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOriention)orient duration:(NSTimeInterval)seconds; - (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)anOrientation;
view controller lifecycle主要调用的函数如下:
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