iOS 中weak的实现代码示例

只要学过 iOS 的人,都会对 strong、weak、copy等关键字应该都会很熟悉。weak 属性关键字就是弱引用,它不会增加引用计数但却能保证指针的安全访问,在对象释放后置为 nil,从而避免错误的内存访问。主要为了解决循环引用的问题。

接下来,我们会从 objc 库中的 NSObject.mm、 objc-weak.h 以及 objc-weak.mm 文件出发,去具体了解 weak 的实现过程。

weak 的内部结构

Runtime 维护了一个weak表,用于存储指向某个对象的所有weak指针。weak 表是由单个自旋锁管理的散列表。
weak表其实是一个hash表,key 是所指对象的指针,value是weak指针的地址(这个地址的值是所指向对象的地址)数组。

在下面涉及的源码中,我们会看到以下几个类型:

sideTable、weak_table_t、weak_entry_t 这几个结构体。

struct SideTable {
  // 自旋锁,用来保证线程安全
  spinlock_t slock;
  // 引用计数表
  RefcountMap refcnts;
  // weak 表
  weak_table_t weak_table;

  ...
};

SideTable,它用来管理引用计数表和 weak 表,并使用 spinlock_lock 自旋锁来防止操作表结构时可能的竞态条件。它用一个 64*128 大小的uint8_t 静态数组作为 buffer 来保存所有的 SideTable 实例。这个结构体里面包含三个变量,第一个spinlock_t,它是一个自旋锁,用来保证线程安全。第二个RefcountMap,是引用计数表,每个对象的引用计数保存在全局的引用计数表中,一个对象地址对应一个引用计数。第三个就是我们接下来要讲的 weak 表,所有的 weak 变量会被加入到全局的weak表中,表的 key 是 weak 修饰的变量指向的对象, value 值就是 weak 修饰的变量。接下来,我们具体看看这个 weak 表

struct weak_table_t {
  // 保存了所有指向指定对象的 weak 指针
  weak_entry_t *weak_entries;
  // 存储空间,即 entries 的数目
  size_t  num_entries;
  // 参与判断引用计数辅助量
  uintptr_t mask;
  // hash key 最大偏移量
  uintptr_t max_hash_displacement;
};

这个是全局弱引用的 hash 表。它的作用就是在对象执行 dealloc 的时候将所有指向该对象的 weak 指针的值设为 nil, 避免悬空指针。它使用不定类型对象的地址的 hash 化后的数值作为 key,用 weak_entry_t 类型的结构体对象作为 value。其中 weak_entry_t 是存储在弱引用表中的一个内部结构体,它负责维护和存储指向一个对象的所有弱引用 hash 表。其定义如下:

// 存储在弱引用表中的一个内部结构体
#define WEAK_INLINE_COUNT 4
struct weak_entry_t {
  DisguisedPtr<objc_object> referent;         // 封装 objc_object 指针,即 weak 修饰的变量指向的对象
  union {
    struct {
      weak_referrer_t *referrers;
      uintptr_t    out_of_line : 1;      // LSB 最低有效元 当标志位为0时,增加引用表指针纬度,
                            // 当其为0的时候, weak_referrer_t 成员将扩展为静态数组型的 hash table
      uintptr_t    num_refs : PTR_MINUS_1;  // 引用数值,这里记录弱引用表中引用有效数字,即里面元素的数量
      uintptr_t    mask;
      uintptr_t    max_hash_displacement;   // hash 元素上限阀值
    };
    struct {
      // out_of_line=0 is LSB of one of these (don't care which)
      weak_referrer_t inline_referrers[WEAK_INLINE_COUNT];
    };
  };
};

在 weak_entry_t 的结构中, DisguisedPtr<objc_object> 是对 objc_object * 指针及其一些操作进行的封装,目的就是为了让它给人看起来不会有内存泄露的样子,其内容可以理解为对象的内存地址。out_of-line 成员为最低有效位,当其为 0 的时候,weak_referrer_t 成员将扩展为一个静态数组型的 hash table。其实 weak_referrer 是objc_objcet 的别名,定义如下:typedef objc_object ** weak_referrer_t;

它通过一个二维指针地址偏移,用下标作为 hash 的 key,做成了一个弱引用散列。

