文档章节

iOS - 自动释放池与@autoreleasepool

H
 HeroHY
发布于 2017/07/13 10:31
字数 4062
阅读 6
收藏 0
点赞 0
评论 0

一、官网关于自动释放池的说明截取 NSAutoreleasePool NSAutoreleasePool 类被用来支持自动引用计数内存管理系统。一个自动释放池存储的对象当自己被销毁的时会向其中的对象发送 release 消息。 Overview 1.png 在一个自动引用计数的环境中(并不是垃圾回收机制),一个包含了多个对象的 NSAutoreleasePool 对象能够接收 autorelease 消息并且当销毁它的时候会对每一个池子中的对象发送 release 消息。因此,发送 autorelease 而不是 release 消息延长了对象的生命周期直到 pool 被清空的时候(当对象被保留的时候会更久)。一个对象能够被放到同一个池子中许多次,在这种情况下每放一次都会收到一个 release 消息。 在引用计数的环境中,Cocoa 期望有一个自动释放池能够保持有效。如果一个池子没有用了,需要自动释放的对象没有被释放从而会造成内存泄漏。在这种情况下,你的程序将会报错。 Application Kit 在事件循环开始的时候在主线程创建了一个自动释放池,并且在结束的时候去清空它,从而释放所有进程事件中生成的自动释放的对象。如果使用了 Application Kit ,就没必要再去创建自己的自动释放池。然而,如果你的应用在事件循环中创建了很多临时的自动释放的对象,创建临时的自动释放池会将有助于削减你内存峰值的占用。 你创建了一个 NSAutoreleasePool 对象根据 alloc 和 init 消息并且用 drain 来清空它。因为你不能够保留一个自动释放池(或者自动释放它。), 清空一个池子最终会影响它的销毁。你应该在创建它的同一个上下文来进行销毁的工作。 每一个线程(包括主线程)包含一个它自己的自动释放池对象的堆栈。作为一个新的被创建的池子,它们被添加到堆栈的顶部。当池子被释放的时候,它们从栈中被移除。自动释放的对象被放在当前线程的自动释放池的顶部。当一个线程终止的时候,它自动清空与它关联的所有的自动释放池。 线程 如果你想要 Cocoa 在 ApplicationKit 的主线程之外调用,比如你创建了一个 Foundation 的 应用或者你创建了一个线程,你需要创建你自己的自动释放池。 如果应用或线程是长久保存的并且潜在的生成了很多自动释放的对象,这时应该定期的清空并且创建自动释放池(就像 Application Kit 在主线程中做的那样);否则,对象的积累会增加内存的占用。如果,独立的线程并没有使用 Cocoa 的调用,你没有必要去创建一个自动释放池。 注意 如果使用了 POSIX 线程 APIS 而不是 NSThread 对象来创建线程,你不能使用 Cocoa,包括 NSautoreleasePool,除非 Cocoa 是在多线程模式下,Cocoa 进入了多线程模式只有在首次创建 NSThread 对象的时候,为了在第二个 POSIX 线程中使用 Cocoa ,你的应用必须首先至少创建了一个独立的 NSThread 对象,这个对象可以立即退出。你可以通过 NSThread 类方法 isMultiTheraded 来测试 Cocoa 是否在多线程模式下。 垃圾回收 在垃圾回收的环境下,是不需要自动释放池的。你可能写了一个 framework ,它被设计用来在垃圾回收环境和引用计数环境下都可工作。在这种情况下,你可以使用自动释放池去提示回收器回收可能是合适的。在垃圾回收环境中,如果必要会发送一个 drain 消息到池子中去触发垃圾回收机制;然而,release,是一个空操作。在引用计数的环境中,drain 和 release 的效果是一样的。通常,你应该使用 drain 而不是 release。 二、什么时候是用 @autoreleasepool 写基于命令行的的程序时,就是没有UI框架,如 AppKit 等 Cocoa 框架时。 当我们的应用有需要创建大量的临时变量的时候,可以是用 @autoreleasepool 来减少内存峰值。 为什么? 自动释放池可以延长对象的声明周期,如果一个事件周期很长,比如有一个很长的循环逻辑,那么一个临时变量可能很长时间都不会被释放,一直在内存中保留,那么内存的峰值就会一直增加,但是其实这个临时变量是我们不再需要的。这个时候就通过创建新的自动释放池来缩短临时变量的生命周期来降低内存的峰值。 这是一个说明这个问题的很好的例子。 YYKit 中的使用 for (int i = 0; i < count; i++) { @autoreleasepool { id imageSrc = _images[i]; NSDictionary *frameProperty = NULL; if (_type == YYImageTypeGIF && count > 1) { frameProperty = @{(NSString *)kCGImagePropertyGIFDictionary : @{(NSString *) kCGImagePropertyGIFDelayTime:_durations[i]}}; } else { frameProperty = @{(id)kCGImageDestinationLossyCompressionQuality : @(_quality)}; } if ([imageSrc isKindOfClass:[UIImage class]]) { UIImage *image = imageSrc; if (image.imageOrientation != UIImageOrientationUp && image.CGImage) { CGBitmapInfo info = CGImageGetBitmapInfo(image.CGImage) | CGImageGetAlphaInfo(image.CGImage); CGImageRef rotated = YYCGImageCreateCopyWithOrientation(image.CGImage, image.imageOrientation, info); if (rotated) { image = [UIImage imageWithCGImage:rotated]; CFRelease(rotated); } } if (image.CGImage) CGImageDestinationAddImage(destination, ((UIImage *)imageSrc).CGImage, (CFDictionaryRef)frameProperty); } else if ([imageSrc isKindOfClass:[NSURL class]]) { CGImageSourceRef source = CGImageSourceCreateWithURL((CFURLRef)imageSrc, NULL); if (source) { CGImageDestinationAddImageFromSource(destination, source, i, (CFDictionaryRef)frameProperty); CFRelease(source); } } else if ([imageSrc isKindOfClass:[NSData class]]) { CGImageSourceRef source = CGImageSourceCreateWithData((CFDataRef)imageSrc, NULL); if (source) { CGImageDestinationAddImageFromSource(destination, source, i, (CFDictionaryRef)frameProperty); CFRelease(source); } } } } 三、release 和 drain的区别 当我们向自动释放池 pool 发送 release 消息,将会向池中临时对象发送一条 release 消息,并且自身也会被销毁。 向它发送drain消息时,将会向池中临时对象发送一条release消息。 官方解释 release: 释放并且出栈接收者。(ARC) drain: 在引用计数环境中,会释放并且出栈接受者。 在垃圾回收环境中,会触发垃圾回收机制如果上次分配的内存集合大于当前的阈值。 四、Autoreleasepool 底层实现 首先我们去可以查看一下,clang 转成 c++ 的 autoreleasepool 的源码: extern "C" __declspec(dllimport) void * objc_autoreleasePoolPush(void); extern "C" __declspec(dllimport) void objc_autoreleasePoolPop(void *); struct __AtAutoreleasePool { __AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();} ~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);} void * atautoreleasepoolobj; }; 可以发现objc_autoreleasePoolPush() 和 objc_autoreleasePoolPop() 这两个方法。 再看一下runtime 中 Autoreleasepool 的结构,通过阅读源码可以看出 Autoreleasepool 是一个由 AutoreleasepoolPage 双向链表的结构,其中 child 指向它的子 page,parent 指向它的父 page。 双向链表结构图 并且每个 AutoreleasepoolPage 对象的大小都是 4096 个字节。 #define PAGE_MAX_SIZE PAGE_SIZE #define PAGE_SIZE I386_PGBYTES #define I386_PGBYTES 4096 /* bytes per 80386 page */ AutoreleasepoolPage 通过压栈的方式来存储每个需要自动释放的对象。 //入栈方法 static inline void *push() { id *dest; if (DebugPoolAllocation) { // Each autorelease pool starts on a new pool page. //在 Debug 情况下每一个自动释放池 都以一个新的 poolPage 开始 dest = autoreleaseNewPage(POOL_BOUNDARY); } else { //正常情况下,调用 push 方法会先插入一个 POOL_BOUNDARY 标志位 dest = autoreleaseFast(POOL_BOUNDARY); } assert(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY); return dest; } 然后我们来看看 runtime 中的源码 /*********************************************************************** 自动释放池的实现: 一个线程的自动释放池是一个指针堆栈 每一个指针或者指向被释放的对象,或者是自动释放池的 POOL_BOUNDARY,POOL_BOUNDARY 是自动释放池的边界。 一个池子的 token 是指向池子 POOL_BOUNDARY 的指针。当池子被出栈的时候,每一个高于标准的对象都会被释放掉。 堆栈被分成一个页面的双向链表。页面按照需要添加或者删除。 本地线程存放着指向当前页的指针,在这里存放着新创建的自动释放的对象。 **********************************************************************/ // Set this to 1 to mprotect() autorelease pool contents //将这个设为 1 可以通过mprotect修改映射存储区的权限来更改自动释放池的内容 #define PROTECT_AUTORELEASEPOOL 0 #define CHECK_AUTORELEASEPOOL (DEBUG) BREAKPOINT_FUNCTION(void objc_autoreleaseNoPool(id obj)); BREAKPOINT_FUNCTION(void objc_autoreleasePoolInvalid(const void *token)); namespace { //对AutoreleasePoolPage进行完整性校验 struct magic_t { static const uint32_t M0 = 0xA1A1A1A1; # define M1 "AUTORELEASE!" static const size_t M1_len = 12; uint32_t m[4]; magic_t() { assert(M1_len == strlen(M1)); assert(M1_len == 3 * sizeof(m[1])); m[0] = M0; strncpy((char *)&m[1], M1, M1_len); } ~magic_t() { m[0] = m[1] = m[2] = m[3] = 0; } bool check() const { return (m[0] == M0 && 0 == strncmp((char *)&m[1], M1, M1_len)); } bool fastcheck() const { #if CHECK_AUTORELEASEPOOL return check(); #else return (m[0] == M0); #endif } # undef M1 }; //自动释放页 class AutoreleasePoolPage { //EMPTY_POOL_PLACEHOLDER 被存放在本地线程存储中当一个池入栈并且没有存放任何对象的时候。这样在栈顶入栈出栈并且没有使用它们的时候会节省内存。 # define EMPTY_POOL_PLACEHOLDER ((id*)1) # define POOL_BOUNDARY nil static pthread_key_t const key = AUTORELEASE_POOL_KEY; static uint8_t const SCRIBBLE = 0xA3; // 0xA3A3A3A3 after releasing static size_t const SIZE = #if PROTECT_AUTORELEASEPOOL PAGE_MAX_SIZE; // must be multiple of vm page size #else PAGE_MAX_SIZE; // size and alignment, power of 2 #endif //对象数量 static size_t const COUNT = SIZE / sizeof(id); //校验完整性 magic_t const magic; //页中对象的下一位索引 id *next; //线程 pthread_t const thread; //父页 AutoreleasePoolPage * const parent; //子页 AutoreleasePoolPage *child; //深度 uint32_t const depth; uint32_t hiwat; // SIZE-sizeof(*this) bytes of contents follow //创建 static void * operator new(size_t size) { return malloc_zone_memalign(malloc_default_zone(), SIZE, SIZE); } //删除 static void operator delete(void * p) { return free(p); } //设置当前内存可读 inline void protect() { #if PROTECT_AUTORELEASEPOOL mprotect(this, SIZE, PROT_READ); check(); #endif } //设置当前内存可读可写 inline void unprotect() { #if PROTECT_AUTORELEASEPOOL check(); mprotect(this, SIZE, PROT_READ | PROT_WRITE); #endif } //初始化 AutoreleasePoolPage(AutoreleasePoolPage *newParent) : magic(), next(begin()), thread(pthread_self()), parent(newParent), child(nil), depth(parent ? 1+parent->depth : 0), hiwat(parent ? parent->hiwat : 0) { if (parent) { parent->check(); assert(!parent->child); parent->unprotect(); parent->child = this; parent->protect(); } protect(); } //析构 ~AutoreleasePoolPage() { check(); unprotect(); assert(empty()); // Not recursive: we don't want to blow out the stack // if a thread accumulates a stupendous amount of garbage assert(!child); } //被破坏的 void busted(bool die = true) { magic_t right; (die ? _objc_fatal : _objc_inform) ("autorelease pool page %p corrupted\n" " magic 0x%08x 0x%08x 0x%08x 0x%08x\n" " should be 0x%08x 0x%08x 0x%08x 0x%08x\n" " pthread %p\n" " should be %p\n", this, magic.m[0], magic.m[1], magic.m[2], magic.m[3], right.m[0], right.m[1], right.m[2], right.m[3], this->thread, pthread_self()); } //校验 void check(bool die = true) { if (!magic.check() || !pthread_equal(thread, pthread_self())) { busted(die); } } //快速校验 void fastcheck(bool die = true) { #if CHECK_AUTORELEASEPOOL check(die); #else if (! magic.fastcheck()) { busted(die); } #endif } //页的开始位置 id * begin() { return (id *) ((uint8_t *)this+sizeof(*this)); } //页的结束位置 id * end() { return (id *) ((uint8_t *)this+SIZE); } //页是否是空的 bool empty() { return next == begin(); } //页是否是满的 bool full() { return next == end(); } //是否少于一半 bool lessThanHalfFull() { return (next - begin() < (end() - begin()) / 2); } //添加对象 id *add(id obj) { assert(!full()); unprotect(); id *ret = next; // faster than `return next-1` because of aliasing *next++ = obj; protect(); return ret; } //释放所有对象 void releaseAll() { releaseUntil(begin()); } //释放到 stop 的位置之前的所有对象 void releaseUntil(id *stop) { // Not recursive: we don't want to blow out the stack // if a thread accumulates a stupendous amount of garbage while (this->next != stop) { // Restart from hotPage() every time, in case -release // autoreleased more objects AutoreleasePoolPage *page = hotPage(); // fixme I think this `while` can be `if`, but I can't prove it while (page->empty()) { page = page->parent; setHotPage(page); } page->unprotect(); id obj = *--page->next; //将页索引内容置为 SCRIBBLE 表示已经被释放 memset((void*)page->next, SCRIBBLE, sizeof(*page->next)); page->protect(); if (obj != POOL_BOUNDARY) { objc_release(obj); } } setHotPage(this); #if DEBUG // we expect any children to be completely empty for (AutoreleasePoolPage *page = child; page; page = page->child) { assert(page->empty()); } #endif } //杀死 void kill() { // Not recursive: we don't want to blow out the stack // if a thread accumulates a stupendous amount of garbage AutoreleasePoolPage *page = this; while (page->child) page = page->child; AutoreleasePoolPage *deathptr; do { deathptr = page; page = page->parent; if (page) { page->unprotect(); page->child = nil; page->protect(); } delete deathptr; } while (deathptr != this); } //释放本地线程存储空间 static void tls_dealloc(void *p) { if (p == (void*)EMPTY_POOL_PLACEHOLDER) { // No objects or pool pages to clean up here. return; } // reinstate TLS value while we work setHotPage((AutoreleasePoolPage *)p); if (AutoreleasePoolPage *page = coldPage()) { if (!page->empty()) pop(page->begin()); // pop all of the pools if (DebugMissingPools || DebugPoolAllocation) { // pop() killed the pages already } else { page->kill(); // free all of the pages } } // clear TLS value so TLS destruction doesn't loop setHotPage(nil); } //获取 AutoreleasePoolPage static AutoreleasePoolPage *pageForPointer(const void *p) { return pageForPointer((uintptr_t)p); } static AutoreleasePoolPage *pageForPointer(uintptr_t p) { AutoreleasePoolPage *result; uintptr_t offset = p % SIZE; assert(offset >= sizeof(AutoreleasePoolPage)); result = (AutoreleasePoolPage *)(p - offset); result->fastcheck(); return result; } //是否有空池占位符 static inline bool haveEmptyPoolPlaceholder() { id *tls = (id *)tls_get_direct(key); return (tls == EMPTY_POOL_PLACEHOLDER); } //设置空池占位符 static inline id* setEmptyPoolPlaceholder() { assert(tls_get_direct(key) == nil); tls_set_direct(key, (void *)EMPTY_POOL_PLACEHOLDER); return EMPTY_POOL_PLACEHOLDER; } //获取当前页 static inline AutoreleasePoolPage *hotPage() { AutoreleasePoolPage *result = (AutoreleasePoolPage *) tls_get_direct(key); if ((id *)result == EMPTY_POOL_PLACEHOLDER) return nil; if (result) result->fastcheck(); return result; } //设置当前页 static inline void setHotPage(AutoreleasePoolPage *page) { if (page) page->fastcheck(); tls_set_direct(key, (void *)page); } //获取 coldPage static inline AutoreleasePoolPage *coldPage() { AutoreleasePoolPage *result = hotPage(); if (result) { while (result->parent) { result = result->parent; result->fastcheck(); } } return result; } //快速释放 static inline id *autoreleaseFast(id obj) { AutoreleasePoolPage *page = hotPage(); if (page && !page->full()) { return page->add(obj); } else if (page) { return autoreleaseFullPage(obj, page); } else { return autoreleaseNoPage(obj); } } //添加自动释放对象,当页满的时候调用这个方法 static __attribute__((noinline)) id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page) { // The hot page is full. // Step to the next non-full page, adding a new page if necessary. // Then add the object to that page. assert(page == hotPage()); assert(page->full() || DebugPoolAllocation); do { if (page->child) page = page->child; else page = new AutoreleasePoolPage(page); } while (page->full()); setHotPage(page); return page->add(obj); } //添加自动释放对象,当没页的时候使用这个方法 static __attribute__((noinline)) id *autoreleaseNoPage(id obj) { // "No page" could mean no pool has been pushed // or an empty placeholder pool has been pushed and has no contents yet assert(!hotPage()); bool pushExtraBoundary = false; if (haveEmptyPoolPlaceholder()) { // We are pushing a second pool over the empty placeholder pool // or pushing the first object into the empty placeholder pool. // Before doing that, push a pool boundary on behalf of the pool // that is currently represented by the empty placeholder. pushExtraBoundary = true; } else if (obj != POOL_BOUNDARY && DebugMissingPools) { // We are pushing an object with no pool in place, // and no-pool debugging was requested by environment. _objc_inform("MISSING POOLS: (%p) Object %p of class %s " "autoreleased with no pool in place - " "just leaking - break on " "objc_autoreleaseNoPool() to debug", pthread_self(), (void*)obj, object_getClassName(obj)); objc_autoreleaseNoPool(obj); return nil; } else if (obj == POOL_BOUNDARY && !DebugPoolAllocation) { // We are pushing a pool with no pool in place, // and alloc-per-pool debugging was not requested. // Install and return the empty pool placeholder. return setEmptyPoolPlaceholder(); } // We are pushing an object or a non-placeholder'd pool. // Install the first page. AutoreleasePoolPage *page = new AutoreleasePoolPage(nil); setHotPage(page); // Push a boundary on behalf of the previously-placeholder'd pool. if (pushExtraBoundary) { page->add(POOL_BOUNDARY); } // Push the requested object or pool. return page->add(obj); } static __attribute__((noinline)) id *autoreleaseNewPage(id obj) { AutoreleasePoolPage *page = hotPage(); if (page) return autoreleaseFullPage(obj, page); else return autoreleaseNoPage(obj); } //公开方法 public: //自动释放 static inline id autorelease(id obj) { assert(obj); assert(!obj->isTaggedPointer()); id *dest __unused = autoreleaseFast(obj); assert(!dest || dest == EMPTY_POOL_PLACEHOLDER || *dest == obj); return obj; } //入栈 static inline void *push() { id *dest; if (DebugPoolAllocation) { // Each autorelease pool starts on a new pool page. dest = autoreleaseNewPage(POOL_BOUNDARY); } else { dest = autoreleaseFast(POOL_BOUNDARY); } assert(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY); return dest; } //兼容老的 SDK 出栈方法 static void badPop(void *token) { // Error. For bincompat purposes this is not // fatal in executables built with old SDKs. if (DebugPoolAllocation || sdkIsAtLeast(10_12, 10_0, 10_0, 3_0)) { // OBJC_DEBUG_POOL_ALLOCATION or new SDK. Bad pop is fatal. _objc_fatal ("Invalid or prematurely-freed autorelease pool %p.", token); } // Old SDK. Bad pop is warned once. static bool complained = false; if (!complained) { complained = true; _objc_inform_now_and_on_crash ("Invalid or prematurely-freed autorelease pool %p. " "Set a breakpoint on objc_autoreleasePoolInvalid to debug. " "Proceeding anyway because the app is old " "(SDK version " SDK_FORMAT "). Memory errors are likely.", token, FORMAT_SDK(sdkVersion())); } objc_autoreleasePoolInvalid(token); } //出栈 static inline void pop(void *token) { AutoreleasePoolPage *page; id *stop; if (token == (void*)EMPTY_POOL_PLACEHOLDER) { // Popping the top-level placeholder pool. if (hotPage()) { // Pool was used. Pop its contents normally. // Pool pages remain allocated for re-use as usual. pop(coldPage()->begin()); } else { // Pool was never used. Clear the placeholder. setHotPage(nil); } return; } page = pageForPointer(token); stop = (id *)token; if (*stop != POOL_BOUNDARY) { if (stop == page->begin() && !page->parent) { // Start of coldest page may correctly not be POOL_BOUNDARY: // 1. top-level pool is popped, leaving the cold page in place // 2. an object is autoreleased with no pool } else { // Error. For bincompat purposes this is not // fatal in executables built with old SDKs. return badPop(token); } } //打印 hiwat if (PrintPoolHiwat) printHiwat(); page->releaseUntil(stop); // memory: delete empty children if (DebugPoolAllocation && page->empty()) { // special case: delete everything during page-per-pool debugging AutoreleasePoolPage *parent = page->parent; page->kill(); setHotPage(parent); } else if (DebugMissingPools && page->empty() && !page->parent) { // special case: delete everything for pop(top) // when debugging missing autorelease pools page->kill(); setHotPage(nil); } else if (page->child) { // hysteresis: keep one empty child if page is more than half full if (page->lessThanHalfFull()) { page->child->kill(); } else if (page->child->child) { page->child->child->kill(); } } } static void init() { int r __unused = pthread_key_init_np(AutoreleasePoolPage::key, AutoreleasePoolPage::tls_dealloc); assert(r == 0); } //打印 void print() { _objc_inform("[%p] ................ PAGE %s %s %s", this, full() ? "(full)" : "", this == hotPage() ? "(hot)" : "", this == coldPage() ? "(cold)" : ""); check(false); for (id *p = begin(); p < next; p++) { if (*p == POOL_BOUNDARY) { _objc_inform("[%p] ################ POOL %p", p, p); } else { _objc_inform("[%p] %#16lx %s", p, (unsigned long)*p, object_getClassName(*p)); } } } //打印所有 static void printAll() { _objc_inform("##############"); _objc_inform("AUTORELEASE POOLS for thread %p", pthread_self()); AutoreleasePoolPage *page; ptrdiff_t objects = 0; for (page = coldPage(); page; page = page->child) { objects += page->next - page->begin(); } _objc_inform("%llu releases pending.", (unsigned long long)objects); if (haveEmptyPoolPlaceholder()) { _objc_inform("[%p] ................ PAGE (placeholder)", EMPTY_POOL_PLACEHOLDER); _objc_inform("[%p] ################ POOL (placeholder)", EMPTY_POOL_PLACEHOLDER); } else { for (page = coldPage(); page; page = page->child) { page->print(); } } _objc_inform("##############"); } //打印 hiwat static void printHiwat() { // Check and propagate high water mark // Ignore high water marks under 256 to suppress noise. AutoreleasePoolPage *p = hotPage(); uint32_t mark = p->depth*COUNT + (uint32_t)(p->next - p->begin()); if (mark > p->hiwat && mark > 256) { for( ; p; p = p->parent) { p->unprotect(); p->hiwat = mark; p->protect(); } _objc_inform("POOL HIGHWATER: new high water mark of %u " "pending releases for thread %p:", mark, pthread_self()); void *stack[128]; int count = backtrace(stack, sizeof(stack)/sizeof(stack[0])); char **sym = backtrace_symbols(stack, count); for (int i = 0; i < count; i++) { _objc_inform("POOL HIGHWATER: %s", sym[i]); } free(sym); } } #undef POOL_BOUNDARY }; 参考文章 官方文档

