iOS底层-类的三顾茅庐(三)

2023-05-16

前言

上文讲解完了类对象的结构体objc_class用来存储类信息的成员bits,整个结构还剩下方法的缓存cache,放在压轴来讲解。

// 简化版
struct objc_class : objc_object {
    // 类对象指针,Class大小是8字节
    Class ISA;
    // 父类对象指针,大小同上8字节
    Class superclass;
    // 方法缓存
    cache_t cache; // formerly cache pointer and vtable
    // 类存储的数据
    class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
}

cache的探索

cache字面意思是缓存,接下来探索它的数据结构。

拿到类对象地址后,根据结构体可知,平移16字节得到cache

image-20220504161311874

查看源码的cache_t数据结构:

struct cache_t {
private:
    // 8字节
    explicit_atomic<uintptr_t> _bucketsAndMaybeMask;
    union {
        struct {
            explicit_atomic<mask_t>    _maybeMask; // 4字节
#if __LP64__
            uint16_t                   _flags; // 2字节
#endif
            uint16_t                   _occupied; // 2字节
        };
        // 8字节
        explicit_atomic<preopt_cache_t *> _originalPreoptCache;
    };
}

explicit_atomic原子性,保证线程安全;_bucketsAndMaybeMask成员变量占8字节;

还有一个联合体,并且联合体里还有一个结构体;通过计算最大也是占8字节;

结构和LLDB输出对应上了。这些内容又是什么意思呢?先放着。既然是缓存,就需要插入数据。在结构体内找到insert方法,参数有SELIMP,这两者决定了一个方法。参数3是方法的接收者。

void insert(SEL sel, IMP imp, id receiver);

来到方法内部,下面的循环就是在操作bucket_t *b这个数据;

image-20220504165031553

那就要看看数据结构bucket_t了。

方法缓存的结构体

查看bucket_t结构体,也有impsel

struct bucket_t {
private:
    // IMP-first is better for arm64e ptrauth and no worse for arm64.
    // SEL-first is better for armv7* and i386 and x86_64.
#if __arm64__
    explicit_atomic<uintptr_t> _imp;
    explicit_atomic<SEL> _sel;
#else
    explicit_atomic<SEL> _sel;
    explicit_atomic<uintptr_t> _imp;
#endif
    ...
}

回到循环,查看set方法。这是一个函数模板:

// <原子操作,是否需要编码>
template<Atomicity atomicity, IMPEncoding impEncoding>

判断impEncoding:

image-20220504165247635

查看encodeImp,通过注释可知这是方法签名用的。

image-20220504165423165

回到set方法,这段是判断原子操作:

image-20220504165646081

这个store就是往内存写入数据,load是读取。set方法就是把newIMPnewSel写入内存,简单概括就是保存方法。

当循环往*b插入数据的时候,插入开始位置通过cache_hash算出来。

image-20220504170147693

cache_hash代码:

static inline mask_t cache_hash(SEL sel, mask_t mask) 
{
    uintptr_t value = (uintptr_t)sel;
#if CONFIG_USE_PREOPT_CACHES
    value ^= value >> 7;
#endif
    return (mask_t)(value & mask);
}

cache_hash(sel, m);入参sel是方法名,那和参数m相关的capacity是什么呢?

回到主方法,通过函数capacity()得到oldCapacity,意思是旧的buckets()容器长度(下文扩容部分会说明),并赋值给capacity

capacity()代码:从成员变量_maybeMask读取mask()

capacity = mask()+1 等价于mask()= capacity - 1

unsigned cache_t::capacity() const
{
    return mask() ? mask() + 1 : 0; 
}
// mask()
mask_t cache_t::mask() const
{
    return _maybeMask.load(memory_order_relaxed);
}

capacity() 函数的作用就是获取当前容器能够缓存方法的最大个数,也就是容器的⻓度。那么入参m就是长度 - 1。

回到hash算法,

image-20220504172600287

sel被转换成uintptr_t,本质是数字。不过这个数字比较大。

#ifndef __has_attribute
typedef unsigned long           uintptr_t;
#else
typedef unsigned long           uintptr_t;
#endif /* __has_attribute */