每个对象的 SideTable 中的 weak_table_t 都是全局 weak 表的入口,以引用计数对象为键找到其所记录的 weak 修饰的对象。weak_entry_t 中的 referrers 有两种形式,当 out_of_line 为 0 的时候,referrers 是一个静态数组型的表,数组大小默认为 WEAK_INLINE_COUNT 大小,当 out_of_line 不为 0 的时候,referrers 是一个动态数组,内容随之增加。

weak 实现原理的过程

当我们用 weak 修饰属性的时候,它是怎么实现当所引用的对象被废弃的时候,变量置为 nil,我们来探究一下。

{
  id obj1 = [[NSObject alloc] init];
  id __weak obj2 = obj1;
}

经过编译期转换之后,以上代码会变成下面这样

id obj2;
objc_initWeak(&obj2, obj1);
objc_destroyWeak(&obj2);

我们发现,weak 修饰符变量是通过 objc_initWeak 函数来初始化的,在变量作用域结束的时候通过 objc_destroyWeak 函数来释放该变量的。接下来,我们看看这两个函数的源码。

id objc_initWeak(id *location, id newObj)
{
  // 查看对象实例是否有效
  // 无效对象直接导致指针释放
  if (!newObj) {
    *location = nil;
    return nil;
  }
  // 这里传递了三个 bool 数值
  // 使用 template 进行常量参数传递是为了优化性能
  return storeWeak<false/*old*/, true/*new*/, true/*crash*/>
    (location, (objc_object*)newObj);
}
void objc_destroyWeak(id *location)
{
  (void)storeWeak<true/*old*/, false/*new*/, false/*crash*/>
    (location, nil);
}

对这两个方法的分析后,我们发现它们都调用了storeWeak 这个函数,但是两个方法传入的参数却稍有不同。

init 方法中,第一个参数为 weak 修饰的变量,第二个参数为引用计数对象。但在 destoryWeak 函数,第一参数依旧为 weak 修饰的变量,第二个参数为 nil。那这块传入不同的参数到底代表什么,我们继续分析 storeWeak 这个函数。

// 更新一个弱引用变量
// 如果 HaveOld 是 true, 变量是个有效值,需要被及时清理。变量可以为 nil。
// 如果 HaveNew 是 true, 需要一个新的 value 来替换变量。变量可以为 nil
// 如果crashifdeallocation 是 ture ,那么如果 newObj 是 deallocating,或者 newObj 的类不支持弱引用,则该进程就会停止。
// 如果crashifdeallocation 是 false,那么 nil 会被存储。

template <bool HaveOld, bool HaveNew, bool CrashIfDeallocating>
static id storeWeak(id *location, objc_object *newObj)
{
  assert(HaveOld || HaveNew);
  if (!HaveNew) assert(newObj == nil);

  Class previouslyInitializedClass = nil;
  id oldObj;

  // 创建新旧散列表
  SideTable *oldTable;
  SideTable *newTable;

  // Acquire locks for old and new values.
  // 获得新值和旧值的锁存位置 (用地址作为唯一标示)
  // Order by lock address to prevent lock ordering problems.
  // 通过地址来建立索引标志,防止桶重复
  // Retry if the old value changes underneath us.
  // 下面指向的操作会改变旧值
 retry:
  if (HaveOld) {
  // 如果 HaveOld 为 true ,更改指针,获得以 oldObj 为索引所存储的值地址
    oldObj = *location;
    oldTable = &SideTables()[oldObj];
  } else {
    oldTable = nil;
  }
  if (HaveNew) {
  // 获得以 newObj 为索引所存储的值对象
    newTable = &SideTables()[newObj];
  } else {
    newTable = nil;
  }

// 对两个 table 进行加锁操作,防止多线程中竞争冲突
  SideTable::lockTwo<HaveOld, HaveNew>(oldTable, newTable);

// location 应该与 oldObj 保持一致,如果不同,说明当前的 location 已经处理过 oldObj 可是又被其他线程所修改, 保证线程安全,这个判断用来避免线程冲突重处理问题
  if (HaveOld && *location != oldObj) {
    SideTable::unlockTwo<HaveOld, HaveNew>(oldTable, newTable);
    goto retry;
  }

  // Prevent a deadlock between the weak reference machinery
  // and the +initialize machinery by ensuring that no
  // weakly-referenced object has an un-+initialized isa.
  // 防止弱引用之间发生死锁,并且通过 +initialize 初始化构造器保证所有弱引用的 isa 非空指向
  if (HaveNew && newObj) {
    // 获得新对象的 isa 指针
    Class cls = newObj->getIsa();
    // 判断 isa 非空且已经初始化
    if (cls != previouslyInitializedClass &&
      !((objc_class *)cls)->isInitialized())
    {
      // 对两个表解锁
      SideTable::unlockTwo<HaveOld, HaveNew>(oldTable, newTable);
      _class_initialize(_class_getNonMetaClass(cls, (id)newObj));

      // If this class is finished with +initialize then we're good.
      // If this class is still running +initialize on this thread
      // (i.e. +initialize called storeWeak on an instance of itself)
      // then we may proceed but it will appear initializing and
      // not yet initialized to the check above.
      // Instead set previouslyInitializedClass to recognize it on retry.
      // 如果该类已经完成执行 +initialize 方法是最好的,如果该类 + initialize 在线程中,例如 +initialize 正在调用storeWeak 方法,那么则需要手动对其增加保护策略,并设置 previouslyInitializedClass 指针进行标记然后重新尝试
      previouslyInitializedClass = cls;

      goto retry;
    }
  }

  // Clean up old value, if any. 清除旧值
  if (HaveOld) {
    weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
  }

  // Assign new value, if any. 分配新值
  if (HaveNew) {
    newObj = (objc_object *)weak_register_no_lock(&newTable->weak_table,
                           (id)newObj, location,
                           CrashIfDeallocating);
    // weak_register_no_lock returns nil if weak store should be rejected
    // 如果弱引用被释放则该方法返回 nil
    // Set is-weakly-referenced bit in refcount table.
    // 在引用计数表中设置弱引用标记位
    if (newObj && !newObj->isTaggedPointer()) {
      newObj->setWeaklyReferenced_nolock();
    }

    // Do not set *location anywhere else. That would introduce a race.
    *location = (id)newObj;
  }
  else {
    // No new value. The storage is not changed.
  }

  SideTable::unlockTwo<HaveOld, HaveNew>(oldTable, newTable);

  return (id)newObj;
}

