2018-09-17 14:26:00 u012109585 阅读数 216

获取 iOS APP 内存占用的大小

当我们想去获取 iOS 应用的占用内存时,通常我们能找到的方法是这样的,用 resident_size

 
#import <mach/mach.h>
- (int64_t)memoryUsage {
    int64_t memoryUsageInByte = 0;
    struct task_basic_info taskBasicInfo;
    mach_msg_type_number_t size = sizeof(taskBasicInfo);
    kern_return_t kernelReturn = task_info(mach_task_self(), TASK_BASIC_INFO, (task_info_t) &taskBasicInfo, &size);
    
    if(kernelReturn == KERN_SUCCESS) {
        memoryUsageInByte = (int64_t) taskBasicInfo.resident_size;
        NSLog(@"Memory in use (in bytes): %lld", memoryUsageInByte);
    } else {
        NSLog(@"Error with task_info(): %s", mach_error_string(kernelReturn));
    }
    
    return memoryUsageInByte;
}

 

但是测试的时候,我们会发现这个跟我们在 Instruments 里面看到的内存大小不一样,有时候甚至差别很大。更加准确的方式应该是用 phys_footprint

 
#import <mach/mach.h>
- (int64_t)memoryUsage {
    int64_t memoryUsageInByte = 0;
    task_vm_info_data_t vmInfo;
    mach_msg_type_number_t count = TASK_VM_INFO_COUNT;
    kern_return_t kernelReturn = task_info(mach_task_self(), TASK_VM_INFO, (task_info_t) &vmInfo, &count);
    if(kernelReturn == KERN_SUCCESS) {
        memoryUsageInByte = (int64_t) vmInfo.phys_footprint;
        NSLog(@"Memory in use (in bytes): %lld", memoryUsageInByte);
    } else {
        NSLog(@"Error with task_info(): %s", mach_error_string(kernelReturn));
    }
    return memoryUsageInByte;

 

关于 phys_footprint 的定义可以在 XNU 源码中,找到 osfmk/kern/task.c 里对于 phys_footprint 的注释:

 
/*
 * phys_footprint
 *   Physical footprint: This is the sum of:
 *     + (internal - alternate_accounting)
 *     + (internal_compressed - alternate_accounting_compressed)
 *     + iokit_mapped
 *     + purgeable_nonvolatile
 *     + purgeable_nonvolatile_compressed
 *     + page_table
 *
 * internal
 *   The task's anonymous memory, which on iOS is always resident.
 *
 * internal_compressed
 *   Amount of this task's internal memory which is held by the compressor.
 *   Such memory is no longer actually resident for the task [i.e., resident in its pmap],
 *   and could be either decompressed back into memory, or paged out to storage, depending
 *   on our implementation.
 *
 * iokit_mapped
 *   IOKit mappings: The total size of all IOKit mappings in this task, regardless of
     clean/dirty or internal/external state].
 *
 * alternate_accounting
 *   The number of internal dirty pages which are part of IOKit mappings. By definition, these pages
 *   are counted in both internal *and* iokit_mapped, so we must subtract them from the total to avoid
 *   double counting.
 */

 

注释里提到的公式计算的应该才是应用实际使用的物理内存。

转载自新浪博客

posted on 2018-09-17 14:26 CodeVector 阅读(...) 评论(...) 编辑 收藏

2016-12-27 11:39:16 kaoshibiguo_1 阅读数 2650

 最近尝试对app内存使用情况进行检测对比,由于是刚刚接触这方面的知识,在很多方面都是处于一个认知的阶段,下面通过以下例子来进行分析对比:

一、(1)一个页面正常的情况下内存的使用情况:

从上图可以看出,这个页面的内存使用是比较平缓的,只是在加载数据或者进行点击事件的时候就会产生一些波动,当点击事件结束或者退出这个页面的时候就会出现一些灰色的区域,代表着这部分的内存已经被回收掉了。接下来通过memory analyzer生成内存使用饼状图,

从上面的图我们只是能看到内存在各个方面使用的大概比例,为了能够直观的体现出来区别,我让其与一个存在内存溢出的例子进行对比。

(2) 内存溢出的情况:

在原来的代码加上以下代码:


在这之后,重复进行“进入—退出”这个界面的操作,我们会发现内存使用会发生很大的变化。



内存使用成倍增长,而且出现了很多凹凸不平的峰值,通过MAT查询语句查询的结果如下:



当我退出这个界面,内存还存在着很多个社区话题详情(CommunityDetailActivity)页面,这明显出现了不正常,退出了这个Activity会回收掉这个页面所消耗的内存,这里非但没有回收掉,反而一直上升,通过上面的方法我们可以定位到CommunityTopicListAdapter中的Thread中,经过查找可以发现CommunityDetailActivity被内部类引用,而内部类又被线程引用,因此退出该页面的时候无法释放,GC无法回收造成内存泄漏。

解决方法:在使用线程的时候控制好睡眠的时间。

二、在很多时候我们通常会用到ListView进行开发,我们会发现在使用适配器的时候通常会进行控件复用来减少对内存的消耗,事实上不知道用跟没有用的区别区别在哪里,所以我通过下面的例子进行对比分析:


使用控件复用的情况(图一)



没有使用控件复用的情况(图二)

使用控件复用时内存消耗较大的几项



没有使用控件复用时内存消耗较大的几项

从图中我们可以看到ListView持有了大量的对象引用,其中TextView数量达到1000多,而在正常情况下,ListView会复用类型相同的view,其中的对象个数不可能如此庞大,因此,从这里就可以断定ListView使用不当出现了内存泄漏。


三、WebView加载html



从上图可以看到,Webview如果加载的页面有很多图片就会发现内存占用会上升,当退出这个页面的重新进来,JVM就会把一些内存释放掉,让webview在加载html时处于一个“上升—下降”的状态,不过通过图片可以看到,在不断加载和浏览多个html后内存会有所上升,因为它在不断加载的过程中会产生一些缓存数据。

解决方法:在退出该webview页面的时候把webview的缓存历史清除掉,webview.clearHistory();

webview.clearCache(true);

webview.freeMemory();

webview = null;


四、我们在使用MAT分析这些页面的时候会发现,大多数情况下MAT会把"android.graphics.Bitmap"作为内存泄漏的怀疑对象,结果如图所示:


dominator_tree页面我们可以看出来因为"android.graphics.Bitmap"Retained Head是比较大的,在分析内存泄漏时,内存最大的是需要去怀疑是否存在着泄漏。


所以下面就对"android.graphics.Bitmap"进行分析,经过Path To GC Roots-> exclude weak references过滤掉弱引用可以得到下图:


从上图可以看出来最后的子文件的文件图标下面有一个黄色的小点,这代表着能够被GC Roots访问到,这就代表着不能被回收掉,这就解决了为什么Mat一直把"android.graphics.Bitmap"作为怀疑的对象。

2019-06-26 16:49:57 u013602835 阅读数 811

     最近在调研iOS中的OOM(Out-Of-Memory),iOS中存在一些机制,当系统的内存不够用时或者当前APP的使用内存超过了阈值,就会导致系统强杀当前APP,由于强杀当前APP的进程是系统做的事情,所以,当前APP是无法知道是什么时候被强杀的。而且,我们平常使用的通过监测signal信号量来获取Crash的工具也是无法获取OOM这类强杀的。在调研测试过程中,用到了计算当前APP占用内存的计算方法和CPU使用率的计算方法,在此记录一下。

 

获取当前APP占用系统内存的方法如下:

#import <mach/mach.h>

- (int64_t)memoryUsage {
    int64_t memoryUsageInByte = 0;
    task_vm_info_data_t vmInfo;
    mach_msg_type_number_t count = TASK_VM_INFO_COUNT;
    kern_return_t kernelReturn = task_info(mach_task_self(), TASK_VM_INFO, (task_info_t) &vmInfo, &count);
    if(kernelReturn == KERN_SUCCESS) {
        memoryUsageInByte = (int64_t) vmInfo.phys_footprint;
        NSLog(@"APP占用内存: %lld字节(B)", memoryUsageInByte);
        int64_t mb = memoryUsageInByte / 1024 / 1024;
        NSLog(@"APP占用内存: %lld兆(MB)", mb);
    } else {
        NSLog(@"Error with task_info(): %s", mach_error_string(kernelReturn));
    }
    return memoryUsageInByte;
}

 

计算当前APP的CPU使用率的方法(数值与Instrument计算值接近):

- (float)cpu_usage
{
    kern_return_t kr;
    task_info_data_t tinfo;
    mach_msg_type_number_t task_info_count;
    
    task_info_count = TASK_INFO_MAX;
    kr = task_info(mach_task_self(), TASK_BASIC_INFO, (task_info_t)tinfo, &task_info_count);
    if (kr != KERN_SUCCESS) {
        return -1;
    }
    
    task_basic_info_t      basic_info;
    thread_array_t         thread_list;
    mach_msg_type_number_t thread_count;
    
    thread_info_data_t     thinfo;
    mach_msg_type_number_t thread_info_count;
    
    thread_basic_info_t basic_info_th;
    uint32_t stat_thread = 0; // Mach threads
    
    basic_info = (task_basic_info_t)tinfo;
    
    // get threads in the task
    kr = task_threads(mach_task_self(), &thread_list, &thread_count);
    if (kr != KERN_SUCCESS) {
        return -1;
    }
    if (thread_count > 0)
        stat_thread += thread_count;
    
    long tot_sec = 0;
    long tot_usec = 0;
    float tot_cpu = 0;
    int j;
    
    for (j = 0; j < thread_count; j++)
    {
        thread_info_count = THREAD_INFO_MAX;
        kr = thread_info(thread_list[j], THREAD_BASIC_INFO,
                         (thread_info_t)thinfo, &thread_info_count);
        if (kr != KERN_SUCCESS) {
            return -1;
        }
        
        basic_info_th = (thread_basic_info_t)thinfo;
        
        if (!(basic_info_th->flags & TH_FLAGS_IDLE)) {
            tot_sec = tot_sec + basic_info_th->user_time.seconds + basic_info_th->system_time.seconds;
            tot_usec = tot_usec + basic_info_th->user_time.microseconds + basic_info_th->system_time.microseconds;
            tot_cpu = tot_cpu + basic_info_th->cpu_usage / (float)TH_USAGE_SCALE * 100.0;
        }
        
    } // for each thread
    
    kr = vm_deallocate(mach_task_self(), (vm_offset_t)thread_list, thread_count * sizeof(thread_t));
    assert(kr == KERN_SUCCESS);
    
    return tot_cpu;
}

 

 

2020-04-04 01:35:52 shengpeng3344 阅读数 43

App崩溃问题

app经常会遇见崩溃问题,比如下

  • 数据越界
  • 多线程操作同一指针,当指针为空时崩溃
  • 野指针问题
  • KVO问题
  • NSNotification线程问题

以及不可捕获的崩溃问题:

  • 后台任务超时
  • App超过系统限制的内存大小被杀死
  • 主线程卡顿被杀死

可捕获的崩溃信息收集

对于可以捕获的崩溃问题,我们可以通过崩溃日志分析,例如PLCrashReporterbugly这些三方崩溃分析工具。


PLCrashReporter实现

在崩溃日志中,Exception信息头部经常有
Exception Type: EXC_CRASH (SIGABRT)

常见的崩溃信息

SIGABRT就代表着一种崩溃信息,类似还有SIGSEGVSIGBUS

PLCrashReporter 通过注册这些崩溃signalHandler回调就可以获得崩溃信息。

PLCrashReporter 源码中

/**
 * Register a new signal @a callback for @a signo.
 *
 * @param signo The signal for which a signal handler should be registered. Note that multiple callbacks may be registered
 * for a single signal, with chaining handled appropriately by the receiver. If multiple callbacks are registered, they may
 * <em>optionally</em> forward the signal to the next callback (and the original signal handler, if any was registered) via PLCrashSignalHandlerForward.
 * @param callback Callback to be issued upon receipt of a signal. The callback will execute on the crashed thread.
 * @param context Context to be passed to the callback. May be NULL.
 * @param outError A pointer to an NSError object variable. If an error occurs, this pointer will contain an error object indicating why
 * the signal handlers could not be registered. If no error occurs, this parameter will be left unmodified. You may specify
 * NULL for this parameter, and no error information will be provided.
 *
 * @warning Once registered, a callback may not be deregistered. This restriction may be removed in a future release.
 * @warning Callers must ensure that the PLCrashSignalHandler instance is not released and deallocated while callbacks remain active; in
 * a future release, this may result in the callbacks also being deregistered.
 */
- (BOOL) registerHandlerForSignal: (int) signo
                         callback: (PLCrashSignalHandlerCallbackFunc) callback
                          context: (void *) context
                            error: (NSError **) outError
{
    /* Register the actual signal handler, if necessary */
    if (![self registerHandlerWithSignal: signo error: outError])
        return NO;
    
    /* Add the new callback to the shared state list. */
    plcrash_signal_user_callback reg = {
        .callback = callback,
        .context = context
    };
    shared_handler_context.callbacks.nasync_prepend(reg);
    
    return YES;
}

可以查看 registerHandlerWithSignal 来查看实现。

PLCrashReporter 将这些崩溃堆栈信息存储后,App就崩溃了,等待下次重启时,就可以取到这些日志,进行上传分析。

系统接口

系统函数的优点在于性能好,但是只能获取简单的信息,无法通过dSYM文件来了解是哪行代码出的问题。

void InstallSignalHandler(void)
{
    signal(SIGHUP, SignalExceptionHandler);
    signal(SIGINT, SignalExceptionHandler);
    signal(SIGQUIT, SignalExceptionHandler);
    
    signal(SIGABRT, SignalExceptionHandler);
    signal(SIGILL, SignalExceptionHandler);
    signal(SIGSEGV, SignalExceptionHandler);
    signal(SIGFPE, SignalExceptionHandler);
    signal(SIGBUS, SignalExceptionHandler);
    signal(SIGPIPE, SignalExceptionHandler);
}

void SignalExceptionHandler(int signal)
{
    NSMutableString *mstr = [[NSMutableString alloc] init];
    [mstr appendString:@"Stack:\n"];
    void* callstack[128];
    int i, frames = backtrace(callstack, 128);
    char** strs = backtrace_symbols(callstack, frames);
    for (i = 0; i <frames; ++i) {
        [mstr appendFormat:@"%s\n", strs[i]];
    }
    //这里写入mstr到你的日志文件

}

不可捕获的崩溃

iOS后台模式

  • 后台模式,例如音乐播放VOIP地图类app
  • Background Fetch设置一个间隔来每隔一段时间请求网络数据。由于用户可以在设置中关闭这种模式,导致它的使用场景很少
  • Slience Push 是推送的一种,会在后台唤醒30秒,优先级很低。
  • Push Kit 后台唤醒app后会保活30秒,主要用于VOIP应用提升体验。
  • Background Task 是最常用也是使用最多的模式,会在进入后台时请求3分钟进行额外的操作。

对于 Background Task 你可以使用UIApplicationbeginBackgroundTaskWithName:expirationHandler: 或者beginBackgroundTaskWithExpirationHandler:方法来申请一些额外的时间。

官方文档

任务最多执行3分钟,超过时间,你的app将会被杀死,造成崩溃。这也是app退到后台容易产生崩溃的原因。

对于这种崩溃,我们一般是在申请的3分钟的Task中,开启一个定时器,当快到3分钟时检测app还在运行就意味着App即将被系统杀死,这个时候进行记录日志上传来达到监控的效果。


Runloop 卡顿

对于Runloop的卡顿分析,我们可以通过通过添加runloopCFRunLoopAddObserver添加观察者来观察主线程Runloop的状态,通过回调返回,然后我们开启子线程一个loop循环来检测单位允许时间内(例如 3秒)是否收到了该状态,如果超过了该值,则说明这个状态转变的时间过长了

注意:这个3秒时间,我们可以通过看门狗每个阶段允许的事件来判断,我们可通过下面的Watch Dog各个阶段允许的超过时间来设置,不要大于看门狗杀死app的时间

CFRunLoopObserverRef 是观察者,每个 Observer 都包含了一个回调(函数指针),当 RunLoop 的状态发生变化时,观察者就能通过回调接受到这个变化。可以观测的时间点有以下几个:

/* Run Loop Observer Activities */
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
    kCFRunLoopEntry         = (1UL << 0), // 即将进入Loop
    kCFRunLoopBeforeTimers  = (1UL << 1), // 即将处理 Timer
    kCFRunLoopBeforeSources = (1UL << 2), // 即将处理 Source
    kCFRunLoopBeforeWaiting = (1UL << 5), // 即将进入休眠
    kCFRunLoopAfterWaiting  = (1UL << 6), // 刚从休眠中唤醒
    kCFRunLoopExit          = (1UL << 7), // 即将退出Loop
    kCFRunLoopAllActivities = 0x0FFFFFFFU // 所有的状态监听
};

