谈到这次面试,还是在简书上看到我的博客联系到我的。这个我很高兴,花了一天时间跑去杭州参加面试。

具体面试也没有做题目,因为在之前电话面试时有介绍过公司的业务和 iOS 开发的工作。一开始就直入主题。

先是人事问了我的基本状况和工作经验:

  • 学校的成绩,没想到也问了。我是老实人,我成绩一般,挂了几门数学的课。
  • 我的简历上连籍贯和年龄都没有。
  • 还问到兴趣爱好,我是什么性格的人。朋友怎么评价我的,这个我真答不上,或许是我标签太少,我回头要问问他们。
  • 未来三五年规划,其实就是看我能否稳定的在公司发展。我的要好的朋友在哪里?
  • 学习新的知识的速度
  • 期望薪金,绩效奖和公司将要搬迁

技术方面:

  • 什么是好的程序员的特质
  • 最近的项目是否有主导的想法
  • 如何学习的知识
  • 有什么主动根据需求提出不同的方案?如果不通过,会不会回去自己偷偷做一套出来?
  • 开发学习有什么特别的方法或诀窍
  • 如何才算一个好 App,平时用的有哪些体验特别好的?这个问题我应该拿出手机好好谈谈的,就像纯银的产品分享会一样。
  • 问我有没有准备的内容,没有被问到的。这个确实我没有做好的地方,主动展示自己亮点或水平。

大概这个多,好多关键的问题我都没好好问答就跳过去了。回去还要好好整理一下,也算是整理一下自己对技术的一些思考。期待我更新吧。

我的表现:

  • 表达的还算正常,比起以前有进步
  • 好多话题我熟悉应该展开,是最好的
  • 好多以前没有想过的问题,及时反应还是很慢,不能够及时总结提炼观点

1. Difference Segue Excute in Container View to AVPlayerViewController

Demo on GitHub