以上就是 store_weak 这个函数的实现,它主要做了以下几件事:

  1. 声明了新旧散列表指针,因为 weak 修饰的变量如果之前已经指向一个对象,然后其再次改变指向另一个对象,那么按理来说我们需要释放旧对象中该 weak 变量的记录,也就是要将旧记录删除,然后在新记录中添加。这里的新旧散列表就是这个作用。
  2. 根据新旧变量的地址获取相应的 SideTable
  3. 对两个表进行加锁操作,防止多线程竞争冲突
  4. 进行线程冲突重处理判断
  5. 判断其 isa 是否为空,为空则需要进行初始化
  6. 如果存在旧值,调用 weak_unregister_no_lock 函数清除旧值
  7. 调用 weak_register_no_lock 函数分配新值
  8. 解锁两个表,并返回第二参数

初始化弱引用对象流程一览

弱引用的初始化,从上文的分析可以看出,主要的操作部分就是在弱引用表的取键、查询散列、创建弱引用等操作,可以总结出如下的流程图:

旧对象解除注册操作 weak_unregister_no_lock

void weak_unregister_no_lock(weak_table_t *weak_table, id referent_id,
            id *referrer_id)
{
  objc_object *referent = (objc_object *)referent_id;
  objc_object **referrer = (objc_object **)referrer_id;

  weak_entry_t *entry;

  if (!referent) return;

  if ((entry = weak_entry_for_referent(weak_table, referent))) {
    remove_referrer(entry, referrer);
    bool empty = true;
    if (entry->out_of_line && entry->num_refs != 0) {
      empty = false;
    }
    else {
      for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
        if (entry->inline_referrers[i]) {
          empty = false;
          break;
        }
      }
    }

    if (empty) {
      weak_entry_remove(weak_table, entry);
    }
  }

  // Do not set *referrer = nil. objc_storeWeak() requires that the
  // value not change.
}

该方法主要作用是将旧对象在 weak_table 中接触 weak 指针的对应绑定。根据函数名,称之为解除注册操作。

来看看这个函数的逻辑。首先参数是 weak_table_t 表,键和值。声明 weak_entry_t 变量,如果key,也就是引用计数对象为空,直接返回。根据全局入口表和键获取对应的 weak_entry_t 对象,也就是 weak 表记录。获取到记录后,将记录表以及 weak 对象作为参数传入 remove_referrer 函数中,这个函数就是解除操作。然后判断这个 weak 记录是否为空,如果为空,从全局记录表中清除相应的引用计数对象的 weak 记录表。