我们可以通过添加runloopCFRunLoopAddObserver添加观察者来观察kCFRunLoopBeforeSourceskCFRunLoopAfterWaiting来检测卡顿。

我想是因为其包含了 Timer Source0 Source1 处理的过程的原因。


dispatchSemaphore = dispatch_semaphore_create(0); //Dispatch Semaphore保证同步
//创建一个观察者
CFRunLoopObserverContext context = {0,(__bridge void*)self,NULL,NULL};
runLoopObserver = CFRunLoopObserverCreate(kCFAllocatorDefault,
                                          kCFRunLoopAllActivities,
                                          YES,
                                          0,
                                          &runLoopObserverCallBack,
                                          &context);
//将观察者添加到主线程runloop的common模式下的观察中
CFRunLoopAddObserver(CFRunLoopGetMain(), runLoopObserver, kCFRunLoopCommonModes);

//回调
static void runLoopObserverCallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info){
    SMLagMonitor *lagMonitor = (__bridge SMLagMonitor*)info;
    lagMonitor->runLoopActivity = activity;
    
    dispatch_semaphore_t semaphore = lagMonitor->dispatchSemaphore;
    dispatch_semaphore_signal(semaphore);
}

再进行创建子线程

//创建子线程监控
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        //子线程开启一个持续的loop用来进行监控
        while (YES) {
            long semaphoreWait = dispatch_semaphore_wait(dispatchSemaphore, dispatch_time(DISPATCH_TIME_NOW, STUCKMONITORRATE * NSEC_PER_MSEC));
            if (semaphoreWait != 0) {
                if (!runLoopObserver) {
                    timeoutCount = 0;
                    dispatchSemaphore = 0;
                    runLoopActivity = 0;
                    return;
                }
                //两个runloop的状态,BeforeSources和AfterWaiting这两个状态区间时间能够检测到是否卡顿
                if (runLoopActivity == kCFRunLoopBeforeSources || runLoopActivity == kCFRunLoopAfterWaiting) {
                    //出现三次出结果
                    if (++timeoutCount < 3) {
                        continue;
                    }
                    // TODO:写入堆栈信息到日志
                } //end activity
            }// end semaphore wait
            timeoutCount = 0;
        }// end while
    });