作者:Mitchell
链接:http://www.jianshu.com/p/554c9fe0f041
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

本文转载自:http://www.jianshu.com/p/554c9fe0f041

共有 人打赏支持
H
粉丝 0
博文 117
码字总数 64999
作品 0
海淀
经过阿里,百度一面,二面后,我总结了50道iOS面试题

前言: 金三银四已经过去,根据统计,很多人都会选择在三月四月跳槽,原因有很多,企业年后会有大量员工离职,员工觉得老公司待遇不怎么样?薪资不够高,想换个新环境等等原因,所以,三月四...

原来是泽镜啊 ⋅ 05/04 ⋅ 0

2018 一份"有点难"的iOS面试题(5年iOS开发)

序言: 之前一时兴致在本站上出过一份iOS的中级面试题,引起一些关注,不少同学表示对”隐藏关卡“感兴趣。升级版iOS面试题来了,目测难倒90%iOS程序员,目测一大波程序员撸着袖子在靠近。 ...

原来是泽镜啊 ⋅ 05/26 ⋅ 0

【AR】开始使用Vuforia开发iOS(2)

原 设置iOS开发环境 安装Vuforia iOS SDK 如何安装Vuforia iOS示例 编译并运行Vuforia iOS示例 支持iOS金属 iOS 64位迁移 设置iOS开发环境 适用于iOS的Vuforia引擎目前支持运行iOS 9及更高版...