value & mask的时候, 最大也就等于mask(主方法里的入参m),也就是 buckets()容器长度 - 1;

// 例如 mask = 6 = 0110
0110 & 11111111111 = 0110, // 与运算,0与上任何数都是0;

bucket_t是散列表,理解为往数组里插入数据。再好的hash算法都会有冲突,也就是2个不同的方法得到相同的内存地址;

image-20220505151448685

所以系统用了两个判断:1.sel有没有值(0代表未使用),2.未使用,存进去之后是否相等;

解决哈希冲突

如果都不满足,就要解决哈希冲突:cache_next方法

// CACHE_END_MARKER:缓存结束标记,值跟随架构变化。
#if CACHE_END_MARKER
static inline mask_t cache_next(mask_t i, mask_t mask) {
    return (i+1) & mask;
}
#elif __arm64__
static inline mask_t cache_next(mask_t i, mask_t mask) {
    return i ? i-1 : mask;
}
#else
#error unexpected configuration
#endif

CACHE_END_MARKER = 1时,方法相当于把i增大,往之后的地址里存(开放地址法),且得到的i不等于begin

while (fastpath((i = cache_next(i, m)) != begin))

综上所述,cache应该就是方法的缓存

接着验证。将几个属性都打印一下;结果都不是…没有一个成员带有selimp信息;

image-20220504185614561

不要慌,找cache_t提供的方法。这不就是返回刚才插入的那些bucket_t吗?

image-20220504185853257

这就打印看看:

image-20220504190008806

接着从bucket_t结构体里找到imp()方法:第一个入参base不知道啥,先传nil试试:

image-20220504200507357

拿到imp之后;同理能找到sel

既然bucket_t是数组,那么存放的可能不止一个方法。通过指针地址+1获得下一个元素地址:由于是哈希表,可能存在nil,于是地址+2得到了respondsToSelector方法地址。

image-20220505134812648

越界的情景:sel已经是null了,value居然有值,这应该是用到其他地方的内存。

image-20220505135745597

没调用过这些方法,为什么会有?

调用方法testInstancePrint之后,重新获取buckets,发现方法丢失了…

image-20220505141543859

按理说testInstancePrint方法缓存进cache里了,首地址的方法怎么也不应该是空的。这就涉及到缓存扩容了。

cache的扩容

回到insert方法:

image-20220504203847608

这两个if是判断初始化等。接着是occupied()

image-20220504204155741

方法内部:

mask_t cache_t::occupied() const
{
    return _occupied;
}

返回成员变量_occupied,初始0;

image-20220504204109538

那么newOccupied = 0 + 1 = 1;

接下来,第一次没有缓存,必定会进入if判断里。初始值就是INIT_CACHE_SIZE

image-20220504204623995

这个初始值是1左移INIT_CACHE_SIZE_LOG2位数得到的。

image-20220504204751748

CACHE_END_MARKER之前也见过。

image-20220504205011471

接着执行reallocate(oldCapacity, capacity, /* freeOld */**false**);,方法内部:

ALWAYS_INLINE
void cache_t::reallocate(mask_t oldCapacity, mask_t newCapacity, bool freeOld)
{
    bucket_t *oldBuckets = buckets();
    bucket_t *newBuckets = allocateBuckets(newCapacity);

    // Cache's old contents are not propagated. 
    // This is thought to save cache memory at the cost of extra cache fills.
    // fixme re-measure this

    ASSERT(newCapacity > 0);
    ASSERT((uintptr_t)(mask_t)(newCapacity-1) == newCapacity-1);

    setBucketsAndMask(newBuckets, newCapacity - 1);
    
    if (freeOld) {
        collect_free(oldBuckets, oldCapacity);
    }
}

头2行就是获取老bucket_t,生成新bucket_t

setBucketsAndMask方法:

image-20220504210300207

arm是32位,arm64才是64位;也就是arm64下,啥也没干。方法本意是给成员变量赋值。第一次来由于没有旧bucket_t,所以freeold = false;不会释放旧bucket_t

