🍓知识库 iOS新手入门 iOS笔试题 iOS面试题 iOS开发知识整理合集
1年=小学 2年=初中 3年=高中 4年+=大学
年限对应表可供参考
结合其他资料学习最佳,该知识库只是辅助,可做‘课外阅读’使用
截止2019年现有的iOS开发资料已经超过16,800,000个,这里整理的主要【不是如何写代码,而是iOS开发需要的一些理论知识和扩展。】根据作者经验,我把他们按照小学、中学、大学来分阶段整理。这样做的好处是可以让不同阶段的程序员更好地进步,希望这里的知识对你写代码有一定帮助。也可以有目的性的了解自己下一阶段需要掌握的知识。
划分年级的想法是我仿照自己人生轨迹,普通人都经历的过程,这个过程会让我们更加身临其境。有小学再开始一次的想法也可以增加我们学习的乐趣。对于iOS新手根据年级从小学一年级逐步学习,在学习其他iOS资料的同时补充一些知识可以更快理解‘这样写’背后的含义,也不至于发现需要学习的太多而无从下手。如果能全部掌握小学到大学的知识,那么常规的iOS问题对你已不再话下了。
开发语言Objective-C和Swift
由于作者对Objective-C运用更多更为熟练,所以涉及到代码例子部分还是以OC为主,但也会尽可能附上Swift版的链接。
对于本文的例子和部分常用代码已封装到bench_ios这个库中,所有【我的应用】标签下的代码均在这个库中,也可通过CocoaPods快速安装。
关于内容的严谨性
这里的年级的划分主要依据作者自身iOS开发成长的道路来划分。作者本身接触iOS开发已有5年+,从iOS4时代开始投入学习,起源是被一个ipod touch上的划水果app吸引。消耗业余时间整理了这些知识点,限于作者能力,有些知识点的年级划分或者整理可能有所偏差和不准确,但每一篇都是经过认真审核和查阅大量资料整理,也希望有大神帮忙指点完善。
开始
我们把这个合集看成一个游戏,从小学到大学读完就通关了。下面一个小测验可快速定位新来的你在哪一个层级。如果您都能自信地回答,请点击左上角关闭按钮离开😂,答案在各年级的文章中~
1、以下代码输出什么?
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
Person *person = [[Person alloc] init];
person.age = 10;
__weak Person *weakPerson = person;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(4.0 * NSEC_PER_SEC)),
dispatch_get_main_queue(), ^{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"2-----age:%p",person);
});
NSLog(@"1-----age:%p",weakPerson);
});
NSLog(@"touchesBegan");
}
2、下面的代码输出什么?
@implementation Son : Father
- (id)init
{
self = [super init];
if (self) {
NSLog(@"%@", NSStringFromClass([self class]));
NSLog(@"%@", NSStringFromClass([super class]));
}
return self;
}
@end
3、#import跟 #include 有什么区别,@class呢,#import<> 跟 #import””有什么区别?
4、RSA的两个常用用途?
5、堆排序的排序过程?
6、以下代码的时间复杂度各多少?
A
for (i=1; i<=n; i++)
x++;
for (i=1; i<=n; i++)
for (j=1; j<=n; j++)
x++;
B
x=1;
for(i=1;i<=n;i++)
for(j=1;j<=i;j++)
for(k=1;k<=j;k++)
x++;
答案在
iOSStudyProject
欢迎fork修改,如果有帮助,希望给颗小星星~
小学
什么是程序员👨
如何开始iOS开发学习
首先需要准备必要的工具,嗯,mac的电脑,然后安装Xcode。
一切准备就绪后,就可以网上搜一些最简单的例子,了解Xcode的工作流程。
使用XCode开发第一个IOS程序
对照教程自己搞一遍就差不多了解开发流程了。
这之后的学习会相对困难起来,你可以逐步来学习UIKit中的各种控件。如果你的英文ok,强烈推荐看斯坦福公开课的教程。
斯坦福大学公开课:iOS 8开发
斯坦福大学公开课:iOS 7应用开发
这个教程至少需要看两遍,第一遍是入门时看,肯定会看的云里雾里,然后需要你开发一段时间,就是亲自打代码做一些demo之后,再去看一遍,会有不同体验。实际上还可以看更多便,因为你在iOS上投入一定时间后会对之前的知识有更深的理解。
那么其他的就交给时间了,你需要投入一定时间的学习,写代码,思考。如果你聪明,那么进步也会很快。
一些常用网站
文章
cocoachina
segmentfault
objc
talk
掘金
教程
raywenderlich
raywenderlich store
demo
论坛
提问
博客
我的CSDN
ibireme的博客
美团技术团队
OneV's Den
唐巧的博客
#import和#include和@class的区别
import一个文件只能被导入一次,因此不会引起交叉编译。包含c/c++头文件时用include。
import会包含这个类的所有信息,包括实体变量和方法(.h文件中),而@class只是告诉编译器,其后面声明的名称是类的名称。
在编译效率方面考虑,如果你有100个头文件都#import了同一个头文件,或者这些文件是依次引用的,如A–>B, B–>C, C–>D这样的引用关系。当最开始的那个头文件有变化的话,后面所有引用它的类都需要重新编译,如果你的类有很多的话,这将耗费大量的时间。而是用@class则不会。
如果有循环依赖关系,如:A–>B, B–>A这样的相互依赖关系,如果使用#import不会出现编译错误。
'#import<>'用于包含系统文件 会扫描系统文件目录
'#import""'用于包含本项目中的文件 扫描项目文件目录
Property属性
成员变量
一般而言,成员变量用于类内部,天生私有属性,因为成员变量本身不能生成set和get方法,不能被外界访问。
属性变量
鉴于成员变量的私有属性,不便于与外界沟通,于是便有了属性变量;它存在的意义是允许其他对象访问到该变量(因为它自动生成了set和get方法)。
实例变量
如果变量的数据类型是一个类则称这个变量为实例变量。因为实例变量是成员变量的一种特殊情况。
【成员变量】=【实例变量】+【基本数据类型的变量】
成员变量的权限修饰符
public:公开型,外界可以通过“->”方式直接使用。
protected:受保护型,只能被本类和子类访问;(默认类型)。
private:私有型,只能本类使用,子类也不可以访问。
属性修饰符
readonly:相当于.h文件中只有get方法的声明,没有声明set方法,不接搜写入操作。
readwrite:默认类型,set和get方法都有声明,接受读写操作。
nonatomic:非原子性,不涉及加锁和解锁。
atomic:原子性,属性读写层次上的线程安全(默认类型。
assign:值传递
copy:也是值传递,不改变对象的引用计数(拷贝对象内容,另存他处,返回新地址)。assign可以用非OC对象,而weak 必须用于OC对象。
assign:不改变对象的引用计数,对象被释放后成为野指针(即仍旧指向该对象,所以尽量不要用assign修饰对象类型)
weak:不该变对象的引用计数,对象被释放后会被自动设置为nil。
strong:将对象的地址赋给变量,同时使对象的引用计数加1。
retain:将对象的地址赋给变量,同时使对象的引用计数加1。
注意点
当我们访问对象类型的时候,可能访问的是指针本身,也有可能访问的是指针所指向的内存区域(对象);对于前者,往往是赋值操作,意在改变指针的指向;对于后者,我们常常是访问(读写)内存区域本身。
因而,Property可以分为三类:
- Primitive Property
- Pointer Property
- Memory
svn和git
通过一二年级的学习,掌握了一些写代码的基本要素。如果你使用的电脑坏了,并且(最坏的情况☹️)硬盘也损坏了,那么你的代码将全部白费了。如果从公司角度看,一位员工带着他的电脑离职了,公司没有备份,那将是一笔巨大损失。所以我们会将代码托管到服务器。那么最常使用的代码管理方式就是svn和git了。
svn和git的区别
SVN是Subversion的简称,是一个开放源代码的版本控制系统。
Git是一个开源的分布式版本控制系统
对比
svn是将代码保存在中央服务器,所有人向中央提交代码、下载代码。缺点就是对中央服务器的依赖较高。
git的记录版本历史只关心文件是否发生变化,而不关心具体变化的内容。每一个克隆的版本库权限相等。每个人都有自己独立的仓库(在本地),当你完成后再push给中央服务器,其他人通过pull拉取你提交的代码。所以git脱离网络你也可以提交代码。
SVN与Git比较的优缺点差异
90%人都不知道:SVN 和 Git 的一些误解和真相
从github使用git clone和download zip的区别
采用git clone的项目包含.git目录,这里面有历史版本信息
采用下载zip文件的是没有版本历史信息的。只是当前分支的最新版本
github中fork和clone
fork和clone的区别在于:fork是GitHub操作,是复制一个仓库(包括文件,提交历史,issues,和其余一些东西),复制后的仓库在你自己的GitHub帐号下。clone是Git操作,是发送"复制仓库"的命令给GitHub,复制后的仓库在你本地计算机上。
那么如果你需要将代码提交给原作者,你就需要使用fork。在完成常规的commit后,你Github上的仓库更新了,这时还没有提交给原作者,当你发送Pull Request时(在github页面Pull Request选项可操作),原作者就会收到你提交的代码,并由他/她决定是否合并。
GitHub的Fork 是什么意思
self.和_的区别
self.会调用set/get方法,会使引用计数加一。在类外部也就是其他类里访问这个类的变量时用。
而_是直接访问成员变量。在类内部访问变量的时候用。
更多解释
懒加载
懒加载就是只在用到的时候才去初始化。也可以理解成延时加载。
我觉得最好也最简单的一个例子就是tableView中图片的加载显示了, 一个延时加载, 避免内存过高,一个异步加载,避免线程堵塞提高用户体验。
中文本地化
如果默认的语言是英文,那么在键盘弹出后的Select、Copy、Paste都是英文显示,如果需要中文,只需在PROJECT中的Localizations里添加中文,添加后如果手机默认语言是中文,就会显示中文选择、复制、粘贴。
isKindOfClass和isMemberOfClass
isKindOfClass:作用是某个对象属于某个类型或者继承自某类型。
isMemberOfClass:某个对象确切属于某个类型。
lldb(gdb)常用的控制台调试命令
- p 输出基本类型。是打印命令,需要指定类型。是print的简写
p (int)[[[self view] subviews] count] - po 打印对象,会调用对象description方法。是print-object的简写
po [self view] - expr 可以在调试时动态执行指定表达式,并将结果打印出来。常用于在调试过程中修改变量的值。
- bt:打印调用堆栈,是thread backtrace的简写,加all可打印所有thread的堆栈
- br l:是breakpoint list的简写
iOS的沙盒目录结构
- Application:存放程序源文件,上架前经过数字签名,上架后不可修改。
- Documents:常用目录,iCloud备份目录,存放数据。(这里不能存缓存文件,否则上架不被通过)
- Library:
- Caches:存放体积大又不需要备份的数据。(常用的缓存路径)
- Preference:设置目录,iCloud会备份设置信息。
- tmp:存放临时文件,不会被备份,而且这个文件下的数据有可能随时被清除的可能。
GCD 与 NSOperation
GCD 基于C语言的底层API,GCD主要与block结合使用,代码简洁高效。
NSOperation 属于Objective-C类,是基于GCD更高一层的封装。复杂任务一般用NSOperation实现。
反射是什么?
反射是指计算机程序在运行时(Runtime)可以访问、检测和修改它本身状态或行为的一种能力。用比喻来说,反射就是程序在运行的时候能够“观察”并且改变自己的而行为。
比如通过类名,生成类 Class * tempClass = NSClassFromString(str);
RunLoop 与 Runtime
从字面上讲就是运行循环,它内部就是do-while循环,在这个循环内部不断地处理各种任务。
一个线程对应一个RunLoop,基本作用就是保持程序的持续运行,处理app中的各种事件。通过runloop,有事运行,没事就休息,可以节省cpu资源,提高程序性能。
Runtime又叫运行时,是一套底层的C语言API,其为iOS内部的核心之一,我们平时编写的OC代码,底层都是基于它来实现的。
xml中SAX和DOM区别
SAX和DOM是两种解析方式。
SAX
SAX解析方式:逐行扫描文档,一遍扫描一遍解析。相比于DOM,SAX可以在解析文档的任意时刻停止解析解析,是一种速度更快,更高效的方法。
优点:解析可以立即开始,速度快,没有内存压力。
缺点:不能对结点做修改。
DOM
DOM解析方式:DOM解析器在解析XML文档时,会把文档中的所有元素,按照其出现的层次关系,解析成一个个Node对象(节点)。
优点:把XML文件在内存中构建属性结构,可以遍历和修改节点。
缺点:如果文件比较大,内存有压力,解析的时间会比较长。
XMPP
XMPP是一种以XML为基础的开放式实时通信协议。
简单的说,XMPP就是一种协议,一种规定。就是说,在网络上传东西,XMM就是规定你上传大小的格式。
xmpp有很多语言写的库,根据需要可以在这里xmpp org寻找你需要的。作者只使用过XMPPFramework和gloox,在使用gloox时高版本在iOS上会编译不通过只能使用低版本,后续因为项目时间原因取消使用gloox了。报的编译错误如下,这里希望有高手能提供帮助。
[图片上传失败...(image-d64bcf-1553653996662)]{:height="100"}
对于XMPPFramework,本人使用时遇到几个小问题,但整体感觉还是不错的,满足基本需求没问题,可以放心使用。
XMPP是基于XML的协议,用于即时消息(IM)以及在线现场探测。最初,XMPP作为一个框架开发,目标是支持企业环境内的即时消息传递和联机状态应用程序。
XMPP的前身是Jabber(1998年),是一个开源组织定义的网络即时通信协议。
XMPP是一个分散型通信网络。
XMPP是一个典型的C/S架构。而不是像大多数即时通讯软件一样,使用P2P客户端到客户端的架构。也就是说在大多数情况下,当两个客户端进行通讯时, 他们的消息都是通过服务器传递的。
优点:
- 采用这种架构,主要是为了简化客户端,将大多数工作放在服务器端进行。
- 可扩展。
- 安全。
缺点:
- 数据负载过重XML。
- 没有二进制传输。
扩展
大致说一下使用流程和一些必要的方法。
@interface XMPPHelper : NSObject
//@property(nonatomic,assign) BOOL isConnecting;
+ (instancetype)getInstance;
- (BOOL)initWithUserName:(NSString *)userName andPassword:(NSString *)password andHostName:(NSString *)hostName andDomain:(NSString*)domain andHostPort:(UInt16)hostPort andInfoDic:(NSDictionary *)infoDic;
- (BOOL)connect;
- (void)disconnect;
- (BOOL)isConnected;
@end
我在使用时创建了一个类,主要有初始化、连接、主动断开、连接状态监测这几个方法。
它的使用过程是 连接-认证-发送消息
初始化发送连接请求
- (BOOL)connect
{
if ([self isConnected]) {
[ShareCallback xmppCallback:@"success"];
return ;
}
if (_jid) {
[self.xmppStream connectWithTimeout:5 error:nil];
return ;
}
_storage = [XMPPStreamManagementMemoryStorage new];
self.xmppStream = [[XMPPStream alloc] init];
[self.xmppStream addDelegate:self delegateQueue:dispatch_get_global_queue(0, 0)];
//设置聊天服务器地址
self.xmppStream.hostName = _hostName;
//设置聊天服务器端口 默认是5222
self.xmppStream.hostPort = _hostPort;
//设置Jid 就是用户名
_jid = [XMPPJID jidWithUser:_userName domain:_domain resource:@"smack"];
self.xmppStream.myJID = _jid;
//接入断线重连模块
_xmppReconnect = [[XMPPReconnect alloc] init];
_xmppReconnect.reconnectTimerInterval=5;
_xmppReconnect.reconnectDelay=0;
[_xmppReconnect setAutoReconnect:YES];
[_xmppReconnect activate:self.xmppStream];
[_xmppReconnect addDelegate:self delegateQueue:dispatch_get_global_queue(0, 0)];
//接入流管理模块,用于流恢复跟消息确认,在移动端很重要
_xmppStreamManagement = [[XMPPStreamManagement alloc] initWithStorage:_storage];
_xmppStreamManagement.autoResume = YES;
[_xmppStreamManagement addDelegate:self delegateQueue:dispatch_get_main_queue()];
[_xmppStreamManagement activate:self.xmppStream];
NSError * error = nil;
//验证连接
[self.xmppStream connectWithTimeout:5 error:&error];
if (error) {
NSLog(@"连接失败:%@",error);
return NO;
}
else
{
NSLog(@"连接成功!");
return YES;
}
}
注意是先建立连接,如果连接成功,在xmppStreamDidConnect回调中验证用户名和密码。
//输入密码验证登陆
- (void)xmppStreamDidConnect:(XMPPStream *)sender
{
NSError *error = nil;
[[self xmppStream] authenticateWithPassword:_password error:&error];
}
错误的回调这里忽略了,如果验证成功,也会有回调,在回调里发送ping,这样服务端就可以标记已经连接了。
//登录成功
- (void)xmppStreamDidAuthenticate:(XMPPStream *)sender
{
NSLog(@"xmpp登录成功%s",__func__);
//发送在线通知给服务器,服务器才会将离线消息推送过来
// [XMPPPresence alloc]initWithType:@"" to:<#(XMPPJID *)#>
XMPPPresence *presence = [XMPPPresence presence]; // 默认"available"
[[self xmppStream] sendElement:presence];
[_xmppStreamManagement enableStreamManagementWithResumption:YES maxTimeout:10];
XMPPAutoPing *xmppAutoPing = [[XMPPAutoPing alloc] init];
xmppAutoPing.pingInterval = 10.0;
[xmppAutoPing activate:_xmppStream];
[xmppAutoPing addDelegate:self delegateQueue:dispatch_get_global_queue(0, 0)];
// NSXMLElement *enable = [NSXMLElement elementWithName:@"r" xmlns:@"urn:xmpp:sm:3"];
// [[self xmppStream] sendElement:enable];
//启用流管理
// [_xmppStreamManagement enableStreamManagementWithResumption:YES maxTimeout:0];
}
这里连接成功后就可以在代理里接收消息了。
- (void)xmppStream:(XMPPStream *)sender didReceiveMessage:(XMPPMessage *)message{
NSString *messageBody = [[message elementForName:@"body"] stringValue];
[ShareCallback xmppPushMsg:messageBody];
}
注意其实所有的连接,认证,发送XMPPPresence都是以xml的格式进行的,通过解析xml标签来判断消息体的类型。所以如果需要调试,可以去找到接收元数据xml的地方,看解析有没有正确处理。
字典的原理
NSDictionary是使用hash表来实现key和value之间的映射和存储的,底层是一个哈希表。
哈希表的本质是一个数组,数组中每一个元素称为一个箱子(bin),箱子中存放的是键值对。在使用拉链法解决哈希冲突时,每个箱子其实是一个链表,属于同一个箱子的所有键值对都会排列在链表中。
扩展
假设我们要做个存储结构,需要存储下来三国中的人物,以及他们的详细信息。我们用他们的名字来作为存储 的关键值,例如:刘备,曹操,孙权,关羽,张飞……等等。这个时候我们如果想用一般的方法来查找这些英雄豪杰,需要遍历整个存储空间,如果这些英雄豪杰一共有n个,那么这时候的时间算法复杂度为O(n)。显然如果n值很大,每次想要找到某个英雄就需要比较长的时间。
此时我们先定义一个大的有序结构数组HashValue[m],用来存放各位英雄豪杰的信息(value值,刘备,曹操...等信息)。然后编写一个哈希函数ChangeToHashValue (name),函数的具体内容就不细说了,反正这个函数会将这些做为关键值的名字转换为HashValue[m]中的某个下标值x。然后可以将英雄的信息放进HashValue[x]中去。这样,可以将所有英雄的信息存储起来。当查询的时候再使用哈希函数ChangeToHashValue(name)得到这个下标值,这样就很容易得到了这个英雄的信息。例如:ChangeToHashValue(刘备)为10,那么就将刘备存储到HashValue [10]里面。当查询的时候再次使用ChangeToHashValue(刘备)得到10,这个时候我们就可以很容易找到刘备的所有信息。在实际应用中如果我们想把所有的英雄豪杰都存储进系统时,需要定义m>n。就是数组的大小要大于需要存储的信息量,所以说哈希表是一个以空间换取时间的数据结构。
这个时候问题来了,出现了这种情况ChangeToHashValue(关羽)和ChangeToHashValue(张飞)得到的值是一样的,都是250,我们岂不是在存储过程中会遇到麻烦,怎么安排他们二位的地方呢(总不能让二位打一架,谁赢了谁呆在那吧),这就需要一个解决冲突的方法。当遇到这 种情况时我们可以这样处理,先存储好了关羽,当张飞进入系统时会发现关羽已经是250了,那咱就加一位,251得了,这不就解决了。我们查找张飞的时候也 是,一看250不是张飞,那就加个1,就找到了。这时还存在一个问题。直接用ChangeToHashValue(赵云)为251,张飞已经早早占了他的 地方,那就再加1存到252呗。呵呵,这时我们会发现,当哈希函数冲突发生的机率很高时,可能会有一群英雄豪杰在250这个值后面扎堆排队。要命的是查找 的时候,时间算法复杂度早已不是O(1)了(所以我们说理想情况下哈希表的时间算法复杂度为O(1))。
这就是说哈希函数的编写是哈希表的一个关键问题,会涉及到一个存储值在哈希表中的统计分布。如果哈希函数已经定义好了,冲突的解决就成为了改变系统性能的关键因素。其实还有很多种方法来解决冲突情况下的存储和查找问题,不一定非要线性向后排队,如果有好的哈希表冲突的解决方法也能很大程度上提高系统的效率。
深入理解哈希表
从根源揭秘HashMap的数据存储过程
什么是哈希表
注意点
iOS中的setObject: forKey: 函数在key为空时会crash。
如果要存入plist,NSDictionary的key只能为NSString。
通过[NSJSONSerialization JSONObjectWithData: data options: NSJSONReadingMutableLeaves error: nil];来获得json的解析字典时会有精度丢失问题,如8.37会转成8.369999999999999。通常是在取值的时候通过[NSString stringWithFormat:@"%.2f",v]这样来定义小数后位数。iOS 处理浮点类型精度丢失问题
我的应用:
bench_ios库为NSMutableDictionary写了一个category,通过safeSetObject来阻止key为空的crash。
通过遍历字典每一个value的类型,来解决精度丢失问题
/**
* key or v空时不闪退
*/
- (void)safeSetObject:(id)anObject forKey:(id<NSCopying>)aKey;
/**
* 修正使用NSJSONSerialization将NSString转换为Dictionary后 有小数部分出现如8.369999999999999问题
例子:
NSString *html = @"{\"71.40\":71.40,\"8.37\":8.37,\"80.40\":80.40,\"188.40\":188.40}";此段html转换成NSMutableDictionary后使用correctNumberLoss处理耗时0.000379秒
*/
- (NSMutableDictionary *)correctDecimalLoss:(NSMutableDictionary *)dic;
通常在接收服务端的数据时,会有很多map的出现,我们需要通过list里的某个key去map里取对应数据,对于这种取法会使我们app使用数据时代码变得非常不雅,所以通过CC_Parser对原始数据做了处理,为了是消除map
/**
* 将map的数据移置list中
*
* NSMutableArray *parr=[CC_Parser getMapParser:result[@"response"][@"purchaseOrders"] idKey:@"order" keepKey:YES pathMap:result[@"response"][@"paidFeeMap"]];
* parr=[CC_Parser addMapParser:parr idKey:@"prize" keepKey:NO map:result[@"response"][@"prizeFeeMap"]];
*
* pathArr 需要获取的list路径 如result[@"response"][@"purchaseOrders"]
* idKey 要取的map字段key 如purchseNo
* keepKey 是否保留原字段 如purchseNo 本身含有意义要保留 会在key最后添加_map区分" 如xxxid 本身没有意义,为了取值而生成的id不保留 被map相应id的数据替换
* mapPath 要取的map的路径 如result[@"response"][@"paidFeeMap"] map中可以是nsstring 也可以是nsdictionary
*/
+ (NSMutableArray *)getMapParser:(NSArray *)pathArr idKey:(NSString *)idKey keepKey:(BOOL)keepKey pathMap:(NSDictionary *)pathMap;
/**
* 将map的数据移置list中 多个map时添加
*/
+ (NSMutableArray *)addMapParser:(NSMutableArray *)pathArr idKey:(NSString *)idKey keepKey:(BOOL)keepKey map:(NSDictionary *)getMap;
block
block本质上也是一个OC对象,它内部也有个isa指针。
block是封装了函数调用以及函数调用环境的OC对象。
block是封装函数及其上下文的OC对象。
void (^block1)(void) = ^{
NSLog(@"block1");
};
NSLog(@"%@",[block1 class]);
NSLog(@"%@",[[block1 class] superclass]);
NSLog(@"%@",[[[block1 class] superclass] superclass]);
NSLog(@"%@",[[[[block1 class] superclass] superclass] superclass]);
NSLog(@"%@",[[[[[block1 class] superclass] superclass] superclass] superclass]);
输出结果:
NSGlobalBlock
__NSGlobalBlock
NSBlock
NSObject
null
上述代码输出了block1的类型,也证实了block是对象,最终继承NSObject。
block捕获变量
这两个例子很好助于理解:
以下代码输出什么?
int age=10;
void (^Block)(void) = ^{
NSLog(@"age:%d",age);
};
age = 20;
Block();
输出值为 age:10
原因:创建block的时候,已经把age的值存储在里面了。
以下代码输出什么?
auto int age = 10;
static int num = 25;
void (^Block)(void) = ^{
NSLog(@"age:%d,num:%d",age,num);
};
age = 20;
num = 11;
Block();
输出结果为:age:10,num:11
愿意:auto变量block访问方式是值传递,static变量block访问方式是指针传递
auto自动变量可能会销毁的,内存可能会消失,不采用指针访问;static变量一直保存在内存中,指针访问即可。
堆和栈的区别
堆:动态分配内存,需要程序员自己申请,程序员自己管理。
栈:自动分配内存,自动销毁,先入后出,栈上的内容存在自动销毁的情况。
block有哪几种类型
__NSGlobalBlock __ 在数据区
__NSMallocBlock __ 在堆区
__NSStackBlock __ 在栈区
block的强引用现象
栈block
- 如果block是在栈上,将不会对auto变量产生强引用。
- 栈上的block随时会被销毁,也没必要去强引用其他对象。
如果block在栈空间,不管外部变量是强引用还是弱引用,block都会弱引用访问对象。
如果block在堆空间,如果外部强引用,block内部也是强引用;如果外部弱引用,block内部也是弱引用。
两个例子:
以下代码输出什么?
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
Person *person = [[Person alloc] init];
person.age = 10;
__weak Person *weakPerson = person;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"age:%p",weakPerson);
});
NSLog(@"touchesBegan");
}
输出结果:
14:38:42.996990+0800 test[1104:347260] touchesBegan
14:38:42.997481+0800 test[1104:347260] Person-dealloc
14:38:44.997136+0800 test[1104:347260] age:0x0
原因:使用__weak修饰过后的对象,堆block会采用弱引用,无法延时Person的寿命,所以在touchesBegan函数结束后,Person就会被释放,gcd就无法捕捉到Person。
以下代码输出什么?
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
Person *person = [[Person alloc] init];
person.age = 10;
__weak Person *weakPerson = person;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(4.0 * NSEC_PER_SEC)),
dispatch_get_main_queue(), ^{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"2-----age:%p",person);
});
NSLog(@"1-----age:%p",weakPerson);
});
NSLog(@"touchesBegan");
}
输出结果:
14:48:01.293818+0800 test[1199:403589] touchesBegan
14:48:05.294127+0800 test[1199:403589] 1-----age:0x604000015eb0
14:48:08.582807+0800 test[1199:403589] 2-----age:0x604000015eb0
14:48:08.583129+0800 test[1199:403589] Person-dealloc
原因:gcd内部只要有强引用Person,Person就会等待执行完再销毁!所以Person销毁时间为7秒。
block能否修改变量值
auto修饰变量,block无法修改,因为block使用的时候是内部创建了变量来保存外部的变量的值,block只有修改内部自己变量的权限,无法修改外部变量的权限。
static修饰变量,block可以修改,因为block把外部static修饰变量的指针存入,block直接修改指针指向变量值,即可修改外部变量值。
全局变量值,全局变量无论哪里都可以修改,当然block内部也可以修改。
__block
修饰符
__block可以用于解决block内部无法修改auto变量值的问题
__block不能修饰全局变量、静态变量(static)
编译器会将__block变量包装成一个对象
__block修改变量:age->__forwarding->age
__Block_byref_age_0结构体内部地址和外部变量age是同一地址
block可以向NSMutableArray添加元素么?
NSMutableArray *arr = [NSMutableArray array];
Block block = ^{
[arr addObject:@"123"];
[arr addObject:@"2345"];
};
答案:可以,因为是addObject是使用NSMutableArray变量,而不是通过指针改变NSMutableArray,如果是arr = nil,这就是改变了NSMutableArray变量,会报错。
解决block循环引用
1.__weak
修饰
不会产生强引用,指向的对象销毁时,会自动让指针置为nil。
Person *person = [[Person alloc] init];
// __weak Person *weakPerson = person;
__weak typeof(person) weakPerson = person;
person.block = ^{
NSLog(@"age is %d", weakPerson.age);
};
缺陷:
- (void)viewDidLoad {
[super viewDidLoad];
MitPerson*person = [[MitPerson alloc]init];
__weak MitPerson * weakPerson = person;
person.mitBlock = ^{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[weakPerson test];
});
};
person.mitBlock();
}
直接运行这段代码会发现[weakPerson test];并没有执行,打印一下会发现,weakPerson已经是 Nil 了,这是由于当我们的viewDidLoad方法运行结束,由于是局部变量,无论是MitPerson和weakPerson都会被释放掉,那么这个时候在Block中就无法拿到正真的person内容了。
解决:
- (void)viewDidLoad {
[super viewDidLoad];
MitPerson*person = [[MitPerson alloc]init];
__weak MitPerson * weakPerson = person;
person.mitBlock = ^{
__strong MitPerson * strongPerson = weakPerson;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[strongPerson test];
});
};
person.mitBlock();
}
2.__block
必须手动把引用对象置位nil,并且要调用该block。
__block Person *person = [[Person alloc] init];
person.block = ^{
NSLog(@"age is %d", person.age);
person = nil;
};
person.block();
3.将变量传入block中
将self传入block,在block中使用传入的self。
4.__unsafe_unretained
不会产生强引用,不安全,指向的对象销毁时,指针存储的地址值不变
__block Person *person = [[Person alloc] init];
person.block = ^{
NSLog(@"age is %d", person.age);
person = nil;
};
person.block();
5.控制器内部
block是控制器对象的一个【属性】,则在block内部使用self将会引起循环应用。
typedef void(^TestBlock)();
@interface SecondViewController ()
@property (nonatomic, copy)TestBlock testBlock;
@end
self.testBlock = ^()
{
NSLog(@"%@",self.mapView);
};
self.testBlock();
当block不是self的【属性】时,block内部使用self也不会造成内存泄露。
TestBlock testBlock = ^()
{
NSLog(@"%@",self.mapView);
};
[self test:testBlock];
KVO和KVC
KVO
当类A的对象第一次被观察的时候,系统会在运行期动态创建类A的派生类。我们称为B。
在派生类B中重写类A的setter方法,B类在被重写的setter方法中实现通知机制。
类B重写会 class方法,将自己伪装成类A。类B还会重写dealloc方法释放资源。
系统将所有指向类A对象的isa指针指向类B的对象。
KVC
KVC运用了isa-swizzing技术。isa-swizzing就是类型混合指针机制。
比如:
[site setValue:@"sitename" forKey:@"name"];
//会被编译器处理成
SEL sel = sel_get_uid(setValue:forKey);
IMP method = objc_msg_loopup(site->isa,sel);
method(site,sel,@"sitename",@"name");
每个类都有一张方法表,是一个hash表,值是还书指针IMP,SEL的名称就是查表时所用的键。
SEL数据类型:查找方法表时所用的键。定义成char*,实质上可以理解成int值。
IMP数据类型:他其实就是一个编译器内部实现时候的函数指针。当Objective-C编译器去处理实现一个方法的时候,就会指向一个IMP对象,这个对象是C语言表述的类型。
OC和JS的交互
OC 调 JS
UIWebView 方式
这种⽅方式说⽩白了了就是使⽤用 JSCore ,通过 UIWebView 来获取 JSContext
WKWebView 方式
WKWebView 没有提供获取 JSContext 的方法,但是它提供了执行 JS 的方法 evaluateJS
JS 调 OC
通过 JSCore 中的 block
通过 JSCore 中的 JSExport
冒泡排序
iOS开发中会用到的排序实现
根据序列中两个元素的比较结果来对换这两个记录在序列中的位置,将键值较大的记录向序列的尾部移动,键值较小的记录向序列的前部移动。因此,每一趟都将较小的元素移到前面,较大的元素自然就逐渐沉到最后面了,也就是说,最大的元素最后才能确定,这就是冒泡。冒泡排序是一种稳定的排序算法。
NSMutableArray *dataArr = [NSMutableArray arrayWithObjects:@1,@19,@2,@65,@876,@0,@63,@-1,@87,@100,@-5,@100,@333, nil];
for (int i = 0; i < dataArr.count; ++i) {
//遍历数组的每一个`索引`(不包括最后一个,因为比较的是j+1)
for (int j = 0; j < dataArr.count-1; ++j) {
//根据索引的`相邻两位`进行`比较`
if ([dataArr[j] integerValue] > [dataArr[j+1] integerValue]) {
[dataArr exchangeObjectAtIndex:j withObjectAtIndex:j+1];
}
}
}
NSLog(@"冒泡排序%@",dataArr);
直接插入排序
当插入第i(i>=1)个元素时,前面的V[0],...,V[i-1]个元素已经有序。这时,将第i个元素和前i-1个元素V[i-1],...,V[0]依次比较,找到插入的位置即将V[i]插入,同时原来位置上的元素向后顺移。
NSMutableArray *dataArr = [NSMutableArray arrayWithObjects:@1,@19,@2,@65,@876,@0,@63,@-1,@87,@100,@-5,@100, nil];
// 时间复杂度O(n^2)
// 控件复杂度O(1)
// 稳定性: 稳定
// 内部排序
for (int i = 0; i < dataArr.count; i++) {
for (int j = i; j > 0; j--) {
if ([dataArr[j] intValue] < [dataArr[j - 1] intValue]) {
[dataArr exchangeObjectAtIndex:j withObjectAtIndex:j-1];
}
}
}
NSLog(@"直接插入排序结果----%@",dataArr);
希尔排序
假设待排序的元素共 N 个元素,首先取一个整数 i<n 作为间隔,将全部元素分为间隔为 i 的 i 个子序列并对每个子序列进行直接插入排序。然后缩小间隔 i ,重复上述操作,直至 i 缩小为1,此时所有的元素位于同一个序列且有序。由于刚开始时 i 较大, 每个子序列元素较少,排序速度较快。后期 i 变小,每个子序列元素较多,但大部分元素有序,所以排序速度仍然较快。一般 i 取 i/2。 希尔排序是一种不稳定的排序算法。
NSMutableArray *dataArr = [NSMutableArray arrayWithObjects:@1,@19,@2,@65,@876,@0,@63,@-1,@87,@100,@-5,@100,@333, nil];
//时间复杂度:O(n) ~ O(n^2)
//空间复杂度:O(1)
//稳定性:不稳定
//内部排序
int n = (int)dataArr.count;
for (int gap = n / 2; gap > 0; gap /= 2){
for (int i = gap; i < n; i++){
for (int j = i - gap; j >= 0 && [dataArr[j] intValue] > [dataArr[j + gap] intValue]; j -= gap){
[dataArr exchangeObjectAtIndex:j withObjectAtIndex:(j + gap)];
}
}
}
NSLog(@"----%@", dataArr);
堆排序
堆排序是借助堆来实现的选择排序,思想同简单的选择排序,以下以大顶堆为例。注意:如果想升序排序就使用大顶堆,反之使用小顶堆。原因是堆顶元素需要交换到序列尾部。堆排序是不稳定排序。
NSMutableArray *dataArr = [NSMutableArray arrayWithObjects:@1,@19,@2,@65,@876,@0,@63,@-1,@87,@100,@-5,@100,@333, nil];
/*
从最后一个非叶子节点开始 自下而上进行调整堆
*/
for (NSInteger i=(dataArr.count/2-1); i >= 0; --i) {
dataArr = [self maxHeapAdjust:dataArr index:i length:dataArr.count] ;
}
NSInteger num = dataArr.count;
/*
剩余的元素个数不为1时则继续调整,取出元素。取出的元素放在最后的一个节点。然后减小堆的元素的个数。所以大顶堆排序出来的是升序的。
*/
while (num > 1) {
[dataArr exchangeObjectAtIndex:0 withObjectAtIndex:num-1];
dataArr=[self maxHeapAdjust:dataArr index:0 length:num-1];
num--;
}
NSLog(@"堆排序-----%@",dataArr);
哈夫曼树(最优二叉树)
哈夫曼树(Huffman)树又称最优二叉树,是指对于一组带有确定权值的叶子结点所构造的具有带权路径长度最短的二叉树。
霍夫曼编码就是寻找这个最优路径的过程。
霍夫曼编码的步骤
- 将信源符号的概率按减小的顺序排队。
- 把两个最小的概率相加,并继续这一步骤,始终将较高的概率分支放在右边,直到最后变成概率1。
- 画出由概率1处到每个信源符号的路径,顺序记下沿路径的0和1,所得就是该符号的霍夫曼码字。
- 将每对组合的左边一个指定为0,右边一个指定为1(或相反)。
简单动画
CABasicAnimation
CABasicAnimation是核心动画类簇中的一个类,其父类是CAPropertyAnimation,其子类是CASpringAnimation,它的祖父是CAAnimation。它主要用于制作比较单一的动画,例如,平移、缩放、旋转、颜色渐变、边框的值的变化等,也就是将layer的某个属性值从一个值到另一个值的变化。
本人比较常用的动画是UIView的animateWithDuration,做一些颜色渐变,位置移动。
[UIView animateWithDuration:.5f animations:^{
//这里实现动画 如alpha,frame
} completion:^(BOOL finished) {
}];
我的应用
在CC_Animation里添加了闪烁和按钮放大动画,更多自定义动画可百度CABasicAnimation这个类
/**
* 不停闪烁
* [noteTextV.layer addAnimation:[CC_Animation opacityForever_Animation:.5] forKey:nil];
*/
+ (CABasicAnimation *)opacityForever_Animation:(float)time;
/**
* 按钮点击放大动画
* [CC_Animation buttonClick:checkBt];
*/
+ (void)buttonClick:(UIButton *)button;
GBK和UTF-8
GBK
GBK是在国家标准GB2312基础上扩容后兼容GB2312的标准(好像还不是国家标准)。GBK编码专门用来解决中文编码的,是双字节的。不论中英文都是双字节的。
unicode
unicode 是一种包含所有字符的编码表格,例如,给一个汉字规定一个代码,一个字母也一个代码。它可以广泛地在全世界使用。
UTF8
UTF8 编码是用以解决国际上字符的一种多字节编码,它对英文使用8位(即一个字节),中文使用24位(三个字节)来编码。对于英文字符较多的论坛则用UTF8 节省空间。UTF8是为传送unicode而想出来的“再编码”方法,将unicode编码之后再在网络传输。
最为透彻的utf-8、unicode详解
使用UTF8
NSString *resultStr= [NSString stringWithContentsOfURL:location encoding:NSUTF8StringEncoding error:&error];
使用GBK
NSStringEncoding enc = CFStringConvertEncodingToNSStringEncoding(kCFStringEncodingGB_18030_2000);
resultStr= [NSString stringWithContentsOfURL:location encoding:enc error:&error];
我的应用
在CC_GHttpSessionTask网络请求结果中加入对UTF8和GBK的同时支持。
/**
* url NSString 或者 NSURL
* paramsDic的关键字
* getDate 可以获取时间
*/
- (void)post:(id)url params:(id)paramsDic model:(ResModel *)model finishCallbackBlock:(void (^)(NSString *, ResModel *))block;
- (void)get:(id)url params:(id)paramsDic model:(ResModel *)model finishCallbackBlock:(void (^)(NSString *, ResModel *))block;
未完待续
网友评论