Objective-C this method won’t be execute.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// In Swift should check the method, even user don't tap the cantainer view.
// But in Objective-C, *** Terminating app due to uncaught exception 'NSInvalidUnarchiveOperationException',
// reason: 'Could not instantiate class named AVPlayerViewController'
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {

// set up the player
if ([segue.identifier isEqual: @"videoSegue"]) {

NSString *path = [[NSBundle mainBundle] pathForResource:@"yishengsuoai" ofType:@"mp4"];
NSURL *url = [[NSURL alloc] initFileURLWithPath:path];

AVPlayerViewController *vc = segue.destinationViewController;
vc.player = [AVPlayer playerWithURL:url];
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// BEGIN avkit_ios_config
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?)
{
if segue.identifier == "videoSegue"
{
// set up the player
let videoURL = NSBundle.mainBundle().URLForResource("TestVideo",
withExtension: "m4v")
let videoViewController =
segue.destinationViewController as! AVPlayerViewController
videoViewController.player = AVPlayer(URL: videoURL)
}
}
// END avkit_ios_config

2. Conclusion

  • Swift + Container View(In IB)
  • Objective-C + AVPlayerLayer (In Code)

3. Definiton

- prepareForSegue:sender:

Notifies the view controller that a segue is about to be performed.

###Discussion

The default implementation of this method does nothing. Your view controller overrides this method when it needs to pass relevant data to the new view controller. The segue object describes the transition and includes references to both view controllers involved in the segue.

Because segues can be triggered from multiple sources, you can use the information in the segue and sender parameters to disambiguate between different logical paths in your app. For example, if the segue originated from a table view, the sender parameter would identify the table view cell that the user tapped. You could use that information to set the data on the destination view controller.

“Delete” the extra separator of UITableView with pain style

ios - Eliminate Extra separators below UITableView - in iphone sdk? - Stack Overflow

Solution 1

1
2
3
4
5
// "Delete" the extra separator of UITableView and the last one Separator in ecah Section
- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section {

return 0.01f;
}

height=0.01f

Solution 2

1
2
3
- (void)viewDidLoad {
self.tableView.tableFooterView = [[UIView alloc] initWithFrame:CGRectZero];
}

CGRectZero

Table View Style

UITableViewStylePlain

A plain table view. Any section headers or footers are displayed as inline separators and float when the table view is scrolled.

UITableViewStyleGrouped

A table view whose sections present distinct groups of rows. The section headers and footers do not float.

极客班iOS应用开发实战测试题二(8月31日)

习题:同学录

项目源码见 GitHub

v1.5以后使用 CocoaPods,.gitignore包含/pod,下载后请 pod install

1. 题目要求:

  • 实现一个同学录列表,包含姓名、简介和头像等信息。
  • 可以添加新的同学,支持拍照添加头像。
  • 可以查看每一个的同学详情页。
  • 实现存储所有同学的信息。

Classmates

2. 实际开发遇到的点:

1
2
3
// Updating Objects with primary keys
// This will make properties no settled be NULL
[GWClassmate createOrUpdateInDefaultRealmWithValue:newClassmate];
  • 两种编辑保存界面: 微信进入编辑的界面,Back 有个Alert确认保存; Phone.app 则是禁用右滑和 Back,显示 Cancel & Done,也是完善的方案。
    Edit View

  • Estimating default values for properties of Classmate

    • In Domain Logic: ClassmateDB.h/m (BEST Solution)
    • In Model: Classmate.h/m, Specify default values. BUT Realm isn’t support nil property.
    • In View Controller: if/esle
    • In Interface Builder: Setting a default value. Especially giving a default avatar is a nice solution for user.

    RLMPropertyType Constants Reference

  • “To use AVAsset to extract metadata informations, this post is useful.” http://stackoverflow.com/questions/16318821/extracting-mp3-album-artwork-in-ios

  • Playing video in iOS

  • Use Clang-Format -> LLVM (default)

3. 项目完成情况:

完成1.0:

  • 基本功能完成
  • 列表使用了缩略图,待优化直接保存数据库
  • 输入键盘时,View 自动升降一个键盘高度,以保持可见
  • 保存是检验用户名,不能为空
  • 使用 Realm 数据库
  • classmate.h/m 为 Model,四个属性分别为:uuid、name、info & avator
  • Using GWDetailViewController as super class to GWShowDetailViewController can easy to create a new view.

2015-08-31 20:17:00

完成1.1:

  • 使用 FMDB

把习题同学录的存储 Realm 换成 FMDB,午饭到16:00还没吃,我的学习新的知识的能力还比较低的。需要投入更多时间来学习了。

2015-09-01 16:09:00

完成1.2:

  • 换回 Realm
  • 增加编辑和删除功能

2015-09-01 20:00:00

完成1.3:

  • 随机添加修改和删除10000个classmate,以测试性能
  • Add a tab bar view as root view
  • UI layout like Wechat
  • Set UUID as parimary key
  • Updating Objects With Primary Keys
  • 把取消按钮改为 Button,并把 Segue to GWDetailViewController & GWShowDetailViewController, 改为 Modle
  • New a manaager class: GWClassmateDB, to abstract most useful SUQD methods and testing methods.

2015-09-02 18:20:47

完成1.4:

  • Change cell’s Accessory form Detail to Disclosure Indicator
  • Specify default values for properties of Classmate.h/m
  • Add Music Tab
  • Add a “red dot badge” on avatars’ view for test.
  • Change the classmates display style. Just looks like WeChat.app.

2015-09-06 11:43:19

Classmates v1.4

进行中1.5:

  • Adapting to MVVM
  • XCTest
  • Add a ‘Edit’ Button, and after taped the ‘back’ button in navigation change to “cancel”, add a “Done” in right. Just looks like Phone.app.
  • Add Sort by name, and index by initial
  • Add Search

4. BUG

以下的代码提示越界错误:Terminating app due to uncaught exception ‘RLMException’, reason: ‘Index is out of bounds.’可能的原因:

需要注意的是,写入操作会相互阻塞,而且其相对应的进程也会受到影响。这和其他的永久数据存储解决方案是一样的,所以我们建议你使用常用的,也是最有效的方案, 将所有写入放到一个单独的进程中。https://realm.io/cn/docs/objc/latest/

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:@"showDetail"]) {

NSIndexPath *indexPath = [self.tableView indexPathForCell:sender];
GWCustomTableViewCell *cell = (GWCustomTableViewCell *)[self.tableView cellForRowAtIndexPath:indexPath];

GWDetailViewController *vc = (GWDetailViewController *)segue.destinationViewController;

// vc.userid = cell.userid;
vc.name.text = cell.name.text;

}
}

