【iOS开源库】SDWebImage源码阅读&原理解析

发表于 4年以前  | 总阅读数:1902 次

汇总记录:

本文基于SDWebImage 4.2.3版本进行分析和整理。

整体目录结构:

SDWebImage |----SDWebImageCompat 处理不同平台(iOS、TV、OS、Watch)宏,以及根据文件名@2x、@3x进行图片处理和缩放 |----SDWebImageOperation.h 添加cancel的delegate +----Cache |--------SDImageCache 主要处理缓存逻辑,重点集中在:NSCache(Memory)、Disk读写、清理Old File |--------SDImageCacheConfig 配置缓存参数:是否压缩、iCloud、InMemory、ReadingOption、时间和CacheSize +----Downloader |--------SDWebImageDownloaderOperation 主要提供下载的Operation操作 |--------SDWebImageDownloader 提供下载管理入口 +----Utils |--------SDWebImageManager 提供外层管理cache和download入口 |--------SDWebImagePrefetcher 预处理获取Image,主要应用预加载的地方 +----Categories |--------NSData+ImageContentType 提供类型判断和ImageIO类型转换 |--------UIImage+GIF Data转UIImage(GIF)扩展 |--------UIImage+MultiFormat 提供BitMap或者未知类型的Data转UIImage扩展 |--------UIImage+WebP Data转WebP扩展 |--------UIImage+ForceDecode 解压操作 |--------UIView+WebCacheOperation 提供顶层关于取消和下载记录的扩展 +----Decoder |--------SDWebImageCodersManager 整体Coders的入口,提供是否可Coder和Coder转发 |--------SDWebImageCoder 主要说明Coder Delegate 需要实现的接口 |--------SDWebImageImageIOCoder PNG/JPEG的Encode和解压操作 |--------SDWebImageGIFCoder GIF的Coder操作 |--------SDWebImageWebPCoder WebP的Coder操作 |--------SDWebImageFrame 辅助类,主要在GIF等动态图使用 |--------SDWebImageCoderHelper 辅助类,包括方向、Gif图合成等


整体组件结构

整体框架结构比较清晰,因为Decoder部分相对比较独立,业务逻辑处理主要在Cache、Downloader层级以及以上。 下文会以Cache、Downloader、Manager、Wrap的顺序进行分解,最后讨论Decoder部分。


1、缓存部分解析

缓存部分逻辑主要是在SDImageCache,包括如下几个方面:

  • 新增
  • 删除
  • 查询
  • 缓存管理(过期)

SDWebImage的缓存中,主要走了一套NSCache管理内存和根据传入的Key转换MD5作为文件名存储。以及创建了一个IO操作的Queue进行管理IO操作。 这里重点注意,任何耗时:包括IO读写、转码等操作,都不应该放到主线程里面使用。

缓存部分其他地方都比较简单易懂,直接看源码即可。

重点说下如下两个值得学习的地方:

1、通过NSOperation管理queue任务
- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key done:(nullable SDCacheQueryCompletedBlock)doneBlock {
    if (!key) {
        if (doneBlock) {
            doneBlock(nil, nil, SDImageCacheTypeNone);
        }
        return nil;
    }

    // First check the in-memory cache...
    UIImage *image = [self imageFromMemoryCacheForKey:key];
    if (image) {
        NSData *diskData = nil;
        if (image.images) {
            diskData = [self diskImageDataBySearchingAllPathsForKey:key];
        }
        if (doneBlock) {
            doneBlock(image, diskData, SDImageCacheTypeMemory);
        }
        return nil;
    }

    NSOperation *operation = [NSOperation new];
    dispatch_async(self.ioQueue, ^{
        if (operation.isCancelled) {
            // do not call the completion if cancelled
            return;
        }

        @autoreleasepool {
            NSData *diskData = [self diskImageDataBySearchingAllPathsForKey:key];
            UIImage *diskImage = [self diskImageForKey:key];
            if (diskImage && self.config.shouldCacheImagesInMemory) {
                NSUInteger cost = SDCacheCostForImage(diskImage);
                [self.memCache setObject:diskImage forKey:key cost:cost];
            }

            if (doneBlock) {
                dispatch_async(dispatch_get_main_queue(), ^{
                    doneBlock(diskImage, diskData, SDImageCacheTypeDisk);
                });
            }
        }
    });

    return operation;
}

