iOS学习笔记-128.SDWebImage4——框架内部调用简单分析
sdwebimage4——框架内部调用简单分析
一、uml图和调用时序图
二 从我们的给uiimageview 设置网络图片开始
首先我们来看看,我们自己调用的方式
/*内存+磁盘缓存*/ -(void)download{ nsstring *path = @"https://www.qqpk.cn/article/uploadfiles/201112/20111208140318990.jpg"; [self.imageview sd_setimagewithurl:[nsurl urlwithstring:path] placeholderimage:[uiimage imagenamed:@"image1"] options:0 progress:^(nsinteger receivedsize, nsinteger expectedsize) { } completed:^(uiimage *image, nserror *error, sdimagecachetype cachetype, nsurl *imageurl) { }]; }
三、sd_setimagewithurl
我们现在进入到上面我们使用的方法中 sd_setimagewithurl
其实这个方法主要做以下事情
设置占位图片
如果url不为空,创建一个 sdwebimageoperation的操作,并且添加到图片加载的操作缓存中
创建操作的时候 调用了 sdwebimagemanager.sharedmanager downloadimagewithurl,获取结束后,
根据有没有图片和我们之前设置的状态,来判断调用 completedblock
如果url为空,那么回调到传递过来的completedblock代码块上 ,把 image 设为nil
//下载图片的核心方法 /* * url 图片的二进制数据 * placeholder uiimageview的占位图片 * options 图片下载选项(策略) * progressblock 进度回调 * completedblock 完成回调 */ - (void)sd_setimagewithurl:(nsurl *)url placeholderimage:(uiimage *)placeholder options:(sdwebimageoptions)options progress:(sdwebimagedownloaderprogressblock)progressblock completed:(sdwebimagecompletionblock)completedblock { // 取消当前图像下载 [self sd_cancelcurrentimageload]; // 利用运行时retain url objc_setassociatedobject(self, &imageurlkey, url, objc_association_retain_nonatomic); //判断,如果传入的下载策略不是延迟显示占位图片,那么在主线程中设置占位图片 if (!(options & sdwebimagedelayplaceholder)) { dispatch_main_async_safe(^{ // 设置占位图像 self.image = placeholder; }); } //如果url不为空 if (url) { // check if activityview is enabled or not //检查activityview是否可用 if ([self showactivityindicatorview]) { [self addactivityindicator]; } __weak __typeof(self)wself = self; // 实例化 sdwebimageoperation 操作 id operation = [sdwebimagemanager.sharedmanager downloadimagewithurl:url options:options progress:progressblock completed:^(uiimage *image, nserror *error, sdimagecachetype cachetype, bool finished, nsurl *imageurl) { //移除uiactivityindicatorview [wself removeactivityindicator]; if (!wself) return; //下面block中的操作在主线程中处理 dispatch_main_sync_safe(^{ if (!wself) return; //如果图片下载完成,且传入的下载选项为手动设置图片则直接执行completedblock回调,并返回 if (image && (options & sdwebimageavoidautosetimage) && completedblock) { completedblock(image, error, cachetype, url); return; } else if (image) { //否则,如果图片存在,则设置图片到uiimageview上面,并刷新重绘视图 wself.image = image; [wself setneedslayout]; } else { //如果没有得到图像 //如果传入的下载选项为延迟显示占位图片,则设置占位图片到uiimageview上面,并刷新重绘视图 if ((options & sdwebimagedelayplaceholder)) { wself.image = placeholder; [wself setneedslayout]; } } if (completedblock && finished) { completedblock(image, error, cachetype, url); } }); }]; [self sd_setimageloadoperation:operation forkey:@"uiimageviewimageload"]; } else { //如果url为空,则在主线中处理下面的操作 dispatch_main_async_safe(^{ //移除uiactivityindicatorview [self removeactivityindicator]; //处理错误信息,并执行任务结束回调,把错误信息作为参数传递出去 nserror *error = [nserror errorwithdomain:sdwebimageerrordomain code:-1 userinfo:@{nslocalizeddescriptionkey : @"trying to load a nil url"}]; if (completedblock) { completedblock(nil, error, sdimagecachetypenone, url); } }); } }
四、downloadimagewithurl
对url进行判断(是否合法,是否在黑名单里面)
如果url不正确或者 选择的下载策略不是『下载失败尝试重新下载』且该url存在于黑名单中,那么直接返回,回调任务完成block块,传递错误信息
把当前的下载任务添加到『当前正在执行任务数组』中
查找urlkey对应的图片缓存是否存在,查找完毕之后把该图片(存在|不存在)和该图片的缓存方法以block的方式传递,
缓存情况查找完毕之后,在block块中进行后续处理。
a. 操作取消,从『当前正在执行任务数组』移除我们的 3 步骤中添加的操作,不在执行block的后续操作
b. 如果图片没有缓存,那么我们调用 sdwebimagedownloader的
downloadimagewithurl方法创建去下载,这个回调的代码块中处理图片的缓存,回调block,从『当前正在执行任务数组』移除我们的 3 步骤中添加的操作
c. 如果缓存图片存在,那么回调block,从『当前正在执行任务数组』移除我们的 3 步骤中添加的操作
d. 图片不存在缓存且不允许代理下载,那么在主线程中回调completedblock,从『当前正在执行任务数组』移除我们的 3 步骤中添加的操作
返回创建的操作。
- (id )downloadimagewithurl:(nsurl *)url options:(sdwebimageoptions)options progress:(sdwebimagedownloaderprogressblock)progressblock completed:(sdwebimagecompletionwithfinishedblock)completedblock { // invoking this method without a completedblock is pointless //没有completedblock,那么调用这个方法是毫无意义的 nsassert(completedblock != nil, @"if you mean to prefetch the image, use -[sdwebimageprefetcher prefetchurls] instead"); // very common mistake is to send the url using nsstring object instead of nsurl. for some strange reason, xcode won't // throw any warning for this type mismatch. here we failsafe this error by allowing urls to be passed as nsstring. //检查用户传入的url是否正确,如果该url是nsstring类型的,那么尝试转换 if ([url iskindofclass:nsstring.class]) { url = [nsurl urlwithstring:(nsstring *)url]; } // prevents app crashing on argument type error like sending nsnull instead of nsurl //防止因参数类型错误而导致应用程序崩溃,判断url是否是nsurl类型的,如果不是则直接设置为nil if (![url iskindofclass:nsurl.class]) { url = nil; } //初始化一个sdwebimagecombinedoperationblock块 __block sdwebimagecombinedoperation *operation = [sdwebimagecombinedoperation new]; __weak sdwebimagecombinedoperation *weakoperation = operation; bool isfailedurl = no; //初始化设定该url是正确的 //加互斥锁,检索请求图片的url是否在曾下载失败的集合中(url黑名单) @synchronized (self.failedurls) { isfailedurl = [self.failedurls containsobject:url]; } //如果url不正确或者 选择的下载策略不是『下载失败尝试重新下载』且该url存在于黑名单中,那么直接返回,回调任务完成block块,传递错误信息 if (url.absolutestring.length == 0 || (!(options & sdwebimageretryfailed) && isfailedurl)) { //该宏保证了completedblock回调在主线程中执行 dispatch_main_sync_safe(^{ nserror *error = [nserror errorwithdomain:nsurlerrordomain code:nsurlerrorfiledoesnotexist userinfo:nil]; completedblock(nil, error, sdimagecachetypenone, yes, url); }); return operation; } //加互斥锁,把当前的下载任务添加到『当前正在执行任务数组』中 @synchronized (self.runningoperations) { [self.runningoperations addobject:operation]; } //得到该url对应的缓存key nsstring *key = [self cachekeyforurl:url]; //该方法查找urlkey对应的图片缓存是否存在,查找完毕之后把该图片(存在|不存在)和该图片的缓存方法以block的方式传递 //缓存情况查找完毕之后,在block块中进行后续处理(如果该图片没有缓存·下载|如果缓存存在|如果用户设置了下载的缓存策略是刷新缓存如何处理等等) operation.cacheoperation = [self.imagecache querydiskcacheforkey:key done:^(uiimage *image, sdimagecachetype cachetype) { //先判断该下载操作是否已经被取消,如果被取消则把当前操作从runningoperations数组中移除,并直接返回 if (operation.iscancelled) { @synchronized (self.runningoperations) { [self.runningoperations removeobject:operation]; } return; } //(图片不存在||下载策略为刷新缓存)且(shoulddownloadimageforurl不能响应||该图片存在缓存) if ((!image || options & sdwebimagerefreshcached) && (![self.delegate respondstoselector:@selector(imagemanager:shoulddownloadimageforurl:)] || [self.delegate imagemanager:self shoulddownloadimageforurl:url])) { //从此处开始,一直在处理downloaderoptions(即下载策略) if (image && options & sdwebimagerefreshcached) { //如果图像存在,但是下载策略为刷新缓存,则通知缓存图像并尝试重新下载 dispatch_main_sync_safe(^{ // if image was found in the cache but sdwebimagerefreshcached is provided, notify about the cached image // and try to re-download it in order to let a chance to nsurlcache to refresh it from server. completedblock(image, nil, cachetype, yes, url); }); } // download if no image or requested to refresh anyway, and download allowed by delegate sdwebimagedownloaderoptions downloaderoptions = 0; //如果下载策略为sdwebimagelowpriority 那么downloaderoptions = 其本身 if (options & sdwebimagelowpriority) downloaderoptions |= sdwebimagedownloaderlowpriority; if (options & sdwebimageprogressivedownload) downloaderoptions |= sdwebimagedownloaderprogressivedownload; if (options & sdwebimagerefreshcached) downloaderoptions |= sdwebimagedownloaderusensurlcache; if (options & sdwebimagecontinueinbackground) downloaderoptions |= sdwebimagedownloadercontinueinbackground; if (options & sdwebimagehandlecookies) downloaderoptions |= sdwebimagedownloaderhandlecookies; if (options & sdwebimageallowinvalidsslcertificates) downloaderoptions |= sdwebimagedownloaderallowinvalidsslcertificates; if (options & sdwebimagehighpriority) downloaderoptions |= sdwebimagedownloaderhighpriority; if (image && options & sdwebimagerefreshcached) { //如果图片存在,且下载策略为刷新刷新缓存 // force progressive off if image already cached but forced refreshing //如果图像已缓存,但需要刷新缓存,那么强制进行刷新 downloaderoptions &= ~sdwebimagedownloaderprogressivedownload; // ignore image read from nsurlcache if image if cached but force refreshing //忽略从nsurlcache读取图片 downloaderoptions |= sdwebimagedownloaderignorecachedresponse; } //到此处位置,downloaderoptions(即下载策略)处理操作结束 //核心方法:使用下载器,下载图片 id suboperation = [self.imagedownloader downloadimagewithurl:url options:downloaderoptions progress:progressblock completed:^(uiimage *downloadedimage, nsdata *data, nserror *error, bool finished) { if (weakoperation.iscancelled) { //如果此时操作被取消,那么什么也不做 // do nothing if the operation was cancelled // see #699 for more details // if we would call the completedblock, there could be a race condition between this block and another completedblock for the same object, so if this one is called second, we will overwrite the new data } else if (error) { //如果下载失败,则处理结束的回调,在合适的情况下把对应图片的url添加到黑名单中 dispatch_main_sync_safe(^{ if (!weakoperation.iscancelled) { completedblock(nil, error, sdimagecachetypenone, finished, url); } }); if ( error.code != nsurlerrornotconnectedtointernet && error.code != nsurlerrorcancelled && error.code != nsurlerrortimedout && error.code != nsurlerrorinternationalroamingoff && error.code != nsurlerrordatanotallowed && error.code != nsurlerrorcannotfindhost && error.code != nsurlerrorcannotconnecttohost) { @synchronized (self.failedurls) { [self.failedurls addobject:url]; } } } else {//下载成功 //先判断当前的下载策略是否是sdwebimageretryfailed,如果是那么把该url从黑名单中删除 if ((options & sdwebimageretryfailed)) { @synchronized (self.failedurls) { [self.failedurls removeobject:url]; } } //是否要进行磁盘缓存? bool cacheondisk = !(options & sdwebimagecachememoryonly); //如果下载策略为sdwebimagerefreshcached且该图片缓存中存在且未下载下来,那么什么都不做 if (options & sdwebimagerefreshcached && image && !downloadedimage) { // image refresh hit the nsurlcache cache, do not call the completion block } else if (downloadedimage && (!downloadedimage.images || (options & sdwebimagetransformanimatedimage)) && [self.delegate respondstoselector:@selector(imagemanager:transformdownloadedimage:withurl:)]) { //否则,如果下载图片存在且(不是可动画图片数组||下载策略为sdwebimagetransformanimatedimage&&transformdownloadedimage方法可用) //开子线程处理 dispatch_async(dispatch_get_global_queue(dispatch_queue_priority_high, 0), ^{ //在下载后立即将图像转换,并进行磁盘和内存缓存 uiimage *transformedimage = [self.delegate imagemanager:self transformdownloadedimage:downloadedimage withurl:url]; #warning 2 if (transformedimage && finished) { bool imagewastransformed = ![transformedimage isequal:downloadedimage]; [self.imagecache storeimage:transformedimage recalculatefromimage:imagewastransformed imagedata:(imagewastransformed ? nil : data) forkey:key todisk:cacheondisk]; } //在主线程中回调completedblock dispatch_main_sync_safe(^{ if (!weakoperation.iscancelled) { completedblock(transformedimage, nil, sdimagecachetypenone, finished, url); } }); }); } else { //得到下载的图片且已经完成,则进行缓存处理 if (downloadedimage && finished) { [self.imagecache storeimage:downloadedimage recalculatefromimage:no imagedata:data forkey:key todisk:cacheondisk]; } dispatch_main_sync_safe(^{ if (!weakoperation.iscancelled) { completedblock(downloadedimage, nil, sdimagecachetypenone, finished, url); } }); } } if (finished) { @synchronized (self.runningoperations) { [self.runningoperations removeobject:operation]; } } }]; //处理cancelblock operation.cancelblock = ^{ [suboperation cancel]; @synchronized (self.runningoperations) { [self.runningoperations removeobject:weakoperation]; } }; } else if (image) { //如果图片存在,且操作没有被取消,那么在主线程中回调completedblock,并把当前操作移除 dispatch_main_sync_safe(^{ if (!weakoperation.iscancelled) { completedblock(image, nil, cachetype, yes, url); } }); @synchronized (self.runningoperations) { [self.runningoperations removeobject:operation]; } } else { // image not in cache and download disallowed by delegate //图片不存在缓存且不允许代理下载,那么在主线程中回调completedblock,并把当前操作移除 dispatch_main_sync_safe(^{ if (!weakoperation.iscancelled) { completedblock(nil, nil, sdimagecachetypenone, yes, url); } }); @synchronized (self.runningoperations) { [self.runningoperations removeobject:operation]; } } }]; return operation; }
五、downloadimagewithurl
处理下载超时,如果没有设置过则初始化为15秒
创建一个 nsmutableurlrequest 的请求,但是现在还不去操作
调用 sdwebimagedownloaderoperation的
initwithrequest创建下载图片的操作。下载一部分回调一下我们的进度的方法。下载完成后进行一系列的判断然后调用回调。
返回操作
//核心方法:下载图片的操作 - (id )downloadimagewithurl:(nsurl *)url options:(sdwebimagedownloaderoptions)options progress:(sdwebimagedownloaderprogressblock)progressblock completed:(sdwebimagedownloadercompletedblock)completedblock { __block sdwebimagedownloaderoperation *operation; __weak __typeof(self)wself = self; //为了避免block的循环引用 //处理进度回调|完成回调等,如果该url在self.urlcallbacks并不存在,则调用createcallback block块 [self addprogresscallback:progressblock completedblock:completedblock forurl:url createcallback:^{ //处理下载超时,如果没有设置过则初始化为15秒 nstimeinterval timeoutinterval = wself.downloadtimeout; if (timeoutinterval == 0.0) { timeoutinterval = 15.0; } // in order to prevent from potential duplicate caching (nsurlcache + sdimagecache) we disable the cache for image requests if told otherwise //根据给定的url和缓存策略创建可变的请求对象,设置请求超时 //请求策略:如果是sdwebimagedownloaderusensurlcache则使用nsurlrequestuseprotocolcachepolicy,否则使用nsurlrequestreloadignoringlocalcachedata /* nsurlrequestuseprotocolcachepolicy:默认的缓存策略 1)如果缓存不存在,直接从服务端获取。 2)如果缓存存在,会根据response中的cache-control字段判断下一步操作,如: cache-control字段为must-revalidata, 则询问服务端该数据是否有更新,无更新的话直接返回给用户缓存数据,若已更新,则请求服务端. nsurlrequestreloadignoringlocalcachedata:忽略本地缓存数据,直接请求服务端。 */ nsmutableurlrequest *request = [[nsmutableurlrequest alloc] initwithurl:url cachepolicy:(options & sdwebimagedownloaderusensurlcache ? nsurlrequestuseprotocolcachepolicy : nsurlrequestreloadignoringlocalcachedata) timeoutinterval:timeoutinterval]; //设置是否使用cookies(采用按位与) /* 关于cookies参考:https://blog.csdn.net/chun799/article/details/17206907 */ request.httpshouldhandlecookies = (options & sdwebimagedownloaderhandlecookies); //开启http管道,这可以显著降低请求的加载时间,但是由于没有被服务器广泛支持,默认是禁用的 request.httpshouldusepipelining = yes; //设置请求头信息(过滤等) if (wself.headersfilter) { request.allhttpheaderfields = wself.headersfilter(url, [wself.httpheaders copy]); } else { request.allhttpheaderfields = wself.httpheaders; } //核心方法:创建下载图片的操作 operation = [[wself.operationclass alloc] initwithrequest:request options:options progress:^(nsinteger receivedsize, nsinteger expectedsize) { sdwebimagedownloader *sself = wself; if (!sself) return; __block nsarray *callbacksforurl; dispatch_sync(sself.barrierqueue, ^{ callbacksforurl = [sself.urlcallbacks[url] copy]; }); //遍历callbacksforurl数组中的所有字典,执行sdwebimagedownloaderprogressblock回调 for (nsdictionary *callbacks in callbacksforurl) { //说明:sdwebimagedownloaderprogressblock作者可能考虑到用户拿到进度数据后会进行刷新处理,因此在主线程中处理了回调 dispatch_async(dispatch_get_main_queue(), ^{ sdwebimagedownloaderprogressblock callback = callbacks[kprogresscallbackkey]; if (callback) callback(receivedsize, expectedsize); }); } } completed:^(uiimage *image, nsdata *data, nserror *error, bool finished) { sdwebimagedownloader *sself = wself; if (!sself) return; __block nsarray *callbacksforurl; dispatch_barrier_sync(sself.barrierqueue, ^{ callbacksforurl = [sself.urlcallbacks[url] copy]; //如果完成,那么把url从urlcallbacks字典中删除 if (finished) { [sself.urlcallbacks removeobjectforkey:url]; } }); //遍历callbacksforurl数组中的所有字典,执行sdwebimagedownloadercompletedblock回调 for (nsdictionary *callbacks in callbacksforurl) { sdwebimagedownloadercompletedblock callback = callbacks[kcompletedcallbackkey]; if (callback) callback(image, data, error, finished); } } cancelled:^{ sdwebimagedownloader *sself = wself; if (!sself) return; //把当前的url从urlcallbacks字典中移除 dispatch_barrier_async(sself.barrierqueue, ^{ [sself.urlcallbacks removeobjectforkey:url]; }); }]; //设置是否需要解码 operation.shoulddecompressimages = wself.shoulddecompressimages; //身份认证 if (wself.urlcredential) { operation.credential = wself.urlcredential; } else if (wself.username && wself.password) { //设置 https 访问时身份验证使用的凭据 operation.credential = [nsurlcredential credentialwithuser:wself.username password:wself.password persistence:nsurlcredentialpersistenceforsession]; } //判断下载策略是否是高优先级的或低优先级,以设置操作的队列优先级 if (options & sdwebimagedownloaderhighpriority) { operation.queuepriority = nsoperationqueuepriorityhigh; } else if (options & sdwebimagedownloaderlowpriority) { operation.queuepriority = nsoperationqueueprioritylow; } //把下载操作添加到下载队列中 //该方法会调用operation内部的start方法开启图片的下载任务 [wself.downloadqueue addoperation:operation]; //判断任务的执行优先级,如果是后进先出,则调整任务的依赖关系,优先执行当前的(最后添加)任务 if (wself.executionorder == sdwebimagedownloaderlifoexecutionorder) { // emulate lifo execution order by systematically adding new operations as last operation's dependency [wself.lastaddedoperation adddependency:operation]; wself.lastaddedoperation = operation;//设置当前下载操作为最后一个操作 } }]; return operation; }
六、initwithrequest
六、initwithrequest
initwithrequest 中会初始化一些操作
我们知道 nsoperation 内部会调用start方法。这里面的下载操作放到了start中
//初始化operation的方法 - (id)initwithrequest:(nsurlrequest *)request options:(sdwebimagedownloaderoptions)options progress:(sdwebimagedownloaderprogressblock)progressblock completed:(sdwebimagedownloadercompletedblock)completedblock cancelled:(sdwebimagenoparamsblock)cancelblock { if ((self = [super init])) { _request = request; _shoulddecompressimages = yes; _shouldusecredentialstorage = yes; _options = options; _progressblock = [progressblock copy]; _completedblock = [completedblock copy]; _cancelblock = [cancelblock copy]; _executing = no; _finished = no; _expectedsize = 0; responsefromcached = yes; // initially wrong until `connection:willcacheresponse:` is called or not called } return self; } //核心方法:在该方法中处理图片下载操作 - (void)start { @synchronized (self) { //判断当前操作是否被取消,如果被取消了,则标记任务结束,并处理后续的block和清理操作 if (self.iscancelled) { self.finished = yes; [self reset]; return; } //条件编译,如果是iphone设备且大于4.0 #if target_os_iphone && __iphone_os_version_max_allowed >= __iphone_4_0 class uiapplicationclass = nsclassfromstring(@"uiapplication"); bool hasapplication = uiapplicationclass && [uiapplicationclass respondstoselector:@selector(sharedapplication)]; //程序即将进入后台 if (hasapplication && [self shouldcontinuewhenappentersbackground]) { __weak __typeof__ (self) wself = self; //获得uiapplication单例对象 uiapplication * app = [uiapplicationclass performselector:@selector(sharedapplication)]; //uibackgroundtaskidentifier:通过uibackgroundtaskidentifier可以实现有限时间内在后台运行程序 //在后台获取一定的时间去指行我们的代码 self.backgroundtaskid = [app beginbackgroundtaskwithexpirationhandler:^{ __strong __typeof (wself) sself = wself; #warning 3 if (sself) { [sself cancel]; //取消当前下载操作 [app endbackgroundtask:sself.backgroundtaskid]; //结束后台任务 sself.backgroundtaskid = uibackgroundtaskinvalid; } }]; } #endif self.executing = yes; //当前任务正在执行 //创建nsurlconnection对象,并设置代理(没有马上发送请求) self.connection = [[nsurlconnection alloc] initwithrequest:self.request delegate:self startimmediately:no]; //获得当前线程 self.thread = [nsthread currentthread]; } [self.connection start]; //发送网络请求 if (self.connection) { if (self.progressblock) { //进度block的回调 self.progressblock(0, nsurlresponseunknownlength); } //注册通知中心,在主线程中发送通知sdwebimagedownloadstartnotification【任务开始下载】 dispatch_async(dispatch_get_main_queue(), ^{ [[nsnotificationcenter defaultcenter] postnotificationname:sdwebimagedownloadstartnotification object:self]; }); //开启线程对应的runloop if (floor(nsfoundationversionnumber) <= nsfoundationversionnumber_ios_5_1) { // make sure to run the runloop in our background thread so it can process downloaded data //确保后台线程的runloop跑起来 // note: we use a timeout to work around an issue with nsurlconnection cancel under ios 5 // not waking up the runloop, leading to dead threads (see https://github.com/rs/sdwebimage/issues/466) cfrunloopruninmode(kcfrunloopdefaultmode, 10, false); } else { //开启runloop cfrunlooprun(); } if (!self.isfinished) { [self.connection cancel]; //取消网络连接 //处理错误信息 [self connection:self.connection didfailwitherror:[nserror errorwithdomain:nsurlerrordomain code:nsurlerrortimedout userinfo:@{nsurlerrorfailingurlerrorkey : self.request.url}]]; } } else { //执行completedblock回调,打印connection初始化失败 if (self.completedblock) { self.completedblock(nil, nil, [nserror errorwithdomain:nsurlerrordomain code:0 userinfo:@{nslocalizeddescriptionkey : @"connection can't be initialized"}], yes); } } #if target_os_iphone && __iphone_os_version_max_allowed >= __iphone_4_0 class uiapplicationclass = nsclassfromstring(@"uiapplication"); if(!uiapplicationclass || ![uiapplicationclass respondstoselector:@selector(sharedapplication)]) { return; } if (self.backgroundtaskid != uibackgroundtaskinvalid) { uiapplication * app = [uiapplication performselector:@selector(sharedapplication)]; [app endbackgroundtask:self.backgroundtaskid]; self.backgroundtaskid = uibackgroundtaskinvalid; } #endif }