最终解决方法是修改 Custom Cell 中添加一个 UILabel 的 text 来存储 UUID,并设置为隐藏。并添加使用 Detail Disclosure 测试 NSLog UUID。具体的越界问题,还需要在研究。

5. Further Reading

按照@杨武老师的学习和复习方法,我整理的 iOS Map。

回家从头开始做了这个 Map,确实对梳理知识点和复习记忆有很多的帮助。其中 Blocks、Network & Persistence 都还有待学习加强的点。以后学习新的还需要更新。

iOS Map

@杨武老师全天辅导

1. 学习,不仅要学,还要习:

  • 作业,不仅仅是作业。有信心拿给未来的面试官看不?

  • 作业是一个机会,做完基本功能后,琢磨着如何完善优化。要求自己的代码容易别人看懂。看我做的多牛逼,风格好。

  • 学习要复习,主动回忆+分散重复。尤其凭空去做。

    被动学习有个线索如书和 Demo,才有思路。只有做练习时,才能主动回忆知识。回忆方法:Mind Map

2. 课堂上共同整理了一份 iOS Map,涉及到的一些知识点:

  • NARC (new alloc retain copy)
  • ARC 不是垃圾收集,只是一个语法糖
  • Xcode Preprocess, Assembly and Disassembly
  • LLDB: help
  • 调试
  • IB: Outlets 直接访问 UI
  • 设计师设计一个功能的难度和程序员实现的难度
  • 常用 UI 文档介绍,追根究底
  • 单元测试等

3. 习题很重要:

  • 想学好,每天都要投入2个小时到开发中
  • 刷题提高技术

段老师的视频教程,采用都是比较通用是 Demo,方便以后代码复用。也是从项目实践经验来教授相关知识如 FMDB,这几节课获益匪浅。

Demo 源码见 Github,并添加了相关注释。

28. 沙盒机制

介绍几种数据持久化的方案,选择合适项目的方案:

  • 沙盒机制
  • NSFileManager
  • Plist、Archive数据归档 * SQLite3应用
  • NSUserDefaults

沙盒目录结构 NSDocumentDirectory

  • Application Bundle/
  • Documents/
  • Library/Caches/
  • Library/Perferences/
  • tmp/

29. NSFileManager & Plist

存取文档的方法封装一个通用的类BLUtility.h/m,方便调用。以下代码是测试保存文本文档。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//
// BLUtility.m

/**
* 创建目录
*
* @param dirPath 待创建的目录
* @param isDir 结尾是否是目录
*
* @return 是否创建成功
*/
+ (NSString*) getPathWithinDocumentDir:(NSString*)aPath {
NSString *fullPath = nil;
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);

if ([paths count] > 0) {
fullPath = (NSString *)[paths objectAtIndex:0];
if ([aPath length] > 0) {
fullPath = [fullPath stringByAppendingPathComponent:aPath];
}
}
return fullPath;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//
// BLArchiveViewController.m

/**
* 测试保存文档和 plist 的按钮关联的 Action 方法
*
* @param sender <#sender description#>
*/
- (void)writeFileButtonClicked:(id)sender {
// 1 - 测试字符串文档,保存到 Documents
// 1.1 - 生成并保存
NSString *string = @"ABC副经理";
NSString *stringFilePath = [BLUtility getPathWithinDocumentDir:@"string.text"];
[BLUtility createDirectory:stringFilePath lastComponentIsDirectory:NO];
[string writeToFile:stringFilePath atomically:YES encoding:NSUTF8StringEncoding error:nil];
}

30. Archive 数据归档和 NSUserDefaults

转化为 NSData,二进制的数据,利于存储和传输。

遵循 NSCoding 协议,即可实现编码和解码的类,进一步对其归档和解档。也用到了封装的类BLUtility.h/m。本例中文档保存到 NSUserDefaults:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//
// BLUser.m