查询缓存的时候,这里采用了NSOperation进行是否取消的操作,因为当下载/缓存内容过多时,毕定存在先后处理顺序的问题,这时候可能由于用户操作等需要取消当前缓存处理,那么NSOperation这里起的唯一作用就是提供取消操作。可以参考具体的Manager里面缓存调起逻辑。

2、申请系统后台时间处理任务
- (void)backgroundDeleteOldFiles {
    Class UIApplicationClass = NSClassFromString(@"UIApplication");    if(!UIApplicationClass || ![UIApplicationClass respondsToSelector:@selector(sharedApplication)]) {        return;
    }    UIApplication *application = [UIApplication performSelector:@selector(sharedApplication)];
    __block UIBackgroundTaskIdentifier bgTask = [application beginBackgroundTaskWithExpirationHandler:^{        // Clean up any unfinished task business by marking where you
        // stopped or ending the task outright.
        [application endBackgroundTask:bgTask];
        bgTask = UIBackgroundTaskInvalid;
    }];    // Start the long-running task and return immediately.
    [self deleteOldFilesWithCompletionBlock:^{
        [application endBackgroundTask:bgTask];
        bgTask = UIBackgroundTaskInvalid;
    }];
}

这里有个疑问点要注意,为啥会存在前后两部分都去释放Task任务。 iOS的后台任务有个背景,不管任何时候,都需要手动去调用endBackgroundTask结束后台任务,其实开启一个后台job的时候,因为时长有限,所以会存在两种结局:

  1. 在允许的时间内执行完成
  2. 规定时间内未执行完成 如上两种情况,在结束后都必须手动调用endBackgroundTask:;

2、下载器(Downloader)

下载部分,主要是提供了一个Operation和一个Manager,其中SDWebImageDownloaderOperation里面提供了常用的Operation操作,也支持自定义的下载逻辑(实现SDWebImageDownloaderOperationInterface即可)。

2.1 SDWebImageDownloaderOperation 逻辑

该文件里面重点是Delegate:SDWebImageDownloaderOperationInterface 的设计和一种实现方式SDWebImageDownloaderOperation(PS:优秀的开源库基本都会设计一套接口,再做一套基础的实现)。

// 这里描述写的很清楚,如果需要自定义的Downloader op,那么需要继承NSOperation并且实现SDWebImageDownloaderOperation

/**
 Describes a downloader operation. If one wants to use a custom downloader op, it needs to inherit from `NSOperation` and conform to this protocol
 */
@protocol SDWebImageDownloaderOperationInterface<NSObject>

- (nonnull instancetype)initWithRequest:(nullable NSURLRequest *)request
                              inSession:(nullable NSURLSession *)session
                                options:(SDWebImageDownloaderOptions)options;

- (nullable id)addHandlersForProgress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                            completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock;

- (BOOL)shouldDecompressImages;
- (void)setShouldDecompressImages:(BOOL)value;

- (nullable NSURLCredential *)credential;
- (void)setCredential:(nullable NSURLCredential *)value;

@end

SDWebImageDownloaderOperation主要是提供内置的下载实现,重点是使用NSURLSessionTask进行下载,逻辑不复杂,详细的参考源码。里面重点有如下部分需要关注:

  • 1、_barrierQueue是共用的并发队列,而且不同的Op都是共用的同一个Queue,这里为啥要做成并发的呢?主要是优化多个线程查询 callbacks的时间。