lichong951 ⋅ 06/11 ⋅ 0

你知道我为什么特别讨厌程序员吗?

你知道我为什么特别讨厌程序员吗? 2018-05-28 11:20编辑: 枣泥布丁分类:程序人生来源:程序师 程序员修电脑装系统 招聘信息: C++工程师 Cocos2d-x游戏客户端开发 iOS开发工程师 京东招聘...

枣泥布丁 ⋅ 05/28 ⋅ 0

开发微信H5视频秀项目遇到的坑

介绍 手头上正好有个项目,需要做一个微信端H5视频秀的一个项目,想想好像挺简单的,由两个视频组成,播放完第一个视频后点击按钮继而播放第二个视频。好了,结果微信的坑TM的多 问题排查 自...

🚲Allen ⋅ 05/18 ⋅ 0

面试官自述:面向高级开发人员的iOS面试问题

当您准备进行技术性iOS面试时,了解您可能会询问哪些主题以及经验丰富的iOS开发人员期望什么是非常重要的。 这是许多硅谷公司用来衡量iOS候选人资历水平的一系列问题。 这些问题涉及iOS开发的...

菇哒微课 ⋅ 04/26 ⋅ 0

iOS查看屏幕帧数工具--YYFPSLabel

学习 YYKit 代码时,发现 ibireme 在项目里加入的一个查看当前屏幕帧数的小工具,效果如下: 挺实用,实现方法也很简单,但是思路特别棒。 这里是Demo: YYFPSLabel 这里我把这个小工具从 中...

