那些需要你知道的iOS开发相关知识(二)

本文详细介绍了网络模型中的五层模型与七层模型,深入解析了TCP/IP协议及HTTP协议的工作原理,并探讨了socket在TCP/IP协议中的应用。此外,还讨论了多线程的实现方式,包括线程的状态、线程资源抢夺问题及其解决方案,以及线程池的使用方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >



4、http TCP/IP  UDP/IP 协议,以及socket


网络模型有两种,一种是五层模型和国际标准化的七层模型。
七层模型:
应用层:网络服务与最终用户的一个接口。协议有 http ftp tftp smtp dns。
表示层:数据的表示、安全、压缩。(在五层模型里已经合并到了应用层);格式有,jpeg,asii,decoic,加密格式等。
会话层:建立、管理、终止会话。(在五层模型里已经合并到了应用层)
传输层:定义传输数据的协议端口号,以及流程控制和差错校验。协议有:TCP UDP,数据包一旦离开网卡即进入网络传输层。
网络层:进行逻辑地址寻址,实现不同网络之间的路径选择。协议有:ICMP IGMP IP (IPV4 IPV6)ARP RARP。
数据链路层:建立逻辑连接,进行硬件地址寻址,差错校验等功能。(由底层网络定义协议),将比特组合成字节进而组合成帧,用MAC地址访问介质,错误发生但不能纠正。
物理层:建立、维护、断开物理连接。(由底层网络定义协议)

五层模型:
应用层:确定进程之间通信的性质以满足用户需求,应用层协议如:支持万维网应用的http协议 、支持电子邮件的smtp协议、支持文件传输的ftp协议等
运输层:负责主机间不同的进程通信,协议有面向连接的TCP,无连接的UDP,数据传输的单位称为报文段或者用户数据报。
网络层:负责分组交换网中不同主机间的通信;作用二:发送数据时,将运输层中的报文段或者用户数据报封装成IP数据报:选择合适路由。
数据链路层:负责将网络层的IP数据报组装成帧
物理层:透明地传输比特流

下面我们来说一下socket,socket是TCP/IP协议的封装和应用(程序员层面上),也可以说,TCP/IP协议是传输层协议,主要解决数据如何在网络中传输,而http是应用层协议,主要解决如何包装数据。关于TCP/IP和http协议的关系,网络有一段比较容易理解的介绍:

我们在传输数据时,可以只使用(传输层)TCP/IP协议,但是那样的话,如 果没有应用层,便无法识别数据内容,如果想要使传输的数据有意义,则必须使用到应用层协议,应用层协议有很多,比如HTTP、FTP、TELNET等,也 可以自己定义应用层协议。WEB使用HTTP协议作应用层协议,以封装HTTP文本信息,然后使用TCP/IP做传输层协议将它发到网络上。
socket本身并不是协议,而是一个调用接口(API),通过scoket,我们才能使用TCP/IP协议,实际上,scoket跟TCP/TP协议没有必然的联系。TCP/IP只是一个协议栈,就像操作系统的运行机制一样,必须要具体实现,同时还要提供对外的操作接口。这个就像操作系统会提供标准的编程接口,比如win32编程接口一样,TCP/IP也要提供可供程序员做网络开发所用的接口,这就是Socket编程接口。”
优快云上有个比较形象的描述:HTTP是轿车,提供了封装或者显示数据的具体形式;Socket是发动机,提供了网络通信的能力


用一句话来总结上面这些东西就是传输层的tcp事基于ip协议的,而应用层的http协议是基于传输层的tcp协议的,而scoket本身不算是协议,它只提供了一个针对tcp或者udp编程的接口。
下面是一些在网络上搜集的面试会遇到的问题
第一次握手:客户端发送syn包(syn=j)到服务器,并进入SYN_SEND状态,等待服务器确认; 
第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态; 
第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISH
ED状态,完成三次握手。 