- (nullable NSArray<id> *)callbacksForKey:(NSString *)key {
    __block NSMutableArray<id> *callbacks = nil;
    dispatch_sync(self.barrierQueue, ^{
        // We need to remove [NSNull null] because there might not always be a progress block for each callback
        callbacks = [[self.callbackBlocks valueForKey:key] mutableCopy];
        [callbacks removeObjectIdenticalTo:[NSNull null]];
    });
    return [callbacks copy];    // strip mutability here
}

然后再配合dispatch_barrier_(a)sync操作来进行增删 操作。

  • 2、在URLSession的Delegate实现中,存在如下函数实现:
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler {
    
    NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
    __block NSURLCredential *credential = nil;
    
    if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
        if (!(self.options & SDWebImageDownloaderAllowInvalidSSLCertificates)) {
            disposition = NSURLSessionAuthChallengePerformDefaultHandling;
        } else {
            credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
            disposition = NSURLSessionAuthChallengeUseCredential;
        }
    } else {
        if (challenge.previousFailureCount == 0) {
            if (self.credential) {
                credential = self.credential;
                disposition = NSURLSessionAuthChallengeUseCredential;
            } else {
                disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
            }
        } else {
            disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
        }
    }
    
    if (completionHandler) {
        completionHandler(disposition, credential);
    }
}

该部分重点是实现Https证书信任请求的,关于Https在iOS上的信任使用,可以搜索学习 iOS上的Https。

2.2 SDWebImageDownloader说明

SDWebImageDownloader主要是基于SDWebImageDownloaderOperation进行任务的添加和管理。使用NSOperationQueue进行任务执行操作。

  • 设置了同一时间最多可执行task为6.

  • 提供设置Http头信息入口

  • 允许使用自定义的SDWebImageDownloaderOperationInterfaceOperation进行操作,如果没有指定,那么就用默认的SDWebImageDownloaderOperation

  • 详细的逻辑参考代码即可,比较简单


3、针对通用组件封装(Wrap)

该部分的代码主要是在WebCache Categories文件夹下面,除了UIView+WebCache 以外,其他的涉及UIImage、UIButton等都是下载完成后赋值给到Image的区别,下载过程处理都是在UIView+WebCache中实现。 这里主要说下UIView+WebCache中的细节,其他的阅读源码即可,里面主要的逻辑函数为:

- (void)sd_internalSetImageWithURL:(nullable NSURL *)url
                  placeholderImage:(nullable UIImage *)placeholder
                           options:(SDWebImageOptions)options
                      operationKey:(nullable NSString *)operationKey
                     setImageBlock:(nullable SDSetImageBlock)setImageBlock
                          progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                         completed:(nullable SDExternalCompletionBlock)completedBlock
                           context:(nullable NSDictionary *)context

