前言
以前一直没有认真的考虑过自己所掌握的iOS相关知识体系,今天老实说不是很想敲代码 懒 ,正巧在看到了 [hherima] 大大的博客链接,看了看觉得很棒,本着好记性不如烂笔头的思想,根据前辈总结出来的知识树,梳理一下自己满是浆糊的大脑。虽说有一些东西年代的确颇为久远了 (看到不少2012、2013年的文章链接),但无伤大雅,依然非常值得一阅。
说的好像敲了就能掌握一样……
我预感到这篇肯定又会跟裹脚布一样又臭又长
8.1追加:写不动了……
UIKit 系
一、UIViewController
1 . UIViewController 在iOS中的作用
总的来说,UIViewController有5个作用
1. 管理视图(View Management)
2. 管理数据(Data Marshalling)
3. 作用于用户交互(Use interactions)
4. 管理资源(Resource Management)
5. 屏幕适配(Adaptivity)
这个总结……怎么讲,其实略略有些过时,现下已经不用这么“分工明确”的形容来描述UIViewController的作用了。特别是MVVM开发模式日渐流行的今天,打开一个项目经常会很惊奇的发现压根就找不到Controller --> 其实没消失,只是被整合了。
2 . UIViewController的生命周期
本来想写代码的,后来发现还是这张图表现的比较直观,也去掉了目前已弃用的 - viewDidUnload 方法 ,Life cycle 会不会显得高端点?
上面这是单个ViewController的生命周期(重点是View在其内部的声明周期),当涉及不同控制器之间的跳转的时候,比如vc1
push to controller: vc2
或者pop回来的时候:
说好的敲呢?怎么都是截图了!
3 . + (void)initialize +(void)load 这两个方法的调用时机
Apple的文档很清楚地说明了initialize和load的区别在于:load是只要类所在文件被引用就会被调用,而initialize是在类或者其子类的第一个方法被调用前调用。所以如果类没有被引用进项目,就不会有load调用;但即使类文件被引用进来,但是没有使用,那么initialize也不会被调用。
再简单点说
+(void)load
// 控制器被加载后立刻运行,不沿用父类方法。
+ (void)initialize
// 只在明确表示使用时会被调用,延用父类方法。
4 . - viewDidLoad ,- viewWillAppear 调用时机
按照Casa大大的说法,viewDidLoad中,应该只完成addSubview的操作,具体的setup,绘制,逻辑处理等等都不应该塞在viewDidLoad中。可能是因为我级别太低吧,暂时无法理解这种思想。但viewDidLoad中负责加载UI控件是毋庸置疑的,如果页面比较简单,也许可以把一些不复杂的数组处理也放在这里。
viewDidLoad 和 viewWillAppear 在代码中的出镜频率都挺高的,一般来说,如果在返回该控制器界面时,需要执行一些刷新的操作时,将reloadData操作放在 viewWillAppear 这里是比较好的一个选择。
5 . 在UIViewController中配置横竖屏
实话目前暂时还没怎么遇到需要横屏的需求,但不代表以后不会遇到
iOS - 视频横竖屏切换的一些事
上面这个链接里说了一些横竖屏切换时的代码,看着还不错,先放在这里,有机会补上。
6 . 交互方式
基本的交互方式就是push 和 present(modal) 了,
对应的回退方式是-popViewController 和 - dismiss 。
- pushViewController:(ViewController *) animate:(BOOL)
// -popViewController 默认退回上一个界面,也可以指定退回到某个界面或者至RootController
- presentViewController:(ViewController *) animated:(BOOL) completion:
// 以模态的方式从屏幕底部弹出一个窗口,占据整个屏幕(默认没有导航条)
// 回退方式为dismiss该控制器
还有一种是将一个controller的view直接加载在另一个controller上
但是要注意保活。
一个ViewController就写的有点想死了……我这到底是在干嘛啊
二、UIView
1 . UIView中 frame 与bounds的区别
frame :相对父视图中坐标系的值;
bounds: 相对于自身的坐标系的值。
在有一些需要执行UIView的- layoutSubViews 操作时,如果出现crash,
可以尝试将 **[super layoutSubviews]** 移至函数的末端,即先重新布局,再执行该父类中的方法。
// 据说目前只有iOS7会出现该crash,待考证。
// 个人觉得应该跟版本关系不大,还是代码写的有问题。
2 . 常用方法
只讨论添加视图、控件的话,最常用的莫过于 - addSubview 了
- addSubview;
// 1. 向视图中添加一个子控件
// 2. 添加的子控件会被塞到subviews数组的最后面
手写自定义view的话,[self.contentView addSubview:_adb]往往很多,
我个人喜欢堆在一起,比较有成就感……(其实不规范的)
此外,还有不少其他的添加方法 :
// 将子控件view插入到subviews数组的index位置
- (void)insertSubview:(UIView *)view atIndex:(NSInteger)index;
// 将子控件view显示到子控件siblingSubview的下面
- (void)insertSubview:(UIView *)view belowSubview:(UIView *)siblingSubview;
// 将子控件view显示到子控件siblingSubview的上面
- (void)insertSubview:(UIView *)view aboveSubview:(UIView *)siblingSubview;
// 将子控件view放到数组的最后面,显示在最上面
- (void)bringSubviewToFront:(UIView *)view;
// 将子控件view放到数组的最前面,显示在最下面
- (void)sendSubviewToBack:(UIView *)view;
用法不尽相同,视具体情况而定。
3 . UIView动画
使用 + animateWithDuration:可以执行一些简单的动画效果;
在动画完成后修改控件的alpha 或者执行hidden操作,使其展现/隐藏在用户面前。
// 代码演示框留白,暂时想不到什么特别具有代表性的……
三、CALayer
1 . 同根同源,与UIView都继承自NSObject,但中间少了UIResponder --> CALayer是一个抽象的简单类,不能响应用户交互;
2 . 主要作用就是在屏幕上展示绘制的矩形区域 ;
3 . UIView可以看做是CALayer的高级封装 (代理) 。
四、UIWindow
1 . 每一个iOS程序都有一个UIWindow ;
2 . keyWindow是指定的用来接收键盘以及非触摸类的消息;而且程序中每一个时刻只能有一个window是keyWindow。
3 . 可以通过scale当前的keyWindow来呈现一些缩放效果
五、UIImage 、UIImageView
关联较多的两个类,前者为显示的具体实例,后者是容器
UIImageView 默认是不具备用户交互功能的,但是可以通过修改userInterface 并添加手势来间接执行用户交互。
1 . UIImage的加载方法 (图片的加载方式)
iOS内存资源稀缺,相对而言,图片的展示又是比较占用资源的。因此UIImage也针对内存方面有不同的加载方式。
1. 最常用的 imageNamed:
[UIImage imageNamed:@"xxxx"];
// 该方式加载的图片会由系统缓存到cache中
2. imageWithContentsOfFile或者imageWithData
[UIImage imageWithContentsOfFile:path] ;
[UIImage imageWithData:data];
// 这两种方式都是不缓存的 ,根据场景的不同加以选用
简单的说,imageNamed:适合相对较小的图片,且经常使用
其他的适合较大的图片,或者只加载一次就完事了的。
PS:TableView中,因为存在复用机制,只出现一次的图片用"ImageNamed"也没啥毛病
其实随着这些年移动端设备的内存越来越大,内存大小已经不算是特别揪心的问题了,
但怎么讲,好的习惯要保持,这也是iOS流畅,受欢迎的原因之一
2 . 拉伸图片。
举例:下拉菜单,聊天气泡等等都会用到
- resizeableImageWithCapInsets:
// 设置好了之后可以达到只拉伸中间一小块区域,四周保持不变的效果。
3 . 加载gif 图片
iOS的gif图片似乎一直被人诟病,无论是存储还是什么,不过你要说苹果搞不定gif相关我是不信的,那么最后只能归结于苹果的坚持 信仰 了。
iOS如何加载gif图片
六、UILabel
1 . 默认是不支持从从顶部往下一行行排列文本的(自然的行文方式),不过变通的方法很多:使用封装好的三方库 , 或者设置numberOfLines = 0 后,动态改变label的高度 。
2 . 重写drawTextInRect:方法,可以自定义绘制区域,比如可设置Inset
3 . 有一些奇怪的用法,比如给label添加删除线(之前遇到一次);
4 . YYText真的很好很强大 (虽然严格的说不算是label)。
// 重写inset
[super drawTextInRect:UIEdgeInsetsInsetRect(rect, self.textInsets)]
七、UIButton
关于UIButton的一些详解
1 . 善于使用contentEdgeInsets,可以设置按钮内的文本边距
2 . UIButton的imageView frame 默认就是原始图片的大小,如果需要适配,需要加一些修改
3 . UIButton 上的title左对齐,仅设置label是没用的
设置按钮标题左对齐
btn.contentHorizontalAlignment = UIControlContentHorizontalAlignmentLeft;
// 这个属性是button独有的
八、UITextField
1 . 跟UITextView算是一母双生,很多地方很相近。
2 . 单行输入,在AlertSheet 时候不好使,得自己定制……
3 . 跟键盘的联动要注意相应的逻辑关系
任意界面隐藏键盘的方法:
[textfield resignFirstResponder]
正常情况下 , 足以应付大部分的 隐藏 / 收起 键盘的需求。
但在某些特殊情况,略略有些麻烦,好在还有其他的解决方案
1. ControllerA出来时候,隐藏当前任意view的键盘。
[[[UIApplication sharedApplication] keyWindow] endEditing:YES];
2. 加入不方便获取第一响应者,还可以使用这个方法
[[UIApplication sharedApplication] sendAction:@selector(resignFirstResponser)
to:nil from:nil forEvent:nil];
九、UIScrollView
嗯……好像也没什么好说……
虽说相对于UITableView之类没什么存在感,但其各种代理方法还是经常用到的,另外注意contentOffset 滚动位置
、contentSize 实际尺寸
、contentInset 滚动边距
这三个很像的家伙……别混淆了。
scrollView 不能滚动的三种可能的原因
1 - 没有设置contentSize;
2 - scrollEnabled = NO;
3 - 没有接收到触摸事件:userInteractionEnabled = NO;
十、UITableView
这里能扯的东西实在是太多了,感觉单独开一篇都不算过分,但为了行文的流畅 来都来了,还是弄一下意思意思……
1 . 重用机制 (缓存池三部曲)
// 记得先注册
1. 设定可重用标识符,使用static修饰
2. 从缓存池中获取
// - dequeueReusableCellWithIdentifier 从重用池中获取,可能是nil
// - dequeueReusableCellWithReuseIdentifier 同上,但是不会是nil
3. 如果无法从缓存池中获取,再创建一个新的
其实能讲的还很多,但核心机制还是上面说的几条
2 . 插入、移动、删除TableView 的section 或者 cell需要严格遵循顺序
如上图所示:
1. 首先更新数据源中的相关数据
2. 调用相应的collection view方法删除或者插入section或item
// 必须严格遵守顺序,不然就是数组越界等等等一系列问题了
3 . TableView右侧音序索引 (通讯录右侧拼音索引)
其实很简单 ,只是代码不常用而已
-(NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView {
return _pinyinIndexArray;
}
_pinyinIndexArray当然不是凭空冒出来的,初始化一下即可
_pinyinIndexArray = @[@"A",@"B",@"C",@"D",@"E",@"F",@"G",@"H".....];
4 . 刷新列表
普通状态下,使用[tableView reloadData] 即可
某些需求要求只刷新某一行cell或者某一个section 可以使用下面的方法:
//一个section刷新
NSIndexSet *indexSet=[[NSIndexSet alloc]initWithIndex:2];
[tableview reloadSections:indexSet withRowAnimation:UITableViewRowAnimationAutomatic];
//一个cell刷新
NSIndexPath *indexPath=[NSIndexPath indexPathForRow:3 inSection:0];
[tableView reloadRowsAtIndexPaths:[NSArray arrayWithObjects:indexPath,nil] withRowAnimation:UITableViewRowAnimationNone];
5 . sectionHeader 浮动效果
UITableViewStylePlain模式下,header不浮动
黑科技点我
十一、UIDevice
常用的几种UIDevice相关方法
// 1. 设备名
[UIDevice currentDevice].name;
// 2. 系统版本号
[[UIDevice currentDevice].systemVersion doubleValue];
// 3. 屏幕旋转方向
[[UIDevice currentDevice] orientation]
// 4. 区分iPad还是iPhone
[UIDevice currentDevice].userInterfaceIdiom;
十二、UIScreen
1 . 最常用的应该还是根据UIScreen 获取屏幕的宽高,可以写成宏来进行相关计算 - > 等比例适配
2 . 根据UIScreen 绘制1像素的线 <-> 保持边距不变,等比例拉伸。
2用的应该不多,个人还是比较习惯直接使用UIView或者UIImageView
直接绘制一条宽度为1的作为separtorLine `
娘的终于写完了……一部分……
NSObject 系
一、NSObject
NSObject,老实说光听这个名字就知道一定很屌!,事实上也的确如此,他是iOS中几乎所有类的基类和协议。绝大多数非视图型号的文件,subClass写这个也是八成都不会错
具体使用:
1 . - isKindOfClass:和 - isMemberOfClass:的异同
same: 都是用来确定/检测某个对象的从属关系 (belong to class )
diff: - isKindOfClass: 还能检测出对象是否是由class派生出来的
// 别纠结,用isKindOfClass (优先级较高)
2 . respondsToSelector: 用法
需要调用协议里的可选方法时,我们不知道遵循协议的类是否已经实现了这些方法。
这时respondsToSelector方法来判断遵循协议的类是否已经实现了某个方法。
if ([self.delegate respondsToSelector:@selector(doSomething:)]) {
[_delegate doSomething:string];
}
// 本质上仍然是从安全性角度考虑,加的一道防护,防止程序崩溃
- conformsToProtocol :
// 可以测试某个对象或者类是否遵守了协议
3 . description 调试方法
允许一个对象返回一个字符串来描述它的内容;这个常用于调试debug
- (NSString *)description{
return [NSString stringWithFormat:@"age = %i, string = %@",_age,_str];
}
// 某些情况下比NSLog好用的多,用于打印程序员需要的信息
4 . encodeWithCoder: 和 initWithCoder:方法
NSCoding协议中仅有的两个方法:
// 对象编译它的实例变量
- encodeWithCoder:
// 允许一个对象初始化它自身的解码实例变量
- initWithCoder:
还有一些感觉更纯理论 (如 "__weak是如何实现自动置nil的"),也的确略微底层了一些 看都看不懂写个毛,先挖个坑,有机会填。
二、NSString & NSMutableString
1 . NSString作为属性时候,用copy还是strong修饰?
看在什么情况下吧
基本上,如果是作为Model的属性时,都选用copy
如果是在Controller或者View层,根据实际情况来进行选用 (参考NSMutableArray应该还是选用strong比较好)
二、NSArray & NSMutableArray
1 . 深拷贝 & 浅拷贝
2 . containsObject
此函数在对比数组中元素的时候,调用元素的isEqual的返回值。
三、NSDictionary & NSMutableDictionary
1 . 大致上跟NSArray差不多,但取值时候,最好先判断object的类型
// 判断类型
if ([object isKindOfClass:[NSString class]]) {
// code here
};
四、NSNumber ,NSInterger ,NSUInterger ,NSRange
1 . 注意使用场合,%zd %ld 等等的区别即可。
无符号长整型用的场合没有想象中的多
五、NSDate & NSDateFormatter & NSCalendar
1 . 目前接触的比较多的是根据服务器返回的国际标准时间戳经过初始化后改为较为"X小时前" "X天前" 这样比较直观的表示类型。
2 . 进行时间比较,获取需要的时间段。 参考新浪微博的时间戳处理
有些时候会显示有8小时的时间差:
根本原因是格林威治时间与北京时间(东八区)的时差,以后台返回的时间为准
// 解决代码
NSDate *date = [NSDate date];
NSTimeZone *zone = [NSTimeZone systemTimeZone];
NSInteger interval = [zone secondsFromGMTForDate: date];
NSDate *localeDate = [date dateByAddingTimeInterval:interval];
NSLog(@"enddate=%@",localeDate);
六、NSFileManager
1 . 删除文件的时候先判断是否存在是个好习惯
好像还有别的用法,一时想不起来,先挖个坑
七、NSTimer
1 . NSTimer需要跟NSRunLoop协同工作才会有效,即定时器需要正确加入到运行循环中才能生效,比如自动图片轮播器、或者定时提醒/推送等。
2 . 但是由于runLoop同时又管理着各种各样的资源,所以NSTimer的精确性不是非常高(应付一般的需求足够了)。总的来说,NSTimer并不是一种实时的机制,会存在些许延迟,延迟的程度跟当前的执行情况有一定关系 。
// 每隔ti秒,调用一次aTarget的aSelector方法,yesOrNo决定了是否重复执行这个任务
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti
invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo;
// 通过invalidate方法可以停止定时器的工作,一旦定时器被停止了,就不能再次执行任务。
// 只能再创建一个新的定时器才能执行新的任务
- (void)invalidate;
// 启动定时器:
– (void)fire
// 暂停
[timer setFireDate:[NSDatedistantFuture]];
八、其他的一些常用项
1 . NSLog
常用的 ** 暴力 ** 打印 ,在项目中最好设置专门的Debug用的Log宏。
2 . NSUserDefaults
可以用来做一些简单的本地存储/读取操作
1. 用来存储一些数据量较小的属性/对象,限于:
NSString, NSNumber, NSDate, NSArray, NSDictionary
2. 并不是立刻写入的,可能会需要等待一段时间,如果需要立刻同步,需要执行:
[[NSUserDefaults standardUserDefaults] synchronize]
举例:
// 存储UISwitch的值
- (IBAction)switchChanged:(id)sender{
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; //获取 NSUserDefaults 单例
[userDefaults setBool:_theSwitch.on forKey:@"switchValue"];
[[NSUserDefaults standardUserDefaults] synchronize];
}
// 读取并赋值
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
BOOL sw = [userDefaults boolForKey:@"switchValue"];
[_theSwitch setOn:sw];
用来判断是不是程序安装后第一次进入App很方便
if(![[NSUserDefaults standardUserDefaults] boolForKey:@"firstInstall"]){
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"firstInstall"];
NSLog(@"第一次启动"); //do something ...
} else {
NSLog(@"不是第一次启动"); //do something else ...
}
3 . NSClassFromString & NSStringFromClass
顾名思义,从字符串中获取类 / 从类名中获取字符串
似乎是有一些黑科技在里面,达到逻辑解耦的效果
Foundation系
这里的知识点实在是太过于零散了,也比较抽象,更多的在于理解起码要知道是个什么玩意, 就不按照上面的 一、二、三、分类了,
NSIndexPath 链式结构,tableView中用的相对多一点
初始化[NSIndexPath indexPathForRow:0 inSection:1];
NSError
处理网络请求的时候经常会遇到
NSException
挖坑待埋
NSStringEncoding
NSString的编码格式,处理某些格式转换时需要用到
NSProgressIndicator
挖坑待埋
NSBoundle
相当于一个文件目录类,包括了程序中使用的资源:图片
,音频片段
,nib
文件等, [NSBoundle mainBloundel] 后调用
NSNetServiceBrowser
在 Cocoa 层, NSNetService API 提供了一个接口, 用于发布和解析 Bonjour 服务的地址信息. 可以通过 NSNetServiceBrowser API 探测网络上可用的服务. 发布 Bonjour 服务(甚至是使用 Cocoa 层的 API)需要理解 Coer Foundation 才能配置好通信所需的 socket.
说的好玄乎,听不懂啊……
NSValue
可以包装任何一个对象 ===> 用NSValue 将struct存到NSArray 和 NSDictionary中
NSURLSession
iOS9中取代了原来冗长的NSUrlConnection 系API ,
主要使用NSURLSession 及其子类 NSURLSessionTask
主要提供了以下功能
1. 通过URL将数据下载到内存
2. 通过URL将数据下载到文件系统
3. 将数据上传到指定URL
4. 在后台晚上上述功能
NSURLRquest
缓存策略
,主要用来包装/指定项目中比较具体的请求信息模式
拥有好几种不同的策略,根据使用场合略有区别。
NSInputStream & NSOutputStream socket编程
挖坑待埋
其实就是没用过……
NSPredicate
谓词查询,原理和用法都类似于SQL中的where
NSLayoutConstraint
AutoLayout 的核心所在,绝大多数三方的布局或计算宽高等的库都只是变着法子为控件添加各种各种 NSLayoutConstraint 约束 不知道说的对不对,前辈说的
NSLock & NSRecursiveLock & NSCondition 多线程锁
本质上是为了防止多份线程同时调用同一资源可能引发的错误。
目前接触过加锁都是比较简单的,也算是一个坑吧。
@synchronized {
//todo
}
上面这个同样也是同步锁,在项目中用来在执行某些特殊读取操作的时候可以考虑加上。
讲真的我已经后悔写这一块了,毛用没有,徒劳打击自己的自信心
还真不是一次搞的定的,先偷个懒吧……