/**
* 解码
*
* @param aDecoder 编码类型
*
* @return 解码得到的对象
*/
- (id)initWithCoder:(NSCoder *)aDecoder {
self = [super init];
if (self) {
self.userName = [aDecoder decodeObjectForKey:@"userName"];
self.email = [aDecoder decodeObjectForKey:@"email"];
self.password = [aDecoder decodeObjectForKey:@"password"];
self.age = [aDecoder decodeIntForKey:@"age"];
}
return self;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//
// BLUtility.m

/**
* 解档
*
* @param archivedData 待解档的 NSData
* @param key key
*
* @return 解档完成的对象
*/
+ (NSObject *) unarchiverObject:(NSData *)archivedData withKey:(NSString *)key {
if(archivedData == nil) {
return nil;
}

NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:archivedData];
NSObject *object = [unarchiver decodeObjectForKey:key];
[unarchiver finishDecoding];
return object;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//
// BLArchiveViewController.m

/**
* 读取按钮关联的 Action 方法
*
* @param sender <#sender description#>
*/
- (void) readButtonClicked:(id)sender {
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
NSInteger test = [userDefaults integerForKey:@"integer"];
NSLog(@"test = %li", test);

NSString *userDataPath = [BLUtility getPathWithinDocumentDir:UserDataName];
if ([userDataPath length] > 0 && [[NSFileManager defaultManager] fileExistsAtPath:userDataPath]) {
NSData *userData = [NSData dataWithContentsOfFile:userDataPath];
BLUser *user = (BLUser *)[BLUtility unarchiverObject:userData withKey:@"UserData"];
_userNameTextField.text = user.userName;
_emailTextField.text = user.email;
_passwordTextField.text = user.password;
_ageTextField.text = [NSString stringWithFormat:@"%ld", user.age];
}
}

###延伸阅读:

31. SQLite3

通过 FMDB 封装,得以 OBjective-C 访问 SQLite,完成常用增删改查。从语法和数据体积实现轻量级存储方案。这个方案稍后用在习题“同学录”上实践一下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
//
// BLPoemDB.h


#import <Foundation/Foundation.h>
#import "FMDatabase.h"
#import "BLPoem.h"

@interface BLPoemDB : NSObject {
/**
* 创建一个 FMDatabase 实例
*/
FMDatabase *_db;
}

/**
* 创建一个表
*
* @return 是否创建成功
*/
- (BOOL) createPoemTable;

/**
* 增加一首诗到表
*
* @param poem 待添加的诗
*
* @return 是否添加成功
*/
- (BOOL) addPoem:(BLPoem *)poem;

/**
* 查询所有诗
*
* @return 查询到的包含所有诗的数组
*/
- (NSMutableArray *) getAllPoems;

/**
* 查询被收藏的诗
*
* @return 查询到的包含所有收藏的诗的数组
*/
- (NSMutableArray *) getFavoritesPoems;

/**
* 更改是非收藏这一首诗
*
* @param favorite 是否收藏
* @param poemId 待更改的诗的 ID
*
* @return 是否更改收藏成功
*/
- (BOOL) setFavorite:(BOOL)favorite favoriteId:(NSInteger)poemId;

/**
* 删除指定 ID 的一首诗
*
* @param poemId 待删除的诗的 ID
*
* @return 是否删除成功
*/
- (BOOL) deletePoemWithPoemId:(NSInteger)poemId;

@end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
//
// BLPoemDB.m
// BLDataSaveDemo


#import "BLPoemDB.h"
#import "BLUtility.h"

#define BLPOEMDBNAME @"BLPoemDB.sqlite"

@implementation BLPoemDB

/**
* 初始化数据库 FMDatabase 实例
*
* @return 初始化得到的 FMDatabase 实例
*/
-(id) init {
self = [super init];
if (self) {
NSString *dbPath = [BLUtility getPathWithinDocumentDir:BLPOEMDBNAME];
NSFileManager *fileManager = [NSFileManager defaultManager];
BOOL existFile = [fileManager fileExistsAtPath:dbPath];
if (existFile == NO) {
NSString *poemDBPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:BLPOEMDBNAME];
[fileManager copyItemAtPath:poemDBPath toPath:dbPath error:nil];
}
_db = [[FMDatabase alloc] initWithPath:dbPath];
if ([_db open] == NO) {
return nil;
}
}
return self;
}


