iOS面試題:阿里-P6一面
阿里-p6-一面
1.介紹下記憶體的幾大區域?
2.你是如何元件化解耦的?
3.runtime如何通過selector找到對應的IMP地址
4.runloop內部實現邏輯?
5.你理解的多執行緒?
6.GCD執行原理?
7.怎麼防止別人反編譯你的app?
8.YYAsyncLayer如何非同步繪製?
9.優化你是從哪幾方面著手?
1.介紹下記憶體的幾大區域?
1.棧區(stack) 由編譯器自動分配並釋放,存放函式的引數值,區域性變數等。棧是系統資料結構,對應執行緒/程序是唯一的。優點是快速高效,缺點時有限制,資料不靈活。[先進後出]
棧空間分靜態分配 和動態分配兩種。
堆區(heap) 由程式設計師分配和釋放,如果程式設計師不釋放,程式結束時,可能會由作業系統回收 ,比如在ios 中 alloc 都是存放在堆中。
優點是靈活方便,資料適應面廣泛,但是效率有一定降低。
雖然程式結束時所有的資料空間都會被釋放回系統,但是精確的申請記憶體,釋放記憶體匹配是良好程式的基本要素。
3.全域性區(靜態區) (static) 全域性變數和靜態變數的儲存是放在一起的,初始化的全域性變數和靜態變數存放在一塊區域,未初始化的全域性變數和靜態變數在相鄰的另一塊區域,程式結束後有系統釋放。
4.文字常量區 存放常量字串,程式結束後由系統釋放;
5.程式碼區 存放函式的二進位制程式碼
大致如圖:
例子程式碼:
可能被追問的問題一:
1.棧區 (stack [stæk]): 由編譯器自動分配釋放
區域性變數是儲存在棧區的
方法呼叫的實參也是儲存在棧區的
2.堆區 (heap [hiːp]): 由程式設計師分配釋放,若程式設計師不釋放,會出現記憶體洩漏,賦值語句右側 使用 new 方法建立的物件,被建立物件的所有 成員變數!
3.BSS 段 : 程式結束後由系統釋放
4.資料段 : 程式結束後由系統釋放
5.程式碼段:程式結束後由系統釋放
程式編譯連結 後的二進位制可執行程式碼
可能被追問的問題二:
比如申請後的系統是如何響應的?
棧:儲存每一個函式在執行的時候都會向作業系統索要資源,棧區就是函式執行時的記憶體,棧區中的變數由編譯器負責分配和釋放,記憶體隨著函式的執行分配,隨著函式的結束而釋放,由系統自動完成。
注意:只要棧的剩餘空間大於所申請空間,系統將為程式提供記憶體,否則將報異常提示棧溢位。
堆:
1.首先應該知道作業系統有一個記錄空閒記憶體地址的連結串列。
2.當系統收到程式的申請時,會遍歷該連結串列,尋找第一個空間大於所申請空間的堆結點,然後將該結點從空閒結點連結串列中刪除,並將該結點的空間分配給程式。
3 .由於找到的堆結點的大小不一定正好等於申請的大小,系統會自動的將多餘的那部分重新放入空閒連結串列中
可能被追問的問題三:
比如:申請大小的限制是怎樣的?
棧:棧是向低地址擴充套件的資料結構,是一塊連續的記憶體的區域。是棧頂的地址和棧的最大容量是系統預先規定好的,棧的大小是2M(也有的說是1M,總之是一個編譯時就確定的常數 ) ,如果申請的空間超過棧的剩餘空間時,將提示overflow。因此,能從棧獲得的空間較小。
堆:堆是向高地址擴充套件的資料結構,是不連續的記憶體區域。這是由於系統是用連結串列來儲存的空閒記憶體地址的,自然是不連續的,而連結串列的遍歷方向是由低地址向高地址。堆的大小受限於計算機系統中有效的虛擬記憶體。由此可見,堆獲得的空間比較靈活,也比較大。
棧:由系統自動分配,速度較快,不會產生記憶體碎片
堆:是由alloc分配的記憶體,速度比較慢,而且容易產生記憶體碎片,不過用起來最方便
打個比喻來說:
使用棧就象我們去飯館裡吃飯,只管點菜(發出申請)、付錢、和吃(使用),吃飽了就走,不必理會切菜、洗菜等準備工作和洗碗、刷鍋等掃尾工作,他的好處是快捷,但是自由度小。
使用堆就象是自己動手做喜歡吃的菜餚,比較麻煩,但是比較符合自己的口味,而且自由度大。
2.你是如何元件化解耦的?
實現程式碼的高內聚低耦合,方便多人多團隊開發!
一般需要解耦的專案都會多多少少出現,一下幾個情況:
耦合比較嚴重(因為沒有明確的約束,「元件」間引用的現象會比較多)
2.容易出現衝突(尤其是使用 Xib,還有就是 Xcode Project,雖說有指令碼可以改善)
3.業務方的開發效率不夠高(只關心自己的元件,卻要編譯整個專案,與其他不相干的程式碼糅合在一起)
先來看下,元件化之後的一個大概架構
「元件化」顧名思義就是把一個大的 App 拆成一個個小的元件,相互之間不直接引用。那如何做呢?
元件間通訊
以 iOS 為例,由於之前就是採用的 URL 跳轉模式,理論上頁面之間的跳轉只需 open 一個 URL 即可。所以對於一個元件來說,只要定義「支援哪些 URL」即可,比如詳情頁,大概可以這麼做的
首頁只需呼叫[MGJRouter openURL:@"mgj://detail?id=404"]就可以開啟相應的詳情頁。
那問題又來了,我怎麼知道有哪些可用的 URL?為此,我們做了一個後臺專門來管理。
然後可以把這些短鏈生成不同平臺所需的檔案,iOS 平臺生成 .{h,m} 檔案,Android 平臺生成 .java 檔案,並注入到專案中。這樣開發人員只需在專案中開啟該檔案就知道所有的可用 URL 了。
目前還有一塊沒有做,就是引數這塊,雖然描述了短鏈,但真想要生成完整的 URL,還需要知道如何傳引數,這個正在開發中。
還有一種情況會稍微麻煩點,就是「元件A」要呼叫「元件B」的某個方法,比如在商品詳情頁要展示購物車的商品數量,就涉及到向購物車元件拿資料。
類似這種同步呼叫,iOS 之前採用了比較簡單的方案,還是依託於MGJRouter,不過添加了新的方法- (id)objectForURL:,註冊時也使用新的方法進行註冊
使用時NSNumber *orderCount = [MGJRouter objectForURL:@"mgj://cart/ordercount"]這樣就拿到了購物車裡的商品數。
稍微複雜但更具通用性的方法是使用「協議」 <-> 「類」繫結的方式,還是以購物車為例,購物車元件可以提供這麼個 Protocol
可以看到通過協議可以直接指定返回的資料型別。然後在購物車元件內再新建個類實現這個協議,假設這個類名為MGJCartImpl,接著就可以把它與協議關聯起來[ModuleManagerregisterClass:MGJCartImplforProtocol:@protocol(MGJCart)],對於使用方來說,要拿到這個MGJCartImpl,需要呼叫[ModuleManagerclassForProtocol:@protocol(MGJCart)]。拿到之後再呼叫+ (NSInteger)orderCount就可以了。
那麼,這個協議放在哪裡比較合適呢?如果跟元件放在一起,使用時還是要先引入元件,如果有多個這樣的元件就會比較麻煩了。所以我們把這些公共的協議統一放到了PublicProtocolDomain.h下,到時只依賴這一個檔案就可以了。
Android 也是採用類似的方式。
元件生命週期管理
理想中的元件可以很方便地整合到主客中,並且有跟AppDelegate一致的回撥方法。這也是ModuleManager做的事情。
先來看看現在的入口方法
其中[MGJApp startApp]主要負責一些 SDK 的初始化。[self trackLaunchTime]是我們打的一個點,用來監測從main方法開始到入口方法呼叫結束花了多長時間。其他的都由ModuleManager搞定,loadModuleFromPlist:pathForResource:方法會讀取 bundle 裡的一個 plist 檔案,這個檔案的內容大概是這樣的
每個Module都實現了ModuleProtocol,其中有一個- (BOOL)applicaiton:didFinishLaunchingWithOptions:方法,如果實現了的話,就會被呼叫。
還有一個問題就是,系統的一些事件會有通知,比如applicationDidBecomeActive會有對應的UIApplicationDidBecomeActiveNotification,元件如果要做響應的話,只需監聽這個系統通知即可。但也有一些事件是沒有通知的,比如- application:didRegisterUserNotificationSettings:,這時元件如果也要做點事情,怎麼辦?
一個簡單的解決方法是在AppDelegate的各個方法裡,手動調一遍元件的對應的方法,如果有就執行。
殼工程
既然已經拆出去了,那拆出去的元件總得有個載體,這個載體就是殼工程,殼工程主要包含一些基礎元件和業務SDK,這也是主工程包含的一些內容,所以如果在殼工程可以正常執行的話,到了主工程也沒什麼問題。不過這裡存在版本同步問題,之後會說到。
遇到的問題
元件拆分
由於之前的程式碼都是在一個工程下的,所以要單獨拿出來作為一個元件就會遇到不少問題。首先是元件的劃分,當時在定義元件粒度時也花了些時間討論,究竟是粒度粗點好,還是細點好。粗點的話比較有利於拆分,細點的話靈活度比較高。最終還是選擇粗一點的粒度,先拆出來再說。
假如要把詳情頁遷出來,就會發現它依賴了一些其他部分的程式碼,那最快的方式就是直接把程式碼拷過來,改個名使用。比較簡單暴力。說起來比較簡單,做的時候也是挺有挑戰的,因為正常的業務並不會因為「元件化」而停止,所以開發同學們需要同時兼顧正常的業務和元件的拆分。
版本管理
我們的元件包括第三方庫都是通過 Cocoapods 來管理的,其中元件使用了私有庫。之所以選擇 Cocoapods,一個是因為它比較方便,還有就是使用者基數比較大,且社群也比較活躍(活躍到了會時不時地觸發 Github 的 rate limit,導致長時間 clone 不下來···見此),當然也有其他的管理方式,比如 submodule / subtree,在開發人員比較多的情況下,方便、靈活的方案容易佔上風,雖然它也有自己的問題。主要有版本同步和更新/編譯慢的問題。
假如基礎元件做了個 API 介面升級,這個升級會對原有的介面做改動,自然就會升一箇中位的版本號,比如原先是 1.6.19,那麼現在就變成 1.7.0 了。而我們在 Podfile 裡都是用~指定的,這樣就會出現主工程的 pod 版本升上去了,但是殼工程沒有同步到,然後群裡就會各種反饋編譯不過,而且這個編譯不過的長尾有時能拖上兩三天。
然後我們就想了個辦法,如果不在殼工程裡指定基礎庫的版本,只在主工程裡指定呢,理論上應該可行,只要不出現某個基礎庫要同時維護多個版本的情況。但實踐中發現,殼工程有時會莫名其妙地升不上去,在 podfile 裡指定最新的版本又可以升上去,所以此路不通。
還有一個問題是pod update時間過長,經常會在Analyzing Dependency上卡 10 多分鐘,非常影響效率。後來排查下來是跟元件的 Podspec 有關,配置了 subspec,且依賴比較多。
然後就是 pod update 之後的編譯,由於是原始碼編譯,所以這塊的時間花費也不少,接下去會考慮 framework 的方式。
持續整合
在剛開始,持續整合還不是很完善,業務方升級元件,直接把 podspec 扔到 private repo 裡就完事了。這樣最簡單,但也經常會帶來編譯通不過的問題。而且這種隨意的版本升級也不太能保證質量。於是我們就搭建了一套持續整合系統,大概如此
每個元件升級之前都需要先通過編譯,然後再決定是否升級。這套體系看起來不復雜,但在實施過程中經常會遇到後端的併發問題,導致業務方要麼整合失敗,要麼要等不少時間。而且也沒有一個地方可以呈現當前版本的元件版本資訊。還有就是業務方對於這種命令列的升級方式接受度也不是很高。
基於此,在經過了幾輪討論之後,有了新版的持續整合平臺,升級操作通過網頁端來完成。
大致思路是,業務方如果要升級元件,假設現在的版本是 0.1.7,添加了一些 feature 之後,殼工程測試通過,想整合到主工程裡看看效果,或者其他元件也想引用這個最新的,就可以在後臺手動把版本升到 0.1.8-rc.1,這樣的話,原先依賴~> 0.1.7的元件,不會升到 0.1.8,同時想要測試這個元件的話,只要手動把版本調到 0.1.8-rc.1 就可以了。這個過程不會觸發 CI 的編譯檢查。
當測試通過後,就可以把尾部的-rc.n去掉,然後點選「整合」,就會走 CI 編譯檢查,通過的話,會在主工程的 podfile 裡寫上固定的版本號 0.1.8。也就是說,podfile 裡所有的元件版本號都是固定的。
周邊設施
基礎元件及元件的文件 / Demo / 單元測試
無線基礎的職能是為集團提供解決方案,只是在蘑菇街 App 裡能 work 是遠遠不夠的,所以就需要提供入口,知道有哪些可用元件,並且如何使用,就像這樣(目前還未實現)
這就要求元件的負責人需要及時地更新 README / CHANGELOG / API,並且當發生 API 變更時,能夠快速通知到使用方。
公共 UI 元件
元件化之後還有一個問題就是資源的重複性,以前在一個工程裡的時候,資源都可以很方便地拿到,現在獨立出去了,也不知道哪些是公用的,哪些是獨有的,索性都放到自己的元件裡,這樣就會導致包變大。還有一個問題是每個元件可能是不同的產品經理在跟,而他們很可能只關注於自己關心的頁面長什麼樣,而忽略了整體的樣式。公共
UI 元件就是用來解決這些問題的,這些元件甚至可以跨 App 使用。(目前還未實現)
參考答案一:http://blog.csdn.net/GGGHub/article/details/52713642
參考答案二:http://limboy.me/tech/2016/03/10/mgj-components.html
3.runtime如何通過selector找到對應的IMP地址?
概述
類物件中有類方法和例項方法的列表,列表中記錄著方法的名詞、引數和實現,而selector本質就是方法名稱,runtime通過這個方法名稱就可以在列表中找到該方法對應的實現。
這裡聲明瞭一個指向struct objc_method_list指標的指標,可以包含類方法列表和例項方法列表
具體實現
在尋找IMP的地址時,runtime提供了兩種方法
IMP class_getMethodImplementation(Class cls, SEL name);IMP method_getImplementation(Method m)
而根據官方描述,第一種方法可能會更快一些
@note \c class_getMethodImplementation may be faster than \c method_getImplementation(class_getInstanceMethod(cls, name)).
對於第一種方法而言,類方法和例項方法實際上都是通過呼叫class_getMethodImplementation()來尋找IMP地址的,不同之處在於傳入的第一個引數不同
類方法(假設有一個類A)
class_getMethodImplementation(objc_getMetaClass("A"),@selector(methodName));
例項方法
class_getMethodImplementation([A class],@selector(methodName));
通過該傳入的引數不同,找到不同的方法列表,方法列表中儲存著下面方法的結構體,結構體中包含這方法的實現,selector本質就是方法的名稱,通過該方法名稱,即可在結構體中找到相應的實現。
struct objc_method {SEL method_namechar *method_typesIMP method_imp}
而對於第二種方法而言,傳入的引數只有method,區分類方法和例項方法在於封裝method的函式
類方法
Method class_getClassMethod(Class cls, SEL name)
例項方法
Method class_getInstanceMethod(Class cls, SEL name)
最後呼叫IMP method_getImplementation(Method m)獲取IMP地址
實驗
這裡有一個叫Test的類,在初始化方法裡,呼叫了兩次getIMPFromSelector:方法,第一個aaa方法是不存在的,test1和test2分別為例項方法和類方法
然後我同時例項化了兩個Test的物件,列印資訊如下
大家注意圖中紅色標註的地址出現了8次:0x1102db280,這個是在呼叫class_getMethodImplementation()方法時,無法找到對應實現時返回的相同的一個地址,無論該方法是在例項方法或類方法,無論是否對一個例項呼叫該方法,返回的地址都是相同的,但是每次執行該程式時返回的地址並不相同,而對於另一種方法,如果找不到對應的實現,則返回0,在圖中我做了藍色標記。
還有一點有趣的是class_getClassMethod()的第一個引數無論傳入objc_getClass()還是objc_getMetaClass(),最終呼叫method_getImplementation()都可以成功的找到類方法的實現。
而class_getInstanceMethod()的第一個引數如果傳入objc_getMetaClass(),再呼叫method_getImplementation()時無法找到例項方法的實現卻可以找到類方法的實現。
4.runloop內部實現邏輯?
蘋果在文件裡的說明,RunLoop 內部的邏輯大致如下:
其內部程式碼整理如下 :
可以看到,實際上 RunLoop 就是這樣一個函式,其內部是一個 do-while 迴圈。當你呼叫 CFRunLoopRun() 時,執行緒就會一直停留在這個迴圈裡;直到超時或被手動停止,該函式才會返回。
RunLoop 的底層實現
從上面程式碼可以看到,RunLoop 的核心是基於 mach port 的,其進入休眠時呼叫的函式是 mach_msg()。為了解釋這個邏輯,下面稍微介紹一下 OSX/iOS 的系統架構。
蘋果官方將整個系統大致劃分為上述4個層次:
應用層包括使用者能接觸到的圖形應用,例如 Spotlight、Aqua、SpringBoard 等。
應用框架層即開發人員接觸到的 Cocoa 等框架。
核心框架層包括各種核心框架、OpenGL 等內容。
Darwin 即作業系統的核心,包括系統核心、驅動、Shell 等內容,這一層是開源的,其所有原始碼都可以在opensource.apple.com裡找到。
我們在深入看一下 Darwin 這個核心的架構:
其中,在硬體層上面的三個組成部分:Mach、BSD、IOKit (還包括一些上面沒標註的內容),共同組成了 XNU 核心。
XNU 核心的內環被稱作 Mach,其作為一個微核心,僅提供了諸如處理器排程、IPC (程序間通訊)等非常少量的基礎服務。
BSD 層可以看作圍繞 Mach 層的一個外環,其提供了諸如程序管理、檔案系統和網路等功能。
IOKit 層是為裝置驅動提供了一個面向物件(C++)的一個框架。
Mach
本身提供的 API 非常有限,而且蘋果也不鼓勵使用 Mach 的
API,但是這些API非常基礎,如果沒有這些API的話,其他任何工作都無法實施。在 Mach
中,所有的東西都是通過自己的物件實現的,程序、執行緒和虛擬記憶體都被稱為"物件"。和其他架構不同, Mach
的物件間不能直接呼叫,只能通過訊息傳遞的方式實現物件間的通訊。"訊息"是 Mach 中最基礎的概念,訊息在兩個埠 (port)
之間傳遞,這就是 Mach 的 IPC (程序間通訊) 的核心。
Mach 的訊息定義是在標頭檔案的,很簡單:
12相關推薦iOS面試題:阿里-P6一面阿里-p6-一面1.介紹下記憶體的幾大區域?2.你是如何元件化解耦的?3.runtime如何通過selector找到對應的IMP地址4.runloop內部實現邏輯?5.你理解的多執行緒?6.GCD執行原理?7.怎麼防止別人反編譯你的app?8.YYAsyncLayer如何非同 Java程式設計師最全技術面試題:阿里11面試+百度+美團網路程式設計 文末有答案解析以及阿里架構師精講視訊資料。 ISO模型與協議 http1.0:需要使用keep-alive引數來告知伺服器端要建立一個長連線 http1.1:預設長連線。支援只發送header資訊,可以用作許可權請求。支援Host域。 阿里面試題:FileInputStream 在使用完以後,不關閉流,想二次使用可以怎麼操作FileInputStream 中有一個方法是open 方法呼叫的是本地的開啟檔案的方法,fileinputStream 就是通過這個方法來開啟檔案的,所以如果要重寫讀取這個檔案,不重新建立物件,那麼只要呼叫這個方法就可以了。 /** * Opens the specifie 阿里巴巴面試題: 為什麼wait()和notify()需要搭配synchonized關鍵字使用本文主要參考 理解此問題先修知識: synchronized 的含義: Java中每一個物件都可以成為一個監視器(Monitor), 該Monitor由一個鎖(lock), 一個 iOS 面試題(1):一個 Objective-C 物件的記憶體結構是怎樣的?接下來分享的將會是唐老師一系列的iOS面試題,因為之前好幾期唐老師都刪掉了,說是要出書,所以轉載過來,需要的朋友們可以看下,也方便我自己鞏固、學習。 轉載自:http://mp.weixin.qq.com/s?__biz=MjM5NTIyNTUyMQ==&mid= 金三銀四:螞蟻金服JAVA開發面試題及答案之一面(持續更新)開發十年,就只剩下這套架構體系了! >>> 49. 搜狗面試題: 大數相乘算法std margin -a pac string out none content ack 分析: 大數能大到整形類型存儲不了。須要借助於其它的算法,來完畢乘法運算。 能夠使用口算乘法的步驟來模擬乘法操作。例如以下: Java(面試題):字符串截取int lan out 試題 void trace 題目 replace odi 在Java中,字符串“abcd”與字符串“ab你好”的長度是一樣,都是四個字符。 但對應的字節數不同,一個漢字占兩個字節。 定義一個方法,按照指定的字節數來取子串。 如:對於“ab你好”,如果 18、iOS面試題·自整理·One新特性 服務器端 ssd 關閉 綁架 利好 pla ken 技巧 ◆如何解決低內存問題? ForExample:將暫時沒有展示在Window中的界面銷毀,以獲得足夠的內存; ◆POST請求的數據類型有哪些? json、xml、二進制、參數拼接; ◆請簡述你理解的面向對 19、iOS面試題·自整理·Three-c 得到 設計 ica oschina 參數 dexp 不能 整理 p.p2 { margin: 0.0px 0.0px 0.0px 0.0px; text-align: justify; text-indent: 21.0px; font: 10.5px "Songti 面試題:軟件測試,如何測微信的朋友圈?功能 此外 nal testing 測試 常用 tar pad 軟件 任何一個東西你都可以這麽測:記住sfdipot: s,structure,結構。考慮其組成部分,微信朋友圈的代碼組成,客戶端是怎麽樣的,服務端是怎麽樣的。 f,function,功能。考慮單個功 面試題:HTTP與HTTPS模型 開頭 tro 工作 ron 傳輸層 進行 證書 str 記錄個面試題 HTTP與HTTPS的不同 1.HTTP的URL為http://開頭,HTTPS的URL為https://開頭 2.HTTP標準端口80,HTTPS標準端口是443 3.在OSI網絡模型中,HTTP 經典面試題:js繼承方式下deep 今天 typeof extend fun col const 繼承 uber 上一篇講解了構造函數的繼承方式,今天來講非構造函數的繼承模式。 一、object()方法 json格式的發明人Douglas Crockford,提出了一個object()函數,可以做到 Java 面試題:百度前200頁都在這裏了serializa 負載 第三方 lin 目的 safe 並排 原理 java虛擬機 基本概念 操作系統中 heap 和 stack 的區別 什麽是基於註解的切面實現 什麽是 對象/關系 映射集成模塊 什麽是 Java 的反射機制 什麽是 ACID BS與CS的聯系與區別 java基礎面試題:switch語句能否作用在byte上,能否作用在long上,能否作用在String上?int 包裝類 println class ava col body package 面試題 package com.swift; public class Switch_Test { public static void main(String[] args java基礎面試題:try{}裏有一個return語句,那麽緊跟在這個try後的finally {}裏的code會不會被執行,什麽時候被執行,在return前還是後?nal java pan clas out bsp 出現 可能 inf package com.swift; public class Try_Catch_Finally_Test { public static void main(String[] args java算法面試題:編寫一個截取字符串的函數,輸入為一個字符串和字節數,輸出為按字節截取的字符串,但要保證漢字不被截取半個, 如“我ABC”,4,應該截取“我AB”,輸入“我ABC漢DEF”,6,應該輸出“我ABC”,而不是“我ABC+漢的半個”。構造 pack n) -- com post nts throw ... package com.swift; import java.util.Scanner; public class Hanzi_jiequ { public static void m java面試題:如果一串字符如"aaaabbc中國1512"要分別統計英文字符的數量,中文字符的數量,和數字字符的數量,假設字符中沒有中文字符、英文字符、數字字符之外的其他特殊字符。rgs info log letter clas [] 面試題 .com ack package com.swift; public class TotalNumber_String { public static void main(String[] arg java算法面試題:排序都有哪幾種方法?請列舉。用JAVA實現一個快速排序。選擇冒泡快速集合至少4種方法排序算法 err div println rda print 算法面試 ++ 快速排序 package com.swift; import java.util.ArrayList; import java.util.Collections; import java.util java算法面試題:遞歸算法題2 第1個人10,第2個比第1個人大2歲,依次遞推,請用遞歸方式計算出第8個人多大?else oid 算法題 body println 算法 ring swift java算法 package com.swift; public class Digui_Return { public static void main(String[] arg |