接下来,我们了解一下,如何获取这个 weak_entry_t 这个变量。

static weak_entry_t *weak_entry_for_referent(weak_table_t *weak_table, objc_object *referent)
{
  assert(referent);

  weak_entry_t *weak_entries = weak_table->weak_entries;

  if (!weak_entries) return nil;

  size_t index = hash_pointer(referent) & weak_table->mask;
  size_t hash_displacement = 0;
  while (weak_table->weak_entries[index].referent != referent) {
    index = (index+1) & weak_table->mask;
    hash_displacement++;
    if (hash_displacement > weak_table->max_hash_displacement) {
      return nil;
    }
  }

  return &weak_table->weak_entries[index];
}

这个函数的逻辑就是先获取全局 weak 表入口,然后将引用计数对象的地址进行 hash 化后与 weak_table->mask 做与操作,作为下标,在全局 weak 表中查找,若找到,返回这个对象的 weak 记录表,若没有,返回nil。

再来了解一下解除对象的函数:

static void remove_referrer(weak_entry_t *entry, objc_object **old_referrer)
{
  if (! entry->out_of_line) {
    for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
      if (entry->inline_referrers[i] == old_referrer) {
        entry->inline_referrers[i] = nil;
        return;
      }
    }
    _objc_inform("Attempted to unregister unknown __weak variable "
           "at %p. This is probably incorrect use of "
           "objc_storeWeak() and objc_loadWeak(). "
           "Break on objc_weak_error to debug.\n",
           old_referrer);
    objc_weak_error();
    return;
  }

  size_t index = w_hash_pointer(old_referrer) & (entry->mask);
  size_t hash_displacement = 0;
  while (entry->referrers[index] != old_referrer) {
    index = (index+1) & entry->mask;
    hash_displacement++;
    if (hash_displacement > entry->max_hash_displacement) {
      _objc_inform("Attempted to unregister unknown __weak variable "
             "at %p. This is probably incorrect use of "
             "objc_storeWeak() and objc_loadWeak(). "
             "Break on objc_weak_error to debug.\n",
             old_referrer);
      objc_weak_error();
      return;
    }
  }
  entry->referrers[index] = nil;
  entry->num_refs--;
}

这个函数传入的是 weak 对象,当 out_of_line 为0 时,遍历数组,找到对应的对象,置nil,如果未找到,报错并返回。当 out_of_line 不为0时,根据对象的地址 hash 化并和 mask 做与操作作为下标,查找相应的对象,若没有,报错并返回,若有,相应的置为 nil,并减少元素数量,即 num_refs 减 1。

新对象添加注册操作 weak_register_no_lock

id weak_register_no_lock(weak_table_t *weak_table, id referent_id,
           id *referrer_id, bool crashIfDeallocating)
{
  objc_object *referent = (objc_object *)referent_id;
  objc_object **referrer = (objc_object **)referrer_id;

  if (!referent || referent->isTaggedPointer()) return referent_id;

  // ensure that the referenced object is viable
  bool deallocating;
  if (!referent->ISA()->hasCustomRR()) {
    deallocating = referent->rootIsDeallocating();
  }
  else {
    BOOL (*allowsWeakReference)(objc_object *, SEL) =
      (BOOL(*)(objc_object *, SEL))
      object_getMethodImplementation((id)referent,
                      SEL_allowsWeakReference);
    if ((IMP)allowsWeakReference == _objc_msgForward) {
      return nil;
    }
    deallocating =
      ! (*allowsWeakReference)(referent, SEL_allowsWeakReference);
  }

  if (deallocating) {
    if (crashIfDeallocating) {
      _objc_fatal("Cannot form weak reference to instance (%p) of "
            "class %s. It is possible that this object was "
            "over-released, or is in the process of deallocation.",
            (void*)referent, object_getClassName((id)referent));
    } else {
      return nil;
    }
  }

  // now remember it and where it is being stored
  weak_entry_t *entry;
  if ((entry = weak_entry_for_referent(weak_table, referent))) {
    append_referrer(entry, referrer);
  }
  else {
    weak_entry_t new_entry;
    new_entry.referent = referent;
    new_entry.out_of_line = 0;
    new_entry.inline_referrers[0] = referrer;
    for (size_t i = 1; i < WEAK_INLINE_COUNT; i++) {
      new_entry.inline_referrers[i] = nil;
    }

    weak_grow_maybe(weak_table);
    weak_entry_insert(weak_table, &new_entry);
  }

  // Do not set *referrer. objc_storeWeak() requires that the
  // value not change.

  return referent_id;
}