- (BOOL) createPoemTable {
[_db beginTransaction];
BOOL success = [_db executeUpdate:@"CREATE TABLE IF NOT EXISTS POEMTABLE ("
@"ID INTEGER PRIMARY KEY NOT NULL,"
@"POEMNAME TEXT NOT NULL,"
@"POETNAME TEXT NOT NULL,"
@"POEMCONTENT TEXT NOT NULL,"
@"WHETHERFAVORITE BOOL NOT NULL);"];
[_db commit];

if(!success || [_db hadError]) {
[_db rollback];
return NO;
}
return YES;
}

- (BOOL) addPoem:(BLPoem *)poem {
FMResultSet *rs = [_db executeQuery:@"SELECT ID FROM POEMTABLE WHERE ID=?", [NSNumber numberWithInteger:poem.poemId]];
if(rs && [rs next]) {
[rs close];
return YES;
}
[rs close];

[_db beginTransaction];
NSString *bookMark = nil;
BOOL success = [_db executeUpdate:@"INSERT OR IGNORE INTO POEMTABLE (ID,POEMNAME,POETNAME,POEMCONTENT,WHETHERFAVORITE) VALUES (?,?,?,?,?);",
[NSNumber numberWithInteger:poem.poemId],
poem.poemName,
poem.poetName,
poem.poemContent,
[NSNumber numberWithBool:poem.whetherFavorite],
bookMark];
[_db commit];

if(!success || [_db hadError]) {
[_db rollback];
return NO;
}
return YES;
}

- (NSMutableArray *) getAllPoems {
NSMutableArray *result = [[NSMutableArray alloc] init];
// FMResultSet *rs = [_db executeQuery:@"SELECT * FROM POEMTABLE WHERE ID=? AND WHETHERFAVORITE=?", [NSNumber numberWithInt:poem.poemId], [NSNumber numberWithBool:poem.whetherFavorite]];
FMResultSet *rs = [_db executeQuery:@"SELECT * FROM POEMTABLE"];
while([rs next]) {
BLPoem *poem = [[BLPoem alloc] init];
poem.poemId = [rs intForColumn:@"ID"];
poem.poemName = [rs stringForColumn:@"POEMNAME"];
poem.poetName = [rs stringForColumn:@"POETNAME"];
poem.poemContent = [rs stringForColumn:@"POEMCONTENT"];
poem.whetherFavorite = [rs boolForColumn:@"WHETHERFAVORITE"];
[result addObject:poem];
}
[rs close];
return result;
}

- (NSMutableArray *) getFavoritesPoems {
NSMutableArray *result = [[NSMutableArray alloc] init];
FMResultSet *rs = [_db executeQuery:@"SELECT * FROM POEMTABLE WHERE WHETHERFAVORITE=?", [NSNumber numberWithBool:YES]];
while([rs next]) {
BLPoem *poem = [[BLPoem alloc] init];
poem.poemId = [rs intForColumn:@"ID"];
poem.poemName = [rs stringForColumn:@"POEMNAME"];
poem.poetName = [rs stringForColumn:@"POETNAME"];
poem.poemContent = [rs stringForColumn:@"POEMCONTENT"];
poem.whetherFavorite = [rs boolForColumn:@"WHETHERFAVORITE"];
[result addObject:poem];
}
[rs close];
return result;
}

- (BOOL) setFavorite:(BOOL)favorite favoriteId:(NSInteger)poemId {
if (_db == nil) {
return NO;
}
[_db beginTransaction];
BOOL result = [_db executeUpdate:@"UPDATE POEMTABLE SET WHETHERFAVORITE=? WHERE ID=?", [NSNumber numberWithBool:favorite], [NSNumber numberWithInteger:poemId]];
[_db commit];
return result;
}

- (BOOL) deletePoemWithPoemId:(NSInteger)poemId {
[_db beginTransaction];
BOOL success = [_db executeUpdate:@"DELETE FROM POEMTABLE WHERE ID=?", [NSNumber numberWithInteger:poemId]];
[_db commit];
if(!success || [_db hadError]) {
[_db rollback];
return NO;
}
return YES;
}

- (void) dealloc {
[_db close];
_db = nil;
}

@end

延伸阅读:

32. 多线程和多媒体