详细的步骤逻辑如下:

  // 第一步:取消老的下载,这就是UITableViewCell重用后,快速滑动时,中间部分的图片不会被下载的原因。
    NSString *validOperationKey = operationKey ?: NSStringFromClass([self class]);
    [self sd_cancelImageLoadOperationWithKey:validOperationKey];
    objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    //////////////////////////////////////////////
    // 第二步:看是否设置默认图片,这里一个点。如果[context valueForKey:SDWebImageInternalSetImageGroupKey],那么就enter group,这个是干啥的呢,其实主要是给到FLAnimatedImageView+WebCache使用,
    // 这里是等待setImageBlock会调用的,会结合dispatch_group_leave 和 dispatch_group_notify 使用
    if (!(options & SDWebImageDelayPlaceholder)) {
        if ([context valueForKey:SDWebImageInternalSetImageGroupKey]) {
            dispatch_group_t group = [context valueForKey:SDWebImageInternalSetImageGroupKey];
            dispatch_group_enter(group);
        }
        dispatch_main_async_safe(^{
            [self sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock];
        });
    }

    if (url) {
    // 这里是状态栏的扩展,主要是显示流量那个菊花,可以忽略
        // check if activityView is enabled or not
        if ([self sd_showActivityIndicatorView]) {
            [self sd_addActivityIndicator];
        }
        ///////////////////////////////////////////////////////////////////
        // 第三步:获取Manager进行下载
        SDWebImageManager *manager;
        if ([context valueForKey:SDWebImageExternalCustomManagerKey]) {
            manager = (SDWebImageManager *)[context valueForKey:SDWebImageExternalCustomManagerKey];
        } else {
            manager = [SDWebImageManager sharedManager];
        }

        __weak __typeof(self)wself = self;
        id <SDWebImageOperation> operation = [manager loadImageWithURL:url options:options progress:progressBlock completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
            __strong __typeof (wself) sself = wself;
            [sself sd_removeActivityIndicator];
            if (!sself) { return; }

            ////////////////////////////////////////////////////////
            // 第四步:下载完成后,进行状态设置和相关状态回调
            BOOL shouldCallCompletedBlock = finished || (options & SDWebImageAvoidAutoSetImage);
            BOOL shouldNotSetImage = ((image && (options & SDWebImageAvoidAutoSetImage)) ||
                                      (!image && !(options & SDWebImageDelayPlaceholder)));
            SDWebImageNoParamsBlock callCompletedBlockClojure = ^{
                if (!sself) { return; }
                if (!shouldNotSetImage) {
                    [sself sd_setNeedsLayout];
                }
                if (completedBlock && shouldCallCompletedBlock) {
                    completedBlock(image, error, cacheType, url);
                }
            };

            // case 1a: we got an image, but the SDWebImageAvoidAutoSetImage flag is set
            // OR
            // case 1b: we got no image and the SDWebImageDelayPlaceholder is not set
            if (shouldNotSetImage) {
                dispatch_main_async_safe(callCompletedBlockClojure);
                return;
            }

            UIImage *targetImage = nil;
            NSData *targetData = nil;
            if (image) {
                // case 2a: we got an image and the SDWebImageAvoidAutoSetImage is not set
                targetImage = image;
                targetData = data;
            } else if (options & SDWebImageDelayPlaceholder) {
                // case 2b: we got no image and the SDWebImageDelayPlaceholder flag is set
                targetImage = placeholder;
                targetData = nil;
            }

            if ([context valueForKey:SDWebImageInternalSetImageGroupKey]) {
                dispatch_group_t group = [context valueForKey:SDWebImageInternalSetImageGroupKey];
                dispatch_group_enter(group);
                dispatch_main_async_safe(^{
                    [sself sd_setImage:targetImage imageData:targetData basedOnClassOrViaCustomSetImageBlock:setImageBlock];
                });
                // ensure completion block is called after custom setImage process finish
                dispatch_group_notify(group, dispatch_get_main_queue(), ^{
                    callCompletedBlockClojure();
                });
            } else {
                dispatch_main_async_safe(^{
                    [sself sd_setImage:targetImage imageData:targetData basedOnClassOrViaCustomSetImageBlock:setImageBlock];
                    callCompletedBlockClojure();
                });
            }
        }];
        [self sd_setImageLoadOperation:operation forKey:validOperationKey];
    } else {
        dispatch_main_async_safe(^{
            [self sd_removeActivityIndicator];
            if (completedBlock) {
                NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey : @"Trying to load a nil url"}];
                completedBlock(nil, error, SDImageCacheTypeNone, url);
            }
        });
    }

4、Decoder模块

Decoder模块部分的源码重点是在目录DecoderCategories两个地方,其中Categories提供NSData、UIImage等的直接调用extend。核心实现还是基于Decoder。 相关模块的详细说明可以参考目录说明部分。 这里主要说下几个部分,主要包括个人觉得可能产生障碍的点,便于理解:

  • 为啥要压缩和Decompressed?
  • SDWebImageFrameSDWebImageCoderHelperanimatedImageWithFrames的逻辑
  • 大图片缩放逻辑辅助说明

