一般都是在網(wǎng)頁上寫一段javascript腳本,校驗上傳文件的后綴名,有白名單形式也有黑名單形式。
查看源代碼可以看到有如下代碼對上傳文件類型進(jìn)行了限制:
<script type="text/javascript"> function checkFile() {
var file=document.getElementsByName('upload_file')[0].value;
if (file==null || file=="") {
alert("請選擇要上傳的文件!");
return false;
}
//定義允許上傳的文件類型
var allow_ext=".jpg|.png|.gif";
//提取上傳文件的類型
var ext_name=file.substring(file.lastIndexOf("."));
//判斷上傳文件類型是否允許上傳
if (allow_ext.indexOf(ext_name)==-1) {
var errMsg="該文件不允許上傳,請上傳" + allow_ext + "類型的文件,當(dāng)前文件類型為:" + ext_name;
alert(errMsg);
return false;
}
} </script>
我們可以看到對上傳文件類型進(jìn)行了限制。
或者可以不加載所有js,還可以將html源碼copy一份到本地,然后對相應(yīng)代碼進(jìn)行修改,本地提交即可。
即可上傳成功:
if (file_exists(UPLOAD_PATH)) {
$deny_ext=array('.asp','.aspx','.php','.jsp');
$file_name=trim($_FILES['upload_file']['name']);
$file_name=deldot($file_name);//刪除文件名末尾的點(diǎn)
$file_ext=strrchr($file_name, '.');
$file_ext=strtolower($file_ext); //轉(zhuǎn)換為小寫
$file_ext=str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA
$file_ext=trim($file_ext); //收尾去空
if(!in_array($file_ext, $deny_ext)) {
$temp_file=$_FILES['upload_file']['tmp_name'];
$img_path=UPLOAD_PATH.'/'.date("YmdHis").rand(1000,9999).$file_ext;
if (move_uploaded_file($temp_file,$img_path)) {
$is_upload=true;
} else {
$msg='上傳出錯!';
}
} else {
$msg='不允許上傳.asp,.aspx,.php,.jsp后綴文件!';
}
} else {
$msg=UPLOAD_PATH . '文件夾不存在,請手工創(chuàng)建!';
}
這里做了黑名單處理,我們可以通過特殊可解析后綴進(jìn)行繞過。
之前在https://www.jianshu.com/p/1ccbab572974中總結(jié)過,這里不再贅述,可以使用php3,phtml等繞過。
if (isset($_POST['submit'])) {
if (file_exists(UPLOAD_PATH)) {
$deny_ext=array(".php",".php5",".php4",".php3",".php2","php1",".html",".htm",".phtml",".pht",".pHp",".pHp5",".pHp4",".pHp3",".pHp2","pHp1",".Html",".Htm",".pHtml",".jsp",".jspa",".jspx",".jsw",".jsv",".jspf",".jtml",".jSp",".jSpx",".jSpa",".jSw",".jSv",".jSpf",".jHtml",".asp",".aspx",".asa",".asax",".ascx",".ashx",".asmx",".cer",".aSp",".aSpx",".aSa",".aSax",".aScx",".aShx",".aSmx",".cEr",".sWf",".swf");
$file_name=trim($_FILES['upload_file']['name']);
$file_name=deldot($file_name);//刪除文件名末尾的點(diǎn)
$file_ext=strrchr($file_name, '.');
$file_ext=strtolower($file_ext); //轉(zhuǎn)換為小寫
$file_ext=str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA
$file_ext=trim($file_ext); //收尾去空
if (!in_array($file_ext, $deny_ext)) {
$temp_file=$_FILES['upload_file']['tmp_name'];
$img_path=UPLOAD_PATH.'/'.$file_name;
if (move_uploaded_file($temp_file, $img_path)) {
$is_upload=true;
} else {
$msg='上傳出錯!';
}
} else {
$msg='此文件不允許上傳!';
}
} else {
$msg=UPLOAD_PATH . '文件夾不存在,請手工創(chuàng)建!';
}
}
?>
我們發(fā)現(xiàn)黑名單限制了很多后綴名,但是沒有限制.htaccess
.htaccess文件是Apache服務(wù)器中的一個配置文件,它負(fù)責(zé)相關(guān)目錄下的網(wǎng)頁配置.通過htaccess文件,可以實現(xiàn):網(wǎng)頁301重定向、自定義404頁面、改變文件擴(kuò)展名、允許/阻止特定的用戶或者目錄的訪問、禁止目錄列表、配置默認(rèn)文檔等功能。
我們需要上傳一個.htaccess文件,內(nèi)容為:
SetHandler application/x-httpd-php
這樣所有的文件都會解析為php,接下來上傳圖片馬即可
if (isset($_POST['submit'])) {
if (file_exists(UPLOAD_PATH)) {
$deny_ext=array(".php",".php5",".php4",".php3",".php2",".html",".htm",".phtml",".pht",".pHp",".pHp5",".pHp4",".pHp3",".pHp2",".Html",".Htm",".pHtml",".jsp",".jspa",".jspx",".jsw",".jsv",".jspf",".jtml",".jSp",".jSpx",".jSpa",".jSw",".jSv",".jSpf",".jHtml",".asp",".aspx",".asa",".asax",".ascx",".ashx",".asmx",".cer",".aSp",".aSpx",".aSa",".aSax",".aScx",".aShx",".aSmx",".cEr",".sWf",".swf",".htaccess");
$file_name=trim($_FILES['upload_file']['name']);
$file_name=deldot($file_name);//刪除文件名末尾的點(diǎn)
$file_ext=strrchr($file_name, '.');
$file_ext=str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA
$file_ext=trim($file_ext); //首尾去空
if (!in_array($file_ext, $deny_ext)) {
$temp_file=$_FILES['upload_file']['tmp_name'];
$img_path=UPLOAD_PATH.'/'.date("YmdHis").rand(1000,9999).$file_ext;
if (move_uploaded_file($temp_file, $img_path)) {
$is_upload=true;
} else {
$msg='上傳出錯!';
}
} else {
$msg='此文件類型不允許上傳!';
}
} else {
$msg=UPLOAD_PATH . '文件夾不存在,請手工創(chuàng)建!';
}
}
我們發(fā)現(xiàn)對.htaccess也進(jìn)行了檢測,但是沒有對大小寫進(jìn)行統(tǒng)一。
后綴名改為PHP即可
if (isset($_POST['submit'])) {
if (file_exists(UPLOAD_PATH)) {
$deny_ext=array(".php",".php5",".php4",".php3",".php2",".html",".htm",".phtml",".pht",".pHp",".pHp5",".pHp4",".pHp3",".pHp2",".Html",".Htm",".pHtml",".jsp",".jspa",".jspx",".jsw",".jsv",".jspf",".jtml",".jSp",".jSpx",".jSpa",".jSw",".jSv",".jSpf",".jHtml",".asp",".aspx",".asa",".asax",".ascx",".ashx",".asmx",".cer",".aSp",".aSpx",".aSa",".aSax",".aScx",".aShx",".aSmx",".cEr",".sWf",".swf",".htaccess");
$file_name=$_FILES['upload_file']['name'];
$file_name=deldot($file_name);//刪除文件名末尾的點(diǎn)
$file_ext=strrchr($file_name, '.');
$file_ext=strtolower($file_ext); //轉(zhuǎn)換為小寫
$file_ext=str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA
if (!in_array($file_ext, $deny_ext)) {
$temp_file=$_FILES['upload_file']['tmp_name'];
$img_path=UPLOAD_PATH.'/'.date("YmdHis").rand(1000,9999).$file_ext;
if (move_uploaded_file($temp_file,$img_path)) {
$is_upload=true;
} else {
$msg='上傳出錯!';
}
} else {
$msg='此文件不允許上傳';
}
} else {
$msg=UPLOAD_PATH . '文件夾不存在,請手工創(chuàng)建!';
}
黑名單沒有對文件中的空格進(jìn)行處理,可在后綴名中加空格繞過。
if (isset($_POST['submit'])) {
if (file_exists(UPLOAD_PATH)) {
$deny_ext=array(".php",".php5",".php4",".php3",".php2",".html",".htm",".phtml",".pht",".pHp",".pHp5",".pHp4",".pHp3",".pHp2",".Html",".Htm",".pHtml",".jsp",".jspa",".jspx",".jsw",".jsv",".jspf",".jtml",".jSp",".jSpx",".jSpa",".jSw",".jSv",".jSpf",".jHtml",".asp",".aspx",".asa",".asax",".ascx",".ashx",".asmx",".cer",".aSp",".aSpx",".aSa",".aSax",".aScx",".aShx",".aSmx",".cEr",".sWf",".swf",".htaccess");
$file_name=trim($_FILES['upload_file']['name']);
$file_ext=strrchr($file_name, '.');
$file_ext=strtolower($file_ext); //轉(zhuǎn)換為小寫
$file_ext=str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA
$file_ext=trim($file_ext); //首尾去空
if (!in_array($file_ext, $deny_ext)) {
$temp_file=$_FILES['upload_file']['tmp_name'];
$img_path=UPLOAD_PATH.'/'.$file_name;
if (move_uploaded_file($temp_file, $img_path)) {
$is_upload=true;
} else {
$msg='上傳出錯!';
}
} else {
$msg='此文件類型不允許上傳!';
}
} else {
$msg=UPLOAD_PATH . '文件夾不存在,請手工創(chuàng)建!';
}
windows會對文件中的點(diǎn)進(jìn)行自動去除,所以可以在文件末尾加點(diǎn)繞過,不再贅述
同windows特性,可在后綴名中加” ::$DATA”繞過,不再贅述
if (isset($_POST['submit'])) {
if (file_exists(UPLOAD_PATH)) {
$deny_ext=array(".php",".php5",".php4",".php3",".php2",".html",".htm",".phtml",".pht",".pHp",".pHp5",".pHp4",".pHp3",".pHp2",".Html",".Htm",".pHtml",".jsp",".jspa",".jspx",".jsw",".jsv",".jspf",".jtml",".jSp",".jSpx",".jSpa",".jSw",".jSv",".jSpf",".jHtml",".asp",".aspx",".asa",".asax",".ascx",".ashx",".asmx",".cer",".aSp",".aSpx",".aSa",".aSax",".aScx",".aShx",".aSmx",".cEr",".sWf",".swf",".htaccess");
$file_name=trim($_FILES['upload_file']['name']);
$file_name=deldot($file_name);//刪除文件名末尾的點(diǎn)
$file_ext=strrchr($file_name, '.');
$file_ext=strtolower($file_ext); //轉(zhuǎn)換為小寫
$file_ext=str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA
$file_ext=trim($file_ext); //首尾去空
if (!in_array($file_ext, $deny_ext)) {
$temp_file=$_FILES['upload_file']['tmp_name'];
$img_path=UPLOAD_PATH.'/'.$file_name;
if (move_uploaded_file($temp_file, $img_path)) {
$is_upload=true;
} else {
$msg='上傳出錯!';
}
} else {
$msg='此文件類型不允許上傳!';
}
} else {
$msg=UPLOAD_PATH . '文件夾不存在,請手工創(chuàng)建!';
}
}
這里對文件名進(jìn)行了處理,刪除了文件名末尾的點(diǎn),并且把處理過的文件名拼接到路徑中。
這里我們可以構(gòu)造文件名1.PHP. . (點(diǎn)+空格+點(diǎn)),經(jīng)過處理后,文件名變成1.PHP.,即可繞過。
if (file_exists(UPLOAD_PATH)) {
$deny_ext=array("php","php5","php4","php3","php2","html","htm","phtml","pht","jsp","jspa","jspx","jsw","jsv","jspf","jtml","asp","aspx","asa","asax","ascx","ashx","asmx","cer","swf","htaccess");
$file_name=trim($_FILES['upload_file']['name']);
$file_name=str_ireplace($deny_ext,"", $file_name);
$temp_file=$_FILES['upload_file']['tmp_name'];
$img_path=UPLOAD_PATH.'/'.$file_name;
if (move_uploaded_file($temp_file, $img_path)) {
$is_upload=true;
} else {
$msg='上傳出錯!';
}
} else {
$msg=UPLOAD_PATH . '文件夾不存在,請手工創(chuàng)建!';
}
}
這里我們可以看到將文件名替換為空,我們可以采用雙寫繞過:1.pphphp
if (isset($_POST['submit'])) {
if (file_exists(UPLOAD_PATH)) {
if (($_FILES['upload_file']['type']=='image/jpeg') || ($_FILES['upload_file']['type']=='image/png') || ($_FILES['upload_file']['type']=='image/gif')) {
$temp_file=$_FILES['upload_file']['tmp_name'];
$img_path=UPLOAD_PATH . '/' . $_FILES['upload_file']['name'];
if (move_uploaded_file($temp_file, $img_path)) {
$is_upload=true;
} else {
$msg='上傳出錯!';
}
} else {
$msg='文件類型不正確,請重新上傳!';
}
} else {
$msg=UPLOAD_PATH.'文件夾不存在,請手工創(chuàng)建!';
}
這里檢查Content-type,我們burp抓包修改即可繞過:
if(isset($_POST['submit'])){
$ext_arr=array('jpg','png','gif');
$file_ext=substr($_FILES['upload_file']['name'],strrpos($_FILES['upload_file']['name'],".")+1);
if(in_array($file_ext,$ext_arr)){
$temp_file=$_FILES['upload_file']['tmp_name'];
$img_path=$_GET['save_path']."/".rand(10, 99).date("YmdHis").".".$file_ext;
if(move_uploaded_file($temp_file,$img_path)){
$is_upload=true;
} else {
$msg='上傳出錯!';
}
} else{
$msg="只允許上傳.jpg|.png|.gif類型文件!";
}
}
$img_path直接拼接,因此可以利用%00截斷繞過
然后直接訪問/upload/1.php即可
if(isset($_POST['submit'])){
$ext_arr=array('jpg','png','gif');
$file_ext=substr($_FILES['upload_file']['name'],strrpos($_FILES['upload_file']['name'],".")+1);
if(in_array($file_ext,$ext_arr)){
$temp_file=$_FILES['upload_file']['tmp_name'];
$img_path=$_POST['save_path']."/".rand(10, 99).date("YmdHis").".".$file_ext;
if(move_uploaded_file($temp_file,$img_path)){
$is_upload=true;
} else {
$msg="上傳失敗";
}
} else {
$msg="只允許上傳.jpg|.png|.gif類型文件!";
}
}
?>
save_path是通過post傳進(jìn)來的,還是利用00截斷,但這次需要在二進(jìn)制中進(jìn)行修改,因為post不會像get對%00進(jìn)行自動解碼。
接下來訪問1.php即可
主要是檢測文件內(nèi)容開始處的文件幻數(shù),比如圖片類型的文件幻數(shù)如下,
要繞過jpg 文件幻數(shù)檢測就要在文件開頭寫上下圖的值:
Value=FF D8 FF E0 00 10 4A 46 49 46
要繞過gif 文件幻數(shù)檢測就要在文件開頭寫上下圖的值
Value=47 49 46 38 39 61
要繞過png 文件幻數(shù)檢測就要在文件開頭寫上下面的值
Value=89 50 4E 47
然后在文件幻數(shù)后面加上自己的一句話木馬代碼就行了
圖像文件相關(guān)信息檢測常用的就是getimagesize()函數(shù)
只需要把文件頭部分偽造好就ok 了,就是在幻數(shù)的基礎(chǔ)上還加了一些文件信息
有點(diǎn)像下面的結(jié)構(gòu)
GIF89a
(...some binary data for image...)
<?php phpinfo(); ?>
(... skipping the rest of binary data ...)
本次環(huán)境中的文件頭檢測,getimagesize,php_exif都可以用圖片馬繞過:
copy normal.jpg /b + shell.php /a webshell.jpg
一般是調(diào)用API 或函數(shù)去進(jìn)行文件加載測試,常見的是圖像渲染測試,甚至是進(jìn)行二次渲染(過濾效果幾乎最強(qiáng))。對渲染/加載測試的攻擊方式是代碼注入繞過,對二次渲染的攻擊方式是攻擊文件加載器自身。
可以用圖像處理軟件對一張圖片進(jìn)行代碼注入
用winhex 看數(shù)據(jù)可以分析出這類工具的原理是
在不破壞文件本身的渲染情況下找一個空白區(qū)進(jìn)行填充代碼,一般會是圖片的注釋區(qū)
對于渲染測試基本上都能繞過,畢竟本身的文件結(jié)構(gòu)是完整的
imagecreatefromjpeg二次渲染它相當(dāng)于是把原本屬于圖像數(shù)據(jù)的部分抓了出來,再用自己的API 或函數(shù)進(jìn)行重新渲染在這個過程中非圖像數(shù)據(jù)的部分直接就隔離開了
if (isset($_POST['submit'])){
// 獲得上傳文件的基本信息,文件名,類型,大小,臨時文件路徑
$filename=$_FILES['upload_file']['name'];
$filetype=$_FILES['upload_file']['type'];
$tmpname=$_FILES['upload_file']['tmp_name'];
$target_path=UPLOAD_PATH.basename($filename);
// 獲得上傳文件的擴(kuò)展名
$fileext=substr(strrchr($filename,"."),1);
//判斷文件后綴與類型,合法才進(jìn)行上傳操作
if(($fileext=="jpg") && ($filetype=="image/jpeg")){
if(move_uploaded_file($tmpname,$target_path))
{
//使用上傳的圖片生成新的圖片
$im=imagecreatefromjpeg($target_path);
if($im==false){
$msg="該文件不是jpg格式的圖片!";
@unlink($target_path);
}else{
//給新圖片指定文件名
srand(time());
$newfilename=strval(rand()).".jpg";
$newimagepath=UPLOAD_PATH.$newfilename;
imagejpeg($im,$newimagepath);
//顯示二次渲染后的圖片(使用用戶上傳圖片生成的新圖片)
$img_path=UPLOAD_PATH.$newfilename;
@unlink($target_path);
$is_upload=true;
}
} else {
$msg="上傳出錯!";
}
}else if(($fileext=="png") && ($filetype=="image/png")){
if(move_uploaded_file($tmpname,$target_path))
{
//使用上傳的圖片生成新的圖片
$im=imagecreatefrompng($target_path);
if($im==false){
$msg="該文件不是png格式的圖片!";
@unlink($target_path);
}else{
//給新圖片指定文件名
srand(time());
$newfilename=strval(rand()).".png";
$newimagepath=UPLOAD_PATH.$newfilename;
imagepng($im,$newimagepath);
//顯示二次渲染后的圖片(使用用戶上傳圖片生成的新圖片)
$img_path=UPLOAD_PATH.$newfilename;
@unlink($target_path);
$is_upload=true;
}
} else {
$msg="上傳出錯!";
}
}else if(($fileext=="gif") && ($filetype=="image/gif")){
if(move_uploaded_file($tmpname,$target_path))
{
//使用上傳的圖片生成新的圖片
$im=imagecreatefromgif($target_path);
if($im==false){
$msg="該文件不是gif格式的圖片!";
@unlink($target_path);
}else{
//給新圖片指定文件名
srand(time());
$newfilename=strval(rand()).".gif";
$newimagepath=UPLOAD_PATH.$newfilename;
imagegif($im,$newimagepath);
//顯示二次渲染后的圖片(使用用戶上傳圖片生成的新圖片)
$img_path=UPLOAD_PATH.$newfilename;
@unlink($target_path);
$is_upload=true;
}
} else {
$msg="上傳出錯!";
}
}else{
$msg="只允許上傳后綴為.jpg|.png|.gif的圖片文件!";
}
}
本關(guān)綜合判斷了后綴名、content-type,以及利用imagecreatefromgif判斷是否為gif圖片,最后再做了一次二次渲染。
得去找圖片經(jīng)過GD庫轉(zhuǎn)化后沒有改變的部分,再將未改變的部分修改為相應(yīng)的php代碼。
if(isset($_POST['submit'])){
$ext_arr=array('jpg','png','gif');
$file_name=$_FILES['upload_file']['name'];
$temp_file=$_FILES['upload_file']['tmp_name'];
$file_ext=substr($file_name,strrpos($file_name,".")+1);
$upload_file=UPLOAD_PATH . '/' . $file_name;
if(move_uploaded_file($temp_file, $upload_file)){
if(in_array($file_ext,$ext_arr)){
$img_path=UPLOAD_PATH . '/'. rand(10, 99).date("YmdHis").".".$file_ext;
rename($upload_file, $img_path);
$is_upload=true;
}else{
$msg="只允許上傳.jpg|.png|.gif類型文件!";
unlink($upload_file);
}
}else{
$msg='上傳出錯!';
}
}
這里先將文件上傳到服務(wù)器,然后通過rename修改名稱,再通過unlink刪除文件,因此可以通過條件競爭的方式在unlink之前,訪問webshell。
然后不斷訪問webshell:
上傳成功。
參考鏈接:
2人點(diǎn)贊
知識歸納
讀
introduction
Swift 是一種適用于iOS/macOS應(yīng)用開發(fā)、服務(wù)器端的編程語言。自2014年蘋果發(fā)布 Swift 語言以來,Swift5 實現(xiàn)了 ABI 穩(wěn)定性、Module 穩(wěn)定性和Library Evolution,與Objective-C(下文簡稱“OC”)相比,Swift 在開發(fā)效率、安全、編譯優(yōu)化、運(yùn)行性能和內(nèi)存管理方面具有顯著優(yōu)勢。(官方博客:https://www.swift.org/about/)
百度App 已在工程和環(huán)境上支持 Swift 開發(fā),百度搜索大前端團(tuán)隊負(fù)責(zé)搜索服務(wù)的穩(wěn)定落地,我們積極探索 Swift的應(yīng)用,希望能大幅提升開發(fā)效率和靈活性、提升端用戶的搜索體驗。然而,在實施過程中可能會遇到各種問題,例如代碼陳舊且不支持Swift,人員對Swift掌握不夠熟練、意識不足,協(xié)作方對Swift的支持不足等。
對于其他語言來說,Swift相對年輕,我們在實踐過程中整理一些常見問題及其解決方法,希望能幫助讀者更順利地使用Swift進(jìn)行編程,提高研發(fā)效率。
全文6947字,預(yù)計閱讀時間18分鐘。
GEEK TALK
01
Swift 適用場景
在決定是否引入Swift前,我們需要判斷場景是否適合。通常情況下,可以用OC的場景均適合使用Swift,但也有一些不太適合直接替換的場景,需要慎重,比如:
1、涉及OC動態(tài)性,頻繁在runtime時操作屬性和方法;
2、核心基礎(chǔ)功能,出現(xiàn)問題影響面較大的邏輯;
3、調(diào)用C++(目前Swift不能直接調(diào)用C++);
4、繼承不支持Swift組件的類。
此外,對使用OC比較久遠(yuǎn)的工程,使用Swift前也應(yīng)注意:
1、能在工程環(huán)境和單獨(dú)模塊上支持Swift;
2、模塊較多的工程,可以內(nèi)外OC和Swift混編;
3、為了避免Swift Waring帶來的潛在問題,可以把SWIFT_TREAT_WARNINGS_AS_ERRORS設(shè)置為YES,這樣警告會作為錯誤,輔助程序員更好的規(guī)范代碼;
4、模塊Module化后,要注意維護(hù) umbrella header 中的公開頭文件。
GEEK TALK
02
Swift的基本用法
2.1 Swift 的字符串為什么這么難用?
如:字符串不能通過索引取字符
2.2 try try? try! 的區(qū)別
當(dāng)你進(jìn)行文件操作時,可能會遇到需要使用try、try?和try!的情況。它們在異常處理方面有所不同。
1、使用try時,如果出現(xiàn)異常,程序會進(jìn)入異常處理流程,你可以在catch語句塊中處理這個異常。
2、使用try?時,如果發(fā)生異常,它不會進(jìn)入異常處理流程,而是返回一個可選值類型。也就是說,如果出現(xiàn)異常,它將返回nil。
3、使用try!時,它不允許異常繼續(xù)傳播。一旦出現(xiàn)異常,程序會立即停止執(zhí)行。
因此,在文件操作中,你可以根據(jù)需要選擇合適的異常處理方式。在百度App中一般推薦使用try?。
2.3 public 和 open 的區(qū)別
在Swift語言中,public和open都是用于在模塊中聲明需要對外界暴露的函數(shù)的關(guān)鍵字,但它們在繼承和公開程度上有所不同。
1、public關(guān)鍵字修飾的類在模塊外部無法被繼承。這意味著,如果其他模塊試圖繼承這個類,編譯器會報錯。這樣的限制可以保護(hù)類的完整性,但也可能限制了其在其他模塊中的可重用性。
2、open關(guān)鍵字則允許任意繼承。如果一個類被open關(guān)鍵字修飾,那么其他模塊中的類可以自由地繼承這個類,不受任何限制。這樣的公開程度使得open關(guān)鍵字修飾的類在模塊間的重用性和擴(kuò)展性更加靈活。
從公開程度上來說,public的限制比open更嚴(yán)格,所以可以說public < open,即public的公開程度比open要低。
2.4 解析JSON情況
在Swift中解析JSON的情況,如果自行將JSON轉(zhuǎn)換為字典,需要涉及到類型判斷、轉(zhuǎn)換等操作,代碼比較復(fù)雜。這時可以使用第三方庫SwiftyJSON、ObjectMapper或者系統(tǒng)庫JSONEncoder來簡化操作,提高開發(fā)效率。
2.5 UIView子類必須添加init?(coder decoder: NSCoder)的原因
1、這是NSCoding protocol定義的,遵守了NSCoding protocol的所有類必須繼承。只是有的情況會隱式繼承,而有的情況下需要顯示實現(xiàn)。
2、當(dāng)我們在子類定義了指定初始化器(包括自定義和重寫父類指定初始化器),那么必須顯示實現(xiàn)required init?(coder aDecoder: NSCoder),而其他情況下則會隱式繼承,我們可以不用理會。
3、當(dāng)我們使用storyboard實現(xiàn)界面的時候,程序會調(diào)用這個初始化器。
4、注意要去掉fatalError,fatalError的意思是無條件停止執(zhí)行并打印。
2.6 Swift類和子類的初始化
Swift的類和子類初始化涉及到兩個關(guān)鍵階段。首先,確保所有的存儲屬性被賦予初始值,然后,在實例準(zhǔn)備使用之前,可以自定義存儲屬性的值。為了確保這兩個階段成功,實施了四步安全檢查,詳細(xì)如下:
1、在完成本類所有存儲屬性賦值之后,指定構(gòu)造器才能向上代理到父類的構(gòu)造器。
2、在為繼承的屬性設(shè)置新值之前,指定構(gòu)造器必須向上代理調(diào)用父類構(gòu)造器。
3、便利構(gòu)造器必須先調(diào)用其他構(gòu)造器,再為任意屬性(包括所有同類中定義的)賦新值。
4、在第一階段構(gòu)造完成之前,構(gòu)造器不能調(diào)用任何實例方法,不能讀取任何實例屬性的值,不能引用self作為一個值。
總之,類初始化必須完成的一個任務(wù)就是讓所有的存儲屬性都有初始值(optional 除外)。如果父類有指定初始化,子類必須也有指定初始化,并且必須調(diào)用父類的其中一個指定初始化(如果是必須初始化,就是重載),并遵循兩段式初始化的規(guī)則。一個便利初始化必須調(diào)用同一類中的初始化方法(可以是另一個便利初始化,也可以是指定初始化),但最終一定會調(diào)用到一個指定初始化。便利初始化不遵循兩段式初始化的規(guī)則,不能被子類調(diào)用或者重載。
GEEK TALK
03
OC與Swift的互相調(diào)用及跳轉(zhuǎn)
3.1 組件內(nèi)Swift文件調(diào)用公開OC頭文件
3.2 組件內(nèi)Swift文件調(diào)用非公開(私有)的OC文件
組件應(yīng)該盡可能少的公開暴露頭文件,但Swift和OC混編不可避免使用OC非公開頭文件,因此我們可以采取以下措施:將Framework 中將私有頭文件聲明為一個私有 module(modulemap內(nèi)聲明),由組件內(nèi)的 Swift 源碼 import 該私有 module 即可。
1、創(chuàng)建Private.modulemap文件,以NewModule做為組件名為例,可以命名為NewModule.private.modulemap,內(nèi)容為下,module后面加_Private
framework module NewModule_Private {
header "xxxxx.h"
}
framework module NewModule_Private {
umbrella header "NewModule_Private.h"
export *
module * { export * }
}
2、在組件build settings中配置MODULEMAP_PRIVATE_FILE路徑,MODULEMAP_PRIVATE_FILE='NewModule.private.modulemap';百度App中在NewModule.boxspec中如下代碼設(shè)置路徑;
s.xcconfig={
'MODULEMAP_PRIVATE_FILE'=> '${BOX_ROOT}/NewModule.private.modulemap'
}
3、將NewModule.private.modulemap添加到工程目錄;百度App中在NewModule.boxspec中如下代碼設(shè)置路徑;
s.refer_files=[
"NewModule.private.modulemap",
]
4、將xxxxx.h設(shè)置為Private header,百度App中在NewModule.boxspec中如下代碼設(shè)置xxxxx.h到Private header
s.private_headers=[
"Sources/xxxxx.h"
]
5、調(diào)用方式
import NewModule_Private
let objectX=xxxxx()
print(objectX)
注意:
3.3 組件內(nèi)OC文件如何調(diào)用Swift文件?
3.4 OC中的向前聲明,被Swift文件引用該組件會報錯
如error: cannot find protocol definition for 'xxxProtocol'
1、暫時設(shè)置 SWIFT_TREAT_WARNINGS_AS_ERRORS 為 NO
2、import xxxProtocol 不要向前聲明
3、使用 pragma 忽略警告
3.8 Swift怎么用OC定義的宏?
下面這種定義為常量的宏可以使用
#define APP_LANGUAGE_EN @"en"
#define kNavigationBarHeight 44.0
下面帶有方法調(diào)用的宏不可以使用
#define kScreenHeight [[UIScreen mainScreen] bounds].size.height
#define kScreenWidth [[UIScreen mainScreen] bounds].size.width
下面帶有靜態(tài)常量swift不能使用,可以改成宏
static NSString *const StopTabRefreshNotifyNameHtml=@"TabRefreshNotifyNameHtml";
3.6 Swift與OC泛型的混編
@interface BBAXYZ<T> : NSObject <BBAXYZEventProtocol>
@property (nonatomic, weak) T page;
@end
這個泛型的使用導(dǎo)致無法使用Swift來繼承和開發(fā)BBAXYZ的子類。然而,這個基礎(chǔ)框架是業(yè)務(wù)的核心部分,因此,我們需要在未來支持Swift的開發(fā)。
@interface BBAXYZ : NSObject <BBAXYZEventProtocol>
- (id<BBAXYZEventProtocol>)page;
@end
然后,我們可以創(chuàng)建一個OC類來實現(xiàn)這個基礎(chǔ)框架,并讓所有的子類繼承這個OC類并實現(xiàn) page 方法,以返回適當(dāng)類型的對象。這樣,我們就可以在Swift中順利地繼承和使用這個基礎(chǔ)框架。
例如:
@interface BBAABC : BBAXYZ
- (UIViewController<BBAXYZEventProtocol> *)page;
@end
需要注意的是,雖然這樣的修改增加了輕量級的中間OC類,但它仍然實現(xiàn)了Swift與OC的混編,并允許我們在Swift中開發(fā)新的子類。這種方式既保證了代碼的兼容性,又使得我們可以繼續(xù)利用OC的優(yōu)點(diǎn)。
class BBAEFG: BBAABC {
}
3.7 Swift調(diào)用OC接口,OC的nullability標(biāo)注使用時的注意事項
問題場景:
1、OC 接口定義為 nonnull,swfit 調(diào)用時正常是當(dāng)做不可選類型使用,這時如果 OC 接口不規(guī)范返回 nil,則出現(xiàn)運(yùn)行時崩潰。
2、OC 接口未定義 nonnull 或 nullable,在這種情況下,編譯器會將 OC 的指針類型當(dāng)成是隱式解析可選類型(例如 String!)導(dǎo)入到 Swift 中。swift 調(diào)用時,OC接口如果返回 nil,將會因為隱式解析一個為 nil 的可選值導(dǎo)致運(yùn)行時崩潰。
解決方式:
1、Swift 調(diào)用 OC 接口時,如果 OC 的接口聲明為 nonnull 或未指定 nullability 時,只有明確 OC 接口不為空的情況下才可調(diào)用。
2、在 OC 環(huán)境下,將 nil 賦值給 nonnull 指針也沒有關(guān)系,編譯器只會產(chǎn)生警告。這就需要程序員按規(guī)范編寫 OC 代碼,正確使用 nullability 標(biāo)注,并增加運(yùn)行時判空的斷言,以支持向后兼容。
GEEK TALK
04
其他常見問題
4.1 Xcode編譯只提示編譯錯誤,提示信息非常少
1、檢查并保障所有依賴的組件都已經(jīng)Module化了,如配置build settings;
2、在組件中新增Swift文件(空文件也行)。
4.2 由于組件開啟了Library Evolution 導(dǎo)致的編譯報錯
錯誤顯示:@objc' instance method in extension of subclass of 'xxxxx' requires iOS 13.0.0
這是由于組件開啟了Library Evolution導(dǎo)致,開關(guān)BUILD_LIBRARY_FOR_DISTRIBUTION 控制的。
一個庫開啟了Library Evolution,在依賴鏈下游的庫中將:
1、對它的類實現(xiàn) @objc 子類。
2、對它的類使用 extension 實現(xiàn) @objc 的方法(這在 UIKit 的 protocol 中經(jīng)常會遇到)。
3、對它的類實現(xiàn)子類,并添加 @objc 方法,且方法中使用父類的類型作為參數(shù)。
這些功能是實現(xiàn)的局限。估計是需要在 Swift 運(yùn)行時有一些對應(yīng)的更改,所以只在 swift 5.1 (iOS 13)運(yùn)行時里才可以運(yùn)行。
除此之外,還會有一些編譯時沒有報錯,但運(yùn)行時 crash 或結(jié)果不正確的情況。
百度App中默認(rèn)開啟Library Evolution,一個組件關(guān)閉Library Evolution會導(dǎo)致二進(jìn)制存在不兼容的情況,暫時無解決方案。
4.3 暴露的Private頭文件如果使用雙引號import,會報警告,需要修改為尖括號
如果使用import <xxxx.h>,Project下其他Target就引用不到了,如百度App中Debug模塊引用此Private頭文件時,會報錯 not found with <angled> include, use "quotes" instead。
GEEK TALK
05
總結(jié)
以上是我們在Swift開發(fā)過程中所遇到的一些常見問題及其相應(yīng)的解決方案。然而,隨著我們不斷深入Swift開發(fā)這片浩渺的海洋,更多獨(dú)特的問題將會逐漸浮現(xiàn)。我們會持續(xù)將這些新問題以及其對應(yīng)的解決方案整理并發(fā)布出來,為廣大的開發(fā)者們提供有價值的參考。歡迎大家留言探討。
作者:路濤、艷紅
來源:微信公眾號:百度Geek說
出處:https://mp.weixin.qq.com/s/hHH4a8tOukqhd9A5SxbzYw
家都說這世上最美的就是玫瑰和女人,當(dāng)這兩者“碰撞”在一起的時候,一切就變得美不可言了。想必大家也知道很多名人的美都與玫瑰結(jié)下了不解之緣,比如超模娜塔莉亞·沃迪亞諾娃。
可能是現(xiàn)實版的灰姑娘變成公主的故事,娜塔莉亞小時候靠擺水果攤維生,14歲那年在自家水果攤忙活時,被模特星探發(fā)掘,飛往法國巴黎,開啟傳奇超模生涯。
之后的她也經(jīng)歷過離婚,執(zhí)意一個人帶著三個孩子生活。但最后和貝爾納·阿爾諾(頂級奢侈品公司LVMH )的兒子安托萬.阿爾諾結(jié)婚,目前兩人育有兩個孩子,帶著五個孩子一起生活。當(dāng)然,于我們而言,她是幸運(yùn)的。但如果沒有足夠的勤奮和自律,她可能還是一個水果攤的老板娘。
還記得當(dāng)時剛生孩子的她,產(chǎn)后第三個星期她就回T臺上了。反正從她出道到現(xiàn)在,肌膚狀態(tài)一直都非常的好。看到她為某時尚雜志拍攝的getready with me 視頻之后就發(fā)現(xiàn)原來皮膚好,很多原因是在于選對了產(chǎn)品呀。
可以說她的“本命“就是Fresh了吧,視頻里所有的推薦幾乎都是Fresh的單品呢~而今天小編就要來推薦一下Fresh家的東西了,感覺最近超多仙女急需一瓶好用的水哦,畢竟換季像清倉一樣,是時候買瓶新的水給自己啦。
推薦產(chǎn)品:Fresh玫瑰花瓣水
今天小編就是要推薦這款超美的Fresh馥蕾詩玫瑰花瓣水,真真實實的玫瑰花哦,搶先用過的小編表示看到這么多玫瑰花心情都變好了!所以Fresh玫瑰花瓣水怎么樣?
答案當(dāng)然是肯定的,光不含酒精這一點(diǎn)就能讓小仙女們愛上了好嘛!而且小編還想說連續(xù)使用Fresh馥蕾詩玫瑰花瓣水兩周,肌膚的水潤度真的得到了極大提升哦。連續(xù)早晚使用,涂抹瞬間,都能馬上感受到水潤修復(fù)的舒適感。
這瓶玫瑰水中含有超多真實的玫瑰花瓣,也正是因為如此,每天涂的時候都有一股天然的玫瑰花的香味,可以說是每天都在期待著這股玫瑰花的味道,恨不得一天涂N次,讓肌膚和自己都一直在真正的花海中一樣。
想必有盆友就要問了,Fresh玫瑰花瓣水功效是什么呢?別急,小編這就告訴你們哦。它不僅可以幫助肌膚補(bǔ)充水分,還能細(xì)致毛孔,對紅腫的痘痘有消炎的效果哦。同時還能舒緩肌膚,改善膚質(zhì)。讓肌膚如花瓣一般,再現(xiàn)水潤舒緩、細(xì)致柔嫩的肌膚,并為后續(xù)的護(hù)膚步驟做好準(zhǔn)備。
使用方法也很簡單哦!每日早晚潔凈肌膚后,用化妝棉或指尖沾取適量玫瑰花瓣水,輕柔掃過面部與頸部就可以啦!所以經(jīng)過小編的一番“講解“,是不是覺得Fresh玫瑰花瓣水好用了呢?在這里小編還想說搭配同系列的Fresh產(chǎn)品效果會更好哦。
比如,玫瑰潤澤密集保濕面霜可以為肌膚帶來持續(xù)潤養(yǎng),深徹補(bǔ)水。當(dāng)然再搭配玫瑰潤澤保濕舒緩面膜,用完之后能讓大家的肌膚看上去更加光彩柔嫩哦。大家可以根據(jù)自己的肌膚狀況選擇購買哦~這里小編就給你帶來點(diǎn)福利啦,F(xiàn)resh玫瑰花瓣水體驗裝免費(fèi)試用申領(lǐng) ~趕緊點(diǎn)擊鏈接領(lǐng)取吧~
http://freshrose.comeyes.cn/index.html?utm_source=Onlylady&utm_medium=Onlyladyeditorial&utm_campaign=fresh2018
*請認(rèn)真填寫需求信息,我們會在24小時內(nèi)與您取得聯(lián)系。