小结一下:

  • occupied() 函数的作用就是获取当前容器已经缓存的方法的个数。
  • INIT_CACHE_SIZEarm64架构下为2,在x86_64架构下为4。
  • 那么当cache中缓存方法的容器为空时,在arm64架构下初始化容器的⻓度为2,在x84_64架构下初始化容器的⻓度为4。

那么下一步:

image-20220504210731982

这个cache_fill_ratio方法又得回到前面的这张图:

image-20220504205011471

函数在x86_64架构下为容器⻓度capacity的3/4,在arm64架构下为7/8。

那么这个分支的判断就是:在x86_64架构下,实际存储的方法的个数小于等于容器的总容量的3/4再减1时,啥也不干。在arm64架构下,实际存储的方法的个数小于等于容器的总容量的 7/8时,啥也不干。

由此可以猜测,fastpath代表大概率会执行到,slowpath代表小概率会执行到。

CACHE_ALLOW_FULL_UTILIZATIONarm64架构下等于1,会多出以下分支:

image-20220504211505550

FULL_UTILIZATION_CACHE_SIZEarm64架构下等于8;

image-20220504211914451

逻辑就是:在arm64架构下,当容器的⻓度小于或等于8时 && 实际存储的方法的个数小于或等于容器的⻓度的时候,又啥也不干。

最终else分支:

image-20220504212111978

2倍扩容,且不超过MAX_CACHE_SIZE(容器的最大⻓度为 1<<16,见前一张图)。这里reallocate(oldCapacity, capacity, true);传了true,方法就会释放旧bucket_t。里面的内容就不存在了,这就解释了扩容后,之前缓存的方法不存在了。

综合上面的代码的出来的结论就是:

  • arm64结构,也就是真机环境下,缓存方法的容器初始⻓度2,大于7/8扩容。注意,当容器的⻓度小于8时,只有满容量了才可能大于7/8,所以系统在容量小于8的情况下,是存满才扩容。
  • x86_64架构下,缓存方法的容器初始⻓度4,大于等于3/4扩容。容器只能存储(容器⻓度 * 3/4 - 1)个方法。

接下来的部分代码就是插入数据。

缓存的插入

回到开始时testInstancePrint方法找不到的问题;testInstancePrintresponseToSelectorclass方法之前调用;class来的时候,因为扩容,旧的bucket_t被释放了;前面的方法位置就变了。

验证:既然要往cache里插入数据,必然会调用insert方法;修改代码打印方法名:

void cache_t::insert(SEL sel, IMP imp, id receiver)
{
    printf("%s\n", sel_getName(sel));
    ...
}

运行方法之后,打印一下此时的class,这时候才插入了2个方法;

image-20220505103236989

既然cache占16字节,如果方法太多了呢?因为存放的只是首地址,具体内容在buckets()里。

image-20220505105517842

通过掩码返回容器首地址:

struct bucket_t *cache_t::buckets() const
{
    uintptr_t addr = _bucketsAndMaybeMask.load(memory_order_relaxed);
    return (bucket_t *)(addr & bucketsMask);
}
// bucketsMask
static constexpr uintptr_t bucketsMask = ~0ul;

扩容测试

模仿类和cache的数据结构,方便写代码读取:


#import <UIKit/UIKit.h>
#import "AppDelegate.h"
#import <objc/message.h>
#import "FFGoods.h"

typedef uint32_t mask_t;  // x86_64 & arm64 asm are less efficient

// preopt_cache_entry_t
struct ff_preopt_cache_entry_t {
    uint32_t sel_offs;
    uint32_t imp_offs;
};

//preopt_cache_t
struct ff_preopt_cache_t {
    int32_t  fallback_class_offset;
    union {
        struct {
            uint16_t shift       :  5;
            uint16_t mask        : 11;
        };
        uint16_t hash_params;
    };
    uint16_t occupied    : 14;
    uint16_t has_inlines :  1;
    uint16_t bit_one     :  1;
    struct ff_preopt_cache_entry_t entries;
    
    inline int capacity() const {
        return mask + 1;
    }
};