一大堆 if-else, 主要是为了判断该对象是不是 taggedPoint 以及是否正在调用 dealloca 等。下面操作开始,同样是先获取 weak 表记录,如果获取到,则调用 append_referrer 插入对象,若没有,则新建一个 weak 表记录,默认为 out_of_line,然后将新对象放到 0 下标位置,其他位置置为 nil 。下面两个函数 weak_grow_maybe 是用来判断是否需要重申请内存重 hash,weak_entry_insert 函数是用来将新建的 weak 表记录插入到全局 weak 表中。插入时同样是以对象地址的 hash 化和 mask 值相与作为下标来记录的。

接下来看看 append_referrer 函数,源代码如下:

static void append_referrer(weak_entry_t *entry, objc_object **new_referrer)
{
  if (! entry->out_of_line) {
    // Try to insert inline.
    for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
      if (entry->inline_referrers[i] == nil) {
        entry->inline_referrers[i] = new_referrer;
        return;
      }
    }

    // Couldn't insert inline. Allocate out of line.
    weak_referrer_t *new_referrers = (weak_referrer_t *)
      calloc(WEAK_INLINE_COUNT, sizeof(weak_referrer_t));
    // This constructed table is invalid, but grow_refs_and_insert
    // will fix it and rehash it.
    for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
      new_referrers[i] = entry->inline_referrers[I];
    }
    entry->referrers = new_referrers;
    entry->num_refs = WEAK_INLINE_COUNT;
    entry->out_of_line = 1;
    entry->mask = WEAK_INLINE_COUNT-1;
    entry->max_hash_displacement = 0;
  }

  assert(entry->out_of_line);

  if (entry->num_refs >= TABLE_SIZE(entry) * 3/4) {
    return grow_refs_and_insert(entry, new_referrer);
  }
  size_t index = w_hash_pointer(new_referrer) & (entry->mask);
  size_t hash_displacement = 0;
  while (entry->referrers[index] != NULL) {
    index = (index+1) & entry->mask;
    hash_displacement++;
  }
  if (hash_displacement > entry->max_hash_displacement) {
    entry->max_hash_displacement = hash_displacement;
  }
  weak_referrer_t &ref = entry->referrers[index];
  ref = new_referrer;
  entry->num_refs++;
}

当 out_of_line 为 0,并且静态数组里面还有位置存放,那么直接存放并返回。如果没有位置存放,则升级为动态数组,并加入。如果 out_of_line 不为 0,先判断是否需要扩容,然后同样的,使用对象地址的 hash 化和 mask 做与操作作为下标,找到相应的位置并插入。

对象的销毁以及 weak 的置 nil 实现

释放时,调用clearDeallocating函数。clearDeallocating 函数首先根据对象地址获取所有weak指针地址的数组,然后遍历这个数组把其中的数据设为nil,最后把这个entry从weak表中删除,最后清理对象的记录。

当weak引用指向的对象被释放时,又是如何去处理weak指针的呢?当释放对象时,其基本流程如下:

  1. 调用 objc_release
  2. 因为对象的引用计数为0,所以执行dealloc
  3. 在dealloc 中,调用了_objc_rootDealloc 函数
  4. 在 _objc_rootDealloc 中,调用了 objec_dispose 函数
  5. 调用objc_destructInstance
  6. 最后调用 objc_clear_deallocating

objc_clear_deallocating的具体实现如下:

void objc_clear_deallocating(id obj)
{
  assert(obj);
  assert(!UseGC);

  if (obj->isTaggedPointer()) return;
  obj->clearDeallocating();
}

这个函数只是做一些判断以及更深层次的函数调用,

void objc_object::sidetable_clearDeallocating()
{
  SideTable& table = SideTables()[this];

  // clear any weak table items
  // clear extra retain count and deallocating bit
  // (fixme warn or abort if extra retain count == 0 ?)
  table.lock();
  // 迭代器
  RefcountMap::iterator it = table.refcnts.find(this);
  if (it != table.refcnts.end()) {
    if (it->second & SIDE_TABLE_WEAKLY_REFERENCED) {
      weak_clear_no_lock(&table.weak_table, (id)this);
    }
    table.refcnts.erase(it);
  }
  table.unlock();
}