yehot ⋅ 2016/04/05 ⋅ 0

HDU ~ 6297 ~ CCPC直播 (模拟,输出格式控制)

思路:模拟就行了,注意Running和RTE的开头字母一样。 iomanip是I/O流控制头文件,就像printf的格式化输出一样。 以下是一些常用的: dec 置基数为10 相当于"%d" hex 置基数为16 相当于"%X" oc...

zscdst ⋅ 05/29 ⋅ 0

Keep the Screen from Locking(阻止屏幕自动上锁)

在ios设备中,如果ios设备一段时间中没收到touch事件,ios设备就会自动锁定,但是在玩游戏时候可能会某段时间不触发touch时间,这时候就会锁定,而这并不是我们所想要的,那么我们可以设置 ...

小曼Study ⋅ 04/09 ⋅ 0

Unity与IOS交互,调用IOS系统相机和相册

前面两篇总结了一下unity与android的简单交互和调用安卓系统相机和相册,比较蛋疼的是,后来发现不同的测试机上会有不同的bug。。。下阶段要一个一个的解决一下 今天总结一下与IOS的交互。这...

qq_32587659 ⋅ 05/16 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

Confluence 6 从其他备份中恢复数据

一般来说,Confluence 数据库可以从 Administration Console 或者 Confluence Setup Wizard 中进行恢复。 如果你在恢复压缩的 XML 备份的时候遇到了问题,你还是可以对整个站点进行恢复的,如...