下面是一些经常在笔试或者面试中碰到的重要的概念,特在此做摘抄和总结。

一。什么是TCP连接的三次握手

握手过程中传送的包里不包含数据,三次握手完毕后,客户端与服务器才正式开始传送数据。理想状态下,TCP连接一旦建立,在通信双方中的任何一方主动关闭 连接之前,TCP 连接都将被一直保持下去。断开连接时服务器和客户端均可以主动发起断开TCP连接的请求,断开过程需要经过“四次握手”(过程就不细写了,就是服务器和客 户端交互,最终确定断开)

二。利用Socket建立网络连接的步骤

建立Socket连接至少需要一对套接字,其中一个运行于客户端,称为ClientSocket ,另一个运行于服务器端,称为ServerSocket 。

套接字之间的连接过程分为三个步骤:服务器监听,客户端请求,连接确认。

1。服务器监听:服务器端套接字并不定位具体的客户端套接字,而是处于等待连接的状态,实时监控网络状态,等待客户端的连接请求。

2。客户端请求:指客户端的套接字提出连接请求,要连接的目标是服务器端的套接字。为此,客户端的套接字必须首先描述它要连接的服务器的套接字,指出服务器端套接字的地址和端口号,然后就向服务器端套接字提出连接请求。

3。连接确认:当服 务器端套接字监听到或者说接收到客户端套接字的连接请求时,就响应客户端套接字的请求,建立一个新的线程,把服务器端套接字的描述发给客户端,一旦客户端 确认了此描述,双方就正式建立连接。而服务器端套接字继续处于监听状态,继续接收其他客户端套接字的连接请求。

三。HTTP链接的特点

HTTP协议即超文本传送协议(Hypertext Transfer Protocol ),是Web联网的基础,也是手机联网常用的协议之一,HTTP协议是建立在TCP协议之上的一种应用。

HTTP连接最显著的特点是客户端发送的每次请求都需要服务器回送响应,在请求结束后,会主动释放连接。从建立连接到关闭连接的过程称为“一次连接”。

四。TCP和UDP的区别

1。TCP是面向链 接的,虽然说网络的不安全不稳定特性决定了多少次握手都不能保证连接的可靠性,但TCP的三次握手在最低限度上(实际上也很大程度上保证了)保证了连接的 可靠性;而UDP不是面向连接的,UDP传送数据前并不与对方建立连接,对接收到的数据也不发送确认信号,发送端不知道数据是否会正确接收,当然也不用重 发,所以说UDP是无连接的、不可靠的一种数据传输协议。

2。也正由于1所说的特点,使得UDP的开销更小数据传输速率更高,因为不必进行收发数据的确认,所以UDP的实时性更好。

知道了TCP和 UDP的区别,就不难理解为何采用TCP传输协议的MSN比采用UDP的QQ传输文件慢了,但并不能说QQ的通信是不安全的,因为程序员可以手动对UDP 的数据收发进行验证,比如发送方对每个数据包进行编号然后由接收方进行验证啊什么的,即使是这样,UDP因为在底层协议的封装上没有采用类似TCP的“三次握手”而实现了TCP所无法达到的传输效率。



5、多线程状态,线程资源抢夺?

线程的状态有:线程的创建、线程的开启、线程的运行和阻塞、线程的死亡。
线程资源的抢夺:这属于多线程的安全隐患,一块资源可能会被多线程共享,当多个线程访问同一块资源时,很容易引发数据错乱和数据安全问题。
如何解决呢?
1、互斥锁@synchronized(锁对象){需要锁的代码} 注意:锁定一份代码只用一把锁,多把锁是无效的
互斥锁的优缺点:
优点:能有效防止因多线程抢夺资源造成的数据安全问题
缺点:需要消耗大量的CPU资源
2、NSLock 
lock:加锁
unlock:解锁
tryLock:尝试加锁,如果失败,并不会阻塞线程,只会立即返回NO
lockBeforeDate:在指定的date之间暂时阻塞线程(如果没有获取锁的话),如果到期还没有获取锁,则线程被唤醒,函数立即返回NO;
3、NSRecursiveLock递归锁:NSRecursiveLock,多次调用不会阻塞已获取该锁的线程。
4、NSConditionLock条件锁:可以设置条件。
5、NSDistributedLock,分布锁:它是文件方式实现,可以跨进程,用tryLock方法获取锁;用unlock方法释放锁。如果一个获取锁的进程在释放之前挂了,那么锁就一直得不到释放了,此时可以通过breakLock强行获取锁。