我们可以看到,在这个函数中,首先取出对象对应的SideTable实例,如果这个对象有关联的弱引用,则调用weak_clear_no_lock来清除对象的弱引用信息,我们在来深入一下,

void weak_clear_no_lock(weak_table_t *weak_table, id referent_id)
{
  objc_object *referent = (objc_object *)referent_id;

  weak_entry_t *entry = weak_entry_for_referent(weak_table, referent);
  if (entry == nil) {
    /// XXX shouldn't happen, but does with mismatched CF/objc
    //printf("XXX no entry for clear deallocating %p\n", referent);
    return;
  }

  // zero out references
  weak_referrer_t *referrers;
  size_t count;

  if (entry->out_of_line) {
    referrers = entry->referrers;
    count = TABLE_SIZE(entry);
  }
  else {
    referrers = entry->inline_referrers;
    count = WEAK_INLINE_COUNT;
  }

  for (size_t i = 0; i < count; ++i) {
    objc_object **referrer = referrers[I];
    if (referrer) {
      if (*referrer == referent) {
        *referrer = nil;
      }
      else if (*referrer) {
        _objc_inform("__weak variable at %p holds %p instead of %p. "
               "This is probably incorrect use of "
               "objc_storeWeak() and objc_loadWeak(). "
               "Break on objc_weak_error to debug.\n",
               referrer, (void*)*referrer, (void*)referent);
        objc_weak_error();
      }
    }
  }

  weak_entry_remove(weak_table, entry);
}

这个函数根据 out_of_line 的值,取得对应的记录表,然后根据引用计数对象,将相应的 weak 对象置 nil。最后清除相应的记录表。