honeymose ⋅ 10分钟前 ⋅ 0

myeclipse10 快速搭建spring boot开发环境(入门)

1.创建一个maven的web项目 注意上面标红的部分记得选上 2.创建的maven目录结构,有缺失的目录可以自己建立目录补充 补充后 这时候一个maven的web项目创建完成 3.配置pom.xml配置文件 <proje...

小海bug ⋅ 22分钟前 ⋅ 0

nginx.conf

=========================================================================== nginx.conf =========================================================================== user nobody; #......

A__17 ⋅ 25分钟前 ⋅ 0

645. Set Mismatch - LeetCode

Question 645. Set Mismatch Solution 思路: 遍历每个数字,然后将其应该出现的位置上的数字变为其相反数,这样如果我们再变为其相反数之前已经成负数了,说明该数字是重复数,将其将入结果r...

yysue ⋅ 38分钟前 ⋅ 0

Python这么强?红包杀手、消息撤回也可以无视,手机App辅助!

论述 标题也许有点不好理解,其实就是一款利用Python实现的可以监控微信APP内的红包与消息撤回的助手。不得不说,这确实是一款大家钟意的神器。 消息撤回是一件很让人恶心的事,毕竟人都是有...

Python燕大侠 ⋅ 54分钟前 ⋅ 0

