逐步探究ObjC的Weak技术底层
前言
之前的文章有说过 Atomic
原子操作的原理,其作为一个特殊的修饰前缀,影响了存取操作。
在属性修饰定义中,还有另一类修饰前缀,他们分别是 strong
weak
assign
copy
,这些又有什么区别呢?
平时喜欢探究的同学,可能也见过 unsafe_unretained
,这个又是什么呢?
让我们从属性修饰入手,逐步揭开弱引用的面纱。
原理
属性自动生成的实现方法是怎么样的?
首先我们先创建一个示例代码文件作为样本。
#import <Foundation/Foundation.h>
@interface PropertyObject : NSObject
@property (nonatomic, strong) NSObject *pStrongObj; //强引用
@property (nonatomic, copy) NSObject *pCopyObj; //拷贝
@property (nonatomic, weak) NSObject *pWeakObj; //弱引用
@property (nonatomic, assign) NSObject *pAssignObj; //申明
@property (nonatomic, unsafe_unretained) NSObject *pUnretainedObj; //非持有
@end
@implementation PropertyObject
@end
然后通过 clang -rewrite-objc -fobjc-arc -stdlib=libc++ -mmacosx-version-min=10.14 -fobjc-runtime=macosx-10.14 -Wno-deprecated-declarations main.m
命令将其解释成 c++
代码。(注意这里要指定版本,不然weak属性不能翻译)
展开的代码比较多,我这里截取关键部分探讨。
struct PropertyObject_IMPL {
NSObject *__strong _pStrongObj;
NSObject *__strong _pCopyObj;
NSObject *__weak _pWeakObj;
NSObject *__unsafe_unretained _pAssignObj;
NSObject *__unsafe_unretained _pUnretainedObj;
};
{"pStrongObj","T@\"NSObject\",&,N,V_pStrongObj"},
{"pCopyObj","T@\"NSObject\",C,N,V_pCopyObj"},
{"pWeakObj","T@\"NSObject\",W,N,V_pWeakObj"},
{"pAssignObj","T@\"NSObject\",N,V_pAssignObj"},
{"pUnretainedObj","T@\"NSObject\",N,V_pUnretainedObj"}
从变量结构体的描述和特性可以看出,strong
和copy
实际都是__strong
修饰,但特性不同,assign
和unsafe_unretained
则完全一致,都是__unsafe_unretained
,weak
则单独使用__weak
修饰。
下面我们来看一下方法具体实现。
// @implementation PropertyObject
//根据偏移取值和赋值
static NSObject * _I_PropertyObject_pStrongObj(PropertyObject * self, SEL _cmd) { return (*(NSObject *__strong *)((char *)self + OBJC_IVAR_$_PropertyObject$_pStrongObj)); }
static void _I_PropertyObject_setPStrongObj_(PropertyObject * self, SEL _cmd, NSObject *pStrongObj) { (*(NSObject *__strong *)((char *)self + OBJC_IVAR_$_PropertyObject$_pStrongObj)) = pStrongObj; }
static NSObject * _I_PropertyObject_pCopyObj(PropertyObject * self, SEL _cmd) { return (*(NSObject *__strong *)((char *)self + OBJC_IVAR_$_PropertyObject$_pCopyObj)); }
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);
//只有Copy不同,setter的实现是objc_setProperty
static void _I_PropertyObject_setPCopyObj_(PropertyObject * self, SEL _cmd, NSObject *pCopyObj) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct PropertyObject, _pCopyObj), (id)pCopyObj, 0, 1); }
static NSObject * _I_PropertyObject_pWeakObj(PropertyObject * self, SEL _cmd) { return (*(NSObject *__weak *)((char *)self + OBJC_IVAR_$_PropertyObject$_pWeakObj)); }
static void _I_PropertyObject_setPWeakObj_(PropertyObject * self, SEL _cmd, NSObject *pWeakObj) { (*(NSObject *__weak *)((char *)self + OBJC_IVAR_$_PropertyObject$_pWeakObj)) = pWeakObj; }
static NSObject * _I_PropertyObject_pAssignObj(PropertyObject * self, SEL _cmd) { return (*(NSObject *__unsafe_unretained *)((char *)self + OBJC_IVAR_$_PropertyObject$_pAssignObj)); }
static void _I_PropertyObject_setPAssignObj_(PropertyObject * self, SEL _cmd, NSObject *pAssignObj) { (*(NSObject *__unsafe_unretained *)((char *)self + OBJC_IVAR_$_PropertyObject$_pAssignObj)) = pAssignObj; }
static NSObject * _I_PropertyObject_pUnretainedObj(PropertyObject * self, SEL _cmd) { return (*(NSObject *__unsafe_unretained *)((char *)self + OBJC_IVAR_$_PropertyObject$_pUnretainedObj)); }
static void _I_PropertyObject_setPUnretainedObj_(PropertyObject * self, SEL _cmd, NSObject *pUnretainedObj) { (*(NSObject *__unsafe_unretained *)((char *)self + OBJC_IVAR_$_PropertyObject$_pUnretainedObj)) = pUnretainedObj; }
// @end
在代码中,只有copy
修饰属性的setter
方法使用了objc_setProperty
,其他几种都是根据 self + 偏移量
的方式计算出内存地址直接进行存取。
那问题来了,如果真的是那么简单的话,arc
是怎么实现根据不同修饰从而进行内存管理的呢?
原来通过 clang -rewrite-objc
的代码只是翻译成 c++
语言,在之后的编译过程中会进一步处理。
接着使用 clang -S -fobjc-arc -emit-llvm main.m -o main.ll
命令生成中间码。
(中间码显示比较杂乱,我根据自己理解整理成简洁版)
//代码整理后
id [PropertyObject pStrongObj] {
return *location;
}
void [PropertyObject setPStrongObj:](self, _cmd, obj) {
@llvm.objc.storeStrong(*location, obj)
}
id [PropertyObject pCopyObj] {
return @objc_getProperty(self, _cmd, offset, atomic)
}
void [PropertyObject setPCopyObj:](self, _cmd, obj) {
@objc_setProperty_nonatomic_copy(self, _cmd, obj, offset)
}
id [PropertyObject pWeakObj] {
id obj = @llvm.objc.loadWeakRetained(*location)
return @llvm.objc.autoreleaseReturnValue(obj)
}
void [PropertyObject setPWeakObj:](self, _cmd, obj) {
@llvm.objc.storeWeak(*location, obj)
}
id [PropertyObject pAssignObj] {
return *location
}
void [PropertyObject setPAssignObj:](self, _cmd, obj) {
*location = obj
}
id [PropertyObject pUnretainedObj] {
return *location
}
void [PropertyObject setPUnretainedObj:](self, _cmd, obj) {
*location = obj
}
可以看出分别针对strong
和 weak
都做了处理,而assign
和 unsafe_unretained
则不做内存管理直接返回,这也说明这两者的处理方式是一样的,区别在于 assign
针对。
strong | copy | weak | assign | unsafe_unretained | |
---|---|---|---|---|---|
Ownership | __strong | __strong | __weak | __unsafe_unretained | __unsafe_unretained |
Getter | *location | objc_getProperty | loadWeakRetained | *location | *location |
Setter | storeStrong | objc_setProperty | storeWeak | *location | *location |
对象 | NSObject | NSObject | NSObject | NSObject | Scalar |
Weak对象怎么实现存取的?
本文篇幅有限,暂不介绍 storeStrong
和 objc_setProperty_nonatomic_copy
,主要介绍 weak
相关操作。
打开 objc4-750
开源代码,翻到 NSObject.mm
,我们来一探究竟。
// 初始化弱引用
id objc_initWeak(id *location, id newObj) {
// 不存在则不保存
if (!newObj) {
*location = nil;
return nil;
}
return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
(location, (objc_object*)newObj);
}
// 销毁弱引用
void objc_destroyWeak(id *location) {
(void)storeWeak<DoHaveOld, DontHaveNew, DontCrashIfDeallocating>
(location, nil);
}
// 交换原有的值
id objc_storeWeak(id *location, id newObj) {
return storeWeak<DoHaveOld, DoHaveNew, DoCrashIfDeallocating>
(location, (objc_object *)newObj);
}
可以看到 runtime
中调用的都是一个方法,区别在于使用了不同的模版,那么我们来看下对一个地址的存取方法。
// 获取操作的具体实现
id objc_loadWeakRetained(id *location) {
id obj;
id result;
Class cls;
SideTable *table;
retry:
// 保证地址有数据且不是伪指针
obj = *location;
if (!obj) return nil;
if (obj->isTaggedPointer()) return obj;
// 根据地址取出对应的表
table = &SideTables()[obj];
// 加锁
table->lock();
// 如果数据被其他线程改变,则重试
if (*location != obj) {
table->unlock();
goto retry;
}
result = obj;
cls = obj->ISA();
if (! cls->hasCustomRR()) {
// 如果使用的是系统默认的内存管理,则保证了已经初始化
// 所以可以直接rootTryRetain
assert(cls->isInitialized());
if (! obj->rootTryRetain()) {
result = nil;
}
} else {
// 如果不是默认的,则需要确保在初始化线程上执行自定义retain操作
if (cls->isInitialized() || _thisThreadIsInitializingClass(cls)) {
BOOL (*tryRetain)(id, SEL) = (BOOL(*)(id, SEL))
class_getMethodImplementation(cls, SEL_retainWeakReference);
if ((IMP)tryRetain == _objc_msgForward) {
result = nil;
} else if (! (*tryRetain)(obj, SEL_retainWeakReference)) {
result = nil;
}
} else {
table->unlock();
_class_initialize(cls);
goto retry;
}
}
//完成后解锁
table->unlock();
return result;
}
// 保存操作的具体实现
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;
// 由于有锁的机制,如果在期间值被改变了,则重试,直到成功
retry:
if (haveOld) {
oldObj = *location;
oldTable = &SideTables()[oldObj]; // 根据内存地址获取表
} else {
oldTable = nil;
}
if (haveNew) {
newTable = &SideTables()[newObj];
} else {
newTable = nil;
}
// 锁住这两张表,注意如果是同一张表也没关系,有对锁做判断
SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable);
// 检查如果已经改变了,则重试
if (haveOld && *location != oldObj) {
SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
goto retry;
}
// 检查新对象类有没有初始化完,没有则重试
if (haveNew && newObj) {
Class cls = newObj->getIsa();
if (cls != previouslyInitializedClass &&
!((objc_class *)cls)->isInitialized()) {
SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
_class_initialize(_class_getNonMetaClass(cls, (id)newObj));
// 如果正在初始化,则让下一次绕过这个判断继续运行
previouslyInitializedClass = cls;
goto retry;
}
}
// 清除之前保存的弱引用数据
if (haveOld) {
weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
}
// 保存新的弱引用数据
if (haveNew) {
newObj = (objc_object *)
weak_register_no_lock(&newTable->weak_table, (id)newObj, location,
crashIfDeallocating);
// 保存成功就记录到对象指针中,这样可以在释放时检查
if (newObj && !newObj->isTaggedPointer()) {
newObj->setWeaklyReferenced_nolock();
}
// 保存到对应位置
*location = (id)newObj;
}
// 操作成功后解锁
SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
// 返回最终数据
return (id)newObj;
}
除去保护方法,其实 objc_loadWeakRetained
方法就是检查后返回 *location
,也就是变量指向的实际地址。
而 storeWeak
方法则是根据模版,对旧对象执行 weak_unregister_no_lock
,对新对象执行 weak_register_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.
// 上面为苹果注释,看这意思应该是objc_storeWeak还需要使用引用地址做后续处理。
}
//注册引用
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; //引用人
// taggedPointer没有引用计数,不需要处理
if (!referent || referent->isTaggedPointer()) return referent_id;
// 保证被引用人不在释放中,不然闪退
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;
}
}
//获取被引用人的引用数组,没有则创建
weak_entry_t *entry;
if ((entry = weak_entry_for_referent(weak_table, referent))) {
append_referrer(entry, referrer);
} else {
weak_entry_t new_entry(referent, referrer);
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;
}
//释放过程清空引用
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) {
//这里应该肯定有entry,因为调用前判断了对象的WeaklyReferenced
//如果确实没有,苹果认为可能是CF/objc原因
return;
}
//清空引用数组
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);
}
可以发现,对申明是 __weak
的变量进行存取操作,其实都是通过被操作的对象地址查找到相应的表,然后增删表的引用数组内容。
SideTable表怎么设计的?
关键就在于怎么申明创建表,以及这个表是怎么设计及使用的。
// SideTables 类型申明
// 这里之所以先使用数据的方式申明是因为考虑到加载顺序的问题
alignas(StripedMap<SideTable>) static uint8_t
SideTableBuf[sizeof(StripedMap<SideTable>)];
// 加载image时执行初始化
static void SideTableInit() {
new (SideTableBuf) StripedMap<SideTable>();
}
// 数组还原成StripedMap类型
static StripedMap<SideTable>& SideTables() {
return *reinterpret_cast<StripedMap<SideTable>*>(SideTableBuf);
}
// StripedMap 的结构
enum { CacheLineSize = 64 };
template<typename T>
class StripedMap {
#if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR
enum { StripeCount = 8 };
#else
enum { StripeCount = 64 };
#endif
// 64位对齐
struct PaddedT {
T value alignas(CacheLineSize);
};
// 手机系统数组个数为8
PaddedT array[StripeCount];
// 把指针地址匹配到数组的序号
static unsigned int indexForPointer(const void *p) {
uintptr_t addr = reinterpret_cast<uintptr_t>(p);
return ((addr >> 4) ^ (addr >> 9)) % StripeCount;
}
}
在加载镜像的过程中,通过 SideTableInit
方法创建全局表数组,可以看到手机系统是8个数组。
源码中使用 &SideTables()[obj]
的方式,其实就是把 obj
的指针地址转成序号获取某一个 table
,通过这种方式分散冗余。
接着我们看 SideTable
类的内部结构。
// 哈希散列表,使用补码的形式把指针地址作为Key,保存引用计数
typedef objc::DenseMap<DisguisedPtr<objc_object>,size_t,true> RefcountMap;
// Template parameters.
enum HaveOld { DontHaveOld = false, DoHaveOld = true };
enum HaveNew { DontHaveNew = false, DoHaveNew = true };
struct SideTable {
spinlock_t slock; // 自旋锁
RefcountMap refcnts; // 引用记数表
weak_table_t weak_table;// 弱引用表
template<HaveOld, HaveNew>
static void lockTwo(SideTable *lock1, SideTable *lock2);
template<HaveOld, HaveNew>
static void unlockTwo(SideTable *lock1, SideTable *lock2);
};
struct weak_table_t {
weak_entry_t *weak_entries; //弱引用数组
size_t num_entries; //数组个数
uintptr_t mask; //计算辅助量,数值为数组总数-1
uintptr_t max_hash_displacement;//哈希最大偏移量
};
#if __LP64__
#define PTR_MINUS_2 62
#else
#define PTR_MINUS_2 30
#endif
typedef DisguisedPtr<objc_object *> weak_referrer_t;
struct weak_entry_t {
// 被引用者
DisguisedPtr<objc_object> referent;
union {
// 引用者数据结构
struct {
// 当数量超过4个时,结构转为指针,每次容量满的时候就扩容两倍
// 需要与数组作区分,所以有out_of_line_ness标记
weak_referrer_t *referrers;
uintptr_t out_of_line_ness : 2;
uintptr_t num_refs : PTR_MINUS_2;
uintptr_t mask;
uintptr_t max_hash_displacement;
};
struct {
// 四个数组
weak_referrer_t inline_referrers[4];
};
};
};
SideTable
存储的不仅有对象引用计数表,还有我们关注的弱引用表,其结构顺序如下:
SideTable->weak_table_t->weak_entry_t->weak_referrer_t
为了方便理解,我模拟一下找弱引用对象的步骤:
sideTable = &SideTables()[referent]
把对象内存地址按照8取余后找到表weakTable = &sideTable->weak_table
取出弱引用表entry = weak_entry_for_referent(weakTable, referent)
根据被引用人地址,遍历弱引用表找出入口referrer = entry->referrers[index]
入口有特殊的数组,其中保存了所有弱引用者的对象地址
仔细一点的同学应该发现了 weak_entry_t
中有一个联合体,这又是怎么操作实现的呢?
// 添加新引用者
static void append_referrer
(weak_entry_t *entry, objc_object **new_referrer) {
// 没有超过4个,就用内敛数组
if (! entry->out_of_line()) {
// 遍历数组,如果有空位置,则插入后返回
for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
if (entry->inline_referrers[i] == nil) {
entry->inline_referrers[i] = new_referrer;
return;
}
}
// 如果超过4个了,就从数组结构转成指针结构
weak_referrer_t *new_referrers = (weak_referrer_t *)
calloc(WEAK_INLINE_COUNT, sizeof(weak_referrer_t));
// 拷贝原数据到指针指向的内容
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_ness = REFERRERS_OUT_OF_LINE; //是否是指针的标记位
entry->mask = WEAK_INLINE_COUNT-1; //数组最大下标,用于取余
entry->max_hash_displacement = 0; //最大hash移位次数,用于优化循环
// 由于只有4个,会在下个判断后执行grow_refs_and_insert初始化并插入新对象
}
// 断言必然是指针结构
assert(entry->out_of_line());
// 如果指针数量超过3/4,就容量翻倍后再插入
if (entry->num_refs >= TABLE_SIZE(entry) * 3/4) {
return grow_refs_and_insert(entry, new_referrer);
}
size_t begin = w_hash_pointer(new_referrer) & (entry->mask);
size_t index = begin;
size_t hash_displacement = 0;
//找一个空位置,不够就从头找
while (entry->referrers[index] != nil) {
hash_displacement++;
index = (index+1) & entry->mask; //下标+1后取余
if (index == begin) bad_weak_table(entry);
}
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++;
}
总结
至此对于弱引用的整体结构和逻辑都清楚了,对象根据修饰符进行内存管理,如果是弱引用,则找到其引用地址的引用表操作。
反过来讲,强对象被引用时在全局引用表中注册一个节点,保存所有引用者的地址,当释放时设置所有地址为空。
问答
被weak修饰的对象在被释放的时候会发生什么?是如何实现的?知道sideTable么?里面的结构可以画出来么?
对象被释放时执行 obj->rootDealloc()
,如果有弱引用标记,则会执行 objc_destructInstance
方法后释放。
void *objc_destructInstance(id obj) {
if (obj) {
// Read all of the flags at once for performance.
bool cxx = obj->hasCxxDtor();
bool assoc = obj->hasAssociatedObjects();
// This order is important.
if (cxx) object_cxxDestruct(obj); //调用析构函数
if (assoc) _object_remove_assocations(obj); //移除关联对象关系
obj->clearDeallocating(); //处理isa
}
return obj;
}
inline void objc_object::clearDeallocating() {
if (slowpath(!isa.nonpointer)) {
// Slow path for raw pointer isa.
sidetable_clearDeallocating();
} else if (slowpath(isa.weakly_referenced || isa.has_sidetable_rc)) {
// Slow path for non-pointer isa with weak refs and/or side table data.
clearDeallocating_slow();
}
assert(!sidetable_present());
}
void objc_object::sidetable_clearDeallocating() {
SideTable& table = SideTables()[this];
// 删除强引用和弱引用
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_clearDeallocating
方法中,最后执行了 weak_clear_no_lock
清空了所有引用关系。
SideTable
表结构如下图:
总结
weak原理是绕不开的经典课题,通过阅读开源代码对苹果如何实现有了大致的了解,受益匪浅。
阅读过程中还惊叹于苹果各种花式小技巧,由于文章篇幅有限没来得及介绍,感兴趣可以了解一下,比如 DisguisedPtr
。
资料分享
Objective-C Class Ivar Layout 探索
逐步探究ObjC的Weak技术底层的更多相关文章
- Docker技术底层架构剖析
[Docker 底层技术] docker底层的 2 个核心技术分别是 Namespaces 和 Control groups 在操作系统中,网络配置,进程,用户,IPC(进程之间的调用)等信息之间的 ...
- js(三) ajax异步局部刷新技术底层代码实现
ajax 异步 javaScript and xml 开发五步骤: 1. 创建对象 XMLHttpRequest(chrome,firefox) ie... jquery 2. 找到连接, http的 ...
- html5 app开发重大消息-腾讯在技术端推进Html5生态发展
中新网5月3日电 日前,腾讯正式发布腾讯浏览服务(Tencent Browser Service,以下简称TBS),宣布为合作伙伴提供整合腾讯底层技术.内容框架.广告体系以及大数据等多方面能力的升级浏 ...
- 写给Android App开发人员看的Android底层知识(1)
这个系列的文章一共8篇,我酝酿了很多年,参考了很多资源,查看了很多源码,直到今天把它写出来,也是战战兢兢,生怕什么地方写错了,贻笑大方. (一)引言 早在我还是Android菜鸟的时候,有很多技术我都 ...
- Objective-C weak深入理解
1.weak是弱引用,所引用的对象计数不会加1. 2.weak变量在其引用的对象被销毁之后,会被置为nil. 3.weak通常用于block, delegate, NSTimer,以解决循环引用带来的 ...
- AOP技术分析
AOP的概述(http://www.cnblogs.com/lxp503238/p/6837653.html) 1. 什么是AOP的技术? * 在软件业,AOP为Aspec ...
- Spring框架的AOP的底层实现
1. Srping框架的AOP技术底层也是采用的代理技术,代理的方式提供了两种 1. 基于JDK的动态代理 * 必须是面向接口的,只有实现了具体接口的类才能生成代理对象 2. 基于CGLIB动态代理 ...
- Spring 框架的核心功能之AOP技术
1. AOP 的概述 AOP, Aspect Oriented Programming, 面向切面编程; 通过预编译方式和运行期动态代理实现程序功能的统一维护的技术; AOP 采取横向抽取机制,取代了 ...
- 【从刷面试题到构建知识体系】Java底层-synchronized锁-1
在技术论坛中,经常看到一种言论:面试造火箭,干活拧螺丝.我们平时写的大部分代码的确是CRDU,再提一个层次,也无非就是揉进去复杂一些的业务逻辑,把一堆的CRDU组合起来. 那么问题来了:我们提倡的研究 ...
随机推荐
- 25 Flutter仿京东商城项目 购物车页面布局
加群452892873 下载对应25课文件,运行方法,建好项目,直接替换lib目录,在往pubspec.yaml添加上一下扩展. cupertino_icons: ^0.1.2 flutter_swi ...
- Qt编写控件属性设计器7-串口采集
一.前言 数据源是组态软件的核心灵魂,少了数据源,组态就是个花架子没卵用,一般数据源有三种方式获取,串口.网络.数据库,至于数据规则是什么,这个用户自己指定,本设计器全部采用第一个字节作为数据来演示. ...
- 文件被sourceTree忽略了怎么办
- pycharm重命名文件
先右键要重命名的文件,然后按照下图操作:
- django模板--循环控制标签
循环控制标签 在django模板中可以通过循环控制标签对列表进行迭代,循环控制标签又称for标签,语法格式如下: {% for value in value_list %} {{ value }} { ...
- EXCEL中,在其中列 前面or后面加一个“元”字的技巧
EXCEL小技巧,我们平常需要用到一些,记录下,供有需要的人参考! 案例: EXCEL其中的一列,每个后面加一个“元”字,如果要1个1个去加,相当麻烦,其实很简单,只需要一个公式即可! 解决方法: ( ...
- 【c# 学习笔记】接口与抽象类
抽象类经常与接口一起使用,共同服务于面向对象的编程,这里简单地分析一下接口与抽象类的区别,如下: 1.抽象类使用abstract关键字进行定义,而接口使用interface进行定义:它们都不能进行实例 ...
- Windows Server 2012 R2蓝屏
最近发现某个医院客户Windows Server 2012 R2 DataCenter的系统频繁蓝屏重启,重启没过一天再一次出现,反反复复折腾好几天,提示信息ntoskrnl.exe.mssmbios ...
- 解决Windows7下virtualbox安装ubuntu出现的0x00000000指令引用0x00000000内存,该内存不能为written问题
公司电脑只能用Windows7,不能用10,也没WSL用,最近想跑个Linux环境,因为之前装docker toolbox装了virtualbox,没道理再装vmware,遂用vbox开始折腾,没想到 ...
- 【VS开发】【CUDA开发】如何在MFC中调用CUDA
如何在MFC中调用CUDA 有时候,我们需要在比较大的项目中调用CUDA,这就涉及到MFC+CUDA的环境配置问题,以矩阵相乘为例,在MFC中调用CUDA程序.我们参考罗振东iylzd@163.com ...