通过上面的描述,我们基本能了解一个weak引用从生到死的过程。从这个流程可以看出,一个weak引用的处理涉及各种查表、添加与删除操作,还是有一定消耗的。所以如果大量使用__weak变量的话,会对性能造成一定的影响。那么,我们应该在什么时候去使用weak呢?《Objective-C高级编程》给我们的建议是只在避免循环引用的时候使用__weak修饰符。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • 一篇文章让你看懂IOS中的block为何再也不需要WeakSelf弱引用

    前言: 最近都在折腾Sagit架框的内存释放的问题,所以对这一块有些心得. 对于新手,学到的文章都在教你用:typeof(self) __weak weakSelf = self. 对于老手,可能早习惯了到处了WeakSelf了. 这次,就来学学,如何不用WeakSelf. 1:从引用计数器开始: 这里先设计一个TableBlock类: @interface BlockTable : NSObject typedef void (^AddCellBlock)(); @property (nona

  • IOS中(assign,retain,copy,weak,strong)的区别以及nonatomic的含义

    IOS中(assign,retain,copy,weak,strong)的区别以及nonatomic的含义 我们在声明@property 属性时,总是要在括号中写上assign.retain.copy.weak.strong中的一个,很多时候,我们仅仅只是按照习惯去写经常写的那一个,但有时候看代码时又会发现别人用的不尽相同,那这些之间的区别是什么呢? 首先,上面五个并不是在一个层面上的,可以分为两部分,第一部分是assign.retain.copy,第二部分是weak.strong. 我们先说第

  • iOS 中weak的实现代码示例

    只要学过 iOS 的人,都会对 strong.weak.copy等关键字应该都会很熟悉.weak 属性关键字就是弱引用,它不会增加引用计数但却能保证指针的安全访问,在对象释放后置为 nil,从而避免错误的内存访问.主要为了解决循环引用的问题. 接下来,我们会从 objc 库中的 NSObject.mm. objc-weak.h 以及 objc-weak.mm 文件出发,去具体了解 weak 的实现过程. weak 的内部结构 Runtime 维护了一个weak表,用于存储指向某个对象的所有wea

  • Java编程GUI中的事件绑定代码示例

    程序绑定的概念: 绑定指的是一个方法的调用与方法所在的类(方法主体)关联起来.对java来说,绑定分为静态绑定和动态绑定:或者叫做前期绑定和后期绑定 静态绑定: 在程序执行前方法已经被绑定,此时由编译器或其它连接程序实现.例如:C. 针对java简单的可以理解为程序编译期的绑定:这里特别说明一点,java当中的方法只有final,static,private和构造方法是前期绑定 动态绑定 后期绑定:在运行时根据具体对象的类型进行绑定. 若一种语言实现了后期绑定,同时必须提供一些机制,可在运行期间

  • java集合中list的用法代码示例

    List接口是Collection接口的子接口,List有一个重要的实现类--ArrayList类,List中的元素是有序排列的而且可重复,所以被称为是序列. List可以精确的控制每个元素的插入位置,或删除某个位置元素,它的实现类ArrayList底层是由数组实现的. List中有增删改查的方法,我们可以通过例子演示: 我们通过对学生选课,来演示List中对课程增删改查的方法 /** * 课程类 * @author lenovo * */ public class KeCheng { publ

  • Python中join函数简单代码示例

    本文简述的是string.join(words[, sep]),它的功能是把字符串或者列表,元组等的元素给拼接起来,返回一个字符串,和split()函数与正好相反,看下面的代码理解. 首先展示下结果吧! 代码分享: a=["豫","N","C8","C89"] b=("豫","N","C8","C89") c="zhang" a

  • Java中filter用法完整代码示例

    本文研究的主要是Java中filter过滤器的相关用法,具体实现代码如下. filter过滤器主要使用于前台向后台传递数据是的过滤操作.程度很简单就不说明了,直接给几个已经写好的代码: 一.使浏览器不缓存页面的过滤器 import javax.servlet.*; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * 用于的使 Browser 不缓存页面的过滤器 */ public cla

  • shell命令while循环中使用sleep命令代码示例

    本文继续练习while循环语句,这里要介绍一个sleep的命令.如果写过自动化脚本,特别是selenium的同学,肯定很熟悉这个sleep.没错,sleep就是暂停的意思,例如sleep 1,表示暂停一秒,看下面练习. 1. 从1到10,每隔一秒打印一个数字 #! /bin/bash # while loops n=1 while (( $n <= 10 )) do echo $n (( n++ )) sleep 1 done 自己运行下,看下效果. 2. 实现一秒开一个终端 我们先要获取终端的

  • Android数据传输中的参数加密代码示例

    Android开发中,难免会遇到需要加解密一些数据内容存到本地文件.或者通过网络传输到其他服务器和设备的问题,但并不是使用了加密就绝对安全了,如果加密函数使用不正确,加密数据很容易受到逆向破解攻击.还有很多开发者没有意识到的加密算法的问题. 1.数据传输 1).http请求中,最常用的方法有两种:get和post:一般post请求适合做提交,而get请求适合做请求数据 2).数据的加密,大概有三种常用的:AES,DES,Base64 2.Base64加密 这里使用的aes加密,然后再将字符串使用

  • iOS中使用对象的弱引用示例代码

    简介 我们都知道使用 UIImage imageNamed 创建的 UIImage 对象会被持有(强引用),如果图片太大会占用内存,损耗 APP 的性能,影响用户体验,如果能改造对其的强引用变为弱引用就可以解决问题. 我们可能会有类似上面的场景,有些对象暂时保存起来,可能后面会用到,也有可能不会使用,但是又不想去管理它们的生命周期,如果它们能够自己被销毁就很省事,不需要去关心这些对象到底耗费了多少内存. 今天跟大家聊聊如何在 iOS 开发中保持对对象的弱引用而不是强引用,希望看完之后,能帮助到大

  • iOS中valueForKeyPath的常用方法法示例

    前言 可能很多人对-(nullableid)valueForKeyPath:(NSString*)keyPath;这个方法不是很理解,实际上,这可以扩展到KVC中的高级用法上去.该方法非常强大.下面就来给大家详细介绍iOS中valueForKeyPath的常用方法,话不多说了,来一起看看详细的介绍吧 valueForKeyPath的常用方法 1.valueForKeyPath可以获取数组中的最小值.最大值.平均值.求和.代码如下: NSArray *array = @[@10, @23, @43

  • iOS中捕获日志与异常示例详解

    前言 在平时自己调试的时候,可以直接连接电脑,直接在窗口中查看结果.但是在测试人员测试,或者灰度测试的时候,怎么才能拿到日志呢?最先想到的肯定是输出到本地文件,然后在需要的时候进行上传. 分享一段之前找到的方法,下面的代码提供了两个主要功能: – 把日志输出到文件中 – 捕捉异常信息 [解析都写在注释中了] 示例代码 - (void)redirectNSLogToDocumentFolder { //如果已经连接Xcode调试则不输出到文件 //该函数用于检测输出 (STDOUT_FILENO)

随机推荐