压缩打包介绍、gzip压缩工具、bzip2压缩工具、xz压缩工具

压缩打包介绍 压缩的好处不仅能节省磁盘空间而且在传输的时候节省传输时间和网络带宽 windows系统下文件带有 .rar .zip .7z 后缀的就是压缩文件 linux系统下则是 .zip, .gz, .bz2, .xz, ...

黄昏残影 ⋅ 59分钟前 ⋅ 0

观察者模式

1.利用java原生类进行操作 package observer;import java.util.Observable;import java.util.Observer;/** * @author shadow * @Date 2016年8月12日下午7:29:31 * @Fun 观察目标 **/......

Cobbage ⋅ 今天 ⋅ 0

Ubuntu打印服务器配置

参考:https://blog.csdn.net/gsls200808/article/details/50950586 https://blog.csdn.net/jiay2/article/details/80252369 https://wiki.gentoo.org/wiki/HPLIP 由于媳妇儿要大量打印资料,......

大熊猫 ⋅ 今天 ⋅ 0

面试的角度诠释Java工程师(二)

原文出处: locality 续言: 相信每一位简书的作者,都会有我这样的思考:怎么写好一篇文章?或者怎么写好一篇技术类的文章?我就先说说我的感悟吧,写文章其实和写程序是一样的。为什么我会...

颖伙虫 ⋅ 今天 ⋅ 0

github中SSH的Key

https://help.github.com/articles/connecting-to-github-with-ssh/ https://help.github.com/articles/testing-your-ssh-connection/ https://help.github.com/articles/adding-a-new-ssh-k......

whoisliang ⋅ 今天 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部