6、线程池(详细请看http://blog.youkuaiyun.com/itjobtxq/article/details/8000581)

对于iOS来说,建议尽可能的情况下避免使用直接操作线程,使用比如NSOperationQueue这样的机制。可以把NSOperationQueue看做一个线程池,可往线程池中添加操作(NSOperation)到队列中。线程池中的线程可看做消费者,从队列中取走操作,并执行它。

编写一个NSOperation的子类,只需实现main方法。这里非常类似Java的Thread,你可以继承它,并覆盖run方法,在该方法里面写入需要执行的代码。这里的main方法和run方法作用是相似的。

 头文件:
@interface MyTask : NSOperation { 
    int operationId; 
}

@property int operationId;

@end
复制代码
这里的operationId属性不是必须的,是我想在后面标识区分多个Task的标识位。

 m文件:
@implementation MyTask

@synthesize operationId;

- (void)main{ 
    NSLog(@"task %i run … ",operationId); 
    [NSThread sleepForTimeInterval:10]; 
    NSLog(@"task %i is finished. ",operationId); 
}

@end
复制代码
这里模拟了一个耗时10秒钟的操作。

 下面需要把Task加入到队列中:
- (void)viewDidLoad { 
    [super viewDidLoad]; 
    queue=[[NSOperationQueue alloc] init]; 
    
    int index=1; 
    MyTask *task=[[[MyTask alloc] init] autorelease]; 
    task.operationId=index++; 
         
    [queue addOperation:task];
复制代码
我直接找了个Controller的方法写上了。运行结果是,界面出现了,而task还未执行完,说明是多线程的。10秒钟后,日志打印完毕,类似这样:

2011-07-18 15:59:14.622 MultiThreadTest[24271:6103] task 1 run … 
 2011-07-18 15:59:24.623 MultiThreadTest[24271:6103] task 1 is finished.


 可以向操作队列(NSOperationQueue)增加多个操作,比如这样:
- (void)viewDidLoad { 
    [super viewDidLoad]; 
    queue=[[NSOperationQueue alloc] init]; 
    
    int index=1; 
    MyTask *task=[[[MyTask alloc] init] autorelease]; 
    task.operationId=index++;     
    [queue addOperation:task]; 
    
    task=[[[MyTask alloc] init] autorelease]; 
    task.operationId=index++;

    [queue addOperation:task]; 
}
复制代码
那么打印出的内容是不定的,有可能是这样:

2011-07-18 15:49:48.087 MultiThreadTest[24139:6203] task 1 run … 
 2011-07-18 15:49:48.087 MultiThreadTest[24139:1903] task 2 run … 
 2011-07-18 15:49:58.122 MultiThreadTest[24139:6203] task 1 is finished. 
 2011-07-18 15:49:58.122 MultiThreadTest[24139:1903] task 2 is finished.


 甚至有可能是这样:

2011-07-18 15:52:24.686 MultiThreadTest[24168:1b03] task 2 run … 
 2011-07-18 15:52:24.685 MultiThreadTest[24168:6003] task 1 run … 
 2011-07-18 15:52:34.708 MultiThreadTest[24168:1b03] task 2 is finished. 
 2011-07-18 15:52:34.708 MultiThreadTest[24168:6003] task 1 is finished.

 


 因为两个操作提交的时间间隔很近,线程池中的线程,谁先启动是不定的。

 那么,如果需要严格意义的顺序执行,怎么办呢?

 

处理操作之间的依赖关系

 如果操作直接有依赖关系,比如第二个操作必须等第一个操作结束后再执行,需要这样写:
queue=[[NSOperationQueue alloc] init];

int index=1; 
MyTask *task=[[[MyTask alloc] init] autorelease]; 
task.operationId=index++;

[queue addOperation:task];

task=[[[MyTask alloc] init] autorelease]; 
task.operationId=index++;

if ([[queue operations] count]>0) { 
    MyTask *theBeforeTask=[[queue operations] lastObject]; 
    [task addDependency:theBeforeTask]; 
}

[queue addOperation:task];
复制代码
这样,即使是多线程情况下,可以看到操作是严格按照先后次序执行的。

 

控制线程池中的线程数

 可以通过类似下面的代码:
[queue setMaxConcurrentOperationCount:2];
复制代码
来设置线程池中的线程数,也就是并发操作数。默认情况下是-1,也就是没有限制,同时运行队列中的全部操作。

 7、block回调,持有block的对象销毁后会出现什么现象?

block回调:(http://blog.youkuaiyun.com/mobanchengshuang/article/details/11751671)<——具体参照这个博文;举个例子:手机充好电了通过回调的方式让我继续玩手机,也可以认为手机充好电了通知我可以继续玩手机,然后我主动继续玩手机。设置block,我们没有把充电函数内部的实现写死,也就是说当我完成之后无论做什么都无所谓,调的地方不同,传不同的代码过去就可以了,这个跟函数指针类似吧。

持有block的对象销毁后:可能会引发retain cycle问题,根源在于Block和对象可能会相互强引用,互相retain对方,这样就导致了retain cycle,最后这个Block和obj就变成了孤岛,谁也释放不了谁。解决这个问题的方法是使用弱引用打断retain cycle(___block)。这样对象被持有者释放后,retainCount变成0,request被dealloc,request释放持有的block,导致retainCount变成0,也就被销毁,这样这两个对象内存都被回收。


8、runtime和runloop

runtime 简称运行时机制,其中最主要的是消息机制,对于C语言,函数的调用在编译的时候会决定调用哪个函数,编译完成之后直接顺序执行,无任何二义性。OC的函数调用称为消息发送。属于动态调用过程,在编译的时候并不能决定真正调用哪个函数(事实证明,在编译阶段oc可以调用任何函数,即使这个函数并未实现,只要申明过就不会报错,而C语言在编译阶段就会报错)。只有在真正运行的时候才会根据函数的名称找到对应的函数来调用。
接下来我们开始仔细的聊一聊这个runtime。
1、消息转发
当向某个对象发送一条消息时,若该对象的方法列表以及它相应继承立链上的方法都无法找到以该消息选择作为key的方法实现,则会触发消息转发机制。
如果用实例对象调用实例方法,会到实例的isa指针指向的对象操作。
如果调用的是类方法,就会到类对象的isa指针指向的对象(也就是原类对象)中操作。
(1)首先,在相应操作的对象中的缓存方法列表中调用的方法,如果找到,转向相应实现并执行。
(2)如果没有找到,在相应操作的对象中的方法列表中找调用的方法,如果找到,转向相应实现执行。
(3)如果没找到,去父类指针所指向的对象中执行1,2.
(4)以此类推,如果一直到根类还没有找到,就转向拦截调用。
(5)如果没有重写拦截调用的方法,程序报错。
下面是消息转发时对应调用的方法。

1444814548720164.png


2、获取列表

下面我们来更加通俗的说一下runtime。其实我们写的代码在程序运行过程中都会被转化成runtime的C代码执行,例如[self createView];会被转化成objc_msgSend(target,@selector(doSomething));

有时候会有这样的需求,我们需要知道当前类中的每个属性的名字(比如字典转模型,字典的key和模型对象的属性名字不匹配)。

我们可以通过runtime的一系列方法获取类的一些信息(包括属性列表,方法列表,成员变量列表,和遵循的协议表)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
   unsigned int count;
     //获取属性列表
     objc_property_t *propertyList = class_copyPropertyList([self class], &count);
     for  (unsigned int i=0; i<count; i++) {         const char *propertyname = " property_getName(propertyList[i]);"          nslog(@ "property----=" ">%@" , [NSString stringWithUTF8String:propertyName]);
     }
     //获取方法列表
     Method *methodList = class_copyMethodList([self class], &count);
     for  (unsigned int i; i<count; i++) {         method method = " methodList[i];"          nslog(@ "method----=" ">%@" , NSStringFromSelector(method_getName(method)));
     }
     //获取成员变量列表
     Ivar *ivarList = class_copyIvarList([self class], &count);
     for  (unsigned int i; i<count; i++) {         ivar myivar = " ivarList[i];"          const char *ivarname = " ivar_getName(myIvar);"          nslog(@ "ivar----=" ">%@" , [NSString stringWithUTF8String:ivarName]);
     }
     //获取协议列表
     __unsafe_unretained Protocol **protocolList = class_copyProtocolList([self class], &count);
     for  (unsigned int i; i<count; i++) {         protocol *myprotocal = " protocolList[i];"          const char *protocolname = " protocol_getName(myProtocal);"          nslog(@ "protocol----=" ">%@" , [NSString stringWithUTF8String:protocolName]);
     }</count; i++) {></count; i++) {></count; i++) {></count; i++) {>
在xcode上跑一下看看输出吧,需要给你当前的类写几个属性,成员变量,方法和协议,不然获取的列表是没有东西啊。


然后然后,我们来说一下这高大上的runtime怎么用吧


在开发项目的过程中,产品经理可能会有这样的要求:可以根据后台返回规则任意跳转相应的界面吗?

推送:根据服务端推送过来的数据规则,跳转到对应的控制器


这样的要求我们内心肯定是拒绝的,但是我们必须接受,如果你用switch 语句写,估计要写死,情况那么多,怎么办?


在这里可以用一下runtime

利用runtime动态生成对象、属性、方法这特性,我们可以跟服务端商量好,定义跳转规则,比如要跳转到A控制器,需要传属性id、type,那么服务端返回字典给我,里面有控制器名,两个属性名跟属性值,客户端可以根据控制器名生成对象,再用kvc给对象赋值,这样就搞定了


比如根据推送规则跳转对应界面HSFeedsViewController


HSFeedsViewController:


进入该界面需要传得属性

1
2
3
4
5
6
7
@interface HSFeedsViewController : UIViewController
// 注:根据下面的两个属性,可以从服务器获取对应的频道列表数据
/** 频道ID */
@property (nonatomic, copy) NSString *ID;
/** 频道type */
@property (nonatomic, copy) NSString *type;
@end

AppDelegate.m:

  • 推送过来的消息规则

1
2
3
4
5
6
7
8
// 这个规则肯定事先跟服务端沟通好,跳转对应的界面需要对应的参数
NSDictionary *userInfo = @{
                            @ "class" : @ "HSFeedsViewController" ,
                            @ "property" : @{
                                         @ "ID" : @ "123" ,
                                         @ "type" : @ "12"
                                    }
                            };
  • 接收推送消息

1
2
3
4
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
{
     [self push:userInfo];
}
  • 跳转界面

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
- (void)push:(NSDictionary *)params
{
     // 类名
     NSString *class =[NSString stringWithFormat:@ "%@" , params[@ "class" ]];
     const char *className = [class cStringUsingEncoding:NSASCIIStringEncoding];
     // 从一个字串返回一个类
     Class newClass = objc_getClass(className);
     if  (!newClass)
     {
         // 创建一个类
         Class superClass = [NSObject class];
         newClass = objc_allocateClassPair(superClass, className, 0);
         // 注册你创建的这个类
         objc_registerClassPair(newClass);
     }
     // 创建对象
     id instance = [[newClass alloc] init];
     // 对该对象赋值属性
     NSDictionary * propertys = params[@ "property" ];
     [propertys enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
         // 检测这个对象是否存在该属性
         if  ([self checkIsExistPropertyWithInstance:instance verifyPropertyName:key]) {
             // 利用kvc赋值
             [instance setValue:obj forKey:key];
         }
     }];
     // 获取导航控制器
     UITabBarController *tabVC = (UITabBarController *)self.window.rootViewController;
     UINavigationController *pushClassStance = (UINavigationController *)tabVC.viewControllers[tabVC.selectedIndex];
     // 跳转到对应的控制器
     [pushClassStance pushViewController:instance animated:YES];
}
  • 检测对象是否存在该属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
- (BOOL)checkIsExistPropertyWithInstance:(id)instance verifyPropertyName:(NSString *)verifyPropertyName
{
     unsigned int outCount, i;
     // 获取对象里的属性列表
     objc_property_t * properties = class_copyPropertyList([instance
                                                            class], &outCount);
     for  (i = 0; i < outCount; i++) {
         objc_property_t property =properties[i];
         //  属性名转成字符串
         NSString *propertyName = [[NSString alloc] initWithCString:property_getName(property) encoding:NSUTF8StringEncoding];
         // 判断该属性是否存在
         if  ([propertyName isEqualToString:verifyPropertyName]) {
             free(properties);
             return  YES;
         }
     }
     free(properties);
     return  NO;
}
具体使用和代码:  https://github.com/HHuiHao/Universal-Jump-ViewController


3、私有API与runtime

1、获取类

2、iOS9白名单的上限是50个,如果想绕过这个上限,扫描系统中所有app的状态,只有使用私有API,需要用到的类有两个:LSApplicationWorksspace、LSApplicationProxy,知道类的名字我们就可以依靠runtime得到这个类,以及这个类的所有方法。

  • 得到LSApplicationWorkspace、LSApplicationProxy

1
2
Class LSApplicationWorkspace_class = objc_getClass( "LSApplicationWorkspace" );
Class LSApplicationProxy_class = object_getClass(@ "LSApplicationProxy" );

得到类的所有方法与成员变量(编程小翁)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//获取不到成员变量
  int count = 0;
  Ivar *members = class_copyIvarList([LSApplicationProxy_class class], &count);   for  (int i = 0 ; i < count; i++) {
      Ivar  var  = members[i];      const char *memberName = ivar_getName( var );      const char *memberType = ivar_getTypeEncoding( var );
      NSLog(@ "%s: %s" ,memberType,memberName);
  }
  
  NSLog(@ "count: %d" ,count);   //获取不到有用的方法
  count = 0;
  Method *memberMethods = class_copyMethodList(LSApplicationProxy_class, &count);   for  (int i = 0; i < count; i++) {
      SEL name = method_getName(memberMethods[i]);
      NSString *methodName = [NSString stringWithCString:sel_getName(name) encoding:NSUTF8StringEncoding];
      NSLog(@ "member method:%@" , methodName);
  }
  
  NSLog(@ "count: %d" ,count);

因为函数class_copyIvarList、class_copyMethodList有时不能返回有用的结果,所以我们使用class-dump(有朋友反映xcode7的库导不出来,大家用源码自己build一个吧),导出类的头文件。

导出MobileCoreServices.framework的所有头文件:

1
class-dump -H /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk/System/Library/Frameworks/MobileCoreServices.framework  -o /Users/credit/Desktop/header004

-o后面是输出路径 改成你需要的。

02.png

MobileCoreServices.framework的所有头文件

03.png

LSApplicationProxy

04.png

LSApplicationWorkspace

好的
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值