4.1 为啥要压缩和Decompressed

我们都知道,在iOS中,我加载PNG和JPEG,直接调用[UIImage imageNamed:@"pic"];就好,而且本来从网络上下载的图片也都是PNG或者JPEG的,又没用Zip等去进行压缩,为啥还有这么一个Decoder模块呢。 其实这里重点就是针对性能做优化,我们平时直接加载图片的时候,因为图片都很小,所以基本不会消耗时间,但是我们从url中拉取的图片,一般都是运营配置,基本都不小。 如果加载过本地未处理的PNG(文件比较大)的兄弟应该有过这种感觉,跑完加载的时候,刷出界面开始会白那么一下,图片才出来。那就是因为图片太大了。 那么针对PNG和JPEG,SDWebImage的Decompressed又是做什么操作呢,我们知道图片显示到设备上,是按照RGBA等显示,但是PNG和JPEG自身的格式并非RGBA的。这里的解压其实就是创建一个BitmapImage,先在非UI线程渲染图片,然后拿到UIImage去显示。 代码参考如下:

    const size_t partialHeight = CGImageGetHeight(partialImageRef);
    CGColorSpaceRef colorSpace = SDCGColorSpaceGetDeviceRGB();
    CGContextRef bmContext = CGBitmapContextCreate(NULL, _width, _height, 8, 0, colorSpace, kCGBitmapByteOrderDefault | kCGImageAlphaPremultipliedFirst);
    if (bmContext) {
       CGContextDrawImage(bmContext, (CGRect){.origin.x = 0.0f, .origin.y = 0.0f, .size.width = _width, .size.height = partialHeight}, partialImageRef);
        CGImageRelease(partialImageRef);
        partialImageRef = CGBitmapContextCreateImage(bmContext);
        CGContextRelease(bmContext);
    }

4.2SDWebImageFrameSDWebImageCoderHelperanimatedImageWithFrames的逻辑

这两个类里面,有部分函数主要是提供GIF图片的辅助,当我们从NSData拿到图片数据的时候,其实是拿到了多张图片的Data,当我们分成多张图(参考SDWebImageGIFCoder)后,我们要合成到UIImage的images中。 每个SDWebImageFrame就代表了一张图片,每个图片有个播放时长,但是总的一个animationImage并未对每个Image进行时长设置。他的逻辑是怎样的呢? 假如我们有两张图AB,动画要显示A3s,显示B1s,那么我们可以指定显示四张图片:AAAB,总时长是4s。 所以当拿到GIF对应的UIImage和显示时长后,怎么去合成AAAB 和 4s 的时长呢,流程如下: -获取每个图片总时长,totalTimes -计算没个图片时长的最大公约数,再用每张图片的时长除以最大公约数,就是每张图片要显示的次数 -根据显示次数构建UIImage的Array,根据UIImage的Array和总时长,就能通过GIF转成UIImage动画了

4.3 大图片缩放逻辑辅助说明

图片缩放逻辑主要函数:

- (nullable UIImage *)sd_decompressedAndScaledDownImageWithImage:(nullable UIImage *)image {
    if (![[self class] shouldDecodeImage:image]) {
        return image;
    }
    
    if (![[self class] shouldScaleDownImage:image]) {
        return [self sd_decompressedImageWithImage:image];
    }
    
    CGContextRef destContext;
    
    // autorelease the bitmap context and all vars to help system to free memory when there are memory warning.
    // on iOS7, do not forget to call [[SDImageCache sharedImageCache] clearMemory];
    @autoreleasepool {
        CGImageRef sourceImageRef = image.CGImage;
        
        CGSize sourceResolution = CGSizeZero;
        sourceResolution.width = CGImageGetWidth(sourceImageRef);
        sourceResolution.height = CGImageGetHeight(sourceImageRef);
        float sourceTotalPixels = sourceResolution.width * sourceResolution.height;
        // Determine the scale ratio to apply to the input image
        // that results in an output image of the defined size.
        // see kDestImageSizeMB, and how it relates to destTotalPixels.
        float imageScale = kDestTotalPixels / sourceTotalPixels;
        CGSize destResolution = CGSizeZero;
        destResolution.width = (int)(sourceResolution.width*imageScale);
        destResolution.height = (int)(sourceResolution.height*imageScale);
        
        // current color space
        CGColorSpaceRef colorspaceRef = [[self class] colorSpaceForImageRef:sourceImageRef];
        
        // kCGImageAlphaNone is not supported in CGBitmapContextCreate.
        // Since the original image here has no alpha info, use kCGImageAlphaNoneSkipLast
        // to create bitmap graphics contexts without alpha info.
        destContext = CGBitmapContextCreate(NULL,
                                            destResolution.width,
                                            destResolution.height,
                                            kBitsPerComponent,
                                            0,
                                            colorspaceRef,
                                            kCGBitmapByteOrderDefault|kCGImageAlphaNoneSkipLast);
        
        if (destContext == NULL) {
            return image;
        }
        CGContextSetInterpolationQuality(destContext, kCGInterpolationHigh);
        
        // Now define the size of the rectangle to be used for the
        // incremental blits from the input image to the output image.
        // we use a source tile width equal to the width of the source
        // image due to the way that iOS retrieves image data from disk.
        // iOS must decode an image from disk in full width 'bands', even
        // if current graphics context is clipped to a subrect within that
        // band. Therefore we fully utilize all of the pixel data that results
        // from a decoding opertion by achnoring our tile size to the full
        // width of the input image.
        CGRect sourceTile = CGRectZero;
        sourceTile.size.width = sourceResolution.width;
        // The source tile height is dynamic. Since we specified the size
        // of the source tile in MB, see how many rows of pixels high it
        // can be given the input image width.
        sourceTile.size.height = (int)(kTileTotalPixels / sourceTile.size.width );
        sourceTile.origin.x = 0.0f;
        // The output tile is the same proportions as the input tile, but
        // scaled to image scale.
        CGRect destTile;
        destTile.size.width = destResolution.width;
        destTile.size.height = sourceTile.size.height * imageScale;
        destTile.origin.x = 0.0f;
        // The source seem overlap is proportionate to the destination seem overlap.
        // this is the amount of pixels to overlap each tile as we assemble the ouput image.
        float sourceSeemOverlap = (int)((kDestSeemOverlap/destResolution.height)*sourceResolution.height);
        CGImageRef sourceTileImageRef;
        // calculate the number of read/write operations required to assemble the
        // output image.
        int iterations = (int)( sourceResolution.height / sourceTile.size.height );
        // If tile height doesn't divide the image height evenly, add another iteration
        // to account for the remaining pixels.
        int remainder = (int)sourceResolution.height % (int)sourceTile.size.height;
        if(remainder) {
            iterations++;
        }
        // Add seem overlaps to the tiles, but save the original tile height for y coordinate calculations.
        float sourceTileHeightMinusOverlap = sourceTile.size.height;
        sourceTile.size.height += sourceSeemOverlap;
        destTile.size.height += kDestSeemOverlap;
        for( int y = 0; y < iterations; ++y ) {
            @autoreleasepool {
                sourceTile.origin.y = y * sourceTileHeightMinusOverlap + sourceSeemOverlap;
                destTile.origin.y = destResolution.height - (( y + 1 ) * sourceTileHeightMinusOverlap * imageScale + kDestSeemOverlap);
                sourceTileImageRef = CGImageCreateWithImageInRect( sourceImageRef, sourceTile );
                if( y == iterations - 1 && remainder ) {
                    float dify = destTile.size.height;
                    destTile.size.height = CGImageGetHeight( sourceTileImageRef ) * imageScale;
                    dify -= destTile.size.height;
                    destTile.origin.y += dify;
                }
                CGContextDrawImage( destContext, destTile, sourceTileImageRef );
                CGImageRelease( sourceTileImageRef );
            }
        }
        
        CGImageRef destImageRef = CGBitmapContextCreateImage(destContext);
        CGContextRelease(destContext);
        if (destImageRef == NULL) {
            return image;
        }
        UIImage *destImage = [UIImage imageWithCGImage:destImageRef scale:image.scale orientation:image.imageOrientation];
        CGImageRelease(destImageRef);
        if (destImage == nil) {
            return image;
        }
        return destImage;
    }
}