戴明老师的 GCDFetchFeed 中就包含了这个处理

关于Runloop卡顿检测的详细说明,我会在另一篇博文中说明。


Watch Dog

内存打爆主线程卡顿时间超过阈值都会被Watch Dog杀死

Watchdog 在app各个阶段所允许的超时时间

launch(启动)	20s
resume(恢复)	10s
suspend(挂起)	10s
quit(退出)	6s
background(后台) 3min(iOS7之前,申请10min,之后改为每次申请3min,可连续申请直到10min)

看门狗Watch Dog的崩溃异常代码通常是“0x8badf00d”,即“ate bad food”。


一些被系统杀掉的情况,我们可以通过异常编码来分析。你可以在维基百科上,查看完整的异常编码。这里列出了 44 种异常编码,但常见的就是如下三种:

0x8badf00d,表示 App 在一定时间内无响应而被 watchdog 杀掉的情况。
0xdeadfa11,表示 App 被用户强制退出。
0xc00010ff,表示 App 因为运行造成设备温度太高而被杀掉。


内存达到单个App上限被杀死

App达到iOS系统对单个app设置的内存占用上限后,被系统杀死,我们称之为 out of memory(OOM)

我们如何获取app的内存上限大小呢?


JetsamEvent 分析内存大小

对于内存问题杀死的问题,我们可以分析JetsamEvent日志。