简单介绍了常见多媒体的播放:

  • UIImagePickerViewController
  • UIActionSheet (👈名字谐音)
  • AVAudioPlayer
  • SystenSounID
  • MPMoviePlayerViewController
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#import <AVFoundation/AVFoundation.h>
#import <AudioToolbox/AudioToolbox.h>
#import <MediaPlayer/MediaPlayer.h>

@interface BLOneViewController ()<UIAlertViewDelegate, UIActionSheetDelegate, UIImagePickerControllerDelegate, UINavigationControllerDelegate> {
AVAudioPlayer *audioPlayer;
SystemSoundID systemSoundId;
}


#pragma mark - Play Music button action

- (IBAction)palyMusic:(UIButton *)sender {



if (!audioPlayer) {
NSString *mp3Path = [[NSBundle mainBundle] pathForResource:@"yishengsuoai" ofType:@"mp3"];
NSURL *mp3Url = [[NSURL alloc] initFileURLWithPath:mp3Path];
audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:mp3Url error:NULL];
audioPlayer.numberOfLoops = -1;
[audioPlayer prepareToPlay];
}
if ([audioPlayer isPlaying]) {
[audioPlayer stop];
sender.titleLabel.text = @"Play Music";

}else {
[audioPlayer play];
sender.titleLabel.text = @"Pasue Music";

}
}

33. 硬件访问、CALayer & Core Graphics

33.1. gravity and user acceleration

Useage: shaking device to feedback or 360° image

1
2
3
4
5
6
7
8
9
10
11
12
13
// 4 - Start update divice motion and print them
// The accelerometer measures the sum of two acceleration vectors: gravity and user acceleration.
NSOperationQueue *diviceMotionQueue = [[NSOperationQueue alloc] init];
[self.motionManager startDeviceMotionUpdatesToQueue:diviceMotionQueue
withHandler:^(CMDeviceMotion *motion, NSError *error) {

NSLog(@"\n \n-------------------------------------------------------------------------->\n \n 1 - attitude: %@ \n 2 - rotation rate x: %f, y: %f, z: %f,\n \n 3 - gravity x: %f, y: %f, z: %f \n \n 4 - user acceleration x: %f, y: %f, z: %f \n \n 5 - magnetic field accuracy: %d, x: %f, y: %f, z: %f \n \n <--------------------------------------------------------------------------\n \n" ,
motion.attitude,
motion.rotationRate.x, motion.rotationRate.y, motion.rotationRate.z,
motion.gravity.x, motion.gravity.y, motion.gravity.z,
motion.userAcceleration.x, motion.userAcceleration.y, motion.userAcceleration.z,
motion.magneticField.accuracy, motion.magneticField.field.x, motion.magneticField.field.y, motion.magneticField.field.z);
}];

Search the definiton of each new word, really 😄😄😄.

Do you believe that, At least I don’t think so.

Output:

1
2
3
4
5
6
7
8
9
10
11
12
13
-------------------------------------------------------------------------->

1 - attitude: CMAttitude Pitch: 0.751292, Roll: 0.555726, Yaw: 22.198534

2 - rotation rate x: 0.001511, y: -0.000795, z: 0.002454,

3 - gravity x: 0.009698, y: -0.013112, z: -0.999867

4 - user acceleration x: -0.002603, y: 0.000783, z: -0.014095

5 - magnetic field accuracy: -1, x: 0.000000, y: 0.000000, z: 0.000000

<--------------------------------------------------------------------------

33.2. CALayer

Modifying the Layer’s Appearance Properties.
Lots of properties really similar to Sketch.

33.3. Core Graphics

CGContextRef: An opaque type that represents a Quartz 2D drawing environment.

Path on CGContextRef vs Vector on Artboard in Sketch, by learn Sketch really make sense of those abstract concepts.

34. 自定义手势、Block & GCD

34.1. 手势

尽量设计使用常用的手势

设计一个小 Demo,实现:Shake Your iPhone To Send Feedback Email To Me😄

34.2. Block

比较简洁的函数,就在当前就可以调用。

34.3. GCD

Group queue:并行执行多任务比较牛。

35. APNS、Core Date、URL Scheme、单元测试、APP发布流

35.1. APNS

Remote Notification

1
2
3
4
// 1 - Register Remote Notification
// 2 - Register deviceToken
// 3 - Receive Remote Notification
// 4 - Update Icon Badge Number

