AFNetworking
是iOS
最常用的网络框架,虽然系统也有NSURLSession
,但是我们一般不会直接用它。AFNetworking
经过了三个大版本,现在用的大多数都是3.x的版本。
AFNetworking
经历了下面三个阶段的发展:- 1.0版本 : 基于NSURLConnection
的封装。
NSURLConnection
和NSURLSession
,是转向NSURLSession
的过渡版。NSURLSession
的封装。AFNetworking3.X
的构成很简单,主要就四部分,除此之外还有一些基于UIKit
的Category
,但这些并不是标配。
Manager
,主要实现都在AFURLSessionManager
中。HTTPS
相关的。在AFN3.0
中,网络请求的manager
主要有AFHTTPSessionManager
和AFURLSessionManager
构成,二者为父子关系。这两个类职责划分很清晰,父类负责处理一些基础的网络请求代码,并且接受NSURLRequest
对象,而子类则负责处理和http
协议有关的逻辑。AFN
的这套设计很便于扩展,如果以后想增加FTP
协议的处理,则基于AFURLSessionManager
创建子类即可。子类中只需要进行很少的代码处理,创建一个NSURLRequest
对象后调用父类代码,由父类去完成具体的请求操作。创建sessionManagerAFHTTPSessionManager
类的初始化方法中并没有太多实现代码,其内部调用的都是父类AFURLSessionManager
的initWithSessionConfiguration
方法,下面是此方法内部的一些关键代码。在初始化方法中包含一个参数sessionConfiguration
,如果没有传入的话默认是使用系统的defaultConfiguration
,我们创建是一般都不会自定义configuration
,所以大多数都是系统的。
if (!configuration) {
configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
}
随后是NSURLSession
的初始化代码,关于NSOperationQueue
后面详细进行讲解。NSURLSession
的初始化方式有两种,一种是使用系统的共享session
,另一种是自己创建session
。AFN
选择的是创建自己的session
,并且每个请求都会创建一个独立的session
。可以通过NSURLSession
进行连接复用,这样可以避免很多握手和挥手的过程,提高网络请求速度,苹果允许iOS
设备上一个域名可以有四个连接同时存在。但是由于AFN
的实现是每个请求都创建一个session
,所以就不能进行连接复用。所以可以通过在外面对AFN
进行二次封装,将AFHTTPSessionManager
复用为单例对象,通过复用sessionManager
的方式,来进行连接的复用。但是这种方案对于不同的requestSerializer
、responseSerializer
等情况,还是要做特殊兼容,所以最好建立一个sessionManager
池,对于同类型的sessionManager
直接拿出来复用,否则就创建新的。
// 共享session连接池
[NSURLSession sharedSession];
// 创建新session,则不能使用共享session连接池
[NSURLSession sessionWithConfiguration:self.sessionConfiguration delegate:self delegateQueue:self.operationQueue];
由于当前AFURLSessionManager
对象的所有sessionTask
请求任务,都是共享同一个回调代理的,所以AFN
为了区分每个sessionTask
,通过下面的可变字典,将所有taskDelegate
和task.taskIdentifier
的进行了一一对应,以便于很容易的对每个请求task
进行操作。
self.mutableTaskDelegatesKeyedByTaskIdentifier = [[NSMutableDictionary alloc] init];
在初始化方法中,可以发现AFN
在创建session
后,调用了getTasksWithCompletionHandler
方法来获取当前所有的task
。但是现在刚创建session
,理论上来说是不应该有task
的。但从AFN的issues
中找到了答案issues 3499。这是因为,在completionHandler
回调中,为了防止进入前台时,通过session id
恢复的task
导致一些崩溃问题,所以这里将之前的task
进行遍历,并将回调都置nil
。
[self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {
for (NSURLSessionDataTask *task in dataTasks) {
[self addDelegateForDataTask:task uploadProgress:nil downloadProgress:nil completionHandler:nil];
}
for (NSURLSessionUploadTask *uploadTask in uploadTasks) {
[self addDelegateForUploadTask:uploadTask progress:nil completionHandler:nil];
}
for (NSURLSessionDownloadTask *downloadTask in downloadTasks) {
[self addDelegateForDownloadTask:downloadTask progress:nil destination:nil completionHandler:nil];
}
}];
在AFURLSessionManager
中进行task
的创建,task
的类型总共分为三种,dataTask
、uploadTask
、downloadTask
,AFN
并没有对streamTask
进行处理。AFHTTPSessionManager
在创建GET
、POST
等请求时,本质上都是调用了下面的方法或其类似的方法,方法内部会创建一个task
对象,并调用addDelegateForDataTask
将后面的处理交给AFURLSessionManagerTaskDelegate
来完成。随后会将task
返回给调用方,调用方获取到task
对象后,也就是子类AFHTTPSessionManager
,会调用resume
方法开始请求。
- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request
uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgressBlock
downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgressBlock
completionHandler:(nullable void (^)(NSURLResponse *response, id _Nullable responseObject, NSError * _Nullable error))completionHandler {
__block NSURLSessionDataTask *dataTask = nil;
url_session_manager_create_task_safely(^{
dataTask = [self.session dataTaskWithRequest:request];
});
[self addDelegateForDataTask:dataTask uploadProgress:uploadProgressBlock downloadProgress:downloadProgressBlock completionHandler:completionHandler];
return dataTask;
}
除了普通请求外,upload
、download
都有类似的处理。在addDelegateForDataTask
方法中,会调用sessionManager
的setDelegate:forTask:
方法,此方法内部将task
和taskDelegate
进行了注册。由于AFN
可以通过通知让外界监听请求状态,所以在此方法中还监听了task
的resume
和suspend
事件,并在实现代码中将事件广播出去。
- (void)addDelegateForDataTask:(NSURLSessionDataTask *)dataTask
uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgressBlock
downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgressBlock
completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler
{
AFURLSessionManagerTaskDelegate *delegate = [[AFURLSessionManagerTaskDelegate alloc] initWithTask:dataTask];
delegate.manager = self;
delegate.completionHandler = completionHandler;
dataTask.taskDescription = self.taskDescriptionForSessionTasks;
[self setDelegate:delegate forTask:dataTask];
delegate.uploadProgressBlock = uploadProgressBlock;
delegate.downloadProgressBlock = downloadProgressBlock;
}
如果从AFHTTPSessionManager
的创建任务开始,按代码逻辑跟到这里,发现其实AFN3.0
的请求代码真的很简单,主要都集中在创建NSMutableURLRequest
那里,其他都依赖于NSURLSession
,因为确实NSURLSession
的API
封装程度比较好,也很好使用。AFN3.0
的作用就是对NSURLSession
的封装性比较好,你不用去写太多重复性的代码,并且可以很容易的通过block
得到回调结果。AFURLSessionManagerTaskDelegateNSURLSession
的回调方法比较多,这里只针对一些关键代码进行讲解,以及梳理整体回调逻辑,不一一列举每个回调方法的作用,详细源码各位可以直接下载AFN
代码查看。在AFURLSessionManager
中,有一个AFURLSessionManagerTaskDelegate
类比较重要,这个类和sessionTask
是一一对应的,负责处理sessionTask
请求的很多逻辑,NSURLSessionDelegate
的回调基本都转发给taskDelegate
去处理了。在NSURLSession
回调中处理了HTTPS
证书验证、下载进度之类的,没有太复杂的处理。taskDelegate
的设计很不错,可以将代理回调任务处理对象化,也可以给AFURLSessionManager
类瘦身。比较理想的是直接将代理设置为taskDelegate
,但是由于会涉及一些AFURLSessionManager
自身的处理逻辑,所以才设计为消息传递的方式。taskDelegate
的功能很简单,主要是NSData
数据的处理,NSProgress
上传下载进度的处理,以及通知参数的处理。在进行AFN
的下载处理时,NSData
的数据拼接、事件回调,及文件处理,都是由taskDelegate
来完成的。下面是downloadTask
任务完成时的处理代码,其他回调代码就不一一列举了。
- (void)URLSession:(NSURLSession *)session
downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location
{
self.downloadFileURL = nil;
if (self.downloadTaskDidFinishDownloading) {
self.downloadFileURL = self.downloadTaskDidFinishDownloading(session, downloadTask, location);
if (self.downloadFileURL) {
NSError *fileManagerError = nil;
if (![[NSFileManager defaultManager] moveItemAtURL:location toURL:self.downloadFileURL error:&fileManagerError]) {
[[NSNotificationCenter defaultCenter] postNotificationName:AFURLSessionDownloadTaskDidFailToMoveFileNotification object:downloadTask userInfo:fileManagerError.userInfo];
}
}
}
}
taskDelegate
中有一个很好的设计,taskDelegate
并不直接在NSURLSession
的代理方法中做进度拼接和回调。而是对于上传和下载任务分别对应不同的NSProgress
,并通过KVO
来监听fractionCompleted
属性,并且实现cancel
、suspend
等状态回调。任务的状态和进度处理交给NSProgress
,在回调方法中直接拼接NSProgress
的进度,从而回调KVO
方法。NSProgress
内部的cancel
、pause
、resume
方法,正好可以对应到sessionTask
的方法调用。但是从代码角度来看,AFN
好像并没有进行相关的调用,但这个设计思路很好。
_uploadProgress = [[NSProgress alloc] initWithParent:nil userInfo:nil];
_downloadProgress = [[NSProgress alloc] initWithParent:nil userInfo:nil];
__weak __typeof__(task) weakTask = task;
for (NSProgress *progress in @[ _uploadProgress, _downloadProgress ])
{
progress.totalUnitCount = NSURLSessionTransferSizeUnknown;
progress.cancellable = YES;
progress.cancellationHandler = ^{
[weakTask cancel];
};
progress.pausable = YES;
progress.pausingHandler = ^{
[weakTask suspend];
};
#if AF_CAN_USE_AT_AVAILABLE
if (@available(iOS 9, macOS 10.11, *))
#else
if ([progress respondsToSelector:@selector(setResumingHandler:)])
#endif
{
progress.resumingHandler = ^{
[weakTask resume];
};
}
[progress addObserver:self
forKeyPath:NSStringFromSelector(@selector(fractionCompleted))
options:NSKeyValueObservingOptionNew
context:NULL];
}
看过源码的话,可以发现AFURLSessionManager
中还有一个_AFURLSessionTaskSwizzling
类,这里我们简称taskSwizzling
类。我认为此类的设计实在是冗余,此类的主要功能就是在+load
方法中进行一个swizzling
,将dataTask
的resume
和suspend
方法进行替换,并且在替换后的方法中发出对应的通知,并没有太多实际的功能。只不过taskSwizzling
类中还是有一些不错的代码设计值得借鉴的,由于sessionTask
存在一系列继承链,所以直接对其进行swizzling
对其他子类并不生效,因为每个子类都有自己的实现,而写一大堆swizzling
又没有什么技术含量。在iOS7
和iOS8
上,sessionTask
的继承关系并不一样,最好进行一个统一的处理。AFN
采取的方式是创建一个dataTask
对象,并对这个对象进行swizzling
,并且遍历其继承链一直进行swizzling
,这样保证集成继承链的正确性。
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration ephemeralSessionConfiguration];
NSURLSession * session = [NSURLSession sessionWithConfiguration:configuration];
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wnonnull"
NSURLSessionDataTask *localDataTask = [session dataTaskWithURL:nil];
#pragma clang diagnostic pop
IMP originalAFResumeIMP = method_getImplementation(class_getInstanceMethod([self class], @selector(af_resume)));
Class currentClass = [localDataTask class];
while (class_getInstanceMethod(currentClass, @selector(resume))) {
Class superClass = [currentClass superclass];
IMP classResumeIMP = method_getImplementation(class_getInstanceMethod(currentClass, @selector(resume)));
IMP superclassResumeIMP = method_getImplementation(class_getInstanceMethod(superClass, @selector(resume)));
if (classResumeIMP != superclassResumeIMP &&
originalAFResumeIMP != classResumeIMP) {
[self swizzleResumeAndSuspendMethodForClass:currentClass];
}
currentClass = [currentClass superclass];
}
+ (void)swizzleResumeAndSuspendMethodForClass:(Class)theClass {
Method afResumeMethod = class_getInstanceMethod(self, @selector(af_resume));
Method afSuspendMethod = class_getInstanceMethod(self, @selector(af_suspend));
if (af_addMethod(theClass, @selector(af_resume), afResumeMethod)) {
af_swizzleSelector(theClass, @selector(resume), @selector(af_resume));
}
if (af_addMethod(theClass, @selector(af_suspend), afSuspendMethod)) {
af_swizzleSelector(theClass, @selector(suspend), @selector(af_suspend));
}
}
AFN
为了避免发生编译器警告,采取了预编译指令对代码进行修饰,预编译指令基本由三部分组成,push
、pop
、ignored
类型。Github
上有人维护了一份clang warning
清单,如果想进行对应的预编译处理可以上去找找有没有合适的。
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
#pragma clang diagnostic pop
NSURLSession
在iOS8
以下会并发创建多个task
,但并发设置task identifier
的时候会存在identifier
重复的问题。为了解决这个问题,在iOS8
以下,系统将所有sessionTask
的创建都放在一个同步的串行队列中进行,保证创建及赋值操作是串行进行的。
url_session_manager_create_task_safely(^{
dataTask = [self.session dataTaskWithRequest:request];
});
url_session_manager_create_task_safely(^{
uploadTask = [self.session uploadTaskWithRequest:request fromData:bodyData];
});
// 如果Foundation版本小于iOS8,则把block任务放在一个同步队列中执行。这个问题是由于在iOS8以下并发创建任务,可能会有多个相同的identifier
static void url_session_manager_create_task_safely(dispatch_block_t block) {
if (NSFoundationVersionNumber < NSFoundationVersionNumber_With_Fixed_5871104061079552_bug) {
dispatch_sync(url_session_manager_creation_queue(), block);
} else {
block();
}
}
一个比较有意思的是,AFN
为了让开发者明白为什么要加这个判断,对iOS8
系统的判断定义成了一个宏,并且用Apple Support
的id
作为宏定义命名,很见名知意。
#define NSFoundationVersionNumber_With_Fixed_5871104061079552_bug NSFoundationVersionNumber_iOS_8_0
AFN
在回调didCompleteWithError
方法,并处理返回数据时,会切换到其他线程和group
去处理,处理完成后再切换到主线程并通知调用方。AFN
提供了两个属性,用来设置请求结束后进行回调的dispatch queue
和dispatch group
,如果不设置的话,AFN
会有默认的实现来处理请求结束的操作。下面是group
和queue
的实现,AFN
对于返回数据的处理,采用的是并发处理。
static dispatch_queue_t url_session_manager_processing_queue() {
static dispatch_queue_t af_url_session_manager_processing_queue;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
af_url_session_manager_processing_queue = dispatch_queue_create("com.alamofire.networking.session.manager.processing", DISPATCH_QUEUE_CONCURRENT);
});
return af_url_session_manager_processing_queue;
}
static dispatch_group_t url_session_manager_completion_group() {
static dispatch_group_t af_url_session_manager_completion_group;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
af_url_session_manager_completion_group = dispatch_group_create();
});
return af_url_session_manager_completion_group;
}
AFN
在创建AFURLSessionManager
的operationQueue
时,将其最大并发数设置为1。这是因为在创建NSURLSSession
时,苹果要求网络请求回来的数据顺序执行,为了保证代理方法的执行顺序,所以需要串行的调用NSURLSSession
的代理方法。
AFHTTPSessionManager
本质上是对父类AFURLSessionManager
的封装,主要实现都在父类中,自己内部代码实现很简单。在创建AFHTTPSessionManager
时会传入一个baseURL
,以及指定requestSerializer
、responseSerializer
对象。从代码实现来看,AFN
的请求并不是单例形式的,每个请求都会创建一个新的请求对象。平时调用的GET
、POST
等网络请求方法,都定义在AFHTTPSessionManager
中。AFHTTPSessionManager
内部则调用父类方法,发起响应的请求并获取到task
对象,调用task
的resume
后返回给调用方。
- (NSURLSessionDataTask *)GET:(NSString *)URLString
parameters:(id)parameters
progress:(void (^)(NSProgress * _Nonnull))downloadProgress
success:(void (^)(NSURLSessionDataTask * _Nonnull, id _Nullable))success
failure:(void (^)(NSURLSessionDataTask * _Nullable, NSError * _Nonnull))failure
{
NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:@"GET"
URLString:URLString
parameters:parameters
uploadProgress:nil
downloadProgress:downloadProgress
success:success
failure:failure];
[dataTask resume];
return dataTask;
}
AFURLRequestSerialization
负责创建NSMutableURLRequest
请求对象,并对request
进行请求的参数拼接、设置缓存策略、设置请求头等关于请求相关的配置。AFURLRequestSerialization
并不是一个类,而是一个文件,其中包含三个requestSerializer
请求对象,分别对应着不同的请求序列化器。- AFHTTPRequestSerializer:普通请求。
JSON
请求。xml
格式请求。这三个类区别就在于Content-Type
不同,其他基本都是一样的。AFN
默认是HTTP的。
在文件中定义了同名的AFURLRequestSerialization
协议,不同的requestSerializer
会对协议方法有不同的实现,下面是AFHTTPRequestSerializer
的实现代码。其核心代码实现也比较直观,就是在创建requestSerializer
的时候,设置请求头的公共参数,以及将请求参数通过NSJSONSerialization
转换为NSData
,并将其赋值给request
对象的httpBody
,下面是精简后的核心代码。
- (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
withParameters:(id)parameters
error:(NSError *__autoreleasing *)error
{
NSMutableURLRequest *mutableRequest = [request mutableCopy];
[self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) {
if (![request valueForHTTPHeaderField:field]) {
[mutableRequest setValue:value forHTTPHeaderField:field];
}
}];
if (parameters) {
if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) {
[mutableRequest setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
}
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:parameters options:self.writingOptions error:error];
[mutableRequest setHTTPBody:jsonData];
}
return mutableRequest;
}
如果想给网络请求设置请求参数的话,需要通过requestSerializer
对外暴露的API
添加参数,AFN的requestManager
并不直接对外提供设置请求头的代码。通过requestSerializer
可以对请求头进行添加和删除、以及清空的操作。从创建AFURLRequestSerialization
对象到最后返回NSURLRequest
对象,中间的过程并不复杂,主要是设置请求头和拼接参数,逻辑很清晰。AFQueryStringPairAFURLRequestSerialization
有一个很重要的功能就是参数处理,AFQueryStringPair
就是负责处理这些参数的。pair
类中定义了两个属性,分别对应请求参数的key
、value
。除此之外,还定义了一些非常实用的C语言函数。
@interface AFQueryStringPair : NSObject
@property (readwrite, nonatomic, strong) id field;
@property (readwrite, nonatomic, strong) id value;
- (id)initWithField:(id)field value:(id)value;
- (NSString *)URLEncodedStringValue;
@end
AFQueryStringFromParameters
函数负责将请求参数字典,转成拼接在URL
后面的参数字符串,这个函数是AFQueryStringPair
类中定义的一个关键函数。函数内部通过AFQueryStringPairsFromDictionary
函数将参数字典,转为存储pair
对象的数组并进行遍历,遍历后调用URLEncodedStringValue
方法对参数进行拼接,最后成为字符串参数。URLEncodedStringValue
方法实现很简单,就是进行一个key
、value
的拼接,并且在中间加上“=”。
static NSString * AFQueryStringFromParameters(NSDictionary *parameters) {
NSMutableArray *mutablePairs = [NSMutableArray array];
for (AFQueryStringPair *pair in AFQueryStringPairsFromDictionary(parameters)) {
[mutablePairs addObject:[pair URLEncodedStringValue]];
}
return [mutablePairs componentsJoinedByString:@"&"];
}
- (NSString *)URLEncodedStringValue {
if (!self.value || [self.value isEqual:[NSNull null]]) {
return AFPercentEscapedStringFromString([self.field description]);
} else {
return [NSString stringWithFormat:@"%@=%@", AFPercentEscapedStringFromString([self.field description]), AFPercentEscapedStringFromString([self.value description])];
}
}
下面是参数拼接的代码,函数内部会将原有的参数,转换为AFQueryStringPair
对象的类型,但之前的层级结构不变。这句话是什么意思呢,就是说对原有传入的对象进行逐层递归调用,并且将最后一层字典的key
、value
参数,转成pair
类型的对象,并且将嵌套有pair
对象的数组返回给调用方。对象层级不变,但字典、集合都会被转换为数组结构,也就是之前传入字典、数组、字典的嵌套结构,返回的时候就是数组、数组、pair
的结构返回。
NSArray * AFQueryStringPairsFromDictionary(NSDictionary *dictionary) {
return AFQueryStringPairsFromKeyAndValue(nil, dictionary);
}
NSArray * AFQueryStringPairsFromKeyAndValue(NSString *key, id value) {
NSMutableArray *mutableQueryStringComponents = [NSMutableArray array];
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"description" ascending:YES selector:@selector(compare:)];
if ([value isKindOfClass:[NSDictionary class]]) {
NSDictionary *dictionary = value;
for (id nestedKey in [dictionary.allKeys sortedArrayUsingDescriptors:@[ sortDescriptor ]]) {
id nestedValue = dictionary[nestedKey];
if (nestedValue) {
[mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue((key ? [NSString stringWithFormat:@"%@[%@]", key, nestedKey] : nestedKey), nestedValue)];
}
}
} else if ([value isKindOfClass:[NSArray class]]) {
NSArray *array = value;
for (id nestedValue in array) {
[mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue([NSString stringWithFormat:@"%@[]", key], nestedValue)];
}
} else if ([value isKindOfClass:[NSSet class]]) {
NSSet *set = value;
for (id obj in [set sortedArrayUsingDescriptors:@[ sortDescriptor ]]) {
[mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue(key, obj)];
}
} else {
[mutableQueryStringComponents addObject:[[AFQueryStringPair alloc] initWithField:key value:value]];
}
return mutableQueryStringComponents;
}
AFHTTPRequestSerializer
在创建NSMutableURLRequest
时,需要为request
设置属性。serializer
对外提供了和request
同名的一些属性,外界直接调用serializer
即可设置request
的属性。AFHTTPRequestSerializer
内部创建request
时,并不是根据设置request
的属性按个赋值,而是通过一个属性数组AFHTTPRequestSerializerObservedKeyPaths
,将serializer
需要赋值给request
的属性,都放在数组中。
static NSArray * AFHTTPRequestSerializerObservedKeyPaths() {
static NSArray *_AFHTTPRequestSerializerObservedKeyPaths = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_AFHTTPRequestSerializerObservedKeyPaths = @[NSStringFromSelector(@selector(allowsCellularAccess)), NSStringFromSelector(@selector(cachePolicy)), NSStringFromSelector(@selector(HTTPShouldHandleCookies)), NSStringFromSelector(@selector(HTTPShouldUsePipelining)), NSStringFromSelector(@selector(networkServiceType)), NSStringFromSelector(@selector(timeoutInterval))];
});
return _AFHTTPRequestSerializerObservedKeyPaths;
}
在初始化AFHTTPRequestSerializer
时,遍历keyPath
数组并通过KVO
的方式,监听serializer
的赋值。如果外界对serializer
对应的属性进行赋值,则将其添加到mutableObservedChangedKeyPaths
数组中。在创建request
对象是,遍历mutableObservedChangedKeyPaths
数组并将值赋值给request
对象。
for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
if ([self respondsToSelector:NSSelectorFromString(keyPath)]) {
[self addObserver:self forKeyPath:keyPath options:NSKeyValueObservingOptionNew context:AFHTTPRequestSerializerObserverContext];
}
}
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(__unused id)object
change:(NSDictionary *)change
context:(void *)context
{
if (context == AFHTTPRequestSerializerObserverContext) {
if ([change[NSKeyValueChangeNewKey] isEqual:[NSNull null]]) {
[self.mutableObservedChangedKeyPaths removeObject:keyPath];
} else {
[self.mutableObservedChangedKeyPaths addObject:keyPath];
}
}
}
for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
if ([self.mutableObservedChangedKeyPaths containsObject:keyPath]) {
[mutableRequest setValue:[self valueForKeyPath:keyPath] forKey:keyPath];
}
}
当进行POST
表单提交时,需要用到AFMultipartFormData
协议。调用POST
方法后,会回调一个遵守此协议的对象,可以通过此对象进行表单提交操作。
[manager POST:requestURL parameters:params constructingBodyWithBlock:^(id<AFMultipartFormData> _Nonnull formData) {
[formData appendPartWithFileData:params[@"front_img"]
name:@"front_img"
fileName:frontImgfileName
mimeType:@"multipart/form-data"];
[formData appendPartWithFileData:params[@"reverse_img"]
name:@"reverse_img"
fileName:reverseImgfileName
mimeType:@"multipart/form-data"];
[formData appendPartWithFileData:params[@"face_img"]
name:@"face_img"
fileName:faceImgfileName
mimeType:@"multipart/form-data"];
} progress:^(NSProgress * _Nonnull uploadProgress) {
// nothing
} success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
// nothing
} failure:nil];
进行表单提交时,可以直接传入文件,也可以传入路径。表单提交可以同时提交多个文件,理论上数量不受限制。缓存策略AFN
的缓存策略和NSURLCache
的缓存策略一致,并且直接使用系统的枚举,这对iOS
开发者是非常友好的。下面是枚举定义,忽略掉一些unimplemented
的,和一些重定向到已有枚举的,可用的都在这。
typedef NS_ENUM(NSUInteger, NSURLRequestCachePolicy)
{
NSURLRequestUseProtocolCachePolicy = 0,
NSURLRequestReloadIgnoringLocalCacheData = 1,
NSURLRequestReturnCacheDataElseLoad = 2,
NSURLRequestReturnCacheDataDontLoad = 3,
NSURLRequestReloadIgnoringLocalAndRemoteCacheData = 4,
NSURLRequestReloadRevalidatingCacheData = 5,
};
AFURLResponseSerialization
负责处理response
相关的逻辑,其功能主要是设置acceptType
、编码格式和处理服务器返回数据。同样的,AFURLResponseSerialization
也有同名的协议,每个子类都遵循代理方法并实现不同的返回值处理代码。
- (nullable id)responseObjectForResponse:(nullable NSURLResponse *)response
data:(nullable NSData *)data
error:(NSError * _Nullable __autoreleasing *)error;
和AFURLRequestSerialization
一样,AFURLResponseSerialization
由一个父类和六个子类构成,子类中有一个是Mac
的,所以这里不做分析,子类的职责只是对acceptType
做修改以及处理具体的返回数据。- AFHTTPResponseSerializer:公共父类,处理返回值类型为NSData二进制。
JSON
返回数据,也是默认类型。XML
返回数据,由系统NSXMLParser
负责处理。XML
返回数据,也就是plis
t数据。由于服务器有时候会返回null
的情况,系统会将其转换为NSNull
对象,而对NSNull
对象发送不正确的消息,就会导致崩溃。从服务器接收到返回值后,AFN
会对返回值进行一个递归查找,找到所有NSNull
对象并将其移除,防止出现向NSNull
对象发送消息导致的崩溃。
static id AFJSONObjectByRemovingKeysWithNullValues(id JSONObject, NSJSONReadingOptions readingOptions) {
if ([JSONObject isKindOfClass:[NSArray class]]) {
NSMutableArray *mutableArray = [NSMutableArray arrayWithCapacity:[(NSArray *)JSONObject count]];
for (id value in (NSArray *)JSONObject) {
[mutableArray addObject:AFJSONObjectByRemovingKeysWithNullValues(value, readingOptions)];
}
return (readingOptions & NSJSONReadingMutableContainers) ? mutableArray : [NSArray arrayWithArray:mutableArray];
} else if ([JSONObject isKindOfClass:[NSDictionary class]]) {
NSMutableDictionary *mutableDictionary = [NSMutableDictionary dictionaryWithDictionary:JSONObject];
for (id <NSCopying> key in [(NSDictionary *)JSONObject allKeys]) {
id value = (NSDictionary *)JSONObject[key];
if (!value || [value isEqual:[NSNull null]]) {
[mutableDictionary removeObjectForKey:key];
} else if ([value isKindOfClass:[NSArray class]] || [value isKindOfClass:[NSDictionary class]]) {
mutableDictionary[key] = AFJSONObjectByRemovingKeysWithNullValues(value, readingOptions);
}
}
return (readingOptions & NSJSONReadingMutableContainers) ? mutableDictionary : [NSDictionary dictionaryWithDictionary:mutableDictionary];
}
return JSONObject;
}
bundleForClass
在使用NSBundle
对象时,我们最常用的就是mainBundle
或者bundleWithPath
这种方式获取bundle
,这种对于都是从app
二进制读取的时候是没有问题的。但是如果涉及到framework
动态库,就不是那么易于使用。framework
中可以包含资源文件,例如.bundle
文件。如果是动态库形式的framework
(framework
也有静态形式),其会以一个独立二进制的形式表现,并且会分配独立的二进制空间。在读取bundle
的时候,就可以考虑使用bundleForClass
的方式读取。bundleForClass
表示从当前类定义的二进制,所在的程序包中读取NSBundle
文件。例如.app
就是从main bundle
中读取,如果是framework
就从其所在的二进制中读取。网络指示器AFN
提供了一些UIKit的Category
,例如网络请求发起时,网络指示器转菊花,则由AFNetworkActivityIndicatorManager
类负责。开启网络指示器很简单,添加下面代码即可,网络指示器默认是关闭的。
[[AFNetworkActivityIndicatorManager sharedManager] setEnabled:YES];这里不对`AFNetworkActivityIndicatorManager`的代码进行过多的分析,只是调其中比较重要的点来分析,下面统称为`indicatorManager`。之前在`_AFURLSessionTaskSwizzling`类中写了很多代码,就是为了发出`resume`和`suspend`两个通知,这两个通知在`indicatorManager`中就用到了。网络指示器监听了下面的三个通知,并且完全由通知来驱动。
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(networkRequestDidStart:) name:AFNetworkingTaskDidResumeNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(networkRequestDidFinish:) name:AFNetworkingTaskDidSuspendNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(networkRequestDidFinish:) name:AFNetworkingTaskDidCompleteNotification object:nil];
如果看indicatorManager
中的源码,你会发现为什么里面还有timer
,完全不需要啊,有网络请求就转菊花,没网络请求就停止不就行了吗?这是因为AFN
考虑,如果一个网络请求很快的话,会导致菊花出现转一下很快就消失的情况,如果网络请求比较多会多次闪现。所以对于这个问题,indicatorManager
通过Timer
的方式实现,如果在指定的区间内网络请求已经结束,则不在显示菊花,如果有多次请求则在请求之间也不进行中断。对于开始转圈设置的是1.0秒,结束转圈设置的是0.17秒。也就是当菊花开始旋转时,需要有1.0秒的延时,这个时间足以保证之前的菊花停止转动。结束转圈则会在0.17秒之后进行,可以保证菊花的旋转至少会有0.17秒。
static NSTimeInterval const kDefaultAFNetworkActivityManagerActivationDelay = 1.0;
static NSTimeInterval const kDefaultAFNetworkActivityManagerCompletionDelay = 0.17;
- (void)startActivationDelayTimer {
self.activationDelayTimer = [NSTimer
timerWithTimeInterval:self.activationDelay target:self selector:@selector(activationDelayTimerFired) userInfo:nil repeats:NO];
[[NSRunLoop mainRunLoop] addTimer:self.activationDelayTimer forMode:NSRunLoopCommonModes];
}
- (void)startCompletionDelayTimer {
[self.completionDelayTimer invalidate];
self.completionDelayTimer = [NSTimer timerWithTimeInterval:self.completionDelay target:self selector:@selector(completionDelayTimerFired) userInfo:nil repeats:NO];
[[NSRunLoop mainRunLoop] addTimer:self.completionDelayTimer forMode:NSRunLoopCommonModes];
}
由于indicatorManager
是采用通知的方式进行回调,所有的网络请求通知都会调到这。所以当多个网络请求到来时,会通过一个_activityCount
来进行计数,可以将其理解为一个队列,这样更容易理解。在显示网络指示器的时候,就是基于_activityCount
来进行判断的,如果队列中有请求则显示网络指示器,无论有多少请求。这种设计思路比较好,在项目中很多地方都可以用到。例如有些方法需要成对进行调用,例如播放开始和暂停,如果某一个方法调用多次就会造成bug
。这种方式就比较适合用count
的方式进行容错,内部针对count
做一些判断操作。
- (void)incrementActivityCount {
[self willChangeValueForKey:@"activityCount"];
@synchronized(self) {
_activityCount++;
}
[self didChangeValueForKey:@"activityCount"];
dispatch_async(dispatch_get_main_queue(), ^{
[self updateCurrentStateForNetworkActivityChange];
});
}
- (void)decrementActivityCount {
[self willChangeValueForKey:@"activityCount"];
@synchronized(self) {
_activityCount = MAX(_activityCount - 1, 0);
}
[self didChangeValueForKey:@"activityCount"];
dispatch_async(dispatch_get_main_queue(), ^{
[self updateCurrentStateForNetworkActivityChange];
});
}
indicatorManager
是多线程安全的,在一些关键地方都通过synchronized
的方式加锁,防止从各个线程调用过来的通知造成资源抢夺的问题。
AFNetworking
中还有很重要的一部分,就是Reachability
,用来做网络状态监控的。AFNetworking
、YYKit
、苹果官方都提供有Reachability
的API
使用,内部实现原理基本差不多。代码实现也很简单,主要依赖SystemConfiguration.framework
框架的SCNetworkReachability
,注册一个Callback
然后等着回调就可以。这里讲一下核心逻辑,一些细枝末节的就忽略了。Reachability
提供了两种初始化方法,一种是通过域名初始化的managerForDomain:
方法,传入一个域名,基于这个域名的访问情况来判断当前网络状态。另一种是通过地址初始化的managerForAddress:
方法,创建一个sockaddr_in
对象,并基于这个对象来判断网络状态。
+ (instancetype)managerForAddress:(const void *)address {
SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, (const struct sockaddr *)address);
AFNetworkReachabilityManager *manager = [[self alloc] initWithReachability:reachability];
CFRelease(reachability);
return manager;
}
+ (instancetype)manager {
struct sockaddr_in address;
bzero(&address, sizeof(address));
address.sin_len = sizeof(address);
address.sin_family = AF_INET;
return [self managerForAddress:&address];
}
下面startMonitoring
中是开启网络监测的核心代码,主要逻辑是设置了两个Callback
,一个是block
的一个是函数的,并添加到Runloop
中开始监控。由此可以推测,Reachability
的代码实现主要依赖Runloop
的事件循环,并且在事件循环中判断网络状态。当网络发生改变时,就会回调AFNetworkReachabilityCallback
函数,回调有三个参数。target
是SCNetworkReachabilityRef
对象,flags
是网络状态,info
是我们设置的block
回调参数。回调Callback
函数后,内部会通过block
以及通知的形式,对外发出回调。
- (void)startMonitoring {
[self stopMonitoring];
if (!self.networkReachability) {
return;
}
__weak __typeof(self)weakSelf = self;
AFNetworkReachabilityStatusBlock callback = ^(AFNetworkReachabilityStatus status) {
__strong __typeof(weakSelf)strongSelf = weakSelf;
strongSelf.networkReachabilityStatus = status;
if (strongSelf.networkReachabilityStatusBlock) {
strongSelf.networkReachabilityStatusBlock(status);
}
};
SCNetworkReachabilityContext context = {0, (__bridge void *)callback, AFNetworkReachabilityRetainCallback, AFNetworkReachabilityReleaseCallback, NULL};
SCNetworkReachabilitySetCallback(self.networkReachability, AFNetworkReachabilityCallback, &context);
SCNetworkReachabilityScheduleWithRunLoop(self.networkReachability, CFRunLoopGetMain(), kCFRunLoopCommonModes);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0),^{
SCNetworkReachabilityFlags flags;
if (SCNetworkReachabilityGetFlags(self.networkReachability, &flags)) {
AFPostReachabilityStatusChange(flags, callback);
}
});
}
- (void)stopMonitoring {
if (!self.networkReachability) {
return;
}
SCNetworkReachabilityUnscheduleFromRunLoop(self.networkReachability, CFRunLoopGetMain(), kCFRunLoopCommonModes);
}
static void AFNetworkReachabilityCallback(SCNetworkReachabilityRef __unused target, SCNetworkReachabilityFlags flags, void *info) {
AFPostReachabilityStatusChange(flags, (__bridge AFNetworkReachabilityStatusBlock)info);
}
AFNetworking
对请求数据的序列化,以及返回数据的反序列化做了很多处理。使开发者只需要传入一个字典即可构建请求参数,无需处理拼接到URL后面或参数转换为body
二进制的细节,这些都由AFNetworking
内部处理并进行容错,开发者只需要指定请求方式即可。
通过AFNetworking
实现https
也很方便,AFSecurityPolicy
可以很好的管理CA
以及自签名证书,以及处理https
请求过程中的一些逻辑,我们只需要告诉AFNetworking
怎么处理即可,如果不指定处理方式则使用默认CA
证书的方式处理。AFNetworking
对于后台下载以及断点续传有很好的支持,我们可以在AFNetworking
的基础上,很简单的就完成一个下载模块的设计。如果自己写后台下载和断点续传的代码,工作量还是不小的。并且AFNetworking
在网络库的设计上还提供了很强的自定义性,例如指定证书、URL
缓存处理,以及下载过程中不同下载阶段的处理。如果没有提供自定义处理,则使用默认处理方式。
AFSecurityPolicy
验证处理
AFN
支持https
请求,并通过AFSecurityPolicy
类来处理https
证书及验证,但其https
请求的执行还是交给NSURLSession
去完成的。下面是NSURLSession
的一个代理方法,当需要进行证书验证时,可以重写此方法并进行自定义的验证处理。验证完成后通过completionHandler
的block
来告知处理结果,并且将验证结果disposition
和公钥credential
传入。AFN
通过AFSecurityPolicy
类提供了验证逻辑,并且在内部可以进行证书的管理。也可以不使用AFN提供的验证逻辑,重写sessionDidReceiveAuthenticationChallenge的block
即可自定义验证逻辑,不走AFN
的逻辑。
- (void)URLSession:(NSURLSession *)session
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
{
NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
__block NSURLCredential *credential = nil;
if (self.sessionDidReceiveAuthenticationChallenge) {
disposition = self.sessionDidReceiveAuthenticationChallenge(session, challenge, &credential);
} else {
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
if ([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) {
credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
if (credential) {
disposition = NSURLSessionAuthChallengeUseCredential;
} else {
disposition = NSURLSessionAuthChallengePerformDefaultHandling;
}
} else {
disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
}
} else {
disposition = NSURLSessionAuthChallengePerformDefaultHandling;
}
}
if (completionHandler) {
completionHandler(disposition, credential);
}
}
除了进行NSURLSession
请求验证的回调,对于每个task
也有对应的代理方法。两个代理方法内部实现基本一样,区别在于对于每个task
,AFN
提供了taskDidReceiveAuthenticationChallenge
回调block
,可以由外界自定义证书验证过程。验证结果是通过一个枚举回调给NSURLSession
的,参数是一个NSURLSessionAuthChallengeDisposition
类型的枚举,表示证书验证的情况,此枚举包含下面几个具体值。
使用当前证书建立SSL
连接,并处理后续请求
使用默认的处理方式,当前证书被忽略
验证不通过,取消整个网络请求
这次验证被忽略,但不取消网络请求
SecurityHTTPS
请求的密钥管理等安全相关的处理,都放在Security.framework
框架中。在AFSecurityPolicy
中经常可以看到SecTrustRef
类型的变量,其表示的就是密钥对象,其中包含了公钥等信息。我们可以通过下面的命令获取到公钥,具体格式这里不做过多介绍,详细的可以Google一下公钥格式。
// 获取公钥命令
SecTrustCopyPublicKey(serverTrust)
// 打印的公钥(公钥已做脱敏)
<SecKeyRef algorithm id: 1, key type: RSAPublicKey, version: 4, block size: 2048 bits, exponent: {hex: 10001, decimal: 65537}, modulus: A51E89C5FFF2748A6F70C4D701D29A39723C3BE495CABC5487B05023D957CD287839F6B5E53F90B963438417547A369BBA5818D018B0E98F2449442DFBD7F405E18D5A827B6F6F0A5A5E5585C6C0F342DDE727681902021B7A7CE0947EFCFDDC4CCF8E100D94A454156FD3E457F4719E3C6B9E408CD4316B976A6C44BD91A057FEA4A115BEB1FE28E71005D2198E3B79B8942779B434A0B08F82B3D390A7B94B958BB71D9B69564C84A1B03FE22D4C4E24BBA2D5ED3F660F1EBC757EF0E52A7CF13A8C167B0E6171B2CD6678CC02EAF1E59147F53B3671C33107D9ED5238CBE33DB617931FFB44DE70043B2A618D8F43608D6F494360FFDEE83AD1DCE120D6F1, addr: 0x280396dc0>
AFSecurityPolicy
的职责比较单一,只处理公钥和验证的逻辑,其定义是一个单例对象。此类主要由四个属性和一个方法构成。
// 证书验证方式
@property (readonly, nonatomic, assign) AFSSLPinningMode SSLPinningMode;
// 本地自签名证书集合
@property (nonatomic, strong, nullable) NSSet <NSData *> *pinnedCertificates;
// 是否验证证书的合法性(是否允许自签名证书)
@property (nonatomic, assign) BOOL allowInvalidCertificates;
// 是否验证域名是否有效
@property (nonatomic, assign) BOOL validatesDomainName;
如果进行细分的话,AFSecurityPolicy
的功能基本就两个。一个是通过CA
的方式进行验证,另一个就是进行SSL Pinning
自签名验证。evaluateServerTrust:forDomain:
是AFSecurityPolicy
最主要的方法,用来进行证书的合法性验证。SSL PinningAFSecurityPolicy
进行SSL Pinning
验证的方式分为以下三种,如果是None
则会执行正常CA
验证的流程,其他两种都是自签名的流程。AFN
中默认的调用是defaultPolicy
方法,其内部设置的是AFSSLPinningModeNone
模式。
正常流程,通过CA
机构颁发的公钥,对服务器下发的证书验证数字签名,并且获得公钥。
不通过CA
的流程进行验证,而是通过本地内置的服务端证书进行验证,验证过程分为两步。首先验证证书是否过期或失效,其次验证本地是否包含此证书。
不进行CA
的验证,也不验证证书,只验证公钥是否有效。
对于本地自签名证书的管理有两种方式,一种是默认会在本地查找遍历所有.cer
的文件,并存在一个自签名证书的集合中。也可以在创建AFSecurityPolicy
对象时传入SSLPinningMode
,下面是查找本地.cer
文件的逻辑。
+ (NSSet *)certificatesInBundle:(NSBundle *)bundle {
NSArray *paths = [bundle pathsForResourcesOfType:@"cer" inDirectory:@"."];
NSMutableSet *certificates = [NSMutableSet setWithCapacity:[paths count]];
for (NSString *path in paths) {
NSData *certificateData = [NSData dataWithContentsOfFile:path];
[certificates addObject:certificateData];
}
return [NSSet setWithSet:certificates];
}
HTTPS
在进行握手时,需要通过CA
的公钥进行验证,保证服务器公钥的合法性,没有被篡改为有问题的公钥。如果使用CA机构颁发的证书,无论使用NSURLSession
还是AFNetworking
都不需要修改代码,这些都会自动完成。如果不想使用CA的证书验证,例如自签名证书在CA证书验证时就会失败。这种情况可以使用自签名的方式进行验证,也就是在客户端本地内置一份证书,服务器进行四次握手时,通过保存在本地的证书和服务器进行对比,证书相同则表示验证成功,不走CA
的验证方式。AFN
为我们提供了自签名证书的验证方法,通过SSLPinningMode
设置验证方式为自签名,并且传入证书集合。如果没有传入证书集合,则AFN
默认会遍历整个沙盒,查找所有.cer
的证书。进行沙盒验证时,需要将AFSecurityPolicy
的allowInvalidCertificates
设置为YES
,默认是NO
,表示允许无效的证书,也就是自签名的证书。
NSString *cerPath = [[NSBundle mainBundle] pathForResource:@"12306" ofType:@"cer"];
NSData *certData = [NSData dataWithContentsOfFile:cerPath];
NSSet *set = [NSSet setWithObject:certData];
AFSecurityPolicy *securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate withPinnedCertificates:set];
securityPolicy.allowInvalidCertificates = YES;
本文由哈喽比特于4年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/-tBRTyivT8BGPaBcr7x1Vw
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。