我们可以通过手机的设置->隐私->分析->分析数据中 找到JetsamEvent开头的相关日志信息。在这里插入图片描述
先找到per-process-limit内容

{
    "uuid" : "092ff2cc-0290-3310-a375-cc69c192b94d",
    "states" : [
      "daemon",
      "idle"
    ],
    "killDelta" : 6781,
    "lifetimeMax" : 8241,
    "age" : 4581135570382,
    "purgeable" : 485,
    "fds" : 50,
    "genCount" : 0,
    "coalition" : 832,
    "rpages" : 7736,
    "reason" : "per-process-limit",
    "pid" : 8326,
    "idleDelta" : 174877955757,
    "name" : "photoanalysisd",
    "cpuTime" : 111.697557
  },

"rpages" : 7736 表示内存页数为 7736 ,再从文件中找到pageSize字段

  "largestZoneSize" : 15192064,
  "pageSize" : 4096,
  "uncompressed" : 77779,
  "zoneMapSize" : 70713344,

则可以计算当前app内存限额 7736*4096/1024/1024 = 30MB

  • iOS是如何发现JetsamEvent
    iOS会开启优先级最高级的线程vm_pressure_monitor来监听系统内存情况,通过一个堆栈来维护所有app的进程。此外iOS还会维护一个内存快照表,来保存每个进程的内存页消耗情况。

    iOS觉得内存有压力,就会发出通知,告诉那些内存有压力的app去释放内存,didReceiveMemoryWarning就是这里产生,在这里释放内存能够可能避免你的app被系统杀死。

  • iOS杀死线程的优先级
    iOS内核有个数组,专门维护线程的优先级。
    内核使用的线程的优先级最高,操作系统其次,App最后。
    App在前台的优先级高于后台,且占用线程多的App优先级将会降低