这里光看代码,肯定会一头雾水,其实逻辑很简单,避免要缩放的图片太大,采用的方式是将图片分割成一系列大小的小方块,然后每个方块去获取Image并draw到目标BitmapContext上。重点还是在于内存优化方面。
 相关推荐

刘强东夫妇:“移民美国”传言被驳斥

京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。

发布于:1年以前  |  808次阅读  |  详细内容 »

博主曝三大运营商,将集体采购百万台华为Mate60系列

日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。

发布于:1年以前  |  770次阅读  |  详细内容 »

ASML CEO警告:出口管制不是可行做法,不要“逼迫中国大陆创新”

据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。

发布于:1年以前  |  756次阅读  |  详细内容 »

抖音中长视频App青桃更名抖音精选,字节再发力对抗B站

今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。

发布于:1年以前  |  648次阅读  |  详细内容 »

威马CDO:中国每百户家庭仅17户有车

日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。

发布于:1年以前  |  589次阅读  |  详细内容 »

研究发现维生素 C 等抗氧化剂会刺激癌症生长和转移

近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。

发布于:1年以前  |  449次阅读  |  详细内容 »

苹果据称正引入3D打印技术,用以生产智能手表的钢质底盘

据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。

发布于:1年以前  |  446次阅读  |  详细内容 »

千万级抖音网红秀才账号被封禁

9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...

发布于:1年以前  |  445次阅读  |  详细内容 »

亚马逊股东起诉公司和贝索斯,称其在购买卫星发射服务时忽视了 SpaceX

9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。

发布于:1年以前  |  444次阅读  |  详细内容 »

苹果上线AppsbyApple网站,以推广自家应用程序

据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。

发布于:1年以前  |  442次阅读  |  详细内容 »

特斯拉美国降价引发投资者不满:“这是短期麻醉剂”

特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。

发布于:1年以前  |  441次阅读  |  详细内容 »

光刻机巨头阿斯麦:拿到许可,继续对华出口

据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。

发布于:1年以前  |  437次阅读  |  详细内容 »

马斯克与库克首次隔空合作:为苹果提供卫星服务

近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。

发布于:1年以前  |  430次阅读  |  详细内容 »

𝕏(推特)调整隐私政策,可拿用户发布的信息训练 AI 模型

据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。

发布于:1年以前  |  428次阅读  |  详细内容 »

荣耀CEO谈华为手机回归:替老同事们高兴,对行业也是好事

9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI操控无人机能力超越人类冠军

《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI生成的蘑菇科普书存在可致命错误

近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。

发布于:1年以前  |  420次阅读  |  详细内容 »

社交媒体平台𝕏计划收集用户生物识别数据与工作教育经历

社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”

发布于:1年以前  |  411次阅读  |  详细内容 »

国产扫地机器人热销欧洲,国产割草机器人抢占欧洲草坪

2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。

发布于:1年以前  |  406次阅读  |  详细内容 »

罗永浩吐槽iPhone15和14不会有区别,除了序列号变了

罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。

发布于:1年以前  |  398次阅读  |  详细内容 »
 相关文章
快速配置 Sign In with Apple 5年以前  |  7676次阅读
使用 GPUImage 实现一个简单相机 5年以前  |  5975次阅读
APP适配iOS11 5年以前  |  5750次阅读
 目录