稍后添加到 Classmates

35.2. Core Data

核心概念:

1
2
3
4
5
6
7
8
// 程序员和管理对象模式之间的桥梁,做数据库的增删改查等操作
@property (readonly, strong, nonatomic) NSManagedObjectContext *managedObjectContext;

// 数据库里的不同管理对象类型
@property (readonly, strong, nonatomic) NSManagedObjectModel *managedObjectModel;

// 数据库文件和程序之间的联系桥梁
@property (readonly, strong, nonatomic) NSPersistentStoreCoordinator *persistentStoreCoordinator;

稍后把 Classmate 整个 Core Data 的版本

35.3. URL Scheme

就是一个通过个地址访问系统软件或第三方软件,如地图和 Launch Center Pro。iOS 9之后估计有更多可能性。

35.4. 单元测试或断点测试

35.5. App 发布流程

设计模式课程给我的感受是:理清各种平时固定用的 API 的背后的原理或思路,尤其是写代码时候反复去审视开发框架和思路都有一定的帮助。

课件下载:https://github.com/gewill/GeekBand-iOS-Demo/tree/master/Design%20Patterns

11. 层次结构

动机:

  • 对象之间关系
  • 允许一组相互协作的对象当成单⼀对象处理
  • 无需⼦类化,实现⾃定义
  • 降低 parents class 复杂度
  • 使用 tree 结构,⽅便数据的存储,操作和搜索

Layers associated with a window

View Hierarchy in Xcode

其实还有更强大的工具:Reveal,附使用方法

节选自UIView.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@interface UIView(UIViewHierarchy)

@property(nonatomic,readonly) UIView *superview;
@property(nonatomic,readonly,copy) NSArray *subviews;
@property(nonatomic,readonly) UIWindow *window;

- (void)removeFromSuperview;
- (void)insertSubview:(UIView *)view atIndex:(NSInteger)index;
- (void)exchangeSubviewAtIndex:(NSInteger)index1 withSubviewAtIndex:(NSInteger)index2;

- (void)addSubview:(UIView *)view;
- (void)insertSubview:(UIView *)view belowSubview:(UIView *)siblingSubview;
- (void)insertSubview:(UIView *)view aboveSubview:(UIView *)siblingSubview;

- (void)bringSubviewToFront:(UIView *)view;
- (void)sendSubviewToBack:(UIView *)view;

- (void)didAddSubview:(UIView *)subview;
- (void)willRemoveSubview:(UIView *)subview;

- (void)willMoveToSuperview:(UIView *)newSuperview;
- (void)didMoveToSuperview;
- (void)willMoveToWindow:(UIWindow *)newWindow;
- (void)didMoveToWindow;

- (BOOL)isDescendantOfView:(UIView *)view; // returns YES for self.
- (UIView *)viewWithTag:(NSInteger)tag; // recursive search. includes self

// Allows you to perform layout before the drawing cycle happens. -layoutIfNeeded forces layout early
- (void)setNeedsLayout;
- (void)layoutIfNeeded;

iOS rendering tree:

  • UIView 负责界⾯显示和事件处理
  • CALayer 负责屏幕渲染(Layer Tree)
  • View/Layer 的变化需要通过渲染器实时渲染到屏幕上
  • layer.presentationLayer

The layer trees for a window

延伸阅读:

12. 响应链(Responder Chain)

The responder chain on iOS

节选自 UIResponder.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
NS_CLASS_AVAILABLE_IOS(2_0) @interface UIResponder : NSObject {
@private
}

- (UIResponder*)nextResponder;

- (BOOL)canBecomeFirstResponder; // default is NO
- (BOOL)becomeFirstResponder;

- (BOOL)canResignFirstResponder; // default is YES
- (BOOL)resignFirstResponder;

- (BOOL)isFirstResponder;

// Generally, all responders which do custom touch handling should override all four of these methods.
// Your responder will receive either touchesEnded:withEvent: or touchesCancelled:withEvent: for each
// touch it is handling (those touches it received in touchesBegan:withEvent:).
// *** You must handle cancelled touches to ensure correct behavior in your application. Failure to
// do so is very likely to lead to incorrect behavior or crashes.
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event;