// bucket_t
struct ff_bucket_t {
    IMP _imp;
    SEL _sel;
};

// cache_t
struct ff_cache_t {
    uintptr_t _bucketsAndMaybeMask; // 8
    struct ff_preopt_cache_t _originalPreoptCache; // 8
    
    // _bucketsAndMaybeMask is a buckets_t pointer in the low 48 bits
    // _maybeMask is unused, the mask is stored in the top 16 bits.

    // How much the mask is shifted by.
    static constexpr uintptr_t maskShift = 48;

    // Additional bits after the mask which must be zero. msgSend
    // takes advantage of these additional bits to construct the value
    // `mask << 4` from `_maskAndBuckets` in a single instruction.
    static constexpr uintptr_t maskZeroBits = 4;

    // The largest mask value we can store.
    static constexpr uintptr_t maxMask = ((uintptr_t)1 << (64 - maskShift)) - 1;
    
    // The mask applied to `_maskAndBuckets` to retrieve the buckets pointer.
    static constexpr uintptr_t bucketsMask = ((uintptr_t)1 << (maskShift - maskZeroBits)) - 1;
    
    static constexpr uintptr_t preoptBucketsMarker = 1ul;

    // 63..60: hash_mask_shift
    // 59..55: hash_shift
    // 54.. 1: buckets ptr + auth
    //      0: always 1
    static constexpr uintptr_t preoptBucketsMask = 0x007ffffffffffffe;
    
    ff_bucket_t *buckets() {
        return (ff_bucket_t *)(_bucketsAndMaybeMask & bucketsMask);
    }
    
    uint32_t mask() const {
        return _bucketsAndMaybeMask >> maskShift;
    }
    
};

// class_data_bits_t
struct ff_class_data_bits_t {
    uintptr_t objc_class;
};

// objc_class
struct ff_objc_class {
    Class isa;
    Class superclass;
    struct ff_cache_t cache;
    struct ff_class_data_bits_t bits;
};

测试思路:

给自定义的类生成30个方法,模拟调用;

扩容后第一次插入方法,数量只有1。打印此时的长度。

void test(Class cls) {
    
    // 将cls的类型转换成自定义的源码ff_objc_class类型,方便后续操作
    struct ff_objc_class *pClass = (__bridge struct ff_objc_class *)(cls);
    
    struct ff_cache_t cache = pClass->cache;
    struct ff_preopt_cache_t origin = cache._originalPreoptCache;
    uintptr_t mask = cache.mask();
    // 扩容后第一次插入方法数量只有1
    if (origin.occupied == 1) {
        NSLog(@"buckets已缓存方法的个数 = %u, buckets的长度 = %lu", origin.occupied, mask + 1);
    }
}

运行:

image-20220505160345788

可以看到几次触发扩容的log。

总结

方法的缓存基于不同架构,缓存策略是不一样的。

  • bucket_t结构体存储方法必备的selimp,并用数组容器存储。在cache_t结构体中,通过bucket()方法返回元素首地址。容器初始长度在arm64架构下为2,在x84_64架构下为4。
  • 扩容条件:在arm64架构下容量小于8,存满才扩容,大于8时,数量大于7/8扩容。在x86_64架构下,都是大于等于3/4。
  • 扩容按照2倍原大小进行,最大⻓度为 1<<16 = 0x10000。扩容之后,之前的方法缓存被清空(内存被释放)。
  • 为什么要释放旧的内存 ? 扩容是按照2倍进行的,如果不释放,随着扩容次数增加,遗留的无用内存也不少。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

iOS底层-类的三顾茅庐(三) 的相关文章

  • 浅谈linux - samba实现linux与windows文件共享

    概述 Samba是一个能让Linux系统应用Microsoft网络通讯协议的软件 xff0c 而SMB是Server Message Block的缩写 xff0c 即为服务器消息块 xff0c SMB主要是作为Microsoft的网络通讯协
  • mysql的update、delete、和insert into时表别名用法

    mysql数据库update delete 和insert into表的时候 xff0c 如果要使用表别名 xff0c 必须按照规范写法来写 xff1a insert xff1a 简写 xff1a insert into t a a a i
  • 浅谈linux - RT Linux解决方案

    概述 RT Linux Real Time Linux 亦称作实时 Linux xff0c 是 Linux 中的一种硬实时操作系统 xff0c 它最早由美国墨西哥理工学院的 V Yodaiken 开发 产品资料提供的 Linux RT 内核
  • 浅谈linux - 线程的基本应用

    概述 线程 xff08 英语 xff1a thread xff09 是操作系统能够进行运算调度的最小单位 它被包含在进程之中 xff0c 是进程中的实际运作单位 注意 线程和进程之间的区别 1 线程是执行的基本单位 xff1b 进程是资源分
  • 浅谈linux - mutex锁应用

    概述 互斥锁是专门用于处理线程之间互斥关系的一种方式 xff0c 它有两种状态 xff1a 上锁状态 解锁状态 如果互斥锁处于上锁状态 xff0c 那么再上锁就会阻塞到这把锁解开为止 xff0c 才能上锁 解锁状态下依然可以解锁 xff0c
  • 浅谈linux - cond条件变量应用

    概述 条件变量不是一个把锁 xff0c 它实质上一个类似信号的东西 xff0c 与锁相互配合使用 xff0c 因为锁所能达到的功能就只有加锁和解锁 xff0c 并不能实现线程之间的一些关联 xff0c 于是条件变量就出现了 xff0c 与锁
  • 设计UI - Adobe xd画板及参考线

    画板 新建画板 a 使用预设画板大小或创建自定义画板 操作步骤 xff1a 打开xd软件 xff0c 点击需要建立的画板模版 xff0c 没有则选择自定义大小 b 使用画板工具创建其它画板 操作步骤 xff1a 选中画板工具 xff0c 选
  • activity的启动模式与newIntent()

    很多知识当我们用到的时候 xff0c 才发觉有多好用 今天需要完成一个功能 xff0c 创建一个悬浮窗 xff0c 点击悬浮窗上按钮 xff0c 加载到一个包含webView 的activity xff0c 为了避免activity重复创建
  • 基于DOCKER安装华为自研数据库高斯(GAUSS)

    华为数据库裸机安装比较困难 xff0c 我们可以采用docker的安装方式 xff0c 自己学习和测试使用 安装环境 1 xff09 centos 7 9 2 xff09 docker版本 opengauss 注意 xff0c 我们在安装此
  • MariaDB 中文乱码问题解决

    编辑文件 vim etc my cnf d server cnf 在 mysqld 下面添加两行 init connect 61 39 SET NAMES utf8 39 character set server 61 utf8 重启Mar
  • 【Linux】安装kali遇到的一些细节

    前言 本文省略安装教程 xff0c 如有安装教程需求 xff0c 请自行查阅 博主安装的版本为 xff1a kali 2023 1 amd 64 一 gdm3默认管理器 情景 其它系统基础配置进度完成后 xff0c 大部分安装的人 xff0
  • Windows 2016 修改密码时提示密码不符合规则

    一 现象描述 在修改服务器密码时 xff0c 遇到如下情况 xff1a 提示密码不满足密码策略要求 二 处理方法 在电脑服务器管理中找到 工具 单击 本地安全策略 进入策略管理 在本地安全策略中找到帐户安全策略 xff0c 单击进入 帐户策
  • MATLAB即将跌出TOP 20,TIOBE 4月编程语言排行榜出炉

    CSDN 编者按 一月一次的编程语言排行榜出炉 xff01 责编 张红月 出品 CSDN xff08 ID xff1a CSDNnews xff09 在TIOBE发布的4月编程语言排行榜中 xff0c 知名数学算法分析语言MATLAB即将跌
  • python3 爬虫实战案例 (抓取淘宝信息)(淘宝加了搜索必须登录的验证,此方法所到的结果都是0)

    需求 xff1a 对比足球 xff0c 篮球 xff0c 乒乓球 xff0c 羽毛球 xff0c 网球 xff0c 相关物品的销售量保存到excle中 和抓取淘宝关键字相关信息的销售量 xff0c 这和之前抓取csdn网站浏览量取不同 xf
  • android studio中Gradle 编译需要重点注意gradle,wrapper,build tools之间的版本对应关系

    Android Studio 2 3 的平台已经已经是相对稳定的发布版 xff0c 新的功能不断推出 xff0c 包括对NDK 的完美支持 它看起来有一些重大的改变也正在等待合适的孵化时机 xff0c 如 xff1a 新的 Gradle 构
  • VNC Viewer 设置屏幕分辨率-解决屏幕分辨率问题

    先介绍一款好用的连接工具介绍一个 VNC连接工具 xff1a http fwqglgj iis7 net cp vnc tscc IIs7服务器管理工具可以批量连接并管理VNC服务器 作为服务器集成管理器 xff0c 它最优秀的功能就是批量
  • LAMP环境简单搭建

    一 简介 LAMP 是Linux Apache MySQL PHP的简写 xff0c 其实就是把Apache MySQL以及PHP安装在Linux系统上 xff0c 组成一个环境来运行php的脚本语言 Apache是最常用的WEB服务软件
  • 转身不带走一丝云彩--我的2014

    时间或许就是这样不管你愿意不愿意都会毫不犹疑的向前 xff0c 逼你成长 2014年得到了很多也失去了很多 xff0c 我对未来还是有诸多憧憬的 谨以此文献给过去的时光 xff0c 也希望对后来人能有所帮助 改变篇 相比于2013年 xff
  • 一年装三次Arch Linux,每次都有新收获

    只要跟随优秀的教程 xff0c 装机过程so easy archlinux 双系统真机安装演示 哔哩哔哩 bilibili Archlinux系统安装 xff0c 配置 xff0c 游戏 黑客驰HackerChi 哔哩哔哩 bilibili
  • Linux系统中PS1命令详解

    本帖主要针对经常使用Linux命令行且对命令行界面美观和可阅读性有一定要求的同学 xff0c 主要讲解如何通过修改PS1命令定义命令行的显示以及提供一条可直接复用的PS1命令 原文链接 简介 xff1a PS1命令是linux系统中的一个全

随机推荐

  • firewalld

    一 firewalld 介绍 firewalld 防火墙 xff0c 其实就是一个隔离工具 xff1a 工作于主机或者网络的边缘 xff0c 对于进出本主机或者网络的报文根据事先定义好的网络规则做匹配检测 xff0c 对于能够被规则所匹配的
  • yum升级CURL到最新版本的方法,非常好用

    首先 xff0c 先为你的服务器获取最新匹配的源 xff1a http mirror city fan org ftp contrib yum repo 安装新版libcurl的yum源 rpm ivh http mirror city f
  • 百度笔试题2018

    题外话 首先我要吐槽一下 xff0c csdn简直是在作死啊 xff0c 复制博文底下的那个引用太恶心了 xff0c 我复制自己的博客 xff0c 还有引用 xff0c 啥玩意啊 所以我决定换地方了 xff0c 以后github xff08
  • 你见过的最全面的Python重点知识总结!

    这是一份来自于 SegmentFault 上的开发者 64 二十一 总结的 Python 重点 由于总结了太多的东西 xff0c 所以篇幅有点长 xff0c 这也是作者 34 缝缝补补 34 总结了好久的东西 xff0c 强烈建议收藏再慢慢
  • 程序员经典语录

    程序员编程语录 1 一个好的程序员是那种过单行线马路都要往两边看的人 xff08 Doug Linder xff09 2 程序有问题时不要担心 如果所有东西都没问题 xff0c 你就失业了 xff08 软件工程的Mosher定律 xff09
  • 使用fastboot命令刷机流程详解

    一 Fastboot是什么 1 1 首先介绍Recovery模式 卡刷 在系统进行定制时 xff0c 编译系统会编译出一份ZIP的压缩包 xff0c 里面是一些系统分区镜像 xff0c 提供给客户进行手动升级 恢复系统 需要提前将压缩包内置
  • 【谷歌插件】谷歌插件制作

    文章目录 谷歌浏览器插件制作教程实现步骤成功示例问题未封装的扩展程序并非来自 Chrome 网上应用商店 谷歌浏览器插件制作 教程 教程1 xff1a https blog csdn net github 35631540 article
  • 一个刚毕业大学生的四个月苦逼程序员经历

    先来一个自我介绍 大学时排名老三 就暂且叫老三吧 xff0c 毕业于河南的一个还算可以的二本院校 xff0c 专业 地球信息科学与技术 首先介绍一下我的专业 xff0c 听着名字很高大上 xff0c 其实 xff0c 我们都叫他四不像专业
  • JS中的require、import、default、export

    刚开始学的时候经常弄混总结一下 xff1a 懒人 xff1a 1 require xff08 导入 xff09 是Commonjs的规范与module exports xff08 导出 xff09 搭配使用 2 import xff08 导
  • Ubuntu安装python3

    sudo apt get install python3 安装python3 xff0c 安装完之后系统默认还是python2 xff0c 要删除python link文件 sudo rm rf usr bin python 然后建立新连接
  • ubuntu安装shutter出现E:无法修正错误

    使用Ubuntu16 04安装shutter时出现如下错误 通过换源可以解决
  • Ubuntu不能访问Windows分区

    将Windows的快速启动关闭即可解决次问题 在电脑中安装了双系统 xff0c 但有时候在Ubuntu中访问Windows分区会出现如下错误 xff1a 以前出现过这种错误 xff0c 是因为windows系统没有完全关闭 xff0c 当时
  • Ubuntu和Windows双系统时间不对的解决办法

    在使用一系统再切换到另一个系统之后 xff0c 系统时间好像是停留在上次关闭该系统的时间 在网上的解决办法通常是 xff1a sudo gedit etc default rcS xff0c 将UTC 61 yes改成UTC 61 no 但
  • Ubuntu出现依赖关系问题 - 仍未被配置问题

    安装软件包时候出现如下错误 xff1a 但这并不是依赖问题 xff0c 使用sudo apt get f install 无法解决 其实问题是因为这六个软件包没有被完全安装或卸载 在安装其他软件的时候会出现 xff1a 就是指这六个软件 使
  • 熬夜总结!最全的Pycharm常用快捷键大全!

    版权声明 xff1a 本文为博主原创文章 xff0c 遵循 CC 4 0 BY SA 版权协议 xff0c 转载请附上原文出处链接和本声明 本文链接 xff1a https blog csdn net momoda118 article d
  • iOS底层-对象里都有什么

    前言 上篇文章说了iOS中alloc方法是怎么创建对象的 xff0c 以及对象的本质是结构体 接下来继续探究对象的内存分布 xff0c 以及对象的isa是个什么样的结构体 xff0c 存储了哪些信息 对象内存分布 已知系统给对象分配内存是1
  • iOS底层-类的三顾茅庐(一)

    前言 了解完对象的底层 xff0c 知道isa指向的是类对象 那么类 xff08 Class xff09 的本质究竟是什么 xff1f 本文顺序isa的指向 xff0c 探索类的继承链 xff0c 和类对象的结构 xff0c 并且尝试获取方
  • iOS底层-alloc方法之旅

    前言 通过汇编调试和源码分析 xff0c 介绍iOS开发当中alloc方法到底做了什么 追踪 alloc 实例化一个对象往往是通过 xxx alloc init 那么alloc和init的区别是什么 xff1f 将两个方法分开调用 xff0
  • iOS底层-类的三顾茅庐(二)

    前言 上篇文章分析了objc class里存储数据的bits xff0c 了解到方法和属性的存储的位置class rw t xff08 以下简称rw xff09 本文将继续研究rw里包含的其他内容 类数据的存储 书接上文 xff0c rw结
  • iOS底层-类的三顾茅庐(三)

    前言 上文讲解完了类对象的结构体objc class用来存储类信息的成员bits xff0c 整个结构还剩下方法的缓存cache xff0c 放在压轴来讲解 简化版 struct objc class objc object 类对象指针 x