App在被iOS系统杀死前,系统大概有6s时间来判断优先级,Jetsam日志也是这个时间生成的.


XNU获取内存限值

XNU获取需要Root权限,通过memorystatus_priority_entry结构体获取

// 获取进程的 pid、优先级、状态、内存阈值等信息
typedef struct memorystatus_priority_entry {
    pid_t pid;
    int32_t priority;
    uint64_t user_data;
    int32_t limit;
    uint32_t state;
} memorystatus_priority_entry_t;

参考文章 : https://www.jianshu.com/p/2a283df2e839


task_info接口

根据XNU源码,我们可以知道apple获取app使用信息的函数。

源码链接

系统提供了一个函数获取当前任务的信息,我们可以通过phys_footprint获取app的使用内存

#import <mach/mach.h>

uint64_t memoryFootprint() {
    task_vm_info_data_t vmInfo;
    mach_msg_type_number_t count = TASK_VM_INFO_COUNT;
    kern_return_t result = task_info(mach_task_self(), TASK_VM_INFO, (task_info_t) &vmInfo, &count);
    if (result != KERN_SUCCESS)
        return 0;
    return vmInfo.phys_footprint;
}

内存分配监听

内存分配函数malloccalloc等默认使用的是nano_zonenano_zone256B以下内存的分配,大于256B 的时候会使用 scalable_zone 来分配。