- (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event NS_AVAILABLE_IOS(3_0);
- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event NS_AVAILABLE_IOS(3_0);
- (void)motionCancelled:(UIEventSubtype)motion withEvent:(UIEvent *)event NS_AVAILABLE_IOS(3_0);

- (void)remoteControlReceivedWithEvent:(UIEvent *)event NS_AVAILABLE_IOS(4_0);

- (BOOL)canPerformAction:(SEL)action withSender:(id)sender NS_AVAILABLE_IOS(3_0);
// Allows an action to be forwarded to another target. By default checks -canPerformAction:withSender: to either return self, or go up the responder chain.
- (id)targetForAction:(SEL)action withSender:(id)sender NS_AVAILABLE_IOS(7_0);

UIKit Inheritance

延伸阅读:

13. Prototype

如 UITableView 的 Prototype Cells:

  • 原型对象的基本特征是可以被复制 NSCopy 和 NSCoding 协议
  • 可以利用界面⼯具和支持复制的类确保互操作性
  • 原型对象使用深复制
  • 利用 NSKeyedArchiver 和 NSKeyedUnarchiver 实现深复制

总结

  • MVC,target-action
  • 两步创建,模版模式
  • 委托模式,观察者模式,消息通知,KVC/KVO
  • 归档和解档(Serialization),复制模式
  • 层次结构,响应链
  • prototype

一句话就是剥离或轻量化 Controller 中关于 View 和 Model 的部分。

MVVM 介绍

MVC 有人称之为 Massive View Controller。

MVC 并没有做太多事情来解决 iOS 应用中日益增长的重量级视图控制器的问题。在典型的 MVC 应用里,许多逻辑被放在 View Controller 里。它们中的一些确实属于 View Controller,但更多的是所谓的“表示逻辑(presentation logic)”,以 MVVM 属术语来说,就是那些将 Model 数据转换为 View 可以呈现的东西的事情,例如将一个 NSDate 转换为一个格式化过的 NSString。根据 Model 属性设置 UILabel、UIButton text。

Model-View-ViewModel

我们的图解里缺少某些东西,那些使我们可以把所有表示逻辑放进去的东西。我们打算将其称为 “View Model” —— 它位于 View/Controller 与 Model 之间:

Model-View-ViewModel 可以:

  • MVVM 可以兼容你当下使用的 MVC 架构。
  • MVVM 增加你的应用的可测试性。
  • MVVM 配合一个绑定机制效果最好。

并没有对我们的 MVC 架构做太多改变。还是同样的代码,只不过移动了位置。它与 MVC 兼容,带来更轻量的 View Controllers。

Model 是不可变的,所以我们可以只在初始化的时候指定我们 View Model 的属性。对于可变 Model,我们还需要使用一些绑定机制,这样 View Model 就能在背后的 Model 改变时更新自身的属性。此外,一旦 View Model 上的 Model 发生改变,那 View 的属性也需要更新。Model 的改变应该级联向下通过 View Model 进入 View。

更轻量的 View Controllers

  • 把 Data Source 和其他 Protocols 分离出来
  • 将 Domain Logic 移到 Model 中
  • 创建 Store 类
  • 把网络请求逻辑移到 Model 层
  • 把 View 代码移到 View 层

我们已经看到一些用来创建更小巧的 view controllers 的技术。我们并不是想把这些技术应用到每一个可能的角落,只是我们有一个目标:写可维护的代码。知道这些模式后,我们就更有可能把那些笨重的 view controllers 变得更整洁。

延伸阅读:

之前直接听英语单词不懂,基础概念也没有,很难真正明白。现在学习了许多中文课程,主要是概念有了,重新看一遍,容易理解的多。

所以最好是想看中文了解相关背景概念,熟悉一下其实的关键词对应的英文,然后再学习英文的课程。

项目驱动,习题驱动学习比较有劲头。

Table View

  • UITableView Segue: NSIndexPath *indexPath = [self.tableView indexPathForCell:sender];
  • UITableView Spinner: UITableViewController has an “activity indicator” built in
  • What if your Model changes? (void)reloadData;

Universal Applications

Generally recommned use different storyboards.

UISplitViewController

  • UISplitViewControllerDelegate: awakeFromNib
  • Hide Master in portrait orientation only (the default)
0%