<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>咸鱼心得 - 初晴神社</title>
	<atom:link href="https://vki.cc/experience/feed/" rel="self" type="application/rss+xml" />
	<link>https://vki.cc/category/greattruth/experience/</link>
	<description>白露的博客</description>
	<lastBuildDate>Fri, 30 Jul 2021 15:57:31 +0000</lastBuildDate>
	<language>zh-Hans</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=7.1</generator>

<image>
	<url>https://cdn.vki.cc/wp-content/uploads/2018/03/cropped-icemaplen-1-32x32.png</url>
	<title>咸鱼心得 - 初晴神社</title>
	<link>https://vki.cc/category/greattruth/experience/</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>Unity 3D 纹理压缩格式指南</title>
		<link>https://vki.cc/greattruth/experience/872/</link>
					<comments>https://vki.cc/greattruth/experience/872/#respond</comments>
		
		<dc:creator><![CDATA[白露]]></dc:creator>
		<pubDate>Fri, 30 Jul 2021 15:57:30 +0000</pubDate>
				<category><![CDATA[咸鱼心得]]></category>
		<category><![CDATA[朝闻道]]></category>
		<category><![CDATA[Unity]]></category>
		<category><![CDATA[Unity 3D]]></category>
		<category><![CDATA[贴图]]></category>
		<guid isPermaLink="false">https://vki.cc/?p=872</guid>

					<description><![CDATA[<p>压缩纹理的主要目的是为了节省空间，缩短加载时间。针对不同的需求和平台需要不同的压缩格式，有的需要高性能，有的需要高质量，Unity本身提供了多种压缩格式供选择。</p>
<p><a href="https://vki.cc/greattruth/experience/872/">Unity 3D 纹理压缩格式指南</a>最先出现在<a href="https://vki.cc">初晴神社</a>。</p>
]]></description>
										<content:encoded><![CDATA[
<p class="has-black-color has-text-color wp-block-paragraph">Unity本身支持多种贴图格式，比如jpg、png、psd等。但是当这些资源导入后，Unity会统一转化为自己的格式（非直接更改源文件），以供游戏运行时使用。</p>



<p class="has-black-color has-text-color wp-block-paragraph">压缩纹理的主要目的是为了节省空间，缩短加载时间。针对不同的需求和平台需要不同的压缩格式，有的需要高性能，有的需要高质量，Unity本身提供了多种压缩格式供选择。</p>



<h3 id="etc" class="wp-block-heading">ETC</h3>



<p class="wp-block-paragraph"><em>Ericsson Texture Compression，是由 Khronos 支持的开放标准，在移动平台中广泛采用。它是一种为感知质量设计的有损算法，其依据是人眼对亮度改变的反应要高于色度改变。类似于DXT，ETC也是把4&#215;4的像素块压缩成一个64或128位的数据块，也是有损压缩。</em></p>



<p class="has-black-color has-text-color wp-block-paragraph">ETC又分为ETC1和ETC2，通常用于Android平台。其中ETC2是较新的标准，也是Unity官方推荐的格式（虽然我还不太理解）。</p>



<p class="has-black-color has-text-color wp-block-paragraph">相对于ETC1，<strong>ETC2向前兼容，不强制要求贴图大小为2的整数次幂，且对RGB的压缩质量更好，支持透明通道。</strong>唯一的缺点是需要设备支持OpenGL ES 3.0标准。不过该标准诞生于2012年，目前不支持它的设备非常少，因此不用担心。</p>



<p class="wp-block-paragraph">注意：<em>可对具有 <a href="https://docs.unity3d.com/cn/current/Manual/HOWTO-alphamaps.html">Alpha 通道</a>的纹理使用 ETC1 格式，但仅当构建目标为 Android 且纹理放置在图集上（通过指定打包标签）时才适用。要启用此功能，请勾选纹理的 <strong>Compress using ETC1</strong> 复选框。Unity 会将生成的图集拆分为两个纹理，每个纹理都没有 Alpha 通道，然后将它们组合在渲染管线的最后部分中。</em></p>



<h3 id="pvrtc" class="wp-block-heading">PVRTC</h3>



<p class="wp-block-paragraph">PowerVR Texture Compression，PVRTC格式与基于块的压缩格式，比如S3TC、ETC的不同之处是，它使用2张双线性放大的<em>低分辨率图，根据精度和每个像素的权重，融合到一起来呈现纹理，并且2-bpp和4-bpp都支持ARGB数据。PVRTC格式压缩比高，也是有损压缩。</em></p>



<p class="has-black-color has-text-color wp-block-paragraph">PVRTC格式一般用于iOS平台，可以用在所有的iOS设备上。缺点是<strong>要求纹理的高、宽必须都是2的整数次幂（POT），最小为8，并且必须是正方形</strong>。</p>



<p class="has-vivid-red-color has-text-color wp-block-paragraph">注意：使用该格式，极端情况下会让内存占用变得更大。比如原先300*300的贴图，因为要求尺寸为2的整数次幂，所以变成512*512 . 这种情况下还不如使用RGBA16 .</p>



<h3 id="rgba16" class="wp-block-heading">RGBA16</h3>



<p class="has-black-color has-text-color wp-block-paragraph">RGBA16，即RGBA4444，每个像素2个字节，RGBA通道各占4位。</p>



<p class="has-black-color has-text-color wp-block-paragraph">相比以上压缩格式，贴图质量相对较好，缺点是内存占用大，且相对于RGBA32来说会有色彩断层问题。</p>



<h3 id="rgba32" class="wp-block-heading">RGBA32</h3>



<p class="has-black-color has-text-color wp-block-paragraph"> RGBA32，即RGBA8888，每个像素4个字节，RGBA通道各占8位。</p>



<p class="has-black-color has-text-color wp-block-paragraph">原图无压缩，质量最好 ，缺点是内存占用极大。除非美术要求极高才会使用。</p>



<p class="has-black-color has-text-color wp-block-paragraph">假如贴图没有透明通道则使用RGB24格式，少占用一个字节的空间。</p>



<h3 id="astc" class="wp-block-heading">ASTC</h3>



<p class="wp-block-paragraph"><em>ASTC 是在 OpenGL ES3.0 出现后在 2012 年中产生的一种业界领先的纹理压缩格式，它的压缩分块从 4&#215;4 到 12&#215;12 最终可以压缩到每个像素占用 1bit 以下，压缩比例有多种可选。ASTC 格式支持 RGBA，且适用于 2 的幂次方长宽等比尺寸和无尺寸要求的 NPOT（非 2 的幂次方）纹理。</em></p>



<p class="has-black-color has-text-color wp-block-paragraph">目前我最推荐使用的一种压缩格式，优点是压缩质量高，显示质量好，且Android和iOS平台都可以使用，比较省心。压缩质量从4&#215;4到12&#215;12，按需选择。</p>



<figure class="wp-block-image size-full"><img  decoding="async"  width="160"  height="261"  src="https://oss.vki.cc/wp-content/uploads/2021/07/1627659914-a905a96f-bb75-4bd8-a53a-adaaf02d9b80.png"  alt=""  class="wp-image-874"  srcset="https://cdn.vki.cc/wp-content/uploads/2021/07/1627659914-a905a96f-bb75-4bd8-a53a-adaaf02d9b80.png 160w, https://cdn.vki.cc/wp-content/uploads/2021/07/1627659914-a905a96f-bb75-4bd8-a53a-adaaf02d9b80-120x196.png 120w, https://cdn.vki.cc/wp-content/uploads/2021/07/1627659914-a905a96f-bb75-4bd8-a53a-adaaf02d9b80-90x147.png 90w"  sizes="(max-width: 160px) 100vw, 160px" ></figure>



<p class="has-black-color has-text-color wp-block-paragraph">缺点和ETC2差不多，老设备不支持。其中iOS设备从iPhone 5s才开始支持，Android主流压缩格式也正在从ETC2转向ASTC（这也是我不太理解Unity还在推荐ETC2的原因）。另一个小缺点是压缩时间稍长，不过也不是问题。</p>



<p class="has-black-color has-text-color wp-block-paragraph"><strong>所以长期来看，ASTC将一统江湖！</strong></p>



<h2 id="%e7%bb%93%e8%ae%ba" class="wp-block-heading">结论</h2>



<p class="has-black-color has-text-color wp-block-paragraph">综上来看，新项目的贴图基本可以无脑使用ASTC，这里有张图，有个大佬总结出来的。</p>



<figure class="wp-block-image size-full"><img  fetchpriority="high"  decoding="async"  width="720"  height="337"  src="https://oss.vki.cc/wp-content/uploads/2021/07/1627660332-8650276a-f037-48d0-a24c-48714dc8e316.jpg"  alt=""  class="wp-image-876"  srcset="https://cdn.vki.cc/wp-content/uploads/2021/07/1627660332-8650276a-f037-48d0-a24c-48714dc8e316.jpg 720w, https://cdn.vki.cc/wp-content/uploads/2021/07/1627660332-8650276a-f037-48d0-a24c-48714dc8e316-120x56.jpg 120w, https://cdn.vki.cc/wp-content/uploads/2021/07/1627660332-8650276a-f037-48d0-a24c-48714dc8e316-90x42.jpg 90w, https://cdn.vki.cc/wp-content/uploads/2021/07/1627660332-8650276a-f037-48d0-a24c-48714dc8e316-320x150.jpg 320w, https://cdn.vki.cc/wp-content/uploads/2021/07/1627660332-8650276a-f037-48d0-a24c-48714dc8e316-560x262.jpg 560w"  sizes="(max-width: 720px) 100vw, 720px" ></figure>



<figure class="wp-block-image size-full"><img  decoding="async"  width="618"  height="100"  src="https://oss.vki.cc/wp-content/uploads/2021/07/1627660356-adfe9ed4-3dd1-42a9-8d7d-b03aa74aab11.png"  alt=""  class="wp-image-877"  srcset="https://cdn.vki.cc/wp-content/uploads/2021/07/1627660356-adfe9ed4-3dd1-42a9-8d7d-b03aa74aab11.png 618w, https://cdn.vki.cc/wp-content/uploads/2021/07/1627660356-adfe9ed4-3dd1-42a9-8d7d-b03aa74aab11-120x19.png 120w, https://cdn.vki.cc/wp-content/uploads/2021/07/1627660356-adfe9ed4-3dd1-42a9-8d7d-b03aa74aab11-90x15.png 90w, https://cdn.vki.cc/wp-content/uploads/2021/07/1627660356-adfe9ed4-3dd1-42a9-8d7d-b03aa74aab11-320x52.png 320w, https://cdn.vki.cc/wp-content/uploads/2021/07/1627660356-adfe9ed4-3dd1-42a9-8d7d-b03aa74aab11-560x91.png 560w"  sizes="(max-width: 618px) 100vw, 618px" ></figure>



<p class="has-black-color has-text-color wp-block-paragraph"><strong>不过尽管ASTC支持非二次幂纹理，但在时机开发中还是建议UI及美术尽量制作二次幂纹理。</strong></p>



<p class="wp-block-paragraph">具体可参考：<a href="https://www.zhihu.com/question/376921536/answer/1063272336" target="_blank" rel="noreferrer noopener">https://www.zhihu.com/question/376921536/answer/1063272336</a></p>
<p><a href="https://vki.cc/greattruth/experience/872/">Unity 3D 纹理压缩格式指南</a>最先出现在<a href="https://vki.cc">初晴神社</a>。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://vki.cc/greattruth/experience/872/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>记一次由接入AppsFlyer导致海外iOS无法登录的问题</title>
		<link>https://vki.cc/greattruth/experience/861/</link>
					<comments>https://vki.cc/greattruth/experience/861/#respond</comments>
		
		<dc:creator><![CDATA[白露]]></dc:creator>
		<pubDate>Thu, 10 Jun 2021 06:11:42 +0000</pubDate>
				<category><![CDATA[咸鱼心得]]></category>
		<category><![CDATA[朝闻道]]></category>
		<category><![CDATA[AppsFlyer]]></category>
		<category><![CDATA[SDK]]></category>
		<guid isPermaLink="false">https://vki.cc/?p=861</guid>

					<description><![CDATA[<p>项目临近发布前测试发现的，具体表现为在iOS设备上，使用TapTap登录，输入完账号密码后点击登录游戏无反应，再次点击需要再次登录。</p>
<p><a href="https://vki.cc/greattruth/experience/861/">记一次由接入AppsFlyer导致海外iOS无法登录的问题</a>最先出现在<a href="https://vki.cc">初晴神社</a>。</p>
]]></description>
										<content:encoded><![CDATA[
<h3 class="wp-block-heading" id="id-记一次由接入AppsFlyerSDK导致海外iOS无法登录的问题-Bug表现"><span id="bug%e8%a1%a8%e7%8e%b0">Bug表现</span></h3>



<p class="wp-block-paragraph">服务器：海外服</p>



<p class="wp-block-paragraph">平台：iOS</p>



<p class="wp-block-paragraph">项目临近发布前测试发现的，具体表现为在iOS设备上，使用TapTap登录，输入完账号密码后点击登录游戏无反应，再次点击需要再次登录。</p>



<h3 class="wp-block-heading" id="id-记一次由接入AppsFlyerSDK导致海外iOS无法登录的问题-排查过程"><span id="%e6%8e%92%e6%9f%a5%e8%bf%87%e7%a8%8b">排查过程</span></h3>



<p class="wp-block-paragraph">查找过程中发现成功拉起TapTap登录没有异常，但是在TapTap上点击登录后游戏<strong>没有收到任何回调</strong>。</p>



<p class="wp-block-paragraph">正常情况下，<strong>点击登录之后SDK会调用对应的回调方法</strong>，包括：</p>



<p class="wp-block-paragraph">登录成功（OnLoginSuccess）</p>



<p class="wp-block-paragraph">登录失败（OnLoginError）</p>



<p class="wp-block-paragraph">登录取消（OnLoginCancel）</p>



<p class="wp-block-paragraph">最终在TapSDK的同学帮助下定位了问题，本应由TapSDK传给客户端的消息被AppsFlyer拦截了。。</p>



<p class="wp-block-paragraph">相关AppsFlyer代码如下：</p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">//  AppsFlyerAppController.mm
//  Unity-iPhone
//
//  Created by Jonathan Wesfield on 30/07/2019.
//
 
#import &lt;Foundation/Foundation.h>
#import "UnityAppController.h"
#import "AppDelegateListener.h"
#import "AppsFlyeriOSWrapper.h"
#if __has_include(&lt;AppsFlyerLib/AppsFlyerLib.h>)
#import &lt;AppsFlyerLib/AppsFlyerLib.h>
#else
#import "AppsFlyerLib.h"
#endif
#import &lt;objc/message.h>
 
/**
 Note if you would like to use method swizzeling see AppsFlyer+AppController.m
 If you are using swizzeling then comment out the method that is being swizzeled in AppsFlyerAppController.mm
 Only use swizzeling if there are conflicts with other plugins that needs to be resolved.
*/
 
 
@interface AppsFlyerAppController : UnityAppController &lt;AppDelegateListener>
{
    BOOL didEnteredBackGround;
}
@end
 
@implementation AppsFlyerAppController
 
- (instancetype)init
{
    self = [super init];
    if (self) {
         
        id swizzleFlag = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"AppsFlyerShouldSwizzle"];
        BOOL shouldSwizzle = swizzleFlag ? [swizzleFlag boolValue] : NO;
         
        if(!shouldSwizzle){
            UnityRegisterAppDelegateListener(self);
        }
    }
    return self;
}
 
- (void)didFinishLaunching:(NSNotification*)notification {
    NSLog(@"got didFinishLaunching = %@",notification.userInfo);
 
 
    if (_AppsFlyerdelegate == nil) {
        _AppsFlyerdelegate = [[AppsFlyeriOSWarpper alloc] init];
    }
    [[AppsFlyerLib shared] setDelegate:_AppsFlyerdelegate];
 
    if (notification.userInfo[@"url"]) {
        [self onOpenURL:notification];
    }
}
 
-(void)didBecomeActive:(NSNotification*)notification {
    NSLog(@"got didBecomeActive(out) = %@", notification.userInfo);
    if (didEnteredBackGround == YES &amp;&amp; AppsFlyeriOSWarpper.didCallStart == YES) {
        [[AppsFlyerLib shared] start];
        didEnteredBackGround = NO;
    }
}
 
- (void)didEnterBackground:(NSNotification*)notification {
    NSLog(@"got didEnterBackground = %@", notification.userInfo);
    didEnteredBackGround = YES;
}
 
- (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray *))restorationHandler {
    [super application:application continueUserActivity:userActivity restorationHandler:restorationHandler];
    [[AppsFlyerAttribution shared] continueUserActivity:userActivity restorationHandler:restorationHandler];
    return YES;
}
 
-(BOOL) application:(UIApplication *)application openURL:(NSURL *)url options:(NSDictionary *)options {
    NSLog(@"got openUrl: %@",url);
    [super application:application openURL:url options:options];
    [[AppsFlyerAttribution shared] handleOpenUrl:url options:options];
    return NO;
}
 
- (void)onOpenURL:(NSNotification*)notification {
    NSLog(@"got onOpenURL = %@", notification.userInfo);
    NSURL *url = notification.userInfo[@"url"];
    NSString *sourceApplication = notification.userInfo[@"sourceApplication"];
     
    if (sourceApplication == nil) {
        sourceApplication = @"";
    }
     
    if (url != nil) {
        [[AppsFlyerAttribution shared] handleOpenUrl:url sourceApplication:sourceApplication annotation:nil];
    }
     
}
 
- (void)didReceiveRemoteNotification:(NSNotification*)notification {
    NSLog(@"got didReceiveRemoteNotification = %@", notification.userInfo);
    [[AppsFlyerLib shared] handlePushNotification:notification.userInfo];
}
 
@end
 
IMPL_APP_CONTROLLER_SUBCLASS(AppsFlyerAppController)
 
 
/**
Note if you would not like to use IMPL_APP_CONTROLLER_SUBCLASS you can replace it with the code below.
 &lt;code>
 +(void)load
 {
 [AppsFlyerAppController plugin];
 }
  
 // Singleton accessor.
 + (AppsFlyerAppController *)plugin
 {
 static AppsFlyerAppController *sharedInstance = nil;
 static dispatch_once_t onceToken;
  
 dispatch_once(&amp;onceToken, ^{
  
 sharedInstance = [[AppsFlyerAppController alloc] init];
 });
  
 return sharedInstance;
 }
&lt;/code>
 **/</pre>



<p class="wp-block-paragraph">首先TapTap的跳转登录是通过修改UnityAppController中的openURL等相关函数实现的；</p>



<p class="wp-block-paragraph">而该SDK通过&nbsp;IMPL_APP_CONTROLLER_SUBCLASS(AppsFlyerAppController) 将自己的AppController文件替换掉了原本的UnityAppController，导致原先TapTap SDK的代码没有生效，以至于无法接收到登录回调，最后无法登录。。</p>



<h3 class="wp-block-heading" id="id-记一次由接入AppsFlyerSDK导致海外iOS无法登录的问题-解决方法"><span id="%e8%a7%a3%e5%86%b3%e6%96%b9%e6%b3%95">解决方法</span></h3>



<ol class="wp-block-list"><li>删除AppsFlyer；</li><li>在AppsFlyer的AppController中调用原UnityAppController的函数；</li></ol>



<p class="wp-block-paragraph">这两种方法都可以，国内版不需要AppsFlyer所以可以删掉。但是海外版还是需要该SDK的。</p>



<p class="wp-block-paragraph">考虑到该AppController类继承自UnityAppController，最终采用了方法2，以openURL函数为例：</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">-(BOOL) application:(UIApplication *)application openURL:(NSURL *)url options:(NSDictionary *)options {
NSLog(@"got openUrl: %@",url);
[super application:application openURL:url options:options];
[[AppsFlyerAttribution shared] handleOpenUrl:url options:options];
return NO;
}</pre>



<p class="wp-block-paragraph">增加&nbsp;[super application:application openURL:url options:options];&nbsp;语句，重新调用了父类UnityAppController的openURL函数，使得游戏可以正常接受来自TapSDK的信息，成功登录。</p>



<p class="wp-block-paragraph">同时，假如以后不需要该SDK了，可以直接删除而不用修改代码（删除之后会默认使用原有的UnityAppController）。</p>



<h3 class="wp-block-heading" id="id-记一次由接入AppsFlyerSDK导致海外iOS无法登录的问题-反思"><span id="%e5%8f%8d%e6%80%9d">反思</span></h3>



<p class="wp-block-paragraph">我的锅。。其实以我的计划来说，AppsFlyer已经接入游戏，但还没有经过测试，这次出版本来不及，所以我加了开关给关掉了。</p>



<p class="wp-block-paragraph">可是万万没想到AppsFlyer SDK还有替换AppController这一手，我接了那么多SDK第一次见，还是我太naive了。。。</p>



<p class="wp-block-paragraph">辛苦几位同事大半夜还在找bug，这里说声抱歉 Orz<a href="https://confluence.xindong.com/pages/viewpage.action?pageId=193701575"></a></p>
<p><a href="https://vki.cc/greattruth/experience/861/">记一次由接入AppsFlyer导致海外iOS无法登录的问题</a>最先出现在<a href="https://vki.cc">初晴神社</a>。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://vki.cc/greattruth/experience/861/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>iOS 开发踩坑日记</title>
		<link>https://vki.cc/greattruth/846/</link>
					<comments>https://vki.cc/greattruth/846/#respond</comments>
		
		<dc:creator><![CDATA[白露]]></dc:creator>
		<pubDate>Wed, 14 Apr 2021 07:08:57 +0000</pubDate>
				<category><![CDATA[咸鱼心得]]></category>
		<category><![CDATA[朝闻道]]></category>
		<category><![CDATA[iOS]]></category>
		<guid isPermaLink="false">https://vki.cc/?p=846</guid>

					<description><![CDATA[<p>XXX must be used from main thread only. 问题场景：某游戏在播放视频时闪退。连接 pc 后使用 XCode 运行，最终停在代码段： 并且提示 XXX must be used from main thread only. 该错误会导致 app 闪退，如&#8230;</p>
<p><a href="https://vki.cc/greattruth/846/">iOS 开发踩坑日记</a>最先出现在<a href="https://vki.cc">初晴神社</a>。</p>
]]></description>
										<content:encoded><![CDATA[
<h4 id="xxx-must-be-used-from-main-thread-only" class="has-vivid-red-color has-text-color wp-block-heading">XXX must be used from main thread only.</h4>



<p class="has-black-color has-text-color wp-block-paragraph">问题场景：某游戏在播放视频时闪退。连接 pc 后使用 XCode 运行，最终停在代码段：</p>



<figure class="wp-block-image size-large"><img  loading="lazy"  decoding="async"  width="960"  height="192"  src="https://oss.vki.cc/wp-content/uploads/2021/04/1618383962-image.png"  alt=""  class="wp-image-847"  title="image.png"  srcset="https://cdn.vki.cc/wp-content/uploads/2021/04/1618383962-image.png 960w, https://cdn.vki.cc/wp-content/uploads/2021/04/1618383962-image-800x160.png 800w, https://cdn.vki.cc/wp-content/uploads/2021/04/1618383962-image-120x24.png 120w, https://cdn.vki.cc/wp-content/uploads/2021/04/1618383962-image-90x18.png 90w, https://cdn.vki.cc/wp-content/uploads/2021/04/1618383962-image-320x64.png 320w, https://cdn.vki.cc/wp-content/uploads/2021/04/1618383962-image-560x112.png 560w"  sizes="auto, (max-width: 960px) 100vw, 960px" ></figure>



<p class="has-black-color has-text-color wp-block-paragraph">并且提示 XXX must be used from main thread only.</p>



<p class="has-black-color has-text-color wp-block-paragraph">该错误会导致 app 闪退，如题，应该将语句放到主线程中执行。</p>



<p class="has-black-color has-text-color wp-block-paragraph">这里使用 dispatch_async 分发线程：</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">dispatch_async(dispatch_get_main_queue(), ^{
        // 代码放这里。
    });</pre>



<p class="has-black-color has-text-color wp-block-paragraph">最终代码：</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">// 自動消灯を無効にする
void UI_SetIdleTimerDisabled(bool b)
{
    dispatch_async(dispatch_get_main_queue(), ^{
        // 主线程执行。
        [[UIApplication sharedApplication] setIdleTimerDisabled:b];
    });
}</pre>



<h4 id="%e7%bb%8f%e8%bf%87-apple-developer-enterprise-program-%e9%87%8d%e7%ad%be%e7%9a%84-app-%e9%97%aa%e9%80%80" class="has-vivid-red-color has-text-color wp-block-heading">经过 Apple Developer Enterprise Program 重签的 app 闪退</h4>



<p class="wp-block-paragraph">Apple Developer Enterprise Program 可让大型组织开发内部专属的 app 并向其员工部署。此计划仅适用于需要使用安全的内部系统或经由移动设备管理解决方案，向员工私密地直接分发的特例情况。</p>



<p class="has-black-color has-text-color wp-block-paragraph">一般游戏公司都会使用这个服务，绕过 APP Store 来给测试设备安装 app。我们公司也是，某天发现一个游戏上传到该平台重签后，在手机上出现闪退情况，但是使用 PC 连接真机安装没有问题。</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">// 报错如下。
Bootstrapping failed for &lt;FBApplicationProcess: 0x10a253640; application&lt;com.xd.collections>:&lt;invalid>> with error: &lt;NSError: 0x28071d920; domain: RBSRequestErrorDomain; code: 5; reason: "Launch failed."> {
    underlyingError = &lt;NSError: 0x28071d140; domain: NSPOSIXErrorDomain; code: 88>;
}</pre>



<p class="has-luminous-vivid-orange-color has-text-color has-medium-font-size wp-block-paragraph"><strong>解决方法：关闭 bitcode &#8230;</strong></p>



<p class="wp-block-paragraph">说实话，我确实没往这方面想。。我以为 Apple Developer Enterprise Program 只是单纯的换个证书重签名，就像 apk 重签那样。</p>
<p><a href="https://vki.cc/greattruth/846/">iOS 开发踩坑日记</a>最先出现在<a href="https://vki.cc">初晴神社</a>。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://vki.cc/greattruth/846/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>网站迁移笔记</title>
		<link>https://vki.cc/greattruth/experience/819/</link>
					<comments>https://vki.cc/greattruth/experience/819/#respond</comments>
		
		<dc:creator><![CDATA[白露]]></dc:creator>
		<pubDate>Fri, 02 Apr 2021 04:23:27 +0000</pubDate>
				<category><![CDATA[咸鱼心得]]></category>
		<category><![CDATA[朝闻道]]></category>
		<category><![CDATA[CDN]]></category>
		<category><![CDATA[Nginx]]></category>
		<category><![CDATA[WordPress]]></category>
		<guid isPermaLink="false">https://vki.cc/?p=819</guid>

					<description><![CDATA[<p>为了网站访问速度以及之后展示摄影照片，近期把服务器从香港迁移到了上海。过程中踩了不少坑，以此来记录一下。 运行环境 网站基于WordPress搭建，WordPress需要PHP，MySQL以及Apache（或者Nginx）。 旧服务&#8230;</p>
<p><a href="https://vki.cc/greattruth/experience/819/">网站迁移笔记</a>最先出现在<a href="https://vki.cc">初晴神社</a>。</p>
]]></description>
										<content:encoded><![CDATA[
<pre class="wp-block-preformatted">为了网站访问速度以及之后展示摄影照片，近期把服务器从香港迁移到了上海。过程中踩了不少坑，以此来记录一下。</pre>



<h2 class="wp-block-heading" id="5T9kc"><span id="%e8%bf%90%e8%a1%8c%e7%8e%af%e5%a2%83">运行环境</span></h2>



<p class="has-black-color has-text-color wp-block-paragraph">网站基于WordPress搭建，WordPress需要PHP，MySQL以及Apache（或者Nginx）。</p>



<p class="has-black-color has-text-color wp-block-paragraph">旧服务器使用CentOS加宝塔面板，使用简单方便，堪称傻瓜式操作。。缺点就是占用资源，还会可能出现漏洞，推荐小白或者不喜欢折腾的用户使用。</p>



<figure class="wp-block-table"><table><tbody><tr><td class="has-text-align-center" data-align="center">项目</td><td class="has-text-align-center" data-align="center">旧服务器</td><td class="has-text-align-center" data-align="center">新服务器</td></tr><tr><td class="has-text-align-center" data-align="center">配置</td><td class="has-text-align-center" data-align="center">1C1G30M</td><td class="has-text-align-center" data-align="center">1C2G5M</td></tr><tr><td class="has-text-align-center" data-align="center">系统</td><td class="has-text-align-center" data-align="center">CentOS（BT-Panel）</td><td class="has-text-align-center" data-align="center">Aliyun Linux 2.19.03</td></tr><tr><td class="has-text-align-center" data-align="center">地理位置</td><td class="has-text-align-center" data-align="center">香港</td><td class="has-text-align-center" data-align="center">上海</td></tr><tr><td class="has-text-align-center" data-align="center">WordPress</td><td class="has-text-align-center" data-align="center">5.7</td></tr><tr><td class="has-text-align-center" data-align="center">PHP</td><td class="has-text-align-center" data-align="center">7.4</td></tr><tr><td class="has-text-align-center" data-align="center">Nginx</td><td class="has-text-align-center" data-align="center">1.18</td><td class="has-text-align-center" data-align="center">1.16</td></tr><tr><td class="has-text-align-center" data-align="center">MySQL</td><td class="has-text-align-center" data-align="center">MySQL 5.6</td><td class="has-text-align-center" data-align="center">阿里云 云数据库RDS（MySQL 8.0）</td></tr><tr><td class="has-text-align-center" data-align="center">OSS</td><td class="has-text-align-center" data-align="center">阿里云OSS</td></tr><tr><td class="has-text-align-center" data-align="center">CDN</td><td class="has-text-align-center" data-align="center">无</td><td class="has-text-align-center" data-align="center">阿里云CDN/DCDN加速</td></tr></tbody></table><figcaption>对比</figcaption></figure>



<p class="has-black-color has-text-color wp-block-paragraph">相较于旧的，新服务器移除了宝塔面板，系统和服务更加精简，且使用了CDN加速，缺点就是大陆服务器需要备案，一套流程走下来费时费力。。</p>



<h2 class="wp-block-heading" id="LJiw8"><span id="%e5%ae%89%e8%a3%85nginx">安装Nginx</span></h2>



<p class="has-black-color has-text-color wp-block-paragraph">注意：以下内容基于Aliyun Linux系统，不过CenOS应该也能用。</p>



<p class="has-black-color has-text-color wp-block-paragraph">老规矩，先更新软件。</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">yum update</pre>



<p class="has-black-color has-text-color wp-block-paragraph">自带的yum源就有nginx，直接安装即可。</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">yum -y install nginx</pre>



<figure class="wp-block-image size-large"><img  loading="lazy"  decoding="async"  width="1024"  height="442"  src="https://oss.vki.cc/wp-content/uploads/2021/04/1617359633-20210402-11.png"  alt=""  class="wp-image-843"  title="image.png" ></figure>



<p class="has-black-color has-text-color wp-block-paragraph">安装完成之后使用以下命令添加到开机启动项。</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">systemctl enable nginx</pre>



<p class="has-black-color has-text-color wp-block-paragraph">然后启动nginx服务。</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">systemctl start nginx</pre>



<p class="has-black-color has-text-color wp-block-paragraph">其他命令：</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">// 关闭Nginx
systemctl stop nginx

// 重启Nginx
systemctl restart nginx</pre>



<p class="has-black-color has-text-color wp-block-paragraph">启动Nginx之后，打开浏览器，地址栏输入服务器的IP并回车，不出意外就能看到成功安装Nginx的提示了。</p>



<figure class="wp-block-image size-large"><img  loading="lazy"  decoding="async"  width="602"  height="222"  src="https://oss.vki.cc/wp-content/uploads/2021/04/1617336538-2021-04-02-2.png"  alt=""  class="wp-image-821"  title="image.png" ></figure>



<h2 class="wp-block-heading" id="rMSMx"><span id="%e5%ae%89%e8%a3%85php">安装PHP</span></h2>



<p class="has-black-color has-text-color wp-block-paragraph">这一步稍显麻烦，首先查看是否有适合的php版本：</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">yum list php</pre>



<p class="has-black-color has-text-color wp-block-paragraph">然后发现只有5.4的版本，这肯定是不行的，推荐使用php 7以上。</p>



<figure class="wp-block-image size-large"><img  loading="lazy"  decoding="async"  width="1038"  height="78"  src="https://oss.vki.cc/wp-content/uploads/2021/04/1617336539-2021-04-02-3.png"  alt=""  class="wp-image-822"  title="image.png" ></figure>



<p class="wp-block-paragraph">安装remi</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">yum install http://rpms.remirepo.net/enterprise/remi-release-7.rpm</pre>



<p class="has-black-color has-text-color wp-block-paragraph">重建yum缓存，可以提高检索速度。</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">yum clean all
yum makecache</pre>



<p class="has-black-color has-text-color wp-block-paragraph">进入正题，安装php全家桶。</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">yum install -y php74-php-fpm php74-php-cli php74-php-bcmath php74-php-gd php74-php-json php74-php-mbstring php74-php-mcrypt php74-php-mysqlnd php74-php-opcache php74-php-pdo php74-php-pecl-crypto php74-php-pecl-mcrypt php74-php-pecl-geoip php74-php-recode php74-php-snmp php74-php-soap php74-php-xmll</pre>



<p class="has-black-color has-text-color wp-block-paragraph">注意，这行是安装php7.4的。如果想安装7.3版本，将上述命令中的74替换成73即可。然后</p>



<p class="has-black-color has-text-color wp-block-paragraph">然后开机启动。</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">systemctl enable php74-php-fpm</pre>



<p class="has-black-color has-text-color wp-block-paragraph">启动php服务。</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">systemctl start php74-php-fpm</pre>



<p class="has-black-color has-text-color wp-block-paragraph">这里面最坑的就是php74-php-fpm这个服务名。。特么的试了好多次才试出来，不知道是不是系统是阿里云的原因。</p>



<p class="has-black-color has-text-color wp-block-paragraph">如果是CentOS，这个服务名应该是php-fpm.service</p>



<h2 class="wp-block-heading" id="KA9tT"><span id="%e9%85%8d%e7%bd%aenginx">配置Nginx</span></h2>



<p class="has-black-color has-text-color wp-block-paragraph">到这一部，已经装好了Nginx和PHP，但是还有一些问题。</p>



<p class="has-black-color has-text-color wp-block-paragraph">新建一个名为info.php的文件，内容如下：</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">&amp;lt;?php 
    phpinfo(); 
?&amp;gt;</pre>



<p class="has-black-color has-text-color wp-block-paragraph">将该文件上传到网站根目录/usr/share/nginx/html，然后使用浏览器打开 服务器IP/info.php ，正常情况下会显示php的详细信息，但是现在会发现浏览器把info.php文件下载下来了。。</p>



<p class="has-black-color has-text-color wp-block-paragraph">原因是Nginx配置错误。</p>



<p class="has-black-color has-text-color wp-block-paragraph">打开nginx的配置文件 /etc/nginx/ngin.conf，里面有如下配置：</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">    server {
        listen       80 default_server;
        listen       [::]:80 default_server;
        server_name  _;
        root         /usr/share/nginx/html;

        # Load configuration files for the default server block.
        include /etc/nginx/default.d/*.conf;

        location / {
        }

        error_page 404 /404.html;
        location = /404.html {
        }

        error_page 500 502 503 504 /50x.html;
        location = /50x.html {
        }
    }</pre>



<p class="has-black-color has-text-color wp-block-paragraph">其中root &nbsp; &nbsp; &nbsp; &nbsp; /usr/share/nginx/html; 就是指网站的根目录。</p>



<p class="has-black-color has-text-color wp-block-paragraph">在server { } 区块内添加如下代码：</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">location ~ .*\.php$ {
  fastcgi_pass 127.0.0.1:9000;
  fastcgi_index index.php;
  fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
  include fastcgi_params;
  }</pre>



<p class="has-black-color has-text-color wp-block-paragraph">这段代码意思是让php后缀的文件传入后端php服务处理，添加之后重启nginx服务。</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">systemctl restart nginx</pre>



<p class="has-black-color has-text-color wp-block-paragraph">再次访问 IP/info.php，页面也正常显示。</p>



<figure class="wp-block-image size-large"><img  loading="lazy"  decoding="async"  width="1892"  height="762"  src="https://oss.vki.cc/wp-content/uploads/2021/04/1617336540-2021-04-02-4.png"  alt=""  class="wp-image-823"  title="image.png" ></figure>



<p class="has-black-color has-text-color wp-block-paragraph">其中<strong>Loaded Configuration File</strong>是php的配置信息目录，以后修改WordPress的文件上传大小限制会用得到。</p>



<h2 class="wp-block-heading" id="Mm4bu"><span id="%e9%85%8d%e7%bd%aemysql%e6%95%b0%e6%8d%ae%e5%ba%93">配置MySQL数据库</span></h2>



<p class="has-black-color has-text-color wp-block-paragraph">为了追求安全和性能，这次不再在本地搭建MySQL，转而使用阿里云的数据库服务。</p>



<p class="has-black-color has-text-color wp-block-paragraph">登录阿里云RDS，创建完用户和数据库之后登陆DMS后台，将备份的sql文件进行导入。</p>



<p class="has-black-color has-text-color wp-block-paragraph">（如果是第一次搭建网站，这一步可以略过）</p>



<figure class="wp-block-image size-large"><img  loading="lazy"  decoding="async"  width="1526"  height="1136"  src="https://oss.vki.cc/wp-content/uploads/2021/04/1617336541-2021-04-02-5.png"  alt=""  class="wp-image-824"  title="image.png" ></figure>



<p class="has-black-color has-text-color wp-block-paragraph">如果新网站的域名和旧的不一样，或者域名还未解析（比如我的还在备案，无法解析到大陆服务器），就要手动改一下数据库里存储的网站地址，不然打开新网站会自动跳转到旧网站。</p>



<p class="has-black-color has-text-color wp-block-paragraph">在DMS后台使用以下命令查询：</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">SELECT * FROM `wp_options`</pre>



<figure class="wp-block-image size-large"><img  loading="lazy"  decoding="async"  width="1818"  height="690"  src="https://oss.vki.cc/wp-content/uploads/2021/04/1617336542-2021-04-02-6.png"  alt=""  class="wp-image-825"  title="image.png" ></figure>



<p class="has-black-color has-text-color wp-block-paragraph">点击开启编辑，将siteurl和home字段改为新的服务器地址。</p>



<p class="has-black-color has-text-color wp-block-paragraph">如果用的其他数据库管理a系统没有可视化界面，也可以使用命令行修改：</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">UPDATE `数据库名`.`wp_options` SET `option_value`='新网站地址' WHERE `option_id`=1;
UPDATE `数据库名`.`wp_options` SET `option_value`='新网站地址' WHERE `option_id`=2;</pre>



<p class="has-black-color has-text-color wp-block-paragraph">另外记得将服务器IP地址加入白名单，否则无法访问。</p>



<figure class="wp-block-image size-large"><img  loading="lazy"  decoding="async"  width="1050"  height="1372"  src="https://oss.vki.cc/wp-content/uploads/2021/04/1617336543-2021-04-02-7.png"  alt=""  class="wp-image-826"  title="image.png" ></figure>



<p class="wp-block-paragraph">设置完毕之后可以在服务器上ping一下数据库地址，如果能ping通，代表能正常访问。</p>



<h2 class="wp-block-heading" id="kQrsJ"><span id="%e5%af%bc%e5%85%a5wordpress">导入WordPress</span></h2>



<p class="has-black-color has-text-color wp-block-paragraph">打开备份好的WordPress目录里的wp-config.php文件，修改数据库配置。</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">// ** MySQL settings - You can get this info from your web host ** //
/** The name of the database for WordPress */
define( 'DB_NAME', '数据库名' );

/** MySQL database username */
define( 'DB_USER', '用户名' );

/** MySQL database password */
define( 'DB_PASSWORD', '密码' );

/** MySQL hostname */
define( 'DB_HOST', '数据库地址' );

/** Database Charset to use in creating database tables. */
define( 'DB_CHARSET', 'utf8mb4' );

/** The Database Collate type. Don't change this if in doubt. */
define( 'DB_COLLATE', '' );</pre>



<p class="has-black-color has-text-color wp-block-paragraph">然后将所有文件上传到服务器根目录。</p>



<p class="has-black-color has-text-color wp-block-paragraph">（如果是第一次搭建网站，直接使用WordPress官网下载的文件即可）</p>



<p class="has-black-color has-text-color wp-block-paragraph">上传完毕后使用浏览器打开网址，可能会出现403错误，这个时候需要继续修改nginx配置。</p>



<p class="has-black-color has-text-color wp-block-paragraph">打开nginx.conf，在location / {&nbsp;} 区块内添加代码如下：</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">location / {
    index index.php index.html index.htm;
}</pre>



<p class="has-black-color has-text-color wp-block-paragraph">然后重启nginx，再次打开网址就正常了。</p>



<p class="has-black-color has-text-color wp-block-paragraph">如果什么都看不到，可能是数据库配置出了问题，建议删除网站根目录的wp-config.php文件，重新打开网址一步步配置。</p>



<h2 class="wp-block-heading" id="RW7LU"><span id="%e5%85%b6%e4%bb%96%e7%9a%84%e5%9d%91">其他的坑</span></h2>



<p class="has-vivid-red-color has-text-color wp-block-paragraph">WordPress-设置-固定链接只能只用朴素类型，其他其他的类型时打开文章或分类出现403，404错误。</p>



<p class="has-black-color has-text-color wp-block-paragraph">这个也是因为nginx配置问题，</p>



<p class="has-black-color has-text-color wp-block-paragraph">打开nginx.conf，在location / {&nbsp;} 区块内添加代码如下：</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">location / {
    index index.php index.html index.htm;
  try_files $uri $uri/ /index.php?$args;
}</pre>



<p class="has-black-color has-text-color wp-block-paragraph">然后重启nginx就可以了。</p>



<p class="has-vivid-red-color has-text-color wp-block-paragraph">更新插件/系统或上传文件提示没有权限，要输入FTP账号。</p>



<p class="has-black-color has-text-color wp-block-paragraph">这个就更坑了。。本质上是Linux系统的权限问题，WordPress没有访问自身文件夹的权限，解决方法有两个：</p>



<p class="has-black-color has-text-color wp-block-paragraph">1.将网站根目录权限改为777；</p>



<p class="has-black-color has-text-color wp-block-paragraph">网上很多教程都是这么建议的，讲道理，但凡有点安全方面的尝试，就不会这么做。</p>



<p class="has-black-color has-text-color wp-block-paragraph">2.创建FTP服务和账号；</p>



<p class="has-black-color has-text-color wp-block-paragraph">这个可以用，但是太麻烦了。。</p>



<p class="has-black-color has-text-color wp-block-paragraph">2.将网站根目录所有者改为WordPress</p>



<p class="has-black-color has-text-color wp-block-paragraph">简单好用，就它了。蛋疼的事情来了，我需要知道WordPress的user名是什么，但这个user名既不是WordPress又不是root，更不是nginx，也不是网上说的www，www-data。</p>



<p class="has-black-color has-text-color wp-block-paragraph">最终试了一圈，嘿！居然是 apache</p>



<p class="has-black-color has-text-color wp-block-paragraph">apache？？？我不是用的nginx吗？？</p>



<p class="has-black-color has-text-color wp-block-paragraph">结果就是，使用chown命令赋予权限。</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">chown -R apache:apache 网站根目录</pre>



<p class="wp-block-paragraph">设置完成后可使用ll命令查看：</p>



<figure class="wp-block-image size-large"><img  loading="lazy"  decoding="async"  width="1032"  height="534"  src="https://oss.vki.cc/wp-content/uploads/2021/04/1617336544-2021-04-02-8.png"  alt=""  class="wp-image-827"  title="image.png" ></figure>



<p class="has-vivid-red-color has-text-color wp-block-paragraph">​使用www.域名方式访问网站报403错误</p>



<p class="has-black-color has-text-color wp-block-paragraph">比如我的网站域名是 vki.cc，本意是想使用 www.vki.cc 也能访问网站。于是在域名解析那里使用 cname 将 www.vki.cc 指向 vki.cc，就有了403错误。</p>



<p class="has-black-color has-text-color wp-block-paragraph">尝试了下，将解析方式改为A记录指向服务器IP就能访问了。</p>



<p class="has-black-color has-text-color wp-block-paragraph">虽然可能造成CDN无法加速，不过问题不大。</p>



<p class="has-vivid-red-color has-text-color wp-block-paragraph">主机记录为@时，记录类型 cname 与邮箱解析 MX 冲突</p>



<p class="has-black-color has-text-color wp-block-paragraph">​换句话说，如果我使用CDN服务，就不能同时<strong>以 vki.cc 访问网站</strong>和<strong>使用 vki.cc 域名邮箱</strong>。</p>



<p class="has-black-color has-text-color wp-block-paragraph">这是我没有料到的，以前没有用CDN的时候，域名直接A记录指向服务器IP，同时使用域名邮箱没有问题。现在这两个冲突，只能选一个，只好放弃邮箱了Orz</p>



<p class="has-black-color has-text-color wp-block-paragraph">搜了一下，有些域名服务商使用link表什么的骚操作可以避免冲突，不过我邮箱用得少，而且转出域名太麻烦，就懒得折腾了，有兴趣可以了解下。</p>



<h2 class="wp-block-heading" id="2luDB"><span id="cdn%e5%8a%a0%e9%80%9f">CDN加速</span></h2>



<p class="wp-block-paragraph">这个有空写个文章详细说，先放两张结构图吧，一个前，一个后。</p>



<p class="wp-block-paragraph"><strong>未使用CDN</strong></p>



<figure class="wp-block-image size-large"><img  loading="lazy"  decoding="async"  width="746"  height="361"  src="https://oss.vki.cc/wp-content/uploads/2021/04/1617336545-2021-04-02-9.png"  alt=""  class="wp-image-828"  title="image.png" ></figure>



<p class="wp-block-paragraph"><strong>使用CDN</strong></p>



<figure class="wp-block-image size-large"><img  loading="lazy"  decoding="async"  width="1440"  height="726"  src="https://oss.vki.cc/wp-content/uploads/2021/04/1617336546-2021-04-02-10.png"  alt=""  class="wp-image-829"  title="image.png" ></figure>
<p><a href="https://vki.cc/greattruth/experience/819/">网站迁移笔记</a>最先出现在<a href="https://vki.cc">初晴神社</a>。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://vki.cc/greattruth/experience/819/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>C#清除委托列表的正确用法</title>
		<link>https://vki.cc/greattruth/experience/709/</link>
					<comments>https://vki.cc/greattruth/experience/709/#comments</comments>
		
		<dc:creator><![CDATA[白露]]></dc:creator>
		<pubDate>Fri, 26 Jul 2019 06:15:32 +0000</pubDate>
				<category><![CDATA[咸鱼心得]]></category>
		<category><![CDATA[C#]]></category>
		<category><![CDATA[Unity]]></category>
		<guid isPermaLink="false">https://bye6.cc/?p=709</guid>

					<description><![CDATA[<p>近期将项目Unity版本从5.6升级到了2018.4.4，在解决完一系列可见语法错误后。遇到了个很神奇的错误，这在升级前是没有的，经过排查，最后锁定在下面的代码上（dramaEndBuildMapCallBack为Action类型）： 可&#8230;</p>
<p><a href="https://vki.cc/greattruth/experience/709/">C#清除委托列表的正确用法</a>最先出现在<a href="https://vki.cc">初晴神社</a>。</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">近期将项目Unity版本从5.6升级到了2018.4.4，在解决完一系列可见语法错误后。遇到了个很神奇的错误，这在升级前是没有的，经过排查，最后锁定在下面的代码上（dramaEndBuildMapCallBack为<strong>Action</strong>类型）：</p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">if (this.dramaEndBuildMapCallback != null)
{
	this.dramaEndBuildMapCallback();
	this.dramaEndBuildMapCallback = null;
}</pre>



<p class="wp-block-paragraph">可以看出，该块代码想表达的意思是执行完委托就清空。事实上在Unity5.6的时候它确实可以正常工作，但是随着Unity升级，.Net版本也升级到4.x，这种方式失效了。</p>



<p class="wp-block-paragraph">经过查询，找到了一个可以正常清空委托列表的方法，首先通过<strong>GetInvocationList()</strong>方法获取委托列表，然后遍历，删掉。。</p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">/// &lt;summary>
/// 清除委托列表。
/// &lt;/summary>
/// &lt;param name="action">&lt;/param>
public static void ClearDelegage(Action action)
{
	if (action != null)
	{
		Delegate[] delegates = action.GetInvocationList();
		for (int i = 0; i &lt; delegates.Length; i++)
		{
			action -= delegates[i] as Action;
		}
	}
}</pre>



<p class="wp-block-paragraph">好想吐槽一下，为什么C#没提供一个像链表Clear()那样的方法来清除委托列表呢_(:з」∠)_</p>
<p><a href="https://vki.cc/greattruth/experience/709/">C#清除委托列表的正确用法</a>最先出现在<a href="https://vki.cc">初晴神社</a>。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://vki.cc/greattruth/experience/709/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
			</item>
		<item>
		<title>Unity安卓项目踩坑日记</title>
		<link>https://vki.cc/greattruth/experience/692/</link>
					<comments>https://vki.cc/greattruth/experience/692/#comments</comments>
		
		<dc:creator><![CDATA[白露]]></dc:creator>
		<pubDate>Tue, 23 Apr 2019 09:38:57 +0000</pubDate>
				<category><![CDATA[咸鱼心得]]></category>
		<category><![CDATA[Unity]]></category>
		<category><![CDATA[Unity 3D]]></category>
		<category><![CDATA[原创]]></category>
		<category><![CDATA[安卓]]></category>
		<category><![CDATA[心得]]></category>
		<category><![CDATA[教程]]></category>
		<guid isPermaLink="false">https://bye6.cc/?p=692</guid>

					<description><![CDATA[<p>失踪人口回归~ 最近在研究游戏在安卓平台的打包，遇到了不少坑，特此写个文章记录下~ 1.Unity与安卓的交互 虽然Unity可以直接导出apk安装包，但由于有些方法，比如读取手机相册图片，获取权限等，或者是添加&#8230;</p>
<p><a href="https://vki.cc/greattruth/experience/692/">Unity安卓项目踩坑日记</a>最先出现在<a href="https://vki.cc">初晴神社</a>。</p>
]]></description>
										<content:encoded><![CDATA[<p><span style="color: #808080;"><em>失踪人口回归~</em></span></p>
<blockquote><p>最近在研究游戏在安卓平台的打包，遇到了不少坑，特此写个文章记录下~</p></blockquote>
<h2 id="1-unity%e4%b8%8e%e5%ae%89%e5%8d%93%e7%9a%84%e4%ba%a4%e4%ba%92">1.Unity与安卓的交互</h2>
<p>虽然Unity可以直接导出apk安装包，但由于有些方法，比如读取手机相册图片，获取权限等，或者是添加SDK都不能直接通过Unity实现。我们一般用Unity导出安卓工程，再通过Android Studio修改之后打出apk包。</p>
<p>通常情况下每次打包都需要修改，为了偷懒~</p>
<p>这里提供个堪称一劳永逸的解决办法，请看文章~</p>
<p><span style="color: #ff6600;"><a style="color: #ff6600;" href="https://www.jianshu.com/p/86b275da600e" target="_blank" rel="noopener noreferrer">Unity与Android交互方案优化版</a></span></p>
<p><span style="text-decoration: underline;"><a href="https://www.jianshu.com/p/86b275da600e" target="_blank" rel="noopener noreferrer">https://www.jianshu.com/p/86b275da600e</a></span></p>
<h2 id="2-android-aar%e6%96%87%e4%bb%b6%e4%bf%ae%e6%94%b9">2.Android aar文件修改</h2>
<p><span style="color: #808080;"><em>今天刚踩得坑，麻蛋！</em></span></p>
<p>不同于jar包只能包含java代码，aar包还可以保存贴图等资源，相当于一个后缀是aar的压缩包。有时候第三方sdk提供的也是aar文件，当需要修改里面的东西时，比如删掉里面一个jar<strong>。切记不能采取先解压，删除完毕再压缩回去，后缀改成aar的操作。这样修改后Android Studio用不了！！！</strong></p>
<p>正确方法是以解压的形式打开（不是解压压缩包），一般Windows平台的压缩软件都可以，然后在压缩软件里增加或删除文件操作。</p>
<h2 id="3-android-aar%e6%96%87%e4%bb%b6%e8%af%bb%e5%8f%96">3.Android aar文件读取</h2>
<p><span style="color: #808080;"><em>同上，又是个巨坑，匪夷所思！</em></span></p>
<p>我目前的项目在安卓端是这样的，把用到的sdk和需要在安卓端才能调用的方法打成aar包，然后添加到Unity导出的安卓工程里，但是不知道为什么在运行的时候会报以下的错误;</p>
<p><img  loading="lazy"  decoding="async"  class="aligncenter size-full wp-image-694"  src="https://cdn.vki.cc/wp-content/uploads/2019/04/QQ20190423-0.jpg"  alt=""  width="661"  height="83"  srcset="https://cdn.vki.cc/wp-content/uploads/2019/04/QQ20190423-0.jpg 661w, https://cdn.vki.cc/wp-content/uploads/2019/04/QQ20190423-0-300x38.jpg 300w"  sizes="auto, (max-width: 661px) 100vw, 661px" ></p>
<p>说的是BGPlayerService类重复，可是我的找遍了真个项目和aar包也只有一个类，根本没有重复的地方。研究了很久都无解，就在准备放弃治疗的时候顺便改了下build.gradle的dependencies里的语句，从</p>
<pre class="pure-highlightjs"><code class="null">dependencies {
    implementation fileTree(include: ['*.jar'], dir: 'libs')
    implementation project(':localizedappnamelib')
    implementation 'com.android.support:support-core-utils:24.2.0'
    implementation (name:'findingparadiselib-debug',ext:'aar')
}</code></pre>
<p>改成了</p>
<pre class="pure-highlightjs"><code class="null">dependencies {
    implementation (name:'findingparadiselib-debug',ext:'aar')
    implementation fileTree(include: ['*.jar'], dir: 'libs')
    implementation project(':localizedappnamelib')
    implementation 'com.android.support:support-core-utils:24.2.0'
}</code></pre>
<p>然后特么问题解决了！</p>
<p>？？？</p>
<p>我怀疑可能是implementation fileTree(include: [&#8216;*.jar&#8217;], dir: &#8216;libs&#8217;)把aar包里的jar也算进去了_(:з」∠)_</p>
<h2 id="4-%e7%94%9f%e6%88%90%e7%ad%be%e5%90%8d%e5%ae%89%e8%a3%85%e5%8c%85%e7%9a%84%e6%97%b6%e5%80%99%e6%8a%a5%e9%94%99">4.生成签名安装包的时候报错</h2>
<p>在第一次生成签名安装包的时候可能提示缺少xxx.pom（忘了是不是pom后缀了，反正差不多）和xxx.jar组件，这个报错在生成debug包的时候是不出现的。具体原因我不清楚，我猜是打包的时候需要这样依赖包，但是谷歌的官方站点没有（Android Studio提供的链接点进去404）。</p>
<p>两个解决办法，一是手动下载这些依赖包并放到指定的目录中去，不过不建议这样做，一个一个下载再放到指定目录太麻烦了。</p>
<p>第二中解决方法是使用阿里提供的站点，具体操作是将以下的代码方放到安卓工程的build.gradle文件的repositories代码块中去：</p>
<pre class="pure-highlightjs"><code class="null">maven { url 'http://maven.aliyun.com/nexus/content/groups/public/' }
maven { url 'http://maven.aliyun.com/nexus/content/repositories/jcenter' }
maven { url 'http://maven.aliyun.com/nexus/content/repositories/google' }
maven { url 'http://maven.aliyun.com/nexus/content/repositories/gradle-plugin' }</code></pre>
<p>这样打包的时候Android Studio会自动去这些站点下载需要的依赖包，问题解决。</p>
<h2 id="5-execution-failed-for-task-appndkbuild">5.Execution failed for task &#8216;:app:ndkBuild&#8217;.</h2>
<p>该问题是在对一个老游戏升级的时候出现的，表现为无法打包，报错：</p>
<pre class="pure-highlightjs"><code class="null">Process 'command 'D:\SDK\***/ndk-build.cmd'' finished with non-zero exit value 2</code></pre>
<p>上网搜索，普遍的解决方式是使用低版本的NDK，又或者是删除src/main/obj文件夹里的内容，尝试无果。</p>
<p>最终发现是build.gradle版本较老，且使用ndkBuild的方式打包，不适用于新版的Android Studio，修改之后问题解决</p>
<p>修改前：</p>
<pre class="pure-highlightjs"><code class="null">android {
    signingConfigs {
    }
    compileSdkVersion 26
    buildToolsVersion '23.0.3'
    defaultConfig {
        applicationId "com.xd.swords.and"
        minSdkVersion 19
        targetSdkVersion 26
        versionCode 18
        versionName "1.1.7"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }

    sourceSets.main.jni.srcDirs = []
    // disable automatic ndk-build call, which ignore our Android.mk
    sourceSets.main.jniLibs.srcDir 'src/main/libs'
    // call regular ndk-build(.cmd) script from app directory
    task ndkBuild(type: Exec) {
        workingDir file('src/main')
        commandLine getNdkBuildCmd()
    }
    tasks.withType(JavaCompile) {
        compileTask -&gt; compileTask.dependsOn ndkBuild
    }
    task cleanNative(type: Exec) {
        workingDir file('src/main')
        commandLine getNdkBuildCmd(), 'clean'
    }
    clean.dependsOn cleanNative
}</code></pre>
<p>修改后：</p>
<pre class="pure-highlightjs"><code class="null">android {
    signingConfigs {
    }
    compileSdkVersion 26
    buildToolsVersion '23.0.3'
    defaultConfig {
        applicationId "com.xd.swords.and"
        minSdkVersion 19
        targetSdkVersion 26
        versionCode 18
        versionName "1.1.7"
        ndk {
            abiFilters 'armeabi-v7a'
        }
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
    externalNativeBuild {
        ndkBuild {
            path "src/main/jni/Android.mk"
        }
    }

    // 下面部分代码应该也可以删掉。
    sourceSets.main.jni.srcDirs = []
    // disable automatic ndk-build call, which ignore our Android.mk
    sourceSets.main.jniLibs.srcDir 'src/main/libs'
    // call regular ndk-build(.cmd) script from app directory
}</code></pre>
<p>对比前后，主要是删除了旧的ndkBuild代码，然后设置abiFilters等。</p>
<p><a href="https://vki.cc/greattruth/experience/692/">Unity安卓项目踩坑日记</a>最先出现在<a href="https://vki.cc">初晴神社</a>。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://vki.cc/greattruth/experience/692/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
			</item>
		<item>
		<title>【设计模式】06 &#8211; 工厂模式</title>
		<link>https://vki.cc/greattruth/experience/688/</link>
					<comments>https://vki.cc/greattruth/experience/688/#comments</comments>
		
		<dc:creator><![CDATA[白露]]></dc:creator>
		<pubDate>Thu, 28 Feb 2019 11:37:48 +0000</pubDate>
				<category><![CDATA[咸鱼心得]]></category>
		<guid isPermaLink="false">https://bye6.cc/?p=688</guid>

					<description><![CDATA[<p>定义一个可以产生对象的接口，但是让子类决定要产生哪一个类的对象。工厂方法模式让类的实例化程序延迟到子类中实施。 工厂模式，顾名思义，主要用在“大量生产某个东西”的地方，并且是有组织，有规律的进行&#8230;</p>
<p><a href="https://vki.cc/greattruth/experience/688/">【设计模式】06 &#8211; 工厂模式</a>最先出现在<a href="https://vki.cc">初晴神社</a>。</p>
]]></description>
										<content:encoded><![CDATA[
<pre class="wp-block-preformatted">定义一个可以产生对象的接口，但是让子类决定要产生哪一个类的对象。工厂方法模式让类的实例化程序延迟到子类中实施。</pre>



<p class="has-black-color has-text-color wp-block-paragraph">工厂模式，顾名思义，主要用在“大量生产某个东西”的地方，并且是有组织，有规律的进行生产。在程序开发中，会遇到大量生成某个对象的情况。比如游戏中，需要生成若干敌人，这些敌人既可以是相同的，也可以是有差别的，像模型，属性等。如果每次生成的时候，都对其进行手动初始化，改下属性啦，位置什么的，肯定很麻烦，还容易出错~</p>



<p class="has-black-color has-text-color wp-block-paragraph">那么有没有一种可以将这些生成行为规范化的方法呢？</p>



<p class="has-black-color has-text-color wp-block-paragraph">那就是工厂模式~</p>



<h2 id="%e7%ae%80%e5%8d%95%e5%b7%a5%e5%8e%82%e6%a8%a1%e5%bc%8f" class="wp-block-heading">简单工厂模式</h2>



<p class="has-black-color has-text-color wp-block-paragraph">举个栗子，巧克力和香子兰肚子饿了，她们要吃好吃的。巧克力说她要吃蛋糕，香子兰说她只要一杯奶茶就够了。以编程的角度看，我们需要生成蛋糕和奶茶。常规思路代码如下:</p>



<p class="has-black-color has-text-color wp-block-paragraph">首先定义蛋糕类和奶茶类;</p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">public class Cake
{
    public Cake()
    {
        Console.WriteLine("生成蛋糕。");
    }
}

public class MilkTea
{
    public MilkTea()
    {
        Console.WriteLine("生成奶茶。");
    }
}</pre>



<p class="has-black-color has-text-color wp-block-paragraph">然后在Main方法里生成;</p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">public static void Main(string[] args)
{
    string str = Console.ReadLine();

    Cake cake;
    MilkTea milkTea;
            
    if (str == "蛋糕")
    {
        cake = new Cake();
    }
    else if (str == "奶茶")
    {
        milkTea = new MilkTea();
    }
}</pre>



<p class="has-black-color has-text-color wp-block-paragraph">可以看到，这样的代码存在不小的问题，首先是即便是生成一种，我们也要把其他所有食物进行声明，不然无法获得引用，当然这个可以通过定义一个食物父类来解决。另外如果要增加别的食物，需要修改main方法。如果食物种类少了还好，但是当达到成百上千时，结构会非常臃肿，而且违反了开闭原则。</p>



<p class="has-black-color has-text-color wp-block-paragraph">接下来我们使用<strong>简单工厂模式</strong>改写下：</p>



<p class="has-black-color has-text-color wp-block-paragraph">首先定义一个食物基类Food，并让蛋糕和奶茶继承：</p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">public abstract class Food
{
        
}

public class Cake : Food
{
    public Cake()
    {
        Console.WriteLine("生成蛋糕。");
    }
}

public class MilkTea : Food
{
    public MilkTea()
    {
        Console.WriteLine("生成奶茶。");
    }
}</pre>



<p class="has-black-color has-text-color wp-block-paragraph">然后定义工厂类用来产生食物：</p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">public class FoodFactory
{
    public Food CreateFood(string foodName)
    {
        if (String.IsNullOrEmpty(foodName))
        {
            return null;
        }

        Food food = null;
        switch (foodName)
        {
            case "蛋糕":
                food = new Cake();
                break;
            case "奶茶":
                food = new MilkTea();
                break;
                
            default:
                break;
        }

        return food;
    }
}</pre>



<p class="has-black-color has-text-color wp-block-paragraph">最后在Main方法里调用：</p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">public static void Main(string[] args)
{
    FoodFactory factory = new FoodFactory();
                
    string str = Console.ReadLine();
    Food food = factory.CreateFood(str);
}</pre>



<p class="has-black-color has-text-color wp-block-paragraph">可以看到，这次Main方法简洁了许多，通过使用简单工厂模式，成功的将“生成”和“使用”进行分离，极大的降低了耦合度。前台只需要知道通过传参获得想要的对象就够了。至于怎么生成那是后台工厂类的事。之后如果想增加新的食物，只需要改一下新建一个类并继承自Food，再switch里的参数就行了。</p>



<h2 id="%e5%b7%a5%e5%8e%82%e6%96%b9%e6%b3%95%e6%a8%a1%e5%bc%8f" class="wp-block-heading">工厂方法模式</h2>



<p class="has-black-color has-text-color wp-block-paragraph">看过了简单工厂模式，是不是觉得已经没问题了呢？然而以上只是为了演示，并没有多少功能性的代码。试想一下，在实际开发中，如果需要很多与对象创建有关的代码，而且需要创建很多对象，是不是意味着要在这个简单工厂类里编写很多方法。比如说蛋糕要调整大小，奶茶要调整甜度等等。这样的话每次都需要打开这个简单工厂类进行修改。。</p>



<p class="has-black-color has-text-color wp-block-paragraph">接下来研究<strong>工厂方法模式</strong>：向上代码：</p>



<p class="has-black-color has-text-color wp-block-paragraph">这次对工厂类进行了抽象：</p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">public interface IFoodFactory
{
    Food Create();
}

public class CakeFactory : IFoodFactory
{
    public Food Create()
    {
        return new Cake();
    }
}
    
public class MilkTeaFactory : IFoodFactory
{
    public Food Create()
    {
        return new MilkTea();
    }
}</pre>



<p class="has-black-color has-text-color wp-block-paragraph">Main里调用，这次取消了判断代码。</p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">public static void Main(string[] args)
{
    IFoodFactory cakeFactory = new CakeFactory();
    IFoodFactory milkFactory = new MilkTeaFactory();
    
    Food cake = cakeFactory.Create();
    Food milkTea = milkFactory.Create();
}</pre>



<p class="has-black-color has-text-color wp-block-paragraph">这个模式是对简单工厂模式的解耦，在工厂方法模式中一个子类对应一个工厂类，而这些工厂类都实现于一个抽象接口。相当于把原来的一个简单工厂类进行了拆分。降低了耦合度。</p>



<h2 id="%e6%8a%bd%e8%b1%a1%e5%b7%a5%e5%8e%82%e6%a8%a1%e5%bc%8f" class="wp-block-heading">抽象工厂模式</h2>



<p class="has-black-color has-text-color wp-block-paragraph">其实绝大多数情况下使用以上的模式就可以了，不过还有种情况，巧克力除了要吃蛋糕还要喝奶茶，香子兰除了喝奶茶还要吃蛋糕。这种情况利用上面的方法也不是不可以，可以如果她们还要冰淇淋，饼干，薯片等等一大堆的东西呢。总不能在main方法里写一大堆调用方法吧。</p>



<p class="has-black-color has-text-color wp-block-paragraph">接下来介绍另一种工厂模式：<strong>抽象工厂模式</strong>。先上代码：</p>



<p class="has-black-color has-text-color wp-block-paragraph">这次对工厂和单个种类的食物进行了抽象~</p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">public abstract class Cake
{
        
}

public class StrawberryCake : Cake
{
    public StrawberryCake()
    {
        Console.WriteLine("生成草莓蛋糕。");
    }
}

public abstract class MilkTea 
{
        
}

public class RedBeanMilkTea : MilkTea
{
    public RedBeanMilkTea()
    {
        Console.WriteLine("生成红豆奶茶。");
    }
}</pre>



<p class="has-black-color has-text-color wp-block-paragraph">将巧克力与香子兰当做工厂：</p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">public abstract class IFoodFactory
{
    public abstract Cake CreateCake();
    public abstract MilkTea CreateMilkTea();

    public virtual void Create()
    {
        CreateCake();
        CreateMilkTea();
    }
}

/// &lt;summary>
/// 巧克力。
/// &lt;/summary>
public class ChocolatFactory : IFoodFactory
{
    public override Cake CreateCake()
    {
        // 此处可以增加条件，生成不同的对象
        return new StrawberryCake();
    }

    public override MilkTea CreateMilkTea()
    {
        // 此处可以增加条件，生成不同的对象
        return new RedBeanMilkTea();
    }
}
    
/// &lt;summary>
/// 香子兰
/// &lt;/summary>
public class VanillaFactory : IFoodFactory
{
    public override Cake CreateCake()
    {
        // 此处可以增加条件，生成不同的对象
        return new StrawberryCake();
    }

    public override MilkTea CreateMilkTea()
    {
        // 此处可以增加条件，生成不同的对象
        return new RedBeanMilkTea();
    }
}</pre>



<p class="has-black-color has-text-color wp-block-paragraph">然后调用：</p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">public static void Main(string[] args)
{
    IFoodFactory chocolat = new ChocolatFactory();
    IFoodFactory vanilla = new VanillaFactory();
                
    chocolat.Create();
    vanilla.Create();
}</pre>
<p><a href="https://vki.cc/greattruth/experience/688/">【设计模式】06 &#8211; 工厂模式</a>最先出现在<a href="https://vki.cc">初晴神社</a>。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://vki.cc/greattruth/experience/688/feed/</wfw:commentRss>
			<slash:comments>3</slash:comments>
		
		
			</item>
		<item>
		<title>【设计模式】05 &#8211; 模板方法模式</title>
		<link>https://vki.cc/greattruth/experience/599/</link>
					<comments>https://vki.cc/greattruth/experience/599/#respond</comments>
		
		<dc:creator><![CDATA[白露]]></dc:creator>
		<pubDate>Thu, 30 Aug 2018 07:45:12 +0000</pubDate>
				<category><![CDATA[咸鱼心得]]></category>
		<category><![CDATA[C#]]></category>
		<category><![CDATA[心得]]></category>
		<category><![CDATA[设计模式]]></category>
		<guid isPermaLink="false">http://www.icemaplen.com/?p=599</guid>

					<description><![CDATA[<p>在一个操作方法中定义算法的流程，某些步骤由子类完成。模板方法模式让子类在不变更原有算法流程的情况下，还能重新定义其中的步骤。 举个栗子，现在巧克力和香子兰两个人肚子饿了，要吃饭。吃饭的过程有三&#8230;</p>
<p><a href="https://vki.cc/greattruth/experience/599/">【设计模式】05 &#8211; 模板方法模式</a>最先出现在<a href="https://vki.cc">初晴神社</a>。</p>
]]></description>
										<content:encoded><![CDATA[
<pre class="wp-block-preformatted">在一个操作方法中定义算法的流程，某些步骤由子类完成。模板方法模式让子类在不变更原有算法流程的情况下，还能重新定义其中的步骤。</pre>



<p class="has-black-color has-text-color wp-block-paragraph">举个栗子，现在巧克力和香子兰两个人肚子饿了，要吃饭。吃饭的过程有三个步骤：</p>



<p class="has-black-color has-text-color wp-block-paragraph">1.吃配菜；</p>



<p class="has-black-color has-text-color wp-block-paragraph">2.吃主食；</p>



<p class="has-black-color has-text-color wp-block-paragraph">3.喝饮料；</p>



<p class="has-black-color has-text-color wp-block-paragraph">虽然吃饭步骤一样，可是两个人吃的东西却不同。巧克力配菜要吃西红柿炒鸡蛋，主食要吃馒头，饮料要喝可乐；而香子兰要吃麻婆豆腐，米饭和牛奶。</p>



<p class="has-black-color has-text-color wp-block-paragraph">如果用程序表示的话，可以看出二者使用相同的验算流程，且实现的内容不一样。一般情况下我们会分别给二者写一套代码，这样做没有什么问题。可是如果后续开发需要添加新的功能呢，比如说红豆，时雨也要吃饭饭。或者她们想改变下吃饭的流程，先吃主食再吃配菜呢？是不是要为每套代码做出修改？显而易见这样的实现不太好，那么有没有一种好的实现方法呢？</p>



<p class="has-black-color has-text-color wp-block-paragraph">答案就是使用<strong>模板方法模式（Template）</strong></p>



<p class="has-black-color has-text-color wp-block-paragraph"><em><span style="color: #808080;">偷来的结构图_(:з」∠)_</span></em></p>



<div class="wp-block-image"><figure class="aligncenter"><a href="https://cdn.vki.cc/wp-content/uploads/2018/08/944365-5c903d791a9d001f.png" target="_blank" rel="noopener"><img  loading="lazy"  decoding="async"  width="500"  height="480"  src="https://cdn.vki.cc/wp-content/uploads/2018/08/944365-5c903d791a9d001f.png"  alt=""  class="wp-image-602"  srcset="https://cdn.vki.cc/wp-content/uploads/2018/08/944365-5c903d791a9d001f.png 500w, https://cdn.vki.cc/wp-content/uploads/2018/08/944365-5c903d791a9d001f-300x288.png 300w"  sizes="auto, (max-width: 500px) 100vw, 500px" ></a></figure></div>



<p class="wp-block-paragraph"><strong>1.定义一个算法的流程，即是很明确的定义算法的每一个步骤，并写在父类的方法中，而每一个步骤都可以是一个方法的调用。</strong></p>



<p class="wp-block-paragraph"><strong>2.某些步骤由子类完成。</strong></p>



<h2 id="%e4%bb%a3%e7%a0%81%e5%ae%9e%e7%8e%b0" class="wp-block-heading">代码实现</h2>



<h3 id="%e7%ae%97%e6%b3%95%e5%ae%9a%e4%b9%89%e7%b1%bb%ef%bc%9a" class="wp-block-heading">算法定义类：</h3>



<p class="wp-block-paragraph">由一个主方法和三个步骤方法组成，主方法不可重写，步骤方法必须在子类实现：</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">abstract class IEat
{
    public void Eat()
    {
        EatDishes();
        EatStaple();
        Drink();
    }

    protected abstract void EatDishes();
    protected abstract void EatStaple();
    protected abstract void Drink();
}</pre>



<h3 id="%e7%ae%97%e6%b3%95%e6%ad%a5%e9%aa%a4%e7%9a%84%e5%ae%9e%e7%8e%b0%e7%b1%bb%ef%bc%9a" class="wp-block-heading">算法步骤的实现类：</h3>



<p class="wp-block-paragraph">巧克力：</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">class ChocolatEat : IEat
{
    protected override void EatDishes()
    {
        Console.WriteLine("配菜：西红柿炒鸡蛋");
    }

    protected override void EatStaple()
    {
        Console.WriteLine("主食：馒头");
    }
    protected override void Drink()
    {
        Console.WriteLine("饮料：可乐");
    }
}</pre>



<p class="wp-block-paragraph">香子兰：</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">class VinillaEat : IEat
{
    protected override void EatDishes()
    {
        Console.WriteLine("配菜：麻婆豆腐");
    }

    protected override void EatStaple()
    {
        Console.WriteLine("主食：米饭");
    }
    protected override void Drink()
    {
        Console.WriteLine("饮料：牛奶");
    }
}</pre>



<p class="wp-block-paragraph">在Main中调用：</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">static void Main(string[] args)
{
    ChocolatEat chocolat = new ChocolatEat();
    VinillaEat vinilla = new VinillaEat();

    Console.WriteLine("巧克力吃了：");
    chocolat.Eat();

    Console.WriteLine("-------------");
    Console.WriteLine("香子兰吃了：");
    vinilla.Eat();
}</pre>



<p class="wp-block-paragraph">结果：</p>



<div class="wp-block-image"><figure class="aligncenter"><img  loading="lazy"  decoding="async"  width="318"  height="218"  src="https://cdn.vki.cc/wp-content/uploads/2018/08/20180830154449.png"  alt=""  class="wp-image-603"  srcset="https://cdn.vki.cc/wp-content/uploads/2018/08/20180830154449.png 318w, https://cdn.vki.cc/wp-content/uploads/2018/08/20180830154449-300x206.png 300w"  sizes="auto, (max-width: 318px) 100vw, 318px" ></figure></div>



<h2 id="%e7%bb%93%e8%ae%ba" class="wp-block-heading">结论</h2>



<p class="has-black-color has-text-color wp-block-paragraph">模板方法模式将可能出现重复的“算法流程”从子类提升到父类中，减少重复的发生，并且也开放子类参与算法中各个步骤的执行或优化。但如果“算法流程”开放太多的步骤，并要求子类必须全部重新实现的话，反而会造成实现的困难，也不容易维护。</p>
<p><a href="https://vki.cc/greattruth/experience/599/">【设计模式】05 &#8211; 模板方法模式</a>最先出现在<a href="https://vki.cc">初晴神社</a>。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://vki.cc/greattruth/experience/599/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>CLR中的垃圾回收（GC）机制</title>
		<link>https://vki.cc/greattruth/test/592/</link>
					<comments>https://vki.cc/greattruth/test/592/#comments</comments>
		
		<dc:creator><![CDATA[白露]]></dc:creator>
		<pubDate>Sat, 25 Aug 2018 13:40:13 +0000</pubDate>
				<category><![CDATA[咸鱼心得]]></category>
		<category><![CDATA[测试]]></category>
		<category><![CDATA[C#]]></category>
		<guid isPermaLink="false">http://www.icemaplen.com/?p=592</guid>

					<description><![CDATA[<p>眼看着同学们一个个陆续收到了offer，好方啊。。 从托管堆分配资源 在谈GC之前我们先看一看CLR中资源是如何创建的。 CLR要求所有对象都从托管堆分配。进程初始化时，CLR划出一个地址空间区域作为托管堆。并&#8230;</p>
<p><a href="https://vki.cc/greattruth/test/592/">CLR中的垃圾回收（GC）机制</a>最先出现在<a href="https://vki.cc">初晴神社</a>。</p>
]]></description>
										<content:encoded><![CDATA[<p><span style="color: #808080;"><em>眼看着同学们一个个陆续收到了offer，好方啊。。</em></span></p>
<h2 id="%e4%bb%8e%e6%89%98%e7%ae%a1%e5%a0%86%e5%88%86%e9%85%8d%e8%b5%84%e6%ba%90">从托管堆分配资源</h2>
<p><span style="color: #808080;"><em>在谈GC之前我们先看一看CLR中资源是如何创建的。</em></span></p>
<p><span style="color: #000000;">CLR要求所有对象都从<strong>托管堆</strong>分配。进程初始化时，CLR划出一个地址空间区域作为托管堆。并维护一个指针，称之为<strong>NextObjPtr，</strong>这个指针指向<strong>下一个对象在堆中的分配位置</strong>。刚开始的时候，NextObjPtr为地址空间区域的基地址。怎么说呢，有些类似栈指针，每安排一个对象，栈顶指针就会移动一个对象的大小。</span></p>
<p>当然这个堆的大小是有上限的，如果超过了上限，即这个区域被非辣鸡对象占满，CLR会尝试分配更多的区域，或者尝试垃圾回收。直到整个进程地址空间都被填满，到那个时候会抛出<strong>OutOfMemoryException</strong>异常。</p>
<p>在创建对象时我们会使用<strong>new</strong>操作符，来看下使用new后在CLR中会执行哪些操作：</p>
<p>1.首先计算类型的字段（以及从基类型继承的字段）所需的字节数。</p>
<p>2.将这个字节数加上对象开销所需的字节数。每个对象有两个开销字段：<strong>类型对象指针</strong>和<strong>同步块索引</strong>。</p>
<blockquote><p>类型对象指针在程序运行，JIT编译时会用到，用来引用和对象对应的基本类型。</p>
<p>同步块索引我暂时只发现在垃圾回收时有用（之后会讲），不知道有没有别的用途。</p></blockquote>
<p>3.CLR检查区域中是否有分配对象所需的字节数。如果有，就在NextObjPtr指针指向的地址处放入对象，并将为对象分配的字节清零。然后调用类型的构造器（对象中的this的值就是NextObjPtr），接着new操作符返回对象引用，分配完成。</p>
<p>然后NextObjPtr的值会加上对象占用的总字节数得到一个新的值，即下次分配时的初始地址。</p>
<h2 id="%e5%9e%83%e5%9c%be%e5%9b%9e%e6%94%b6%e7%ae%97%e6%b3%95">垃圾回收算法</h2>
<p><em><span style="color: #808080;">应用程序调用new操作符创建对象时，可能没有足够的空间来分配。发现空间不够，CLR就执行垃圾回收操作。</span></em></p>
<p><span style="color: #000000;">.NET垃圾回收器采用的是mark-and-compact算法。这种算法只关心引用类型的变量，并将其成为<strong>根</strong>。</span></p>
<p>CLR开始垃圾回收时，首先<strong>暂停进程中的所有线程</strong>。这样可以防止线程在CLR检查期间访问对象并更改状态。然后CLR进入垃圾回收的mark阶段，这时CLR遍历堆中所有对象，将同步块索引中的一位设为0。这表明所有对象都应该删除。然后CLR检查所有活动根，查看它们引用了哪些对象。任何根如果引用了堆上的对象，表明这个对象时可达的（还有用的），CLR将这个对象的同步块索引中的位从0改为1。一个对象被标记后，CLR会检查那个对象中的根，标记它们引用的对象。如果发现对象已经标记，就不重新检查对象的字段，这就避免了因为循环引用产生的死循环。</p>
<p>检查完成后，堆中的对象就被分成0（不可达）和1（可达）两部分，接下来CLR就要处理那些不可达的对象。即进入compact阶段，这个过程类似于磁盘碎片整理，并不是删除不可达对象，而是<strong>通过移动可达的对象，覆盖掉不可达的对象</strong>。这种做法不仅减小了应用程序的工作集，也解决了堆的空间碎片化问题。</p>
<p>不过完成上述步骤后还有一个问题需要解决。<strong>引用幸存对象的根还是指向移动前的位置</strong>，就好比你已经搬了家，但别人知道的还是你之前的地址。所以CLR还要从每个根减去所引用的对象在内存中偏移的字节数。这样就能保证每个根的引用还是和之前一样的对象。</p>
<p>compact之后，NextObjPtr指针指向最后一个幸存对象之后的位置。CLR恢复所有线程的运行。</p>
<p>如果在一次垃圾回收之后回收不了内存，而且进程中没有空间来分配新的GC区域，就说明该进程的内侧已耗尽。此时，试图分配更多内存的new操作符会抛出<strong>OutOfMemoryException</strong>异常。这可能导致操作系统终止进程并回收内存，即程序崩溃_(:з」∠)_</p>
<h2 id="%e4%bb%a3">代</h2>
<p>.NET垃圾回收的特别之处在于引入了“代”的概念，并非所有垃圾都一定会在一个垃圾回收周期中被回收。它有以下几点假设：</p>
<p>1.对象越新，生存期越短。</p>
<p>2.对象越老，生存期越长。</p>
<p>3.回收堆的一部分，速度快与回收整个堆。</p>
<p>即垃圾回收期会以更快的频率尝试清除生存时间较短的对象（新对象）。</p>
<p>总共分了0,1,2三代。新生对象被列为0代，假如第一次垃圾回收没有清除掉它，他就会提升至1代，同理直到提升到2代。相较于2代的对象，垃圾回收器会以更快的频率对第0代的对象执行垃圾回收。</p>
<p>除此之外，还有个优化效率的方法。上文提到如果一个对象被标记后，CLR会检查那个对象中的根，标记它们引用的对象。如果这个根引用了老一代的某个对象，垃圾回收器就可以忽略老对象内部的所有引用，也就能在更短时间内构造好可达对象图。当然老对象的字段也有可能引用新对象，为了确保对老对象的已更新字段进行检查，垃圾回收器利用了JIT编译器内部的一个机制，这个机制在对象的引用字段发生变化时，会设置一个对应的位标识。这样，垃圾回收器就知道自上一次垃圾回收以来，那些老对象（如果有的话）已被写入，只有字段发生变化的老对象才需检查是否引用了第0代中的任何新对象。</p>
<h2 id="%e5%9e%83%e5%9c%be%e5%9b%9e%e6%94%b6%e8%a7%a6%e5%8f%91%e6%9d%a1%e4%bb%b6">垃圾回收触发条件</h2>
<p>1.CLR检测到第0代的大小超出预算。</p>
<p>2.代码显式调用System.GC的静态Collect()方法。</p>
<p>尽量不要使用这种方法，因为在调用后会暂停所有线程的执行，所以降低性能。</p>
<p>不过在实现对性能要求高，多线程运行的方法前可以手动调用一次，尽量避免在方法运行时触发垃圾回收。</p>
<p>3.Windows报告低内存情况。</p>
<p>4.CLR卸载AppDomain。</p>
<p>5.CLR正在关闭。</p>
<h2 id="gc%e4%b8%8e%e7%bb%88%e7%bb%93%e5%99%a8">GC与终结器</h2>
<p>之前在q群看到有人问，既然有GC了为什么还需要终结器机制？</p>
<p>要回答这个问题首先要了解下托管资源和非托管资源：</p>
<ul>
<li>托管资源指的是.NET可以自动进行回收的资源，主要是指托管堆上分配的内存资源。托管资源的回收工作是不需要人工干预的，有.NET运行库在合适调用垃圾回收器进行回收。</li>
<li>非托管资源指的是.NET不知道如何回收的资源，最常见的一类非托管资源是包装操作系统资源的对象，例如文件，窗口，网络连接，数据库连接，画刷，图标等。这类资源，垃圾回收器在清理的时候会调用Object.Finalize()方法。默认情况下，方法是空的，对于非托管对象，需要在此方法中编写回收非托管资源的代码，以便垃圾回收器正确回收资源。</li>
</ul>
<p>简单说，GC只能处理堆上的资源，比如对象。而像文件句柄，数据库连接这些GC是无能为力的。需要我们手动释放它们，而终结器可以当做是保底机制，我们可以在终结器中写好清理非托管的资源的代码。假如我们忘记在代码中显式清理资源，当终结器被CLR调用的时候就可以帮我们清理。</p>
<hr />
<p>参考书籍;</p>
<p>1.《CLR via C#（第4版）》</p>
<p>2.《C# 6.0 本质论》</p>
<p><a href="https://vki.cc/greattruth/test/592/">CLR中的垃圾回收（GC）机制</a>最先出现在<a href="https://vki.cc">初晴神社</a>。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://vki.cc/greattruth/test/592/feed/</wfw:commentRss>
			<slash:comments>3</slash:comments>
		
		
			</item>
		<item>
		<title>【设计模式】04 &#8211; 外观模式</title>
		<link>https://vki.cc/greattruth/experience/563/</link>
					<comments>https://vki.cc/greattruth/experience/563/#comments</comments>
		
		<dc:creator><![CDATA[白露]]></dc:creator>
		<pubDate>Fri, 10 Aug 2018 14:45:21 +0000</pubDate>
				<category><![CDATA[咸鱼心得]]></category>
		<category><![CDATA[心得]]></category>
		<category><![CDATA[设计模式]]></category>
		<guid isPermaLink="false">http://www.icemaplen.com/?p=563</guid>

					<description><![CDATA[<p>外观模式：为子系统定义一组统一的接口，这个高级的接口会让子系统更容易被使用。 外观模式有点黑箱的意思，即把内部细节封装起来，对外只有简单的接口方便调用。比如一台电视机内部有显像系统，声音系统，&#8230;</p>
<p><a href="https://vki.cc/greattruth/experience/563/">【设计模式】04 &#8211; 外观模式</a>最先出现在<a href="https://vki.cc">初晴神社</a>。</p>
]]></description>
										<content:encoded><![CDATA[
<pre class="wp-block-preformatted">外观模式：为子系统定义一组统一的接口，这个高级的接口会让子系统更容易被使用。</pre>



<p class="has-black-color has-text-color wp-block-paragraph">外观模式有点黑箱的意思，即把内部细节封装起来，对外只有简单的接口方便调用。比如一台电视机内部有显像系统，声音系统，电子系统等等，通过这些系统协调工作才组成了一台功能正常的电视机。而当我们想看电视时，并不用关系这些系统怎么配合，只需要接通电源，打开电视机就可以看了。</p>



<p class="has-black-color has-text-color wp-block-paragraph">所以，外观模式的重点在于，<strong>它能将系统内部的互动细节隐藏以来，并提供简单方便的接口，之后客户端只需要通过这个接口，就可以操作一个复杂系统并让他们顺利运行。</strong></p>



<p class="wp-block-paragraph">惯例盗张结构图_(:з」∠)_</p>



<div class="wp-block-image"><figure class="aligncenter"><a href="https://cdn.vki.cc/wp-content/uploads/2018/08/20170901204306468.png" target="_blank" rel="noopener"><img  loading="lazy"  decoding="async"  width="1433"  height="795"  src="https://cdn.vki.cc/wp-content/uploads/2018/08/20170901204306468.png"  alt=""  class="wp-image-566"  srcset="https://cdn.vki.cc/wp-content/uploads/2018/08/20170901204306468.png 1433w, https://cdn.vki.cc/wp-content/uploads/2018/08/20170901204306468-300x166.png 300w, https://cdn.vki.cc/wp-content/uploads/2018/08/20170901204306468-768x426.png 768w, https://cdn.vki.cc/wp-content/uploads/2018/08/20170901204306468-1024x568.png 1024w, https://cdn.vki.cc/wp-content/uploads/2018/08/20170901204306468-750x416.png 750w"  sizes="auto, (max-width: 1433px) 100vw, 1433px" ></a></figure></div>



<blockquote><p>上图是使用外观模式之后的，如果未使用状态模式大概长这样：</p><figure><img  loading="lazy"  decoding="async"  class="aligncenter size-full wp-image-567"  src="https://cdn.vki.cc/wp-content/uploads/2018/08/20180810225336.png"  alt=""  width="602"  height="251"  srcset="https://cdn.vki.cc/wp-content/uploads/2018/08/20180810225336.png 602w, https://cdn.vki.cc/wp-content/uploads/2018/08/20180810225336-300x125.png 300w"  sizes="auto, (max-width: 602px) 100vw, 602px" ></figure><p>看起来貌似也没多大问题，不过当Client多了可就不一样了：</p><figure><img  loading="lazy"  decoding="async"  class="aligncenter size-full wp-image-569"  src="https://cdn.vki.cc/wp-content/uploads/2018/08/20180810225617.png"  alt=""  width="663"  height="427"  srcset="https://cdn.vki.cc/wp-content/uploads/2018/08/20180810225617.png 663w, https://cdn.vki.cc/wp-content/uploads/2018/08/20180810225617-300x193.png 300w"  sizes="auto, (max-width: 663px) 100vw, 663px" ></figure><p>这下调用结果相当混乱，很容易就晕了。。</p></blockquote>



<p class="has-black-color has-text-color wp-block-paragraph">可以看到整个系统分为三个部分：</p>



<ul class="has-luminous-vivid-orange-color has-text-color wp-block-list"><li>客户端，用户。从原本需要操作多个子系统的情况，改为只需要面对一个整合后的界面。</li><li>统一对外的界面。整合所有子系统的接口及功能，并提供接口供Client使用。&lt;br>同时接收客户端的信息后，将信息传送给子系统。</li><li>子系统。原本会由不同的Client来操作，改为只会由内部系统互相使用。</li></ul>



<h2 id="%e4%bb%a3%e7%a0%81%e6%bc%94%e7%a4%ba" class="wp-block-heading">代码演示</h2>



<h3 id="sussystem" class="wp-block-heading">SusSystem</h3>



<p class="has-black-color has-text-color wp-block-paragraph">为了方便演示每个系统都只有启动和关闭方法</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">class SubSystem1
{
    public void Start()
    {
        Console.WriteLine("SubSystem1已启动");
    }

    public void Close()
    {
        Console.WriteLine("SubSystem1已关闭");
    }
}

class SubSystem2
{
    public void Start()
    {
        Console.WriteLine("SubSystem2已启动");
    }

    public void Close()
    {
        Console.WriteLine("SubSystem2已关闭");
    }
}

class SubSystem3
{
    public void Start()
    {
        Console.WriteLine("SubSystem3已启动");
    }

    public void Close()
    {
        Console.WriteLine("SubSystem3已关闭");
    }
}</pre>



<h3 id="facade" class="wp-block-heading">Facade</h3>



<p class="has-black-color has-text-color wp-block-paragraph">Facade持有了以上单个子系统的引用，并定义了总开关方法</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">class Facade
{
    private SubSystem1 sub1;
    private SubSystem2 sub2;
    private SubSystem3 sub3;

    public Facade()
    {
        sub1 = new SubSystem1();
        sub2 = new SubSystem2();
        sub3 = new SubSystem3();
    }

    public void StartAllSubSystem()
    {
        sub1.Start();
        sub2.Start();
        sub3.Start();
    }

    public void CloseAllSubSystem()
    {
        sub1.Close();
        sub2.Close();
        sub3.Close();
    }
}</pre>



<h3 id="client" class="wp-block-heading">Client</h3>



<p class="has-black-color has-text-color wp-block-paragraph">这里我们将Main方法当做Client，只需要一条命令就可以实现打开或关闭所有子系统的方法。</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">static void Main(string[] args)
{
    Facade facade = new Facade();
    facade.StartAllSubSystem();
    Console.WriteLine();
    facade.CloseAllSubSystem();
}</pre>



<h3 id="%e8%bf%90%e8%a1%8c%e7%bb%93%e6%9e%9c%ef%bc%9a" class="wp-block-heading">运行结果：</h3>



<div class="wp-block-image"><figure class="aligncenter"><img  loading="lazy"  decoding="async"  width="231"  height="164"  src="https://cdn.vki.cc/wp-content/uploads/2018/08/20180810232529.png"  alt=""  class="wp-image-570" ></figure></div>



<h2 id="%e4%bd%bf%e7%94%a8%e5%a4%96%e8%a7%82%e6%a8%a1%e5%bc%8f%e7%9a%84%e4%bc%98%e7%bc%ba%e7%82%b9" class="wp-block-heading">使用外观模式的优缺点</h2>



<p class="has-black-color has-text-color wp-block-paragraph">将复杂的子系统沟通交给单一的一个类负责，并提供单一界面给客户端使用，使客户端减少对系统的耦合度。</p>



<p class="has-black-color has-text-color wp-block-paragraph">使用状态模式可以将Client类单一化，方便的调用一个接口，而不必关心这个接口功能背后是一堆子系统怎么实现的。</p>



<p class="has-black-color has-text-color wp-block-paragraph">减少了Client类不必要的类引用及功能整合（只需要引用一个Facade就够了），增加了Client类被复用的机会。</p>



<p class="has-black-color has-text-color wp-block-paragraph">由此可见，外观模式很利于分工开发。在分工配合时，开发者只需要了解对方负责系统的Facade接口类，不必更进一步关心对方的实现方式。</p>



<p class="has-black-color has-text-color wp-block-paragraph">缺点即是由于将所有子系统集中在Facade接口中，最终会导致Facade接口类过于庞大和难以维护。</p>



<p class="has-black-color has-text-color wp-block-paragraph">这种情况下可以重构Facade接口类，将功能相近的子系统进行整合，以减少内部系统的依赖性，或是整合其他设计模式来减少接口类大小。</p>
<p><a href="https://vki.cc/greattruth/experience/563/">【设计模式】04 &#8211; 外观模式</a>最先出现在<a href="https://vki.cc">初晴神社</a>。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://vki.cc/greattruth/experience/563/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
			</item>
	</channel>
</rss>