使用 scalable_zone 分配内存的函数都会调用malloc_logger 函数,因为系统需要有一个地方统计并管理内存的分配情况。

我们可以通过 fishhook 进行hook内存分配的 mallloc_logger ,就可以掌握内存分配的大小。


参考文章 :
https://www.jianshu.com/p/302ed945e9cf
https://www.jianshu.com/p/2a283df2e839

2017-07-04 17:04:36 qq_25303213 阅读数 2716

最近项目中遇到一个问题

在tableview上展示多张图片,在加载的过程中,图片过多时,加载几张图片就崩溃了。内存溢出


为什么会出现这种情况

这个原因其实很明显,SDWebImage产生的图片缓存过多,造成APP内存溢出,崩溃了。


查了一些资料

有三种解决方法

1. 更改图片的大小,在tableview中展示缩略图,点击查看的时候显示原图。其中看到的一篇意思到了(传送门)


2. 可以每次加载图片清空memcache,但是效果并不好。

 [[SDImageCache sharedImageCache] setValue:nil forKey:@"memCache"];

使用下面这个方法的地方全部注掉

+ (UIImage *)decodedImageWithImage:(UIImage *)image 

但是效果并不明显。同时加载5-7张高分辨率图片还是会立即崩溃


3. 直接更改SDWebImage的SDK,但是这么一改,整个APP的加载图片就都变成这样了,总是让我感觉有点顾虑,不过博客写的很不错。浏览量近万次!(完美解决SDWebImage加载多个图片内存崩溃的问题


我这里也找到了一个新的解决办法,问题解决了,也不用改SDWebImage的SDK。

记得,我第一次工作面试的时候,一个面试官问我,用什么第三方加载图片的,我说SDWebImage啊,

她有问我:有没有用过AFNetWorking啊,

我说:用过啊,

她有说:我告诉你呀,其实AFNetWorking也是可以加载图片的哦,我说真的呀,66的


然后我就知道哇,原来用AFNetWorking也是可以加载图片的,没错!我的新办法就是吧因为用SDWebImage同时加载多张图片出现崩溃的地方,改为使用AFNetWorking来加载


#import "UIImageView+AFNetworking.h"


/** 1. 通过指定的NSURL对象异步加载一个图片 */
- (void)setImageWithURL:(NSURL *)url

/** 通过指定的NSURL对象异步加载一个图片, 并在加载完成之前设置一个占位图片. */
- (void)setImageWithURL:(NSURL *)url
       placeholderImage:(UIImage *)placeholderImage

/** 2. 通过指定的NSRequest对象异步加载一个图片,  */ 
- (void)setImageWithURLRequest:(NSURLRequest *)urlRequest
              placeholderImage:(UIImage *)placeholderImage
                       success:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image))success
                       failure:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error))failure

/** 3. 取消请求操作 */             
- (void)cancelImageRequestOperation
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

    刚好也看见,有一篇文章专门介绍AFNetworking加载网络图片的博客: (iOS_AFNetworking_UIImageView+AFNetworking(实现图片异步加载)

     


    英雄所见略同啊,额,长友厉害啊!




    iOS 动态库的使用

    阅读数 949

    没